Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * nodeWindowAgg.c
4 : * routines to handle WindowAgg nodes.
5 : *
6 : * A WindowAgg node evaluates "window functions" across suitable partitions
7 : * of the input tuple set. Any one WindowAgg works for just a single window
8 : * specification, though it can evaluate multiple window functions sharing
9 : * identical window specifications. The input tuples are required to be
10 : * delivered in sorted order, with the PARTITION BY columns (if any) as
11 : * major sort keys and the ORDER BY columns (if any) as minor sort keys.
12 : * (The planner generates a stack of WindowAggs with intervening Sort nodes
13 : * as needed, if a query involves more than one window specification.)
14 : *
15 : * Since window functions can require access to any or all of the rows in
16 : * the current partition, we accumulate rows of the partition into a
17 : * tuplestore. The window functions are called using the WindowObject API
18 : * so that they can access those rows as needed.
19 : *
20 : * We also support using plain aggregate functions as window functions.
21 : * For these, the regular Agg-node environment is emulated for each partition.
22 : * As required by the SQL spec, the output represents the value of the
23 : * aggregate function over all rows in the current row's window frame.
24 : *
25 : *
26 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
27 : * Portions Copyright (c) 1994, Regents of the University of California
28 : *
29 : * IDENTIFICATION
30 : * src/backend/executor/nodeWindowAgg.c
31 : *
32 : *-------------------------------------------------------------------------
33 : */
34 : #include "postgres.h"
35 :
36 : #include "access/htup_details.h"
37 : #include "catalog/objectaccess.h"
38 : #include "catalog/pg_aggregate.h"
39 : #include "catalog/pg_proc.h"
40 : #include "executor/executor.h"
41 : #include "executor/nodeWindowAgg.h"
42 : #include "miscadmin.h"
43 : #include "nodes/nodeFuncs.h"
44 : #include "optimizer/clauses.h"
45 : #include "optimizer/optimizer.h"
46 : #include "parser/parse_agg.h"
47 : #include "parser/parse_coerce.h"
48 : #include "utils/acl.h"
49 : #include "utils/builtins.h"
50 : #include "utils/datum.h"
51 : #include "utils/expandeddatum.h"
52 : #include "utils/lsyscache.h"
53 : #include "utils/memutils.h"
54 : #include "utils/regproc.h"
55 : #include "utils/syscache.h"
56 : #include "windowapi.h"
57 :
58 : /*
59 : * All the window function APIs are called with this object, which is passed
60 : * to window functions as fcinfo->context.
61 : */
62 : typedef struct WindowObjectData
63 : {
64 : NodeTag type;
65 : WindowAggState *winstate; /* parent WindowAggState */
66 : List *argstates; /* ExprState trees for fn's arguments */
67 : void *localmem; /* WinGetPartitionLocalMemory's chunk */
68 : int markptr; /* tuplestore mark pointer for this fn */
69 : int readptr; /* tuplestore read pointer for this fn */
70 : int64 markpos; /* row that markptr is positioned on */
71 : int64 seekpos; /* row that readptr is positioned on */
72 : uint8 **notnull_info; /* not null info for each func args */
73 : int64 *num_notnull_info; /* track size (number of tuples in
74 : * partition) of the notnull_info array
75 : * for each func args */
76 :
77 : /*
78 : * Null treatment options. One of: NO_NULLTREATMENT, PARSER_IGNORE_NULLS,
79 : * PARSER_RESPECT_NULLS or IGNORE_NULLS.
80 : */
81 : int ignore_nulls;
82 : } WindowObjectData;
83 :
84 : /*
85 : * We have one WindowStatePerFunc struct for each window function and
86 : * window aggregate handled by this node.
87 : */
88 : typedef struct WindowStatePerFuncData
89 : {
90 : /* Links to WindowFunc expr and state nodes this working state is for */
91 : WindowFuncExprState *wfuncstate;
92 : WindowFunc *wfunc;
93 :
94 : int numArguments; /* number of arguments */
95 :
96 : FmgrInfo flinfo; /* fmgr lookup data for window function */
97 :
98 : Oid winCollation; /* collation derived for window function */
99 :
100 : /*
101 : * We need the len and byval info for the result of each function in order
102 : * to know how to copy/delete values.
103 : */
104 : int16 resulttypeLen;
105 : bool resulttypeByVal;
106 :
107 : bool plain_agg; /* is it just a plain aggregate function? */
108 : int aggno; /* if so, index of its WindowStatePerAggData */
109 : uint8 ignore_nulls; /* ignore nulls */
110 :
111 : WindowObject winobj; /* object used in window function API */
112 : } WindowStatePerFuncData;
113 :
114 : /*
115 : * For plain aggregate window functions, we also have one of these.
116 : */
117 : typedef struct WindowStatePerAggData
118 : {
119 : /* Oids of transition functions */
120 : Oid transfn_oid;
121 : Oid invtransfn_oid; /* may be InvalidOid */
122 : Oid finalfn_oid; /* may be InvalidOid */
123 :
124 : /*
125 : * fmgr lookup data for transition functions --- only valid when
126 : * corresponding oid is not InvalidOid. Note in particular that fn_strict
127 : * flags are kept here.
128 : */
129 : FmgrInfo transfn;
130 : FmgrInfo invtransfn;
131 : FmgrInfo finalfn;
132 :
133 : int numFinalArgs; /* number of arguments to pass to finalfn */
134 :
135 : /*
136 : * initial value from pg_aggregate entry
137 : */
138 : Datum initValue;
139 : bool initValueIsNull;
140 :
141 : /*
142 : * cached value for current frame boundaries
143 : */
144 : Datum resultValue;
145 : bool resultValueIsNull;
146 :
147 : /*
148 : * We need the len and byval info for the agg's input, result, and
149 : * transition data types in order to know how to copy/delete values.
150 : */
151 : int16 inputtypeLen,
152 : resulttypeLen,
153 : transtypeLen;
154 : bool inputtypeByVal,
155 : resulttypeByVal,
156 : transtypeByVal;
157 :
158 : int wfuncno; /* index of associated WindowStatePerFuncData */
159 :
160 : /* Context holding transition value and possibly other subsidiary data */
161 : MemoryContext aggcontext; /* may be private, or winstate->aggcontext */
162 :
163 : /* Current transition value */
164 : Datum transValue; /* current transition value */
165 : bool transValueIsNull;
166 :
167 : int64 transValueCount; /* number of currently-aggregated rows */
168 :
169 : /* Data local to eval_windowaggregates() */
170 : bool restart; /* need to restart this agg in this cycle? */
171 : } WindowStatePerAggData;
172 :
173 : static void initialize_windowaggregate(WindowAggState *winstate,
174 : WindowStatePerFunc perfuncstate,
175 : WindowStatePerAgg peraggstate);
176 : static void advance_windowaggregate(WindowAggState *winstate,
177 : WindowStatePerFunc perfuncstate,
178 : WindowStatePerAgg peraggstate);
179 : static bool advance_windowaggregate_base(WindowAggState *winstate,
180 : WindowStatePerFunc perfuncstate,
181 : WindowStatePerAgg peraggstate);
182 : static void finalize_windowaggregate(WindowAggState *winstate,
183 : WindowStatePerFunc perfuncstate,
184 : WindowStatePerAgg peraggstate,
185 : Datum *result, bool *isnull);
186 :
187 : static void eval_windowaggregates(WindowAggState *winstate);
188 : static void eval_windowfunction(WindowAggState *winstate,
189 : WindowStatePerFunc perfuncstate,
190 : Datum *result, bool *isnull);
191 :
192 : static void begin_partition(WindowAggState *winstate);
193 : static void spool_tuples(WindowAggState *winstate, int64 pos);
194 : static void release_partition(WindowAggState *winstate);
195 :
196 : static int row_is_in_frame(WindowObject winobj, int64 pos,
197 : TupleTableSlot *slot, bool fetch_tuple);
198 : static void update_frameheadpos(WindowAggState *winstate);
199 : static void update_frametailpos(WindowAggState *winstate);
200 : static void update_grouptailpos(WindowAggState *winstate);
201 :
202 : static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
203 : WindowFunc *wfunc,
204 : WindowStatePerAgg peraggstate);
205 : static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
206 :
207 : static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
208 : TupleTableSlot *slot2);
209 : static bool window_gettupleslot(WindowObject winobj, int64 pos,
210 : TupleTableSlot *slot);
211 :
212 : static Datum ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
213 : int relpos, int seektype,
214 : bool set_mark, bool *isnull,
215 : bool *isout);
216 : static Datum gettuple_eval_partition(WindowObject winobj, int argno,
217 : int64 abs_pos, bool *isnull,
218 : bool *isout);
219 : static void init_notnull_info(WindowObject winobj,
220 : WindowStatePerFunc perfuncstate);
221 : static void grow_notnull_info(WindowObject winobj,
222 : int64 pos, int argno);
223 : static uint8 get_notnull_info(WindowObject winobj,
224 : int64 pos, int argno);
225 : static void put_notnull_info(WindowObject winobj,
226 : int64 pos, int argno, bool isnull);
227 :
228 : /*
229 : * Not null info bit array consists of 2-bit items
230 : */
231 : #define NN_UNKNOWN 0x00 /* value not calculated yet */
232 : #define NN_NULL 0x01 /* NULL */
233 : #define NN_NOTNULL 0x02 /* NOT NULL */
234 : #define NN_MASK 0x03 /* mask for NOT NULL MAP */
235 : #define NN_BITS_PER_MEMBER 2 /* number of bits in not null map */
236 : /* number of items per variable */
237 : #define NN_ITEM_PER_VAR (BITS_PER_BYTE / NN_BITS_PER_MEMBER)
238 : /* convert map position to byte offset */
239 : #define NN_POS_TO_BYTES(pos) ((pos) / NN_ITEM_PER_VAR)
240 : /* bytes offset to map position */
241 : #define NN_BYTES_TO_POS(bytes) ((bytes) * NN_ITEM_PER_VAR)
242 : /* calculate shift bits */
243 : #define NN_SHIFT(pos) ((pos) % NN_ITEM_PER_VAR) * NN_BITS_PER_MEMBER
244 :
245 : /*
246 : * initialize_windowaggregate
247 : * parallel to initialize_aggregates in nodeAgg.c
248 : */
249 : static void
250 4060 : initialize_windowaggregate(WindowAggState *winstate,
251 : WindowStatePerFunc perfuncstate,
252 : WindowStatePerAgg peraggstate)
253 : {
254 : MemoryContext oldContext;
255 :
256 : /*
257 : * If we're using a private aggcontext, we may reset it here. But if the
258 : * context is shared, we don't know which other aggregates may still need
259 : * it, so we must leave it to the caller to reset at an appropriate time.
260 : */
261 4060 : if (peraggstate->aggcontext != winstate->aggcontext)
262 2924 : MemoryContextReset(peraggstate->aggcontext);
263 :
264 4060 : if (peraggstate->initValueIsNull)
265 1566 : peraggstate->transValue = peraggstate->initValue;
266 : else
267 : {
268 2494 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
269 4988 : peraggstate->transValue = datumCopy(peraggstate->initValue,
270 2494 : peraggstate->transtypeByVal,
271 2494 : peraggstate->transtypeLen);
272 2494 : MemoryContextSwitchTo(oldContext);
273 : }
274 4060 : peraggstate->transValueIsNull = peraggstate->initValueIsNull;
275 4060 : peraggstate->transValueCount = 0;
276 4060 : peraggstate->resultValue = (Datum) 0;
277 4060 : peraggstate->resultValueIsNull = true;
278 4060 : }
279 :
280 : /*
281 : * advance_windowaggregate
282 : * parallel to advance_aggregates in nodeAgg.c
283 : */
284 : static void
285 177742 : advance_windowaggregate(WindowAggState *winstate,
286 : WindowStatePerFunc perfuncstate,
287 : WindowStatePerAgg peraggstate)
288 : {
289 177742 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
290 177742 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
291 177742 : int numArguments = perfuncstate->numArguments;
292 : Datum newVal;
293 : ListCell *arg;
294 : int i;
295 : MemoryContext oldContext;
296 177742 : ExprContext *econtext = winstate->tmpcontext;
297 177742 : ExprState *filter = wfuncstate->aggfilter;
298 :
299 177742 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
300 :
301 : /* Skip anything FILTERed out */
302 177742 : if (filter)
303 : {
304 : bool isnull;
305 342 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
306 :
307 342 : if (isnull || !DatumGetBool(res))
308 : {
309 162 : MemoryContextSwitchTo(oldContext);
310 162 : return;
311 : }
312 : }
313 :
314 : /* We start from 1, since the 0th arg will be the transition value */
315 177580 : i = 1;
316 294710 : foreach(arg, wfuncstate->args)
317 : {
318 117130 : ExprState *argstate = (ExprState *) lfirst(arg);
319 :
320 117130 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
321 : &fcinfo->args[i].isnull);
322 117130 : i++;
323 : }
324 :
325 177580 : if (peraggstate->transfn.fn_strict)
326 : {
327 : /*
328 : * For a strict transfn, nothing happens when there's a NULL input; we
329 : * just keep the prior transValue. Note transValueCount doesn't
330 : * change either.
331 : */
332 102724 : for (i = 1; i <= numArguments; i++)
333 : {
334 21200 : if (fcinfo->args[i].isnull)
335 : {
336 198 : MemoryContextSwitchTo(oldContext);
337 198 : return;
338 : }
339 : }
340 :
341 : /*
342 : * For strict transition functions with initial value NULL we use the
343 : * first non-NULL input as the initial state. (We already checked
344 : * that the agg's input type is binary-compatible with its transtype,
345 : * so straight copy here is OK.)
346 : *
347 : * We must copy the datum into aggcontext if it is pass-by-ref. We do
348 : * not need to pfree the old transValue, since it's NULL.
349 : */
350 81524 : if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
351 : {
352 462 : MemoryContextSwitchTo(peraggstate->aggcontext);
353 924 : peraggstate->transValue = datumCopy(fcinfo->args[1].value,
354 462 : peraggstate->transtypeByVal,
355 462 : peraggstate->transtypeLen);
356 462 : peraggstate->transValueIsNull = false;
357 462 : peraggstate->transValueCount = 1;
358 462 : MemoryContextSwitchTo(oldContext);
359 462 : return;
360 : }
361 :
362 81062 : if (peraggstate->transValueIsNull)
363 : {
364 : /*
365 : * Don't call a strict function with NULL inputs. Note it is
366 : * possible to get here despite the above tests, if the transfn is
367 : * strict *and* returned a NULL on a prior cycle. If that happens
368 : * we will propagate the NULL all the way to the end. That can
369 : * only happen if there's no inverse transition function, though,
370 : * since we disallow transitions back to NULL when there is one.
371 : */
372 0 : MemoryContextSwitchTo(oldContext);
373 : Assert(!OidIsValid(peraggstate->invtransfn_oid));
374 0 : return;
375 : }
376 : }
377 :
378 : /*
379 : * OK to call the transition function. Set winstate->curaggcontext while
380 : * calling it, for possible use by AggCheckCallContext.
381 : */
382 176920 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
383 : numArguments + 1,
384 : perfuncstate->winCollation,
385 : (Node *) winstate, NULL);
386 176920 : fcinfo->args[0].value = peraggstate->transValue;
387 176920 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
388 176920 : winstate->curaggcontext = peraggstate->aggcontext;
389 176920 : newVal = FunctionCallInvoke(fcinfo);
390 176908 : winstate->curaggcontext = NULL;
391 :
392 : /*
393 : * Moving-aggregate transition functions must not return null, see
394 : * advance_windowaggregate_base().
395 : */
396 176908 : if (fcinfo->isnull && OidIsValid(peraggstate->invtransfn_oid))
397 0 : ereport(ERROR,
398 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
399 : errmsg("moving-aggregate transition function must not return null")));
400 :
401 : /*
402 : * We must track the number of rows included in transValue, since to
403 : * remove the last input, advance_windowaggregate_base() mustn't call the
404 : * inverse transition function, but simply reset transValue back to its
405 : * initial value.
406 : */
407 176908 : peraggstate->transValueCount++;
408 :
409 : /*
410 : * If pass-by-ref datatype, must copy the new value into aggcontext and
411 : * free the prior transValue. But if transfn returned a pointer to its
412 : * first input, we don't need to do anything. Also, if transfn returned a
413 : * pointer to a R/W expanded object that is already a child of the
414 : * aggcontext, assume we can adopt that value without copying it. (See
415 : * comments for ExecAggCopyTransValue, which this code duplicates.)
416 : */
417 185266 : if (!peraggstate->transtypeByVal &&
418 8358 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
419 : {
420 960 : if (!fcinfo->isnull)
421 : {
422 960 : MemoryContextSwitchTo(peraggstate->aggcontext);
423 960 : if (DatumIsReadWriteExpandedObject(newVal,
424 : false,
425 960 : peraggstate->transtypeLen) &&
426 6 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
427 : /* do nothing */ ;
428 : else
429 954 : newVal = datumCopy(newVal,
430 954 : peraggstate->transtypeByVal,
431 954 : peraggstate->transtypeLen);
432 : }
433 960 : if (!peraggstate->transValueIsNull)
434 : {
435 900 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
436 : false,
437 : peraggstate->transtypeLen))
438 0 : DeleteExpandedObject(peraggstate->transValue);
439 : else
440 900 : pfree(DatumGetPointer(peraggstate->transValue));
441 : }
442 : }
443 :
444 176908 : MemoryContextSwitchTo(oldContext);
445 176908 : peraggstate->transValue = newVal;
446 176908 : peraggstate->transValueIsNull = fcinfo->isnull;
447 : }
448 :
449 : /*
450 : * advance_windowaggregate_base
451 : * Remove the oldest tuple from an aggregation.
452 : *
453 : * This is very much like advance_windowaggregate, except that we will call
454 : * the inverse transition function (which caller must have checked is
455 : * available).
456 : *
457 : * Returns true if we successfully removed the current row from this
458 : * aggregate, false if not (in the latter case, caller is responsible
459 : * for cleaning up by restarting the aggregation).
460 : */
461 : static bool
462 4626 : advance_windowaggregate_base(WindowAggState *winstate,
463 : WindowStatePerFunc perfuncstate,
464 : WindowStatePerAgg peraggstate)
465 : {
466 4626 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
467 4626 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
468 4626 : int numArguments = perfuncstate->numArguments;
469 : Datum newVal;
470 : ListCell *arg;
471 : int i;
472 : MemoryContext oldContext;
473 4626 : ExprContext *econtext = winstate->tmpcontext;
474 4626 : ExprState *filter = wfuncstate->aggfilter;
475 :
476 4626 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
477 :
478 : /* Skip anything FILTERed out */
479 4626 : if (filter)
480 : {
481 : bool isnull;
482 102 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
483 :
484 102 : if (isnull || !DatumGetBool(res))
485 : {
486 48 : MemoryContextSwitchTo(oldContext);
487 48 : return true;
488 : }
489 : }
490 :
491 : /* We start from 1, since the 0th arg will be the transition value */
492 4578 : i = 1;
493 9138 : foreach(arg, wfuncstate->args)
494 : {
495 4560 : ExprState *argstate = (ExprState *) lfirst(arg);
496 :
497 4560 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
498 : &fcinfo->args[i].isnull);
499 4560 : i++;
500 : }
501 :
502 4578 : if (peraggstate->invtransfn.fn_strict)
503 : {
504 : /*
505 : * For a strict (inv)transfn, nothing happens when there's a NULL
506 : * input; we just keep the prior transValue. Note transValueCount
507 : * doesn't change either.
508 : */
509 5604 : for (i = 1; i <= numArguments; i++)
510 : {
511 2832 : if (fcinfo->args[i].isnull)
512 : {
513 78 : MemoryContextSwitchTo(oldContext);
514 78 : return true;
515 : }
516 : }
517 : }
518 :
519 : /* There should still be an added but not yet removed value */
520 : Assert(peraggstate->transValueCount > 0);
521 :
522 : /*
523 : * In moving-aggregate mode, the state must never be NULL, except possibly
524 : * before any rows have been aggregated (which is surely not the case at
525 : * this point). This restriction allows us to interpret a NULL result
526 : * from the inverse function as meaning "sorry, can't do an inverse
527 : * transition in this case". We already checked this in
528 : * advance_windowaggregate, but just for safety, check again.
529 : */
530 4500 : if (peraggstate->transValueIsNull)
531 0 : elog(ERROR, "aggregate transition value is NULL before inverse transition");
532 :
533 : /*
534 : * We mustn't use the inverse transition function to remove the last
535 : * input. Doing so would yield a non-NULL state, whereas we should be in
536 : * the initial state afterwards which may very well be NULL. So instead,
537 : * we simply re-initialize the aggregate in this case.
538 : */
539 4500 : if (peraggstate->transValueCount == 1)
540 : {
541 90 : MemoryContextSwitchTo(oldContext);
542 90 : initialize_windowaggregate(winstate,
543 90 : &winstate->perfunc[peraggstate->wfuncno],
544 : peraggstate);
545 90 : return true;
546 : }
547 :
548 : /*
549 : * OK to call the inverse transition function. Set
550 : * winstate->curaggcontext while calling it, for possible use by
551 : * AggCheckCallContext.
552 : */
553 4410 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
554 : numArguments + 1,
555 : perfuncstate->winCollation,
556 : (Node *) winstate, NULL);
557 4410 : fcinfo->args[0].value = peraggstate->transValue;
558 4410 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
559 4410 : winstate->curaggcontext = peraggstate->aggcontext;
560 4410 : newVal = FunctionCallInvoke(fcinfo);
561 4410 : winstate->curaggcontext = NULL;
562 :
563 : /*
564 : * If the function returns NULL, report failure, forcing a restart.
565 : */
566 4410 : if (fcinfo->isnull)
567 : {
568 254 : MemoryContextSwitchTo(oldContext);
569 254 : return false;
570 : }
571 :
572 : /* Update number of rows included in transValue */
573 4156 : peraggstate->transValueCount--;
574 :
575 : /*
576 : * If pass-by-ref datatype, must copy the new value into aggcontext and
577 : * free the prior transValue. But if invtransfn returned a pointer to its
578 : * first input, we don't need to do anything. Also, if invtransfn
579 : * returned a pointer to a R/W expanded object that is already a child of
580 : * the aggcontext, assume we can adopt that value without copying it. (See
581 : * comments for ExecAggCopyTransValue, which this code duplicates.)
582 : *
583 : * Note: the checks for null values here will never fire, but it seems
584 : * best to have this stanza look just like advance_windowaggregate.
585 : */
586 6286 : if (!peraggstate->transtypeByVal &&
587 2130 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
588 : {
589 666 : if (!fcinfo->isnull)
590 : {
591 666 : MemoryContextSwitchTo(peraggstate->aggcontext);
592 666 : if (DatumIsReadWriteExpandedObject(newVal,
593 : false,
594 666 : peraggstate->transtypeLen) &&
595 0 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
596 : /* do nothing */ ;
597 : else
598 666 : newVal = datumCopy(newVal,
599 666 : peraggstate->transtypeByVal,
600 666 : peraggstate->transtypeLen);
601 : }
602 666 : if (!peraggstate->transValueIsNull)
603 : {
604 666 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
605 : false,
606 : peraggstate->transtypeLen))
607 0 : DeleteExpandedObject(peraggstate->transValue);
608 : else
609 666 : pfree(DatumGetPointer(peraggstate->transValue));
610 : }
611 : }
612 :
613 4156 : MemoryContextSwitchTo(oldContext);
614 4156 : peraggstate->transValue = newVal;
615 4156 : peraggstate->transValueIsNull = fcinfo->isnull;
616 :
617 4156 : return true;
618 : }
619 :
620 : /*
621 : * finalize_windowaggregate
622 : * parallel to finalize_aggregate in nodeAgg.c
623 : */
624 : static void
625 10632 : finalize_windowaggregate(WindowAggState *winstate,
626 : WindowStatePerFunc perfuncstate,
627 : WindowStatePerAgg peraggstate,
628 : Datum *result, bool *isnull)
629 : {
630 : MemoryContext oldContext;
631 :
632 10632 : oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
633 :
634 : /*
635 : * Apply the agg's finalfn if one is provided, else return transValue.
636 : */
637 10632 : if (OidIsValid(peraggstate->finalfn_oid))
638 : {
639 5884 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
640 5884 : int numFinalArgs = peraggstate->numFinalArgs;
641 : bool anynull;
642 : int i;
643 :
644 5884 : InitFunctionCallInfoData(fcinfodata.fcinfo, &(peraggstate->finalfn),
645 : numFinalArgs,
646 : perfuncstate->winCollation,
647 : (Node *) winstate, NULL);
648 5884 : fcinfo->args[0].value =
649 5884 : MakeExpandedObjectReadOnly(peraggstate->transValue,
650 : peraggstate->transValueIsNull,
651 : peraggstate->transtypeLen);
652 5884 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
653 5884 : anynull = peraggstate->transValueIsNull;
654 :
655 : /* Fill any remaining argument positions with nulls */
656 5984 : for (i = 1; i < numFinalArgs; i++)
657 : {
658 100 : fcinfo->args[i].value = (Datum) 0;
659 100 : fcinfo->args[i].isnull = true;
660 100 : anynull = true;
661 : }
662 :
663 5884 : if (fcinfo->flinfo->fn_strict && anynull)
664 : {
665 : /* don't call a strict function with NULL inputs */
666 0 : *result = (Datum) 0;
667 0 : *isnull = true;
668 : }
669 : else
670 : {
671 : Datum res;
672 :
673 5884 : winstate->curaggcontext = peraggstate->aggcontext;
674 5884 : res = FunctionCallInvoke(fcinfo);
675 5872 : winstate->curaggcontext = NULL;
676 5872 : *isnull = fcinfo->isnull;
677 5872 : *result = MakeExpandedObjectReadOnly(res,
678 : fcinfo->isnull,
679 : peraggstate->resulttypeLen);
680 : }
681 : }
682 : else
683 : {
684 4748 : *result =
685 4748 : MakeExpandedObjectReadOnly(peraggstate->transValue,
686 : peraggstate->transValueIsNull,
687 : peraggstate->transtypeLen);
688 4748 : *isnull = peraggstate->transValueIsNull;
689 : }
690 :
691 10620 : MemoryContextSwitchTo(oldContext);
692 10620 : }
693 :
694 : /*
695 : * eval_windowaggregates
696 : * evaluate plain aggregates being used as window functions
697 : *
698 : * This differs from nodeAgg.c in two ways. First, if the window's frame
699 : * start position moves, we use the inverse transition function (if it exists)
700 : * to remove rows from the transition value. And second, we expect to be
701 : * able to call aggregate final functions repeatedly after aggregating more
702 : * data onto the same transition value. This is not a behavior required by
703 : * nodeAgg.c.
704 : */
705 : static void
706 160244 : eval_windowaggregates(WindowAggState *winstate)
707 : {
708 : WindowStatePerAgg peraggstate;
709 : int wfuncno,
710 : numaggs,
711 : numaggs_restart,
712 : i;
713 : int64 aggregatedupto_nonrestarted;
714 : MemoryContext oldContext;
715 : ExprContext *econtext;
716 : WindowObject agg_winobj;
717 : TupleTableSlot *agg_row_slot;
718 : TupleTableSlot *temp_slot;
719 :
720 160244 : numaggs = winstate->numaggs;
721 160244 : if (numaggs == 0)
722 0 : return; /* nothing to do */
723 :
724 : /* final output execution is in ps_ExprContext */
725 160244 : econtext = winstate->ss.ps.ps_ExprContext;
726 160244 : agg_winobj = winstate->agg_winobj;
727 160244 : agg_row_slot = winstate->agg_row_slot;
728 160244 : temp_slot = winstate->temp_slot_1;
729 :
730 : /*
731 : * If the window's frame start clause is UNBOUNDED_PRECEDING and no
732 : * exclusion clause is specified, then the window frame consists of a
733 : * contiguous group of rows extending forward from the start of the
734 : * partition, and rows only enter the frame, never exit it, as the current
735 : * row advances forward. This makes it possible to use an incremental
736 : * strategy for evaluating aggregates: we run the transition function for
737 : * each row added to the frame, and run the final function whenever we
738 : * need the current aggregate value. This is considerably more efficient
739 : * than the naive approach of re-running the entire aggregate calculation
740 : * for each current row. It does assume that the final function doesn't
741 : * damage the running transition value, but we have the same assumption in
742 : * nodeAgg.c too (when it rescans an existing hash table).
743 : *
744 : * If the frame start does sometimes move, we can still optimize as above
745 : * whenever successive rows share the same frame head, but if the frame
746 : * head moves beyond the previous head we try to remove those rows using
747 : * the aggregate's inverse transition function. This function restores
748 : * the aggregate's current state to what it would be if the removed row
749 : * had never been aggregated in the first place. Inverse transition
750 : * functions may optionally return NULL, indicating that the function was
751 : * unable to remove the tuple from aggregation. If this happens, or if
752 : * the aggregate doesn't have an inverse transition function at all, we
753 : * must perform the aggregation all over again for all tuples within the
754 : * new frame boundaries.
755 : *
756 : * If there's any exclusion clause, then we may have to aggregate over a
757 : * non-contiguous set of rows, so we punt and recalculate for every row.
758 : * (For some frame end choices, it might be that the frame is always
759 : * contiguous anyway, but that's an optimization to investigate later.)
760 : *
761 : * In many common cases, multiple rows share the same frame and hence the
762 : * same aggregate value. (In particular, if there's no ORDER BY in a RANGE
763 : * window, then all rows are peers and so they all have window frame equal
764 : * to the whole partition.) We optimize such cases by calculating the
765 : * aggregate value once when we reach the first row of a peer group, and
766 : * then returning the saved value for all subsequent rows.
767 : *
768 : * 'aggregatedupto' keeps track of the first row that has not yet been
769 : * accumulated into the aggregate transition values. Whenever we start a
770 : * new peer group, we accumulate forward to the end of the peer group.
771 : */
772 :
773 : /*
774 : * First, update the frame head position.
775 : *
776 : * The frame head should never move backwards, and the code below wouldn't
777 : * cope if it did, so for safety we complain if it does.
778 : */
779 160244 : update_frameheadpos(winstate);
780 160238 : if (winstate->frameheadpos < winstate->aggregatedbase)
781 0 : elog(ERROR, "window frame head moved backward");
782 :
783 : /*
784 : * If the frame didn't change compared to the previous row, we can re-use
785 : * the result values that were previously saved at the bottom of this
786 : * function. Since we don't know the current frame's end yet, this is not
787 : * possible to check for fully. But if the frame end mode is UNBOUNDED
788 : * FOLLOWING or CURRENT ROW, no exclusion clause is specified, and the
789 : * current row lies within the previous row's frame, then the two frames'
790 : * ends must coincide. Note that on the first row aggregatedbase ==
791 : * aggregatedupto, meaning this test must fail, so we don't need to check
792 : * the "there was no previous row" case explicitly here.
793 : */
794 160238 : if (winstate->aggregatedbase == winstate->frameheadpos &&
795 156460 : (winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
796 154540 : FRAMEOPTION_END_CURRENT_ROW)) &&
797 154540 : !(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
798 154360 : winstate->aggregatedbase <= winstate->currentpos &&
799 154324 : winstate->aggregatedupto > winstate->currentpos)
800 : {
801 302740 : for (i = 0; i < numaggs; i++)
802 : {
803 151376 : peraggstate = &winstate->peragg[i];
804 151376 : wfuncno = peraggstate->wfuncno;
805 151376 : econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
806 151376 : econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
807 : }
808 151364 : return;
809 : }
810 :
811 : /*----------
812 : * Initialize restart flags.
813 : *
814 : * We restart the aggregation:
815 : * - if we're processing the first row in the partition, or
816 : * - if the frame's head moved and we cannot use an inverse
817 : * transition function, or
818 : * - we have an EXCLUSION clause, or
819 : * - if the new frame doesn't overlap the old one
820 : *
821 : * Note that we don't strictly need to restart in the last case, but if
822 : * we're going to remove all rows from the aggregation anyway, a restart
823 : * surely is faster.
824 : *----------
825 : */
826 8874 : numaggs_restart = 0;
827 19530 : for (i = 0; i < numaggs; i++)
828 : {
829 10656 : peraggstate = &winstate->peragg[i];
830 10656 : if (winstate->currentpos == 0 ||
831 8600 : (winstate->aggregatedbase != winstate->frameheadpos &&
832 5206 : !OidIsValid(peraggstate->invtransfn_oid)) ||
833 8524 : (winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
834 7408 : winstate->aggregatedupto <= winstate->frameheadpos)
835 : {
836 3716 : peraggstate->restart = true;
837 3716 : numaggs_restart++;
838 : }
839 : else
840 6940 : peraggstate->restart = false;
841 : }
842 :
843 : /*
844 : * If we have any possibly-moving aggregates, attempt to advance
845 : * aggregatedbase to match the frame's head by removing input rows that
846 : * fell off the top of the frame from the aggregations. This can fail,
847 : * i.e. advance_windowaggregate_base() can return false, in which case
848 : * we'll restart that aggregate below.
849 : */
850 12042 : while (numaggs_restart < numaggs &&
851 8530 : winstate->aggregatedbase < winstate->frameheadpos)
852 : {
853 : /*
854 : * Fetch the next tuple of those being removed. This should never fail
855 : * as we should have been here before.
856 : */
857 3168 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
858 : temp_slot))
859 0 : elog(ERROR, "could not re-fetch previously fetched frame row");
860 :
861 : /* Set tuple context for evaluation of aggregate arguments */
862 3168 : winstate->tmpcontext->ecxt_outertuple = temp_slot;
863 :
864 : /*
865 : * Perform the inverse transition for each aggregate function in the
866 : * window, unless it has already been marked as needing a restart.
867 : */
868 7806 : for (i = 0; i < numaggs; i++)
869 : {
870 : bool ok;
871 :
872 4638 : peraggstate = &winstate->peragg[i];
873 4638 : if (peraggstate->restart)
874 12 : continue;
875 :
876 4626 : wfuncno = peraggstate->wfuncno;
877 4626 : ok = advance_windowaggregate_base(winstate,
878 4626 : &winstate->perfunc[wfuncno],
879 : peraggstate);
880 4626 : if (!ok)
881 : {
882 : /* Inverse transition function has failed, must restart */
883 254 : peraggstate->restart = true;
884 254 : numaggs_restart++;
885 : }
886 : }
887 :
888 : /* Reset per-input-tuple context after each tuple */
889 3168 : ResetExprContext(winstate->tmpcontext);
890 :
891 : /* And advance the aggregated-row state */
892 3168 : winstate->aggregatedbase++;
893 3168 : ExecClearTuple(temp_slot);
894 : }
895 :
896 : /*
897 : * If we successfully advanced the base rows of all the aggregates,
898 : * aggregatedbase now equals frameheadpos; but if we failed for any, we
899 : * must forcibly update aggregatedbase.
900 : */
901 8874 : winstate->aggregatedbase = winstate->frameheadpos;
902 :
903 : /*
904 : * If we created a mark pointer for aggregates, keep it pushed up to frame
905 : * head, so that tuplestore can discard unnecessary rows.
906 : */
907 8874 : if (agg_winobj->markptr >= 0)
908 6224 : WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
909 :
910 : /*
911 : * Now restart the aggregates that require it.
912 : *
913 : * We assume that aggregates using the shared context always restart if
914 : * *any* aggregate restarts, and we may thus clean up the shared
915 : * aggcontext if that is the case. Private aggcontexts are reset by
916 : * initialize_windowaggregate() if their owning aggregate restarts. If we
917 : * aren't restarting an aggregate, we need to free any previously saved
918 : * result for it, else we'll leak memory.
919 : */
920 8874 : if (numaggs_restart > 0)
921 3726 : MemoryContextReset(winstate->aggcontext);
922 19530 : for (i = 0; i < numaggs; i++)
923 : {
924 10656 : peraggstate = &winstate->peragg[i];
925 :
926 : /* Aggregates using the shared ctx must restart if *any* agg does */
927 : Assert(peraggstate->aggcontext != winstate->aggcontext ||
928 : numaggs_restart == 0 ||
929 : peraggstate->restart);
930 :
931 10656 : if (peraggstate->restart)
932 : {
933 3970 : wfuncno = peraggstate->wfuncno;
934 3970 : initialize_windowaggregate(winstate,
935 3970 : &winstate->perfunc[wfuncno],
936 : peraggstate);
937 : }
938 6686 : else if (!peraggstate->resultValueIsNull)
939 : {
940 6452 : if (!peraggstate->resulttypeByVal)
941 2152 : pfree(DatumGetPointer(peraggstate->resultValue));
942 6452 : peraggstate->resultValue = (Datum) 0;
943 6452 : peraggstate->resultValueIsNull = true;
944 : }
945 : }
946 :
947 : /*
948 : * Non-restarted aggregates now contain the rows between aggregatedbase
949 : * (i.e., frameheadpos) and aggregatedupto, while restarted aggregates
950 : * contain no rows. If there are any restarted aggregates, we must thus
951 : * begin aggregating anew at frameheadpos, otherwise we may simply
952 : * continue at aggregatedupto. We must remember the old value of
953 : * aggregatedupto to know how long to skip advancing non-restarted
954 : * aggregates. If we modify aggregatedupto, we must also clear
955 : * agg_row_slot, per the loop invariant below.
956 : */
957 8874 : aggregatedupto_nonrestarted = winstate->aggregatedupto;
958 8874 : if (numaggs_restart > 0 &&
959 3726 : winstate->aggregatedupto != winstate->frameheadpos)
960 : {
961 1400 : winstate->aggregatedupto = winstate->frameheadpos;
962 1400 : ExecClearTuple(agg_row_slot);
963 : }
964 :
965 : /*
966 : * Advance until we reach a row not in frame (or end of partition).
967 : *
968 : * Note the loop invariant: agg_row_slot is either empty or holds the row
969 : * at position aggregatedupto. We advance aggregatedupto after processing
970 : * a row.
971 : */
972 : for (;;)
973 175440 : {
974 : int ret;
975 :
976 : /* Fetch next row if we didn't already */
977 184314 : if (TupIsNull(agg_row_slot))
978 : {
979 180452 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
980 : agg_row_slot))
981 4148 : break; /* must be end of partition */
982 : }
983 :
984 : /*
985 : * Exit loop if no more rows can be in frame. Skip aggregation if
986 : * current row is not in frame but there might be more in the frame.
987 : */
988 180166 : ret = row_is_in_frame(agg_winobj, winstate->aggregatedupto,
989 : agg_row_slot, false);
990 180154 : if (ret < 0)
991 4702 : break;
992 175452 : if (ret == 0)
993 1896 : goto next_tuple;
994 :
995 : /* Set tuple context for evaluation of aggregate arguments */
996 173556 : winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
997 :
998 : /* Accumulate row into the aggregates */
999 367578 : for (i = 0; i < numaggs; i++)
1000 : {
1001 194034 : peraggstate = &winstate->peragg[i];
1002 :
1003 : /* Non-restarted aggs skip until aggregatedupto_nonrestarted */
1004 194034 : if (!peraggstate->restart &&
1005 115944 : winstate->aggregatedupto < aggregatedupto_nonrestarted)
1006 16292 : continue;
1007 :
1008 177742 : wfuncno = peraggstate->wfuncno;
1009 177742 : advance_windowaggregate(winstate,
1010 177742 : &winstate->perfunc[wfuncno],
1011 : peraggstate);
1012 : }
1013 :
1014 173544 : next_tuple:
1015 : /* Reset per-input-tuple context after each tuple */
1016 175440 : ResetExprContext(winstate->tmpcontext);
1017 :
1018 : /* And advance the aggregated-row state */
1019 175440 : winstate->aggregatedupto++;
1020 175440 : ExecClearTuple(agg_row_slot);
1021 : }
1022 :
1023 : /* The frame's end is not supposed to move backwards, ever */
1024 : Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
1025 :
1026 : /*
1027 : * finalize aggregates and fill result/isnull fields.
1028 : */
1029 19470 : for (i = 0; i < numaggs; i++)
1030 : {
1031 : Datum *result;
1032 : bool *isnull;
1033 :
1034 10632 : peraggstate = &winstate->peragg[i];
1035 10632 : wfuncno = peraggstate->wfuncno;
1036 10632 : result = &econtext->ecxt_aggvalues[wfuncno];
1037 10632 : isnull = &econtext->ecxt_aggnulls[wfuncno];
1038 10632 : finalize_windowaggregate(winstate,
1039 10632 : &winstate->perfunc[wfuncno],
1040 : peraggstate,
1041 : result, isnull);
1042 :
1043 : /*
1044 : * save the result in case next row shares the same frame.
1045 : *
1046 : * XXX in some framing modes, eg ROWS/END_CURRENT_ROW, we can know in
1047 : * advance that the next row can't possibly share the same frame. Is
1048 : * it worth detecting that and skipping this code?
1049 : */
1050 10620 : if (!peraggstate->resulttypeByVal && !*isnull)
1051 : {
1052 2752 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
1053 2752 : peraggstate->resultValue =
1054 2752 : datumCopy(*result,
1055 2752 : peraggstate->resulttypeByVal,
1056 2752 : peraggstate->resulttypeLen);
1057 2752 : MemoryContextSwitchTo(oldContext);
1058 : }
1059 : else
1060 : {
1061 7868 : peraggstate->resultValue = *result;
1062 : }
1063 10620 : peraggstate->resultValueIsNull = *isnull;
1064 : }
1065 : }
1066 :
1067 : /*
1068 : * eval_windowfunction
1069 : *
1070 : * Arguments of window functions are not evaluated here, because a window
1071 : * function can need random access to arbitrary rows in the partition.
1072 : * The window function uses the special WinGetFuncArgInPartition and
1073 : * WinGetFuncArgInFrame functions to evaluate the arguments for the rows
1074 : * it wants.
1075 : */
1076 : static void
1077 870928 : eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
1078 : Datum *result, bool *isnull)
1079 : {
1080 870928 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
1081 : MemoryContext oldContext;
1082 :
1083 870928 : oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
1084 :
1085 : /*
1086 : * We don't pass any normal arguments to a window function, but we do pass
1087 : * it the number of arguments, in order to permit window function
1088 : * implementations to support varying numbers of arguments. The real info
1089 : * goes through the WindowObject, which is passed via fcinfo->context.
1090 : */
1091 870928 : InitFunctionCallInfoData(*fcinfo, &(perfuncstate->flinfo),
1092 : perfuncstate->numArguments,
1093 : perfuncstate->winCollation,
1094 : (Node *) perfuncstate->winobj, NULL);
1095 : /* Just in case, make all the regular argument slots be null */
1096 1120234 : for (int argno = 0; argno < perfuncstate->numArguments; argno++)
1097 249306 : fcinfo->args[argno].isnull = true;
1098 : /* Window functions don't have a current aggregate context, either */
1099 870928 : winstate->curaggcontext = NULL;
1100 :
1101 870928 : *result = FunctionCallInvoke(fcinfo);
1102 870766 : *isnull = fcinfo->isnull;
1103 :
1104 : /*
1105 : * The window function might have returned a pass-by-ref result that's
1106 : * just a pointer into one of the WindowObject's temporary slots. That's
1107 : * not a problem if it's the only window function using the WindowObject;
1108 : * but if there's more than one function, we'd better copy the result to
1109 : * ensure it's not clobbered by later window functions.
1110 : */
1111 870766 : if (!perfuncstate->resulttypeByVal && !fcinfo->isnull &&
1112 1020 : winstate->numfuncs > 1)
1113 108 : *result = datumCopy(*result,
1114 108 : perfuncstate->resulttypeByVal,
1115 108 : perfuncstate->resulttypeLen);
1116 :
1117 870766 : MemoryContextSwitchTo(oldContext);
1118 870766 : }
1119 :
1120 : /*
1121 : * prepare_tuplestore
1122 : * Prepare the tuplestore and all of the required read pointers for the
1123 : * WindowAggState's frameOptions.
1124 : *
1125 : * Note: We use pg_noinline to avoid bloating the calling function with code
1126 : * which is only called once.
1127 : */
1128 : static pg_noinline void
1129 2286 : prepare_tuplestore(WindowAggState *winstate)
1130 : {
1131 2286 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1132 2286 : int frameOptions = winstate->frameOptions;
1133 2286 : int numfuncs = winstate->numfuncs;
1134 :
1135 : /* we shouldn't be called if this was done already */
1136 : Assert(winstate->buffer == NULL);
1137 :
1138 : /* Create new tuplestore */
1139 2286 : winstate->buffer = tuplestore_begin_heap(false, false, work_mem);
1140 :
1141 : /*
1142 : * Set up read pointers for the tuplestore. The current pointer doesn't
1143 : * need BACKWARD capability, but the per-window-function read pointers do,
1144 : * and the aggregate pointer does if we might need to restart aggregation.
1145 : */
1146 2286 : winstate->current_ptr = 0; /* read pointer 0 is pre-allocated */
1147 :
1148 : /* reset default REWIND capability bit for current ptr */
1149 2286 : tuplestore_set_eflags(winstate->buffer, 0);
1150 :
1151 : /* create read pointers for aggregates, if needed */
1152 2286 : if (winstate->numaggs > 0)
1153 : {
1154 1124 : WindowObject agg_winobj = winstate->agg_winobj;
1155 1124 : int readptr_flags = 0;
1156 :
1157 : /*
1158 : * If the frame head is potentially movable, or we have an EXCLUSION
1159 : * clause, we might need to restart aggregation ...
1160 : */
1161 1124 : if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
1162 390 : (frameOptions & FRAMEOPTION_EXCLUSION))
1163 : {
1164 : /* ... so create a mark pointer to track the frame head */
1165 752 : agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
1166 : /* and the read pointer will need BACKWARD capability */
1167 752 : readptr_flags |= EXEC_FLAG_BACKWARD;
1168 : }
1169 :
1170 1124 : agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1171 : readptr_flags);
1172 : }
1173 :
1174 : /* create mark and read pointers for each real window function */
1175 5334 : for (int i = 0; i < numfuncs; i++)
1176 : {
1177 3048 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1178 :
1179 3048 : if (!perfuncstate->plain_agg)
1180 : {
1181 1828 : WindowObject winobj = perfuncstate->winobj;
1182 :
1183 1828 : winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer,
1184 : 0);
1185 1828 : winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1186 : EXEC_FLAG_BACKWARD);
1187 : }
1188 : }
1189 :
1190 : /*
1191 : * If we are in RANGE or GROUPS mode, then determining frame boundaries
1192 : * requires physical access to the frame endpoint rows, except in certain
1193 : * degenerate cases. We create read pointers to point to those rows, to
1194 : * simplify access and ensure that the tuplestore doesn't discard the
1195 : * endpoint rows prematurely. (Must create pointers in exactly the same
1196 : * cases that update_frameheadpos and update_frametailpos need them.)
1197 : */
1198 2286 : winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */
1199 :
1200 2286 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1201 : {
1202 1262 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
1203 68 : node->ordNumCols != 0) ||
1204 1194 : (frameOptions & FRAMEOPTION_START_OFFSET))
1205 722 : winstate->framehead_ptr =
1206 722 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1207 1262 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
1208 492 : node->ordNumCols != 0) ||
1209 936 : (frameOptions & FRAMEOPTION_END_OFFSET))
1210 1040 : winstate->frametail_ptr =
1211 1040 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1212 : }
1213 :
1214 : /*
1215 : * If we have an exclusion clause that requires knowing the boundaries of
1216 : * the current row's peer group, we create a read pointer to track the
1217 : * tail position of the peer group (i.e., first row of the next peer
1218 : * group). The head position does not require its own pointer because we
1219 : * maintain that as a side effect of advancing the current row.
1220 : */
1221 2286 : winstate->grouptail_ptr = -1;
1222 :
1223 2286 : if ((frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
1224 180 : FRAMEOPTION_EXCLUDE_TIES)) &&
1225 180 : node->ordNumCols != 0)
1226 : {
1227 168 : winstate->grouptail_ptr =
1228 168 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1229 : }
1230 2286 : }
1231 :
1232 : /*
1233 : * begin_partition
1234 : * Start buffering rows of the next partition.
1235 : */
1236 : static void
1237 3608 : begin_partition(WindowAggState *winstate)
1238 : {
1239 3608 : PlanState *outerPlan = outerPlanState(winstate);
1240 3608 : int numfuncs = winstate->numfuncs;
1241 :
1242 3608 : winstate->partition_spooled = false;
1243 3608 : winstate->framehead_valid = false;
1244 3608 : winstate->frametail_valid = false;
1245 3608 : winstate->grouptail_valid = false;
1246 3608 : winstate->spooled_rows = 0;
1247 3608 : winstate->currentpos = 0;
1248 3608 : winstate->frameheadpos = 0;
1249 3608 : winstate->frametailpos = 0;
1250 3608 : winstate->currentgroup = 0;
1251 3608 : winstate->frameheadgroup = 0;
1252 3608 : winstate->frametailgroup = 0;
1253 3608 : winstate->groupheadpos = 0;
1254 3608 : winstate->grouptailpos = -1; /* see update_grouptailpos */
1255 3608 : ExecClearTuple(winstate->agg_row_slot);
1256 3608 : if (winstate->framehead_slot)
1257 1024 : ExecClearTuple(winstate->framehead_slot);
1258 3608 : if (winstate->frametail_slot)
1259 1714 : ExecClearTuple(winstate->frametail_slot);
1260 :
1261 : /*
1262 : * If this is the very first partition, we need to fetch the first input
1263 : * row to store in first_part_slot.
1264 : */
1265 3608 : if (TupIsNull(winstate->first_part_slot))
1266 : {
1267 2364 : TupleTableSlot *outerslot = ExecProcNode(outerPlan);
1268 :
1269 2364 : if (!TupIsNull(outerslot))
1270 2346 : ExecCopySlot(winstate->first_part_slot, outerslot);
1271 : else
1272 : {
1273 : /* outer plan is empty, so we have nothing to do */
1274 18 : winstate->partition_spooled = true;
1275 18 : winstate->more_partitions = false;
1276 18 : return;
1277 : }
1278 : }
1279 :
1280 : /* Create new tuplestore if not done already. */
1281 3590 : if (unlikely(winstate->buffer == NULL))
1282 2286 : prepare_tuplestore(winstate);
1283 :
1284 3590 : winstate->next_partition = false;
1285 :
1286 3590 : if (winstate->numaggs > 0)
1287 : {
1288 1864 : WindowObject agg_winobj = winstate->agg_winobj;
1289 :
1290 : /* reset mark and see positions for aggregate functions */
1291 1864 : agg_winobj->markpos = -1;
1292 1864 : agg_winobj->seekpos = -1;
1293 :
1294 : /* Also reset the row counters for aggregates */
1295 1864 : winstate->aggregatedbase = 0;
1296 1864 : winstate->aggregatedupto = 0;
1297 : }
1298 :
1299 : /* reset mark and seek positions for each real window function */
1300 8248 : for (int i = 0; i < numfuncs; i++)
1301 : {
1302 4658 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1303 :
1304 4658 : if (!perfuncstate->plain_agg)
1305 : {
1306 2596 : WindowObject winobj = perfuncstate->winobj;
1307 :
1308 2596 : winobj->markpos = -1;
1309 2596 : winobj->seekpos = -1;
1310 :
1311 : /* reset null map */
1312 2596 : if (winobj->ignore_nulls == IGNORE_NULLS ||
1313 2566 : winobj->ignore_nulls == PARSER_IGNORE_NULLS)
1314 : {
1315 216 : int numargs = perfuncstate->numArguments;
1316 :
1317 498 : for (int j = 0; j < numargs; j++)
1318 : {
1319 282 : int n = winobj->num_notnull_info[j];
1320 :
1321 282 : if (n > 0)
1322 30 : memset(winobj->notnull_info[j], 0,
1323 30 : NN_POS_TO_BYTES(n));
1324 : }
1325 : }
1326 : }
1327 : }
1328 :
1329 : /*
1330 : * Store the first tuple into the tuplestore (it's always available now;
1331 : * we either read it above, or saved it at the end of previous partition)
1332 : */
1333 3590 : tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
1334 3590 : winstate->spooled_rows++;
1335 : }
1336 :
1337 : /*
1338 : * Read tuples from the outer node, up to and including position 'pos', and
1339 : * store them into the tuplestore. If pos is -1, reads the whole partition.
1340 : */
1341 : static void
1342 1859108 : spool_tuples(WindowAggState *winstate, int64 pos)
1343 : {
1344 1859108 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1345 : PlanState *outerPlan;
1346 : TupleTableSlot *outerslot;
1347 : MemoryContext oldcontext;
1348 :
1349 1859108 : if (!winstate->buffer)
1350 6 : return; /* just a safety check */
1351 1859102 : if (winstate->partition_spooled)
1352 123026 : return; /* whole partition done already */
1353 :
1354 : /*
1355 : * When in pass-through mode we can just exhaust all tuples in the current
1356 : * partition. We don't need these tuples for any further window function
1357 : * evaluation, however, we do need to keep them around if we're not the
1358 : * top-level window as another WindowAgg node above must see these.
1359 : */
1360 1736076 : if (winstate->status != WINDOWAGG_RUN)
1361 : {
1362 : Assert(winstate->status == WINDOWAGG_PASSTHROUGH ||
1363 : winstate->status == WINDOWAGG_PASSTHROUGH_STRICT);
1364 :
1365 30 : pos = -1;
1366 : }
1367 :
1368 : /*
1369 : * If the tuplestore has spilled to disk, alternate reading and writing
1370 : * becomes quite expensive due to frequent buffer flushes. It's cheaper
1371 : * to force the entire partition to get spooled in one go.
1372 : *
1373 : * XXX this is a horrid kluge --- it'd be better to fix the performance
1374 : * problem inside tuplestore. FIXME
1375 : */
1376 1736046 : else if (!tuplestore_in_memory(winstate->buffer))
1377 12 : pos = -1;
1378 :
1379 1736076 : outerPlan = outerPlanState(winstate);
1380 :
1381 : /* Must be in query context to call outerplan */
1382 1736076 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1383 :
1384 4494040 : while (winstate->spooled_rows <= pos || pos == -1)
1385 : {
1386 1025280 : outerslot = ExecProcNode(outerPlan);
1387 1025280 : if (TupIsNull(outerslot))
1388 : {
1389 : /* reached the end of the last partition */
1390 2148 : winstate->partition_spooled = true;
1391 2148 : winstate->more_partitions = false;
1392 2148 : break;
1393 : }
1394 :
1395 1023132 : if (node->partNumCols > 0)
1396 : {
1397 138702 : ExprContext *econtext = winstate->tmpcontext;
1398 :
1399 138702 : econtext->ecxt_innertuple = winstate->first_part_slot;
1400 138702 : econtext->ecxt_outertuple = outerslot;
1401 :
1402 : /* Check if this tuple still belongs to the current partition */
1403 138702 : if (!ExecQualAndReset(winstate->partEqfunction, econtext))
1404 : {
1405 : /*
1406 : * end of partition; copy the tuple for the next cycle.
1407 : */
1408 1244 : ExecCopySlot(winstate->first_part_slot, outerslot);
1409 1244 : winstate->partition_spooled = true;
1410 1244 : winstate->more_partitions = true;
1411 1244 : break;
1412 : }
1413 : }
1414 :
1415 : /*
1416 : * Remember the tuple unless we're the top-level window and we're in
1417 : * pass-through mode.
1418 : */
1419 1021888 : if (winstate->status != WINDOWAGG_PASSTHROUGH_STRICT)
1420 : {
1421 : /* Still in partition, so save it into the tuplestore */
1422 1021876 : tuplestore_puttupleslot(winstate->buffer, outerslot);
1423 1021876 : winstate->spooled_rows++;
1424 : }
1425 : }
1426 :
1427 1736076 : MemoryContextSwitchTo(oldcontext);
1428 : }
1429 :
1430 : /*
1431 : * release_partition
1432 : * clear information kept within a partition, including
1433 : * tuplestore and aggregate results.
1434 : */
1435 : static void
1436 5980 : release_partition(WindowAggState *winstate)
1437 : {
1438 : int i;
1439 :
1440 13706 : for (i = 0; i < winstate->numfuncs; i++)
1441 : {
1442 7726 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1443 :
1444 : /* Release any partition-local state of this window function */
1445 7726 : if (perfuncstate->winobj)
1446 4126 : perfuncstate->winobj->localmem = NULL;
1447 : }
1448 :
1449 : /*
1450 : * Release all partition-local memory (in particular, any partition-local
1451 : * state that we might have trashed our pointers to in the above loop, and
1452 : * any aggregate temp data). We don't rely on retail pfree because some
1453 : * aggregates might have allocated data we don't have direct pointers to.
1454 : */
1455 5980 : MemoryContextReset(winstate->partcontext);
1456 5980 : MemoryContextReset(winstate->aggcontext);
1457 9580 : for (i = 0; i < winstate->numaggs; i++)
1458 : {
1459 3600 : if (winstate->peragg[i].aggcontext != winstate->aggcontext)
1460 1932 : MemoryContextReset(winstate->peragg[i].aggcontext);
1461 : }
1462 :
1463 5980 : if (winstate->buffer)
1464 3422 : tuplestore_clear(winstate->buffer);
1465 5980 : winstate->partition_spooled = false;
1466 5980 : winstate->next_partition = true;
1467 5980 : }
1468 :
1469 : /*
1470 : * row_is_in_frame
1471 : * Determine whether a row is in the current row's window frame according
1472 : * to our window framing rule
1473 : *
1474 : * The caller must have already determined that the row is in the partition
1475 : * and fetched it into a slot if fetch_tuple is false.
1476 : * This function just encapsulates the framing rules.
1477 : *
1478 : * Returns:
1479 : * -1, if the row is out of frame and no succeeding rows can be in frame
1480 : * 0, if the row is out of frame but succeeding rows might be in frame
1481 : * 1, if the row is in frame
1482 : *
1483 : * May clobber winstate->temp_slot_2.
1484 : */
1485 : static int
1486 190930 : row_is_in_frame(WindowObject winobj, int64 pos, TupleTableSlot *slot,
1487 : bool fetch_tuple)
1488 : {
1489 190930 : WindowAggState *winstate = winobj->winstate;
1490 190930 : int frameOptions = winstate->frameOptions;
1491 :
1492 : Assert(pos >= 0); /* else caller error */
1493 :
1494 : /*
1495 : * First, check frame starting conditions. We might as well delegate this
1496 : * to update_frameheadpos always; it doesn't add any notable cost.
1497 : */
1498 190930 : update_frameheadpos(winstate);
1499 190930 : if (pos < winstate->frameheadpos)
1500 144 : return 0;
1501 :
1502 : /*
1503 : * Okay so far, now check frame ending conditions. Here, we avoid calling
1504 : * update_frametailpos in simple cases, so as not to spool tuples further
1505 : * ahead than necessary.
1506 : */
1507 190786 : if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1508 : {
1509 158122 : if (frameOptions & FRAMEOPTION_ROWS)
1510 : {
1511 : /* rows after current row are out of frame */
1512 2208 : if (pos > winstate->currentpos)
1513 972 : return -1;
1514 : }
1515 155914 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1516 : {
1517 : /* following row that is not peer is out of frame */
1518 155914 : if (pos > winstate->currentpos)
1519 : {
1520 152544 : if (fetch_tuple) /* need to fetch tuple? */
1521 0 : if (!window_gettupleslot(winobj, pos, slot))
1522 0 : return -1;
1523 152544 : if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
1524 1264 : return -1;
1525 : }
1526 : }
1527 : else
1528 : Assert(false);
1529 : }
1530 32664 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1531 : {
1532 19914 : if (frameOptions & FRAMEOPTION_ROWS)
1533 : {
1534 5928 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1535 :
1536 : /* rows after current row + offset are out of frame */
1537 5928 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1538 114 : offset = -offset;
1539 :
1540 5928 : if (pos > winstate->currentpos + offset)
1541 1206 : return -1;
1542 : }
1543 13986 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1544 : {
1545 : /* hard cases, so delegate to update_frametailpos */
1546 13986 : update_frametailpos(winstate);
1547 13944 : if (pos >= winstate->frametailpos)
1548 1470 : return -1;
1549 : }
1550 : else
1551 : Assert(false);
1552 : }
1553 :
1554 : /* Check exclusion clause */
1555 185832 : if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
1556 : {
1557 2946 : if (pos == winstate->currentpos)
1558 498 : return 0;
1559 : }
1560 182886 : else if ((frameOptions & FRAMEOPTION_EXCLUDE_GROUP) ||
1561 180024 : ((frameOptions & FRAMEOPTION_EXCLUDE_TIES) &&
1562 2970 : pos != winstate->currentpos))
1563 : {
1564 5292 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1565 :
1566 : /* If no ORDER BY, all rows are peers with each other */
1567 5292 : if (node->ordNumCols == 0)
1568 468 : return 0;
1569 : /* Otherwise, check the group boundaries */
1570 4824 : if (pos >= winstate->groupheadpos)
1571 : {
1572 2592 : update_grouptailpos(winstate);
1573 2592 : if (pos < winstate->grouptailpos)
1574 1008 : return 0;
1575 : }
1576 : }
1577 :
1578 : /* If we get here, it's in frame */
1579 183858 : return 1;
1580 : }
1581 :
1582 : /*
1583 : * update_frameheadpos
1584 : * make frameheadpos valid for the current row
1585 : *
1586 : * Note that frameheadpos is computed without regard for any window exclusion
1587 : * clause; the current row and/or its peers are considered part of the frame
1588 : * for this purpose even if they must be excluded later.
1589 : *
1590 : * May clobber winstate->temp_slot_2.
1591 : */
1592 : static void
1593 362996 : update_frameheadpos(WindowAggState *winstate)
1594 : {
1595 362996 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1596 362996 : int frameOptions = winstate->frameOptions;
1597 : MemoryContext oldcontext;
1598 :
1599 362996 : if (winstate->framehead_valid)
1600 197418 : return; /* already known for current row */
1601 :
1602 : /* We may be called in a short-lived context */
1603 165578 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1604 :
1605 165578 : if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
1606 : {
1607 : /* In UNBOUNDED PRECEDING mode, frame head is always row 0 */
1608 154878 : winstate->frameheadpos = 0;
1609 154878 : winstate->framehead_valid = true;
1610 : }
1611 10700 : else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
1612 : {
1613 2804 : if (frameOptions & FRAMEOPTION_ROWS)
1614 : {
1615 : /* In ROWS mode, frame head is the same as current */
1616 2376 : winstate->frameheadpos = winstate->currentpos;
1617 2376 : winstate->framehead_valid = true;
1618 : }
1619 428 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1620 : {
1621 : /* If no ORDER BY, all rows are peers with each other */
1622 428 : if (node->ordNumCols == 0)
1623 : {
1624 0 : winstate->frameheadpos = 0;
1625 0 : winstate->framehead_valid = true;
1626 0 : MemoryContextSwitchTo(oldcontext);
1627 0 : return;
1628 : }
1629 :
1630 : /*
1631 : * In RANGE or GROUPS START_CURRENT_ROW mode, frame head is the
1632 : * first row that is a peer of current row. We keep a copy of the
1633 : * last-known frame head row in framehead_slot, and advance as
1634 : * necessary. Note that if we reach end of partition, we will
1635 : * leave frameheadpos = end+1 and framehead_slot empty.
1636 : */
1637 428 : tuplestore_select_read_pointer(winstate->buffer,
1638 : winstate->framehead_ptr);
1639 428 : if (winstate->frameheadpos == 0 &&
1640 212 : TupIsNull(winstate->framehead_slot))
1641 : {
1642 : /* fetch first row into framehead_slot, if we didn't already */
1643 82 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1644 : winstate->framehead_slot))
1645 0 : elog(ERROR, "unexpected end of tuplestore");
1646 : }
1647 :
1648 744 : while (!TupIsNull(winstate->framehead_slot))
1649 : {
1650 744 : if (are_peers(winstate, winstate->framehead_slot,
1651 : winstate->ss.ss_ScanTupleSlot))
1652 428 : break; /* this row is the correct frame head */
1653 : /* Note we advance frameheadpos even if the fetch fails */
1654 316 : winstate->frameheadpos++;
1655 316 : spool_tuples(winstate, winstate->frameheadpos);
1656 316 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1657 : winstate->framehead_slot))
1658 0 : break; /* end of partition */
1659 : }
1660 428 : winstate->framehead_valid = true;
1661 : }
1662 : else
1663 : Assert(false);
1664 : }
1665 7896 : else if (frameOptions & FRAMEOPTION_START_OFFSET)
1666 : {
1667 7896 : if (frameOptions & FRAMEOPTION_ROWS)
1668 : {
1669 : /* In ROWS mode, bound is physically n before/after current */
1670 1992 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1671 :
1672 1992 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1673 1932 : offset = -offset;
1674 :
1675 1992 : winstate->frameheadpos = winstate->currentpos + offset;
1676 : /* frame head can't go before first row */
1677 1992 : if (winstate->frameheadpos < 0)
1678 336 : winstate->frameheadpos = 0;
1679 1656 : else if (winstate->frameheadpos > winstate->currentpos + 1)
1680 : {
1681 : /* make sure frameheadpos is not past end of partition */
1682 0 : spool_tuples(winstate, winstate->frameheadpos - 1);
1683 0 : if (winstate->frameheadpos > winstate->spooled_rows)
1684 0 : winstate->frameheadpos = winstate->spooled_rows;
1685 : }
1686 1992 : winstate->framehead_valid = true;
1687 : }
1688 5904 : else if (frameOptions & FRAMEOPTION_RANGE)
1689 : {
1690 : /*
1691 : * In RANGE START_OFFSET mode, frame head is the first row that
1692 : * satisfies the in_range constraint relative to the current row.
1693 : * We keep a copy of the last-known frame head row in
1694 : * framehead_slot, and advance as necessary. Note that if we
1695 : * reach end of partition, we will leave frameheadpos = end+1 and
1696 : * framehead_slot empty.
1697 : */
1698 4524 : int sortCol = node->ordColIdx[0];
1699 : bool sub,
1700 : less;
1701 :
1702 : /* We must have an ordering column */
1703 : Assert(node->ordNumCols == 1);
1704 :
1705 : /* Precompute flags for in_range checks */
1706 4524 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1707 3702 : sub = true; /* subtract startOffset from current row */
1708 : else
1709 822 : sub = false; /* add it */
1710 4524 : less = false; /* normally, we want frame head >= sum */
1711 : /* If sort order is descending, flip both flags */
1712 4524 : if (!winstate->inRangeAsc)
1713 : {
1714 654 : sub = !sub;
1715 654 : less = true;
1716 : }
1717 :
1718 4524 : tuplestore_select_read_pointer(winstate->buffer,
1719 : winstate->framehead_ptr);
1720 4524 : if (winstate->frameheadpos == 0 &&
1721 2502 : TupIsNull(winstate->framehead_slot))
1722 : {
1723 : /* fetch first row into framehead_slot, if we didn't already */
1724 570 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1725 : winstate->framehead_slot))
1726 0 : elog(ERROR, "unexpected end of tuplestore");
1727 : }
1728 :
1729 7266 : while (!TupIsNull(winstate->framehead_slot))
1730 : {
1731 : Datum headval,
1732 : currval;
1733 : bool headisnull,
1734 : currisnull;
1735 :
1736 7062 : headval = slot_getattr(winstate->framehead_slot, sortCol,
1737 : &headisnull);
1738 7062 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1739 : &currisnull);
1740 7062 : if (headisnull || currisnull)
1741 : {
1742 : /* order of the rows depends only on nulls_first */
1743 108 : if (winstate->inRangeNullsFirst)
1744 : {
1745 : /* advance head if head is null and curr is not */
1746 48 : if (!headisnull || currisnull)
1747 : break;
1748 : }
1749 : else
1750 : {
1751 : /* advance head if head is not null and curr is null */
1752 60 : if (headisnull || !currisnull)
1753 : break;
1754 : }
1755 : }
1756 : else
1757 : {
1758 6954 : if (DatumGetBool(FunctionCall5Coll(&winstate->startInRangeFunc,
1759 : winstate->inRangeColl,
1760 : headval,
1761 : currval,
1762 : winstate->startOffsetValue,
1763 : BoolGetDatum(sub),
1764 : BoolGetDatum(less))))
1765 4170 : break; /* this row is the correct frame head */
1766 : }
1767 : /* Note we advance frameheadpos even if the fetch fails */
1768 2796 : winstate->frameheadpos++;
1769 2796 : spool_tuples(winstate, winstate->frameheadpos);
1770 2796 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1771 : winstate->framehead_slot))
1772 54 : break; /* end of partition */
1773 : }
1774 4476 : winstate->framehead_valid = true;
1775 : }
1776 1380 : else if (frameOptions & FRAMEOPTION_GROUPS)
1777 : {
1778 : /*
1779 : * In GROUPS START_OFFSET mode, frame head is the first row of the
1780 : * first peer group whose number satisfies the offset constraint.
1781 : * We keep a copy of the last-known frame head row in
1782 : * framehead_slot, and advance as necessary. Note that if we
1783 : * reach end of partition, we will leave frameheadpos = end+1 and
1784 : * framehead_slot empty.
1785 : */
1786 1380 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1787 : int64 minheadgroup;
1788 :
1789 1380 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1790 1128 : minheadgroup = winstate->currentgroup - offset;
1791 : else
1792 252 : minheadgroup = winstate->currentgroup + offset;
1793 :
1794 1380 : tuplestore_select_read_pointer(winstate->buffer,
1795 : winstate->framehead_ptr);
1796 1380 : if (winstate->frameheadpos == 0 &&
1797 750 : TupIsNull(winstate->framehead_slot))
1798 : {
1799 : /* fetch first row into framehead_slot, if we didn't already */
1800 372 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1801 : winstate->framehead_slot))
1802 0 : elog(ERROR, "unexpected end of tuplestore");
1803 : }
1804 :
1805 3522 : while (!TupIsNull(winstate->framehead_slot))
1806 : {
1807 2118 : if (winstate->frameheadgroup >= minheadgroup)
1808 1320 : break; /* this row is the correct frame head */
1809 798 : ExecCopySlot(winstate->temp_slot_2, winstate->framehead_slot);
1810 : /* Note we advance frameheadpos even if the fetch fails */
1811 798 : winstate->frameheadpos++;
1812 798 : spool_tuples(winstate, winstate->frameheadpos);
1813 798 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1814 : winstate->framehead_slot))
1815 36 : break; /* end of partition */
1816 762 : if (!are_peers(winstate, winstate->temp_slot_2,
1817 : winstate->framehead_slot))
1818 522 : winstate->frameheadgroup++;
1819 : }
1820 1380 : ExecClearTuple(winstate->temp_slot_2);
1821 1380 : winstate->framehead_valid = true;
1822 : }
1823 : else
1824 : Assert(false);
1825 : }
1826 : else
1827 : Assert(false);
1828 :
1829 165530 : MemoryContextSwitchTo(oldcontext);
1830 : }
1831 :
1832 : /*
1833 : * update_frametailpos
1834 : * make frametailpos valid for the current row
1835 : *
1836 : * Note that frametailpos is computed without regard for any window exclusion
1837 : * clause; the current row and/or its peers are considered part of the frame
1838 : * for this purpose even if they must be excluded later.
1839 : *
1840 : * May clobber winstate->temp_slot_2.
1841 : */
1842 : static void
1843 202742 : update_frametailpos(WindowAggState *winstate)
1844 : {
1845 202742 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1846 202742 : int frameOptions = winstate->frameOptions;
1847 : MemoryContext oldcontext;
1848 :
1849 202742 : if (winstate->frametail_valid)
1850 17940 : return; /* already known for current row */
1851 :
1852 : /* We may be called in a short-lived context */
1853 184802 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1854 :
1855 184802 : if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
1856 : {
1857 : /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
1858 240 : spool_tuples(winstate, -1);
1859 240 : winstate->frametailpos = winstate->spooled_rows;
1860 240 : winstate->frametail_valid = true;
1861 : }
1862 184562 : else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1863 : {
1864 177938 : if (frameOptions & FRAMEOPTION_ROWS)
1865 : {
1866 : /* In ROWS mode, exactly the rows up to current are in frame */
1867 120 : winstate->frametailpos = winstate->currentpos + 1;
1868 120 : winstate->frametail_valid = true;
1869 : }
1870 177818 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1871 : {
1872 : /* If no ORDER BY, all rows are peers with each other */
1873 177818 : if (node->ordNumCols == 0)
1874 : {
1875 60 : spool_tuples(winstate, -1);
1876 60 : winstate->frametailpos = winstate->spooled_rows;
1877 60 : winstate->frametail_valid = true;
1878 60 : MemoryContextSwitchTo(oldcontext);
1879 60 : return;
1880 : }
1881 :
1882 : /*
1883 : * In RANGE or GROUPS END_CURRENT_ROW mode, frame end is the last
1884 : * row that is a peer of current row, frame tail is the row after
1885 : * that (if any). We keep a copy of the last-known frame tail row
1886 : * in frametail_slot, and advance as necessary. Note that if we
1887 : * reach end of partition, we will leave frametailpos = end+1 and
1888 : * frametail_slot empty.
1889 : */
1890 177758 : tuplestore_select_read_pointer(winstate->buffer,
1891 : winstate->frametail_ptr);
1892 177758 : if (winstate->frametailpos == 0 &&
1893 694 : TupIsNull(winstate->frametail_slot))
1894 : {
1895 : /* fetch first row into frametail_slot, if we didn't already */
1896 694 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1897 : winstate->frametail_slot))
1898 0 : elog(ERROR, "unexpected end of tuplestore");
1899 : }
1900 :
1901 354834 : while (!TupIsNull(winstate->frametail_slot))
1902 : {
1903 330714 : if (winstate->frametailpos > winstate->currentpos &&
1904 273316 : !are_peers(winstate, winstate->frametail_slot,
1905 : winstate->ss.ss_ScanTupleSlot))
1906 152956 : break; /* this row is the frame tail */
1907 : /* Note we advance frametailpos even if the fetch fails */
1908 177758 : winstate->frametailpos++;
1909 177758 : spool_tuples(winstate, winstate->frametailpos);
1910 177758 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1911 : winstate->frametail_slot))
1912 682 : break; /* end of partition */
1913 : }
1914 177758 : winstate->frametail_valid = true;
1915 : }
1916 : else
1917 : Assert(false);
1918 : }
1919 6624 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1920 : {
1921 6624 : if (frameOptions & FRAMEOPTION_ROWS)
1922 : {
1923 : /* In ROWS mode, bound is physically n before/after current */
1924 420 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1925 :
1926 420 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1927 0 : offset = -offset;
1928 :
1929 420 : winstate->frametailpos = winstate->currentpos + offset + 1;
1930 : /* smallest allowable value of frametailpos is 0 */
1931 420 : if (winstate->frametailpos < 0)
1932 0 : winstate->frametailpos = 0;
1933 420 : else if (winstate->frametailpos > winstate->currentpos + 1)
1934 : {
1935 : /* make sure frametailpos is not past end of partition */
1936 420 : spool_tuples(winstate, winstate->frametailpos - 1);
1937 420 : if (winstate->frametailpos > winstate->spooled_rows)
1938 96 : winstate->frametailpos = winstate->spooled_rows;
1939 : }
1940 420 : winstate->frametail_valid = true;
1941 : }
1942 6204 : else if (frameOptions & FRAMEOPTION_RANGE)
1943 : {
1944 : /*
1945 : * In RANGE END_OFFSET mode, frame end is the last row that
1946 : * satisfies the in_range constraint relative to the current row,
1947 : * frame tail is the row after that (if any). We keep a copy of
1948 : * the last-known frame tail row in frametail_slot, and advance as
1949 : * necessary. Note that if we reach end of partition, we will
1950 : * leave frametailpos = end+1 and frametail_slot empty.
1951 : */
1952 4884 : int sortCol = node->ordColIdx[0];
1953 : bool sub,
1954 : less;
1955 :
1956 : /* We must have an ordering column */
1957 : Assert(node->ordNumCols == 1);
1958 :
1959 : /* Precompute flags for in_range checks */
1960 4884 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1961 912 : sub = true; /* subtract endOffset from current row */
1962 : else
1963 3972 : sub = false; /* add it */
1964 4884 : less = true; /* normally, we want frame tail <= sum */
1965 : /* If sort order is descending, flip both flags */
1966 4884 : if (!winstate->inRangeAsc)
1967 : {
1968 690 : sub = !sub;
1969 690 : less = false;
1970 : }
1971 :
1972 4884 : tuplestore_select_read_pointer(winstate->buffer,
1973 : winstate->frametail_ptr);
1974 4884 : if (winstate->frametailpos == 0 &&
1975 822 : TupIsNull(winstate->frametail_slot))
1976 : {
1977 : /* fetch first row into frametail_slot, if we didn't already */
1978 588 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1979 : winstate->frametail_slot))
1980 0 : elog(ERROR, "unexpected end of tuplestore");
1981 : }
1982 :
1983 9006 : while (!TupIsNull(winstate->frametail_slot))
1984 : {
1985 : Datum tailval,
1986 : currval;
1987 : bool tailisnull,
1988 : currisnull;
1989 :
1990 7440 : tailval = slot_getattr(winstate->frametail_slot, sortCol,
1991 : &tailisnull);
1992 7440 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1993 : &currisnull);
1994 7440 : if (tailisnull || currisnull)
1995 : {
1996 : /* order of the rows depends only on nulls_first */
1997 108 : if (winstate->inRangeNullsFirst)
1998 : {
1999 : /* advance tail if tail is null or curr is not */
2000 48 : if (!tailisnull)
2001 3270 : break;
2002 : }
2003 : else
2004 : {
2005 : /* advance tail if tail is not null or curr is null */
2006 60 : if (!currisnull)
2007 36 : break;
2008 : }
2009 : }
2010 : else
2011 : {
2012 7332 : if (!DatumGetBool(FunctionCall5Coll(&winstate->endInRangeFunc,
2013 : winstate->inRangeColl,
2014 : tailval,
2015 : currval,
2016 : winstate->endOffsetValue,
2017 : BoolGetDatum(sub),
2018 : BoolGetDatum(less))))
2019 2730 : break; /* this row is the correct frame tail */
2020 : }
2021 : /* Note we advance frametailpos even if the fetch fails */
2022 4602 : winstate->frametailpos++;
2023 4602 : spool_tuples(winstate, winstate->frametailpos);
2024 4602 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2025 : winstate->frametail_slot))
2026 480 : break; /* end of partition */
2027 : }
2028 4836 : winstate->frametail_valid = true;
2029 : }
2030 1320 : else if (frameOptions & FRAMEOPTION_GROUPS)
2031 : {
2032 : /*
2033 : * In GROUPS END_OFFSET mode, frame end is the last row of the
2034 : * last peer group whose number satisfies the offset constraint,
2035 : * and frame tail is the row after that (if any). We keep a copy
2036 : * of the last-known frame tail row in frametail_slot, and advance
2037 : * as necessary. Note that if we reach end of partition, we will
2038 : * leave frametailpos = end+1 and frametail_slot empty.
2039 : */
2040 1320 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
2041 : int64 maxtailgroup;
2042 :
2043 1320 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
2044 72 : maxtailgroup = winstate->currentgroup - offset;
2045 : else
2046 1248 : maxtailgroup = winstate->currentgroup + offset;
2047 :
2048 1320 : tuplestore_select_read_pointer(winstate->buffer,
2049 : winstate->frametail_ptr);
2050 1320 : if (winstate->frametailpos == 0 &&
2051 384 : TupIsNull(winstate->frametail_slot))
2052 : {
2053 : /* fetch first row into frametail_slot, if we didn't already */
2054 366 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2055 : winstate->frametail_slot))
2056 0 : elog(ERROR, "unexpected end of tuplestore");
2057 : }
2058 :
2059 3588 : while (!TupIsNull(winstate->frametail_slot))
2060 : {
2061 2040 : if (winstate->frametailgroup > maxtailgroup)
2062 744 : break; /* this row is the correct frame tail */
2063 1296 : ExecCopySlot(winstate->temp_slot_2, winstate->frametail_slot);
2064 : /* Note we advance frametailpos even if the fetch fails */
2065 1296 : winstate->frametailpos++;
2066 1296 : spool_tuples(winstate, winstate->frametailpos);
2067 1296 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2068 : winstate->frametail_slot))
2069 348 : break; /* end of partition */
2070 948 : if (!are_peers(winstate, winstate->temp_slot_2,
2071 : winstate->frametail_slot))
2072 600 : winstate->frametailgroup++;
2073 : }
2074 1320 : ExecClearTuple(winstate->temp_slot_2);
2075 1320 : winstate->frametail_valid = true;
2076 : }
2077 : else
2078 : Assert(false);
2079 : }
2080 : else
2081 : Assert(false);
2082 :
2083 184694 : MemoryContextSwitchTo(oldcontext);
2084 : }
2085 :
2086 : /*
2087 : * update_grouptailpos
2088 : * make grouptailpos valid for the current row
2089 : *
2090 : * May clobber winstate->temp_slot_2.
2091 : */
2092 : static void
2093 4872 : update_grouptailpos(WindowAggState *winstate)
2094 : {
2095 4872 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
2096 : MemoryContext oldcontext;
2097 :
2098 4872 : if (winstate->grouptail_valid)
2099 3954 : return; /* already known for current row */
2100 :
2101 : /* We may be called in a short-lived context */
2102 918 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
2103 :
2104 : /* If no ORDER BY, all rows are peers with each other */
2105 918 : if (node->ordNumCols == 0)
2106 : {
2107 0 : spool_tuples(winstate, -1);
2108 0 : winstate->grouptailpos = winstate->spooled_rows;
2109 0 : winstate->grouptail_valid = true;
2110 0 : MemoryContextSwitchTo(oldcontext);
2111 0 : return;
2112 : }
2113 :
2114 : /*
2115 : * Because grouptail_valid is reset only when current row advances into a
2116 : * new peer group, we always reach here knowing that grouptailpos needs to
2117 : * be advanced by at least one row. Hence, unlike the otherwise similar
2118 : * case for frame tail tracking, we do not need persistent storage of the
2119 : * group tail row.
2120 : */
2121 : Assert(winstate->grouptailpos <= winstate->currentpos);
2122 918 : tuplestore_select_read_pointer(winstate->buffer,
2123 : winstate->grouptail_ptr);
2124 : for (;;)
2125 : {
2126 : /* Note we advance grouptailpos even if the fetch fails */
2127 1758 : winstate->grouptailpos++;
2128 1758 : spool_tuples(winstate, winstate->grouptailpos);
2129 1758 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2130 : winstate->temp_slot_2))
2131 258 : break; /* end of partition */
2132 1500 : if (winstate->grouptailpos > winstate->currentpos &&
2133 1242 : !are_peers(winstate, winstate->temp_slot_2,
2134 : winstate->ss.ss_ScanTupleSlot))
2135 660 : break; /* this row is the group tail */
2136 : }
2137 918 : ExecClearTuple(winstate->temp_slot_2);
2138 918 : winstate->grouptail_valid = true;
2139 :
2140 918 : MemoryContextSwitchTo(oldcontext);
2141 : }
2142 :
2143 : /*
2144 : * calculate_frame_offsets
2145 : * Determine the startOffsetValue and endOffsetValue values for the
2146 : * WindowAgg's frame options.
2147 : */
2148 : static pg_noinline void
2149 2364 : calculate_frame_offsets(PlanState *pstate)
2150 : {
2151 2364 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2152 : ExprContext *econtext;
2153 2364 : int frameOptions = winstate->frameOptions;
2154 : Datum value;
2155 : bool isnull;
2156 : int16 len;
2157 : bool byval;
2158 :
2159 : /* Ensure we've not been called before for this scan */
2160 : Assert(winstate->all_first);
2161 :
2162 2364 : econtext = winstate->ss.ps.ps_ExprContext;
2163 :
2164 2364 : if (frameOptions & FRAMEOPTION_START_OFFSET)
2165 : {
2166 : Assert(winstate->startOffset != NULL);
2167 864 : value = ExecEvalExprSwitchContext(winstate->startOffset,
2168 : econtext,
2169 : &isnull);
2170 864 : if (isnull)
2171 0 : ereport(ERROR,
2172 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
2173 : errmsg("frame starting offset must not be null")));
2174 : /* copy value into query-lifespan context */
2175 864 : get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
2176 : &len,
2177 : &byval);
2178 864 : winstate->startOffsetValue = datumCopy(value, byval, len);
2179 864 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2180 : {
2181 : /* value is known to be int8 */
2182 348 : int64 offset = DatumGetInt64(value);
2183 :
2184 348 : if (offset < 0)
2185 0 : ereport(ERROR,
2186 : (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
2187 : errmsg("frame starting offset must not be negative")));
2188 : }
2189 : }
2190 :
2191 2364 : if (frameOptions & FRAMEOPTION_END_OFFSET)
2192 : {
2193 : Assert(winstate->endOffset != NULL);
2194 960 : value = ExecEvalExprSwitchContext(winstate->endOffset,
2195 : econtext,
2196 : &isnull);
2197 960 : if (isnull)
2198 0 : ereport(ERROR,
2199 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
2200 : errmsg("frame ending offset must not be null")));
2201 : /* copy value into query-lifespan context */
2202 960 : get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
2203 : &len,
2204 : &byval);
2205 960 : winstate->endOffsetValue = datumCopy(value, byval, len);
2206 960 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2207 : {
2208 : /* value is known to be int8 */
2209 378 : int64 offset = DatumGetInt64(value);
2210 :
2211 378 : if (offset < 0)
2212 0 : ereport(ERROR,
2213 : (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
2214 : errmsg("frame ending offset must not be negative")));
2215 : }
2216 : }
2217 2364 : winstate->all_first = false;
2218 2364 : }
2219 :
2220 : /* -----------------
2221 : * ExecWindowAgg
2222 : *
2223 : * ExecWindowAgg receives tuples from its outer subplan and
2224 : * stores them into a tuplestore, then processes window functions.
2225 : * This node doesn't reduce nor qualify any row so the number of
2226 : * returned rows is exactly the same as its outer subplan's result.
2227 : * -----------------
2228 : */
2229 : static TupleTableSlot *
2230 907134 : ExecWindowAgg(PlanState *pstate)
2231 : {
2232 907134 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2233 : TupleTableSlot *slot;
2234 : ExprContext *econtext;
2235 : int i;
2236 : int numfuncs;
2237 :
2238 907134 : CHECK_FOR_INTERRUPTS();
2239 :
2240 907134 : if (winstate->status == WINDOWAGG_DONE)
2241 0 : return NULL;
2242 :
2243 : /*
2244 : * Compute frame offset values, if any, during first call (or after a
2245 : * rescan). These are assumed to hold constant throughout the scan; if
2246 : * user gives us a volatile expression, we'll only use its initial value.
2247 : */
2248 907134 : if (unlikely(winstate->all_first))
2249 2364 : calculate_frame_offsets(pstate);
2250 :
2251 : /* We need to loop as the runCondition or qual may filter out tuples */
2252 : for (;;)
2253 : {
2254 907266 : if (winstate->next_partition)
2255 : {
2256 : /* Initialize for first partition and set current row = 0 */
2257 2364 : begin_partition(winstate);
2258 : /* If there are no input rows, we'll detect that and exit below */
2259 : }
2260 : else
2261 : {
2262 : /* Advance current row within partition */
2263 904902 : winstate->currentpos++;
2264 : /* This might mean that the frame moves, too */
2265 904902 : winstate->framehead_valid = false;
2266 904902 : winstate->frametail_valid = false;
2267 : /* we don't need to invalidate grouptail here; see below */
2268 : }
2269 :
2270 : /*
2271 : * Spool all tuples up to and including the current row, if we haven't
2272 : * already
2273 : */
2274 907266 : spool_tuples(winstate, winstate->currentpos);
2275 :
2276 : /* Move to the next partition if we reached the end of this partition */
2277 907266 : if (winstate->partition_spooled &&
2278 63014 : winstate->currentpos >= winstate->spooled_rows)
2279 : {
2280 3356 : release_partition(winstate);
2281 :
2282 3356 : if (winstate->more_partitions)
2283 : {
2284 1244 : begin_partition(winstate);
2285 : Assert(winstate->spooled_rows > 0);
2286 :
2287 : /* Come out of pass-through mode when changing partition */
2288 1244 : winstate->status = WINDOWAGG_RUN;
2289 : }
2290 : else
2291 : {
2292 : /* No further partitions? We're done */
2293 2112 : winstate->status = WINDOWAGG_DONE;
2294 2112 : return NULL;
2295 : }
2296 : }
2297 :
2298 : /* final output execution is in ps_ExprContext */
2299 905154 : econtext = winstate->ss.ps.ps_ExprContext;
2300 :
2301 : /* Clear the per-output-tuple context for current row */
2302 905154 : ResetExprContext(econtext);
2303 :
2304 : /*
2305 : * Read the current row from the tuplestore, and save in
2306 : * ScanTupleSlot. (We can't rely on the outerplan's output slot
2307 : * because we may have to read beyond the current row. Also, we have
2308 : * to actually copy the row out of the tuplestore, since window
2309 : * function evaluation might cause the tuplestore to dump its state to
2310 : * disk.)
2311 : *
2312 : * In GROUPS mode, or when tracking a group-oriented exclusion clause,
2313 : * we must also detect entering a new peer group and update associated
2314 : * state when that happens. We use temp_slot_2 to temporarily hold
2315 : * the previous row for this purpose.
2316 : *
2317 : * Current row must be in the tuplestore, since we spooled it above.
2318 : */
2319 905154 : tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);
2320 905154 : if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |
2321 : FRAMEOPTION_EXCLUDE_GROUP |
2322 2898 : FRAMEOPTION_EXCLUDE_TIES)) &&
2323 2898 : winstate->currentpos > 0)
2324 : {
2325 2358 : ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot);
2326 2358 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2327 : winstate->ss.ss_ScanTupleSlot))
2328 0 : elog(ERROR, "unexpected end of tuplestore");
2329 2358 : if (!are_peers(winstate, winstate->temp_slot_2,
2330 : winstate->ss.ss_ScanTupleSlot))
2331 : {
2332 1242 : winstate->currentgroup++;
2333 1242 : winstate->groupheadpos = winstate->currentpos;
2334 1242 : winstate->grouptail_valid = false;
2335 : }
2336 2358 : ExecClearTuple(winstate->temp_slot_2);
2337 : }
2338 : else
2339 : {
2340 902796 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2341 : winstate->ss.ss_ScanTupleSlot))
2342 0 : elog(ERROR, "unexpected end of tuplestore");
2343 : }
2344 :
2345 : /* don't evaluate the window functions when we're in pass-through mode */
2346 905154 : if (winstate->status == WINDOWAGG_RUN)
2347 : {
2348 : /*
2349 : * Evaluate true window functions
2350 : */
2351 905088 : numfuncs = winstate->numfuncs;
2352 1937892 : for (i = 0; i < numfuncs; i++)
2353 : {
2354 1032966 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
2355 :
2356 1032966 : if (perfuncstate->plain_agg)
2357 162038 : continue;
2358 870928 : eval_windowfunction(winstate, perfuncstate,
2359 870928 : &(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),
2360 870928 : &(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));
2361 : }
2362 :
2363 : /*
2364 : * Evaluate aggregates
2365 : */
2366 904926 : if (winstate->numaggs > 0)
2367 160244 : eval_windowaggregates(winstate);
2368 : }
2369 :
2370 : /*
2371 : * If we have created auxiliary read pointers for the frame or group
2372 : * boundaries, force them to be kept up-to-date, because we don't know
2373 : * whether the window function(s) will do anything that requires that.
2374 : * Failing to advance the pointers would result in being unable to
2375 : * trim data from the tuplestore, which is bad. (If we could know in
2376 : * advance whether the window functions will use frame boundary info,
2377 : * we could skip creating these pointers in the first place ... but
2378 : * unfortunately the window function API doesn't require that.)
2379 : */
2380 904950 : if (winstate->framehead_ptr >= 0)
2381 6236 : update_frameheadpos(winstate);
2382 904950 : if (winstate->frametail_ptr >= 0)
2383 183914 : update_frametailpos(winstate);
2384 904950 : if (winstate->grouptail_ptr >= 0)
2385 1500 : update_grouptailpos(winstate);
2386 :
2387 : /*
2388 : * Truncate any no-longer-needed rows from the tuplestore.
2389 : */
2390 904950 : tuplestore_trim(winstate->buffer);
2391 :
2392 : /*
2393 : * Form and return a projection tuple using the windowfunc results and
2394 : * the current row. Setting ecxt_outertuple arranges that any Vars
2395 : * will be evaluated with respect to that row.
2396 : */
2397 904950 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
2398 :
2399 904950 : slot = ExecProject(winstate->ss.ps.ps_ProjInfo);
2400 :
2401 904950 : if (winstate->status == WINDOWAGG_RUN)
2402 : {
2403 904884 : econtext->ecxt_scantuple = slot;
2404 :
2405 : /*
2406 : * Now evaluate the run condition to see if we need to go into
2407 : * pass-through mode, or maybe stop completely.
2408 : */
2409 904884 : if (!ExecQual(winstate->runcondition, econtext))
2410 : {
2411 : /*
2412 : * Determine which mode to move into. If there is no
2413 : * PARTITION BY clause and we're the top-level WindowAgg then
2414 : * we're done. This tuple and any future tuples cannot
2415 : * possibly match the runcondition. However, when there is a
2416 : * PARTITION BY clause or we're not the top-level window we
2417 : * can't just stop as we need to either process other
2418 : * partitions or ensure WindowAgg nodes above us receive all
2419 : * of the tuples they need to process their WindowFuncs.
2420 : */
2421 132 : if (winstate->use_pass_through)
2422 : {
2423 : /*
2424 : * When switching into a pass-through mode, we'd better
2425 : * NULLify the aggregate results as these are no longer
2426 : * updated and NULLifying them avoids the old stale
2427 : * results lingering. Some of these might be byref types
2428 : * so we can't have them pointing to free'd memory. The
2429 : * planner insisted that quals used in the runcondition
2430 : * are strict, so the top-level WindowAgg will always
2431 : * filter these NULLs out in the filter clause.
2432 : */
2433 90 : numfuncs = winstate->numfuncs;
2434 264 : for (i = 0; i < numfuncs; i++)
2435 : {
2436 174 : econtext->ecxt_aggvalues[i] = (Datum) 0;
2437 174 : econtext->ecxt_aggnulls[i] = true;
2438 : }
2439 :
2440 : /*
2441 : * STRICT pass-through mode is required for the top window
2442 : * when there is a PARTITION BY clause. Otherwise we must
2443 : * ensure we store tuples that don't match the
2444 : * runcondition so they're available to WindowAggs above.
2445 : */
2446 90 : if (winstate->top_window)
2447 : {
2448 72 : winstate->status = WINDOWAGG_PASSTHROUGH_STRICT;
2449 72 : continue;
2450 : }
2451 : else
2452 : {
2453 18 : winstate->status = WINDOWAGG_PASSTHROUGH;
2454 : }
2455 : }
2456 : else
2457 : {
2458 : /*
2459 : * Pass-through not required. We can just return NULL.
2460 : * Nothing else will match the runcondition.
2461 : */
2462 42 : winstate->status = WINDOWAGG_DONE;
2463 42 : return NULL;
2464 : }
2465 : }
2466 :
2467 : /*
2468 : * Filter out any tuples we don't need in the top-level WindowAgg.
2469 : */
2470 904770 : if (!ExecQual(winstate->ss.ps.qual, econtext))
2471 : {
2472 18 : InstrCountFiltered1(winstate, 1);
2473 18 : continue;
2474 : }
2475 :
2476 904752 : break;
2477 : }
2478 :
2479 : /*
2480 : * When not in WINDOWAGG_RUN mode, we must still return this tuple if
2481 : * we're anything apart from the top window.
2482 : */
2483 66 : else if (!winstate->top_window)
2484 24 : break;
2485 : }
2486 :
2487 904776 : return slot;
2488 : }
2489 :
2490 : /* -----------------
2491 : * ExecInitWindowAgg
2492 : *
2493 : * Creates the run-time information for the WindowAgg node produced by the
2494 : * planner and initializes its outer subtree
2495 : * -----------------
2496 : */
2497 : WindowAggState *
2498 2750 : ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
2499 : {
2500 : WindowAggState *winstate;
2501 : Plan *outerPlan;
2502 : ExprContext *econtext;
2503 : ExprContext *tmpcontext;
2504 : WindowStatePerFunc perfunc;
2505 : WindowStatePerAgg peragg;
2506 2750 : int frameOptions = node->frameOptions;
2507 : int numfuncs,
2508 : wfuncno,
2509 : numaggs,
2510 : aggno;
2511 : TupleDesc scanDesc;
2512 : ListCell *l;
2513 :
2514 : /* check for unsupported flags */
2515 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
2516 :
2517 : /*
2518 : * create state structure
2519 : */
2520 2750 : winstate = makeNode(WindowAggState);
2521 2750 : winstate->ss.ps.plan = (Plan *) node;
2522 2750 : winstate->ss.ps.state = estate;
2523 2750 : winstate->ss.ps.ExecProcNode = ExecWindowAgg;
2524 :
2525 : /* copy frame options to state node for easy access */
2526 2750 : winstate->frameOptions = frameOptions;
2527 :
2528 : /*
2529 : * Create expression contexts. We need two, one for per-input-tuple
2530 : * processing and one for per-output-tuple processing. We cheat a little
2531 : * by using ExecAssignExprContext() to build both.
2532 : */
2533 2750 : ExecAssignExprContext(estate, &winstate->ss.ps);
2534 2750 : tmpcontext = winstate->ss.ps.ps_ExprContext;
2535 2750 : winstate->tmpcontext = tmpcontext;
2536 2750 : ExecAssignExprContext(estate, &winstate->ss.ps);
2537 :
2538 : /* Create long-lived context for storage of partition-local memory etc */
2539 2750 : winstate->partcontext =
2540 2750 : AllocSetContextCreate(CurrentMemoryContext,
2541 : "WindowAgg Partition",
2542 : ALLOCSET_DEFAULT_SIZES);
2543 :
2544 : /*
2545 : * Create mid-lived context for aggregate trans values etc.
2546 : *
2547 : * Note that moving aggregates each use their own private context, not
2548 : * this one.
2549 : */
2550 2750 : winstate->aggcontext =
2551 2750 : AllocSetContextCreate(CurrentMemoryContext,
2552 : "WindowAgg Aggregates",
2553 : ALLOCSET_DEFAULT_SIZES);
2554 :
2555 : /* Only the top-level WindowAgg may have a qual */
2556 : Assert(node->plan.qual == NIL || node->topWindow);
2557 :
2558 : /* Initialize the qual */
2559 2750 : winstate->ss.ps.qual = ExecInitQual(node->plan.qual,
2560 : (PlanState *) winstate);
2561 :
2562 : /*
2563 : * Setup the run condition, if we received one from the query planner.
2564 : * When set, this may allow us to move into pass-through mode so that we
2565 : * don't have to perform any further evaluation of WindowFuncs in the
2566 : * current partition or possibly stop returning tuples altogether when all
2567 : * tuples are in the same partition.
2568 : */
2569 2750 : winstate->runcondition = ExecInitQual(node->runCondition,
2570 : (PlanState *) winstate);
2571 :
2572 : /*
2573 : * When we're not the top-level WindowAgg node or we are but have a
2574 : * PARTITION BY clause we must move into one of the WINDOWAGG_PASSTHROUGH*
2575 : * modes when the runCondition becomes false.
2576 : */
2577 2750 : winstate->use_pass_through = !node->topWindow || node->partNumCols > 0;
2578 :
2579 : /* remember if we're the top-window or we are below the top-window */
2580 2750 : winstate->top_window = node->topWindow;
2581 :
2582 : /*
2583 : * initialize child nodes
2584 : */
2585 2750 : outerPlan = outerPlan(node);
2586 2750 : outerPlanState(winstate) = ExecInitNode(outerPlan, estate, eflags);
2587 :
2588 : /*
2589 : * initialize source tuple type (which is also the tuple type that we'll
2590 : * store in the tuplestore and use in all our working slots).
2591 : */
2592 2750 : ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple);
2593 2750 : scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
2594 :
2595 : /* the outer tuple isn't the child's tuple, but always a minimal tuple */
2596 2750 : winstate->ss.ps.outeropsset = true;
2597 2750 : winstate->ss.ps.outerops = &TTSOpsMinimalTuple;
2598 2750 : winstate->ss.ps.outeropsfixed = true;
2599 :
2600 : /*
2601 : * tuple table initialization
2602 : */
2603 2750 : winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2604 : &TTSOpsMinimalTuple);
2605 2750 : winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2606 : &TTSOpsMinimalTuple);
2607 2750 : winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,
2608 : &TTSOpsMinimalTuple);
2609 2750 : winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
2610 : &TTSOpsMinimalTuple);
2611 :
2612 : /*
2613 : * create frame head and tail slots only if needed (must create slots in
2614 : * exactly the same cases that update_frameheadpos and update_frametailpos
2615 : * need them)
2616 : */
2617 2750 : winstate->framehead_slot = winstate->frametail_slot = NULL;
2618 :
2619 2750 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
2620 : {
2621 1550 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
2622 76 : node->ordNumCols != 0) ||
2623 1474 : (frameOptions & FRAMEOPTION_START_OFFSET))
2624 742 : winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2625 : &TTSOpsMinimalTuple);
2626 1550 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
2627 766 : node->ordNumCols != 0) ||
2628 1066 : (frameOptions & FRAMEOPTION_END_OFFSET))
2629 1210 : winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2630 : &TTSOpsMinimalTuple);
2631 : }
2632 :
2633 : /*
2634 : * Initialize result slot, type and projection.
2635 : */
2636 2750 : ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual);
2637 2750 : ExecAssignProjectionInfo(&winstate->ss.ps, NULL);
2638 :
2639 : /* Set up data for comparing tuples */
2640 2750 : if (node->partNumCols > 0)
2641 682 : winstate->partEqfunction =
2642 682 : execTuplesMatchPrepare(scanDesc,
2643 : node->partNumCols,
2644 682 : node->partColIdx,
2645 682 : node->partOperators,
2646 682 : node->partCollations,
2647 : &winstate->ss.ps);
2648 :
2649 2750 : if (node->ordNumCols > 0)
2650 2194 : winstate->ordEqfunction =
2651 2194 : execTuplesMatchPrepare(scanDesc,
2652 : node->ordNumCols,
2653 2194 : node->ordColIdx,
2654 2194 : node->ordOperators,
2655 2194 : node->ordCollations,
2656 : &winstate->ss.ps);
2657 :
2658 : /*
2659 : * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes.
2660 : */
2661 2750 : numfuncs = winstate->numfuncs;
2662 2750 : numaggs = winstate->numaggs;
2663 2750 : econtext = winstate->ss.ps.ps_ExprContext;
2664 2750 : econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numfuncs);
2665 2750 : econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numfuncs);
2666 :
2667 : /*
2668 : * allocate per-wfunc/per-agg state information.
2669 : */
2670 2750 : perfunc = (WindowStatePerFunc) palloc0(sizeof(WindowStatePerFuncData) * numfuncs);
2671 2750 : peragg = (WindowStatePerAgg) palloc0(sizeof(WindowStatePerAggData) * numaggs);
2672 2750 : winstate->perfunc = perfunc;
2673 2750 : winstate->peragg = peragg;
2674 :
2675 2750 : wfuncno = -1;
2676 2750 : aggno = -1;
2677 6340 : foreach(l, winstate->funcs)
2678 : {
2679 3590 : WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
2680 3590 : WindowFunc *wfunc = wfuncstate->wfunc;
2681 : WindowStatePerFunc perfuncstate;
2682 : AclResult aclresult;
2683 : int i;
2684 :
2685 3590 : if (wfunc->winref != node->winref) /* planner screwed up? */
2686 0 : elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u",
2687 : wfunc->winref, node->winref);
2688 :
2689 : /*
2690 : * Look for a previous duplicate window function, which needs the same
2691 : * ignore_nulls value
2692 : */
2693 4694 : for (i = 0; i <= wfuncno; i++)
2694 : {
2695 1110 : if (equal(wfunc, perfunc[i].wfunc) &&
2696 6 : !contain_volatile_functions((Node *) wfunc))
2697 6 : break;
2698 : }
2699 3590 : if (i <= wfuncno && wfunc->ignore_nulls == perfunc[i].ignore_nulls)
2700 : {
2701 : /* Found a match to an existing entry, so just mark it */
2702 6 : wfuncstate->wfuncno = i;
2703 6 : continue;
2704 : }
2705 :
2706 : /* Nope, so assign a new PerAgg record */
2707 3584 : perfuncstate = &perfunc[++wfuncno];
2708 :
2709 : /* Mark WindowFunc state node with assigned index in the result array */
2710 3584 : wfuncstate->wfuncno = wfuncno;
2711 :
2712 : /* Check permission to call window function */
2713 3584 : aclresult = object_aclcheck(ProcedureRelationId, wfunc->winfnoid, GetUserId(),
2714 : ACL_EXECUTE);
2715 3584 : if (aclresult != ACLCHECK_OK)
2716 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
2717 0 : get_func_name(wfunc->winfnoid));
2718 3584 : InvokeFunctionExecuteHook(wfunc->winfnoid);
2719 :
2720 : /* Fill in the perfuncstate data */
2721 3584 : perfuncstate->wfuncstate = wfuncstate;
2722 3584 : perfuncstate->wfunc = wfunc;
2723 3584 : perfuncstate->numArguments = list_length(wfuncstate->args);
2724 3584 : perfuncstate->winCollation = wfunc->inputcollid;
2725 :
2726 3584 : get_typlenbyval(wfunc->wintype,
2727 : &perfuncstate->resulttypeLen,
2728 : &perfuncstate->resulttypeByVal);
2729 :
2730 : /*
2731 : * If it's really just a plain aggregate function, we'll emulate the
2732 : * Agg environment for it.
2733 : */
2734 3584 : perfuncstate->plain_agg = wfunc->winagg;
2735 3584 : if (wfunc->winagg)
2736 : {
2737 : WindowStatePerAgg peraggstate;
2738 :
2739 1544 : perfuncstate->aggno = ++aggno;
2740 1544 : peraggstate = &winstate->peragg[aggno];
2741 1544 : initialize_peragg(winstate, wfunc, peraggstate);
2742 1544 : peraggstate->wfuncno = wfuncno;
2743 : }
2744 : else
2745 : {
2746 2040 : WindowObject winobj = makeNode(WindowObjectData);
2747 :
2748 2040 : winobj->winstate = winstate;
2749 2040 : winobj->argstates = wfuncstate->args;
2750 2040 : winobj->localmem = NULL;
2751 2040 : perfuncstate->winobj = winobj;
2752 2040 : winobj->ignore_nulls = wfunc->ignore_nulls;
2753 2040 : init_notnull_info(winobj, perfuncstate);
2754 :
2755 : /* It's a real window function, so set up to call it. */
2756 2040 : fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
2757 : econtext->ecxt_per_query_memory);
2758 2040 : fmgr_info_set_expr((Node *) wfunc, &perfuncstate->flinfo);
2759 : }
2760 : }
2761 :
2762 : /* Update numfuncs, numaggs to match number of unique functions found */
2763 2750 : winstate->numfuncs = wfuncno + 1;
2764 2750 : winstate->numaggs = aggno + 1;
2765 :
2766 : /* Set up WindowObject for aggregates, if needed */
2767 2750 : if (winstate->numaggs > 0)
2768 : {
2769 1430 : WindowObject agg_winobj = makeNode(WindowObjectData);
2770 :
2771 1430 : agg_winobj->winstate = winstate;
2772 1430 : agg_winobj->argstates = NIL;
2773 1430 : agg_winobj->localmem = NULL;
2774 : /* make sure markptr = -1 to invalidate. It may not get used */
2775 1430 : agg_winobj->markptr = -1;
2776 1430 : agg_winobj->readptr = -1;
2777 1430 : winstate->agg_winobj = agg_winobj;
2778 : }
2779 :
2780 : /* Set the status to running */
2781 2750 : winstate->status = WINDOWAGG_RUN;
2782 :
2783 : /* initialize frame bound offset expressions */
2784 2750 : winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
2785 : (PlanState *) winstate);
2786 2750 : winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
2787 : (PlanState *) winstate);
2788 :
2789 : /* Lookup in_range support functions if needed */
2790 2750 : if (OidIsValid(node->startInRangeFunc))
2791 522 : fmgr_info(node->startInRangeFunc, &winstate->startInRangeFunc);
2792 2750 : if (OidIsValid(node->endInRangeFunc))
2793 588 : fmgr_info(node->endInRangeFunc, &winstate->endInRangeFunc);
2794 2750 : winstate->inRangeColl = node->inRangeColl;
2795 2750 : winstate->inRangeAsc = node->inRangeAsc;
2796 2750 : winstate->inRangeNullsFirst = node->inRangeNullsFirst;
2797 :
2798 2750 : winstate->all_first = true;
2799 2750 : winstate->partition_spooled = false;
2800 2750 : winstate->more_partitions = false;
2801 2750 : winstate->next_partition = true;
2802 :
2803 2750 : return winstate;
2804 : }
2805 :
2806 : /* -----------------
2807 : * ExecEndWindowAgg
2808 : * -----------------
2809 : */
2810 : void
2811 2546 : ExecEndWindowAgg(WindowAggState *node)
2812 : {
2813 : PlanState *outerPlan;
2814 : int i;
2815 :
2816 2546 : if (node->buffer != NULL)
2817 : {
2818 2082 : tuplestore_end(node->buffer);
2819 :
2820 : /* nullify so that release_partition skips the tuplestore_clear() */
2821 2082 : node->buffer = NULL;
2822 : }
2823 :
2824 2546 : release_partition(node);
2825 :
2826 4048 : for (i = 0; i < node->numaggs; i++)
2827 : {
2828 1502 : if (node->peragg[i].aggcontext != node->aggcontext)
2829 786 : MemoryContextDelete(node->peragg[i].aggcontext);
2830 : }
2831 2546 : MemoryContextDelete(node->partcontext);
2832 2546 : MemoryContextDelete(node->aggcontext);
2833 :
2834 2546 : pfree(node->perfunc);
2835 2546 : pfree(node->peragg);
2836 :
2837 2546 : outerPlan = outerPlanState(node);
2838 2546 : ExecEndNode(outerPlan);
2839 2546 : }
2840 :
2841 : /* -----------------
2842 : * ExecReScanWindowAgg
2843 : * -----------------
2844 : */
2845 : void
2846 78 : ExecReScanWindowAgg(WindowAggState *node)
2847 : {
2848 78 : PlanState *outerPlan = outerPlanState(node);
2849 78 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
2850 :
2851 78 : node->status = WINDOWAGG_RUN;
2852 78 : node->all_first = true;
2853 :
2854 : /* release tuplestore et al */
2855 78 : release_partition(node);
2856 :
2857 : /* release all temp tuples, but especially first_part_slot */
2858 78 : ExecClearTuple(node->ss.ss_ScanTupleSlot);
2859 78 : ExecClearTuple(node->first_part_slot);
2860 78 : ExecClearTuple(node->agg_row_slot);
2861 78 : ExecClearTuple(node->temp_slot_1);
2862 78 : ExecClearTuple(node->temp_slot_2);
2863 78 : if (node->framehead_slot)
2864 0 : ExecClearTuple(node->framehead_slot);
2865 78 : if (node->frametail_slot)
2866 6 : ExecClearTuple(node->frametail_slot);
2867 :
2868 : /* Forget current wfunc values */
2869 156 : MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numfuncs);
2870 78 : MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numfuncs);
2871 :
2872 : /*
2873 : * if chgParam of subnode is not null then plan will be re-scanned by
2874 : * first ExecProcNode.
2875 : */
2876 78 : if (outerPlan->chgParam == NULL)
2877 6 : ExecReScan(outerPlan);
2878 78 : }
2879 :
2880 : /*
2881 : * initialize_peragg
2882 : *
2883 : * Almost same as in nodeAgg.c, except we don't support DISTINCT currently.
2884 : */
2885 : static WindowStatePerAggData *
2886 1544 : initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
2887 : WindowStatePerAgg peraggstate)
2888 : {
2889 : Oid inputTypes[FUNC_MAX_ARGS];
2890 : int numArguments;
2891 : HeapTuple aggTuple;
2892 : Form_pg_aggregate aggform;
2893 : Oid aggtranstype;
2894 : AttrNumber initvalAttNo;
2895 : AclResult aclresult;
2896 : bool use_ma_code;
2897 : Oid transfn_oid,
2898 : invtransfn_oid,
2899 : finalfn_oid;
2900 : bool finalextra;
2901 : char finalmodify;
2902 : Expr *transfnexpr,
2903 : *invtransfnexpr,
2904 : *finalfnexpr;
2905 : Datum textInitVal;
2906 : int i;
2907 : ListCell *lc;
2908 :
2909 1544 : numArguments = list_length(wfunc->args);
2910 :
2911 1544 : i = 0;
2912 2962 : foreach(lc, wfunc->args)
2913 : {
2914 1418 : inputTypes[i++] = exprType((Node *) lfirst(lc));
2915 : }
2916 :
2917 1544 : aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(wfunc->winfnoid));
2918 1544 : if (!HeapTupleIsValid(aggTuple))
2919 0 : elog(ERROR, "cache lookup failed for aggregate %u",
2920 : wfunc->winfnoid);
2921 1544 : aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
2922 :
2923 : /*
2924 : * Figure out whether we want to use the moving-aggregate implementation,
2925 : * and collect the right set of fields from the pg_aggregate entry.
2926 : *
2927 : * It's possible that an aggregate would supply a safe moving-aggregate
2928 : * implementation and an unsafe normal one, in which case our hand is
2929 : * forced. Otherwise, if the frame head can't move, we don't need
2930 : * moving-aggregate code. Even if we'd like to use it, don't do so if the
2931 : * aggregate's arguments (and FILTER clause if any) contain any calls to
2932 : * volatile functions. Otherwise, the difference between restarting and
2933 : * not restarting the aggregation would be user-visible.
2934 : *
2935 : * We also don't risk using moving aggregates when there are subplans in
2936 : * the arguments or FILTER clause. This is partly because
2937 : * contain_volatile_functions() doesn't look inside subplans; but there
2938 : * are other reasons why a subplan's output might be volatile. For
2939 : * example, syncscan mode can render the results nonrepeatable.
2940 : */
2941 1544 : if (!OidIsValid(aggform->aggminvtransfn))
2942 196 : use_ma_code = false; /* sine qua non */
2943 1348 : else if (aggform->aggmfinalmodify == AGGMODIFY_READ_ONLY &&
2944 1348 : aggform->aggfinalmodify != AGGMODIFY_READ_ONLY)
2945 0 : use_ma_code = true; /* decision forced by safety */
2946 1348 : else if (winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
2947 538 : use_ma_code = false; /* non-moving frame head */
2948 810 : else if (contain_volatile_functions((Node *) wfunc))
2949 12 : use_ma_code = false; /* avoid possible behavioral change */
2950 798 : else if (contain_subplans((Node *) wfunc))
2951 0 : use_ma_code = false; /* subplans might contain volatile functions */
2952 : else
2953 798 : use_ma_code = true; /* yes, let's use it */
2954 1544 : if (use_ma_code)
2955 : {
2956 798 : peraggstate->transfn_oid = transfn_oid = aggform->aggmtransfn;
2957 798 : peraggstate->invtransfn_oid = invtransfn_oid = aggform->aggminvtransfn;
2958 798 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggmfinalfn;
2959 798 : finalextra = aggform->aggmfinalextra;
2960 798 : finalmodify = aggform->aggmfinalmodify;
2961 798 : aggtranstype = aggform->aggmtranstype;
2962 798 : initvalAttNo = Anum_pg_aggregate_aggminitval;
2963 : }
2964 : else
2965 : {
2966 746 : peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
2967 746 : peraggstate->invtransfn_oid = invtransfn_oid = InvalidOid;
2968 746 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
2969 746 : finalextra = aggform->aggfinalextra;
2970 746 : finalmodify = aggform->aggfinalmodify;
2971 746 : aggtranstype = aggform->aggtranstype;
2972 746 : initvalAttNo = Anum_pg_aggregate_agginitval;
2973 : }
2974 :
2975 : /*
2976 : * ExecInitWindowAgg already checked permission to call aggregate function
2977 : * ... but we still need to check the component functions
2978 : */
2979 :
2980 : /* Check that aggregate owner has permission to call component fns */
2981 : {
2982 : HeapTuple procTuple;
2983 : Oid aggOwner;
2984 :
2985 1544 : procTuple = SearchSysCache1(PROCOID,
2986 : ObjectIdGetDatum(wfunc->winfnoid));
2987 1544 : if (!HeapTupleIsValid(procTuple))
2988 0 : elog(ERROR, "cache lookup failed for function %u",
2989 : wfunc->winfnoid);
2990 1544 : aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
2991 1544 : ReleaseSysCache(procTuple);
2992 :
2993 1544 : aclresult = object_aclcheck(ProcedureRelationId, transfn_oid, aggOwner,
2994 : ACL_EXECUTE);
2995 1544 : if (aclresult != ACLCHECK_OK)
2996 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
2997 0 : get_func_name(transfn_oid));
2998 1544 : InvokeFunctionExecuteHook(transfn_oid);
2999 :
3000 1544 : if (OidIsValid(invtransfn_oid))
3001 : {
3002 798 : aclresult = object_aclcheck(ProcedureRelationId, invtransfn_oid, aggOwner,
3003 : ACL_EXECUTE);
3004 798 : if (aclresult != ACLCHECK_OK)
3005 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
3006 0 : get_func_name(invtransfn_oid));
3007 798 : InvokeFunctionExecuteHook(invtransfn_oid);
3008 : }
3009 :
3010 1544 : if (OidIsValid(finalfn_oid))
3011 : {
3012 842 : aclresult = object_aclcheck(ProcedureRelationId, finalfn_oid, aggOwner,
3013 : ACL_EXECUTE);
3014 842 : if (aclresult != ACLCHECK_OK)
3015 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
3016 0 : get_func_name(finalfn_oid));
3017 842 : InvokeFunctionExecuteHook(finalfn_oid);
3018 : }
3019 : }
3020 :
3021 : /*
3022 : * If the selected finalfn isn't read-only, we can't run this aggregate as
3023 : * a window function. This is a user-facing error, so we take a bit more
3024 : * care with the error message than elsewhere in this function.
3025 : */
3026 1544 : if (finalmodify != AGGMODIFY_READ_ONLY)
3027 0 : ereport(ERROR,
3028 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3029 : errmsg("aggregate function %s does not support use as a window function",
3030 : format_procedure(wfunc->winfnoid))));
3031 :
3032 : /* Detect how many arguments to pass to the finalfn */
3033 1544 : if (finalextra)
3034 26 : peraggstate->numFinalArgs = numArguments + 1;
3035 : else
3036 1518 : peraggstate->numFinalArgs = 1;
3037 :
3038 : /* resolve actual type of transition state, if polymorphic */
3039 1544 : aggtranstype = resolve_aggregate_transtype(wfunc->winfnoid,
3040 : aggtranstype,
3041 : inputTypes,
3042 : numArguments);
3043 :
3044 : /* build expression trees using actual argument & result types */
3045 1544 : build_aggregate_transfn_expr(inputTypes,
3046 : numArguments,
3047 : 0, /* no ordered-set window functions yet */
3048 : false, /* no variadic window functions yet */
3049 : aggtranstype,
3050 : wfunc->inputcollid,
3051 : transfn_oid,
3052 : invtransfn_oid,
3053 : &transfnexpr,
3054 : &invtransfnexpr);
3055 :
3056 : /* set up infrastructure for calling the transfn(s) and finalfn */
3057 1544 : fmgr_info(transfn_oid, &peraggstate->transfn);
3058 1544 : fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
3059 :
3060 1544 : if (OidIsValid(invtransfn_oid))
3061 : {
3062 798 : fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
3063 798 : fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
3064 : }
3065 :
3066 1544 : if (OidIsValid(finalfn_oid))
3067 : {
3068 842 : build_aggregate_finalfn_expr(inputTypes,
3069 : peraggstate->numFinalArgs,
3070 : aggtranstype,
3071 : wfunc->wintype,
3072 : wfunc->inputcollid,
3073 : finalfn_oid,
3074 : &finalfnexpr);
3075 842 : fmgr_info(finalfn_oid, &peraggstate->finalfn);
3076 842 : fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
3077 : }
3078 :
3079 : /* get info about relevant datatypes */
3080 1544 : get_typlenbyval(wfunc->wintype,
3081 : &peraggstate->resulttypeLen,
3082 : &peraggstate->resulttypeByVal);
3083 1544 : get_typlenbyval(aggtranstype,
3084 : &peraggstate->transtypeLen,
3085 : &peraggstate->transtypeByVal);
3086 :
3087 : /*
3088 : * initval is potentially null, so don't try to access it as a struct
3089 : * field. Must do it the hard way with SysCacheGetAttr.
3090 : */
3091 1544 : textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple, initvalAttNo,
3092 : &peraggstate->initValueIsNull);
3093 :
3094 1544 : if (peraggstate->initValueIsNull)
3095 838 : peraggstate->initValue = (Datum) 0;
3096 : else
3097 706 : peraggstate->initValue = GetAggInitVal(textInitVal,
3098 : aggtranstype);
3099 :
3100 : /*
3101 : * If the transfn is strict and the initval is NULL, make sure input type
3102 : * and transtype are the same (or at least binary-compatible), so that
3103 : * it's OK to use the first input value as the initial transValue. This
3104 : * should have been checked at agg definition time, but we must check
3105 : * again in case the transfn's strictness property has been changed.
3106 : */
3107 1544 : if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
3108 : {
3109 164 : if (numArguments < 1 ||
3110 164 : !IsBinaryCoercible(inputTypes[0], aggtranstype))
3111 0 : ereport(ERROR,
3112 : (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
3113 : errmsg("aggregate %u needs to have compatible input type and transition type",
3114 : wfunc->winfnoid)));
3115 : }
3116 :
3117 : /*
3118 : * Insist that forward and inverse transition functions have the same
3119 : * strictness setting. Allowing them to differ would require handling
3120 : * more special cases in advance_windowaggregate and
3121 : * advance_windowaggregate_base, for no discernible benefit. This should
3122 : * have been checked at agg definition time, but we must check again in
3123 : * case either function's strictness property has been changed.
3124 : */
3125 1544 : if (OidIsValid(invtransfn_oid) &&
3126 798 : peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
3127 0 : ereport(ERROR,
3128 : (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
3129 : errmsg("strictness of aggregate's forward and inverse transition functions must match")));
3130 :
3131 : /*
3132 : * Moving aggregates use their own aggcontext.
3133 : *
3134 : * This is necessary because they might restart at different times, so we
3135 : * might never be able to reset the shared context otherwise. We can't
3136 : * make it the aggregates' responsibility to clean up after themselves,
3137 : * because strict aggregates must be restarted whenever we remove their
3138 : * last non-NULL input, which the aggregate won't be aware is happening.
3139 : * Also, just pfree()ing the transValue upon restarting wouldn't help,
3140 : * since we'd miss any indirectly referenced data. We could, in theory,
3141 : * make the memory allocation rules for moving aggregates different than
3142 : * they have historically been for plain aggregates, but that seems grotty
3143 : * and likely to lead to memory leaks.
3144 : */
3145 1544 : if (OidIsValid(invtransfn_oid))
3146 798 : peraggstate->aggcontext =
3147 798 : AllocSetContextCreate(CurrentMemoryContext,
3148 : "WindowAgg Per Aggregate",
3149 : ALLOCSET_DEFAULT_SIZES);
3150 : else
3151 746 : peraggstate->aggcontext = winstate->aggcontext;
3152 :
3153 1544 : ReleaseSysCache(aggTuple);
3154 :
3155 1544 : return peraggstate;
3156 : }
3157 :
3158 : static Datum
3159 706 : GetAggInitVal(Datum textInitVal, Oid transtype)
3160 : {
3161 : Oid typinput,
3162 : typioparam;
3163 : char *strInitVal;
3164 : Datum initVal;
3165 :
3166 706 : getTypeInputInfo(transtype, &typinput, &typioparam);
3167 706 : strInitVal = TextDatumGetCString(textInitVal);
3168 706 : initVal = OidInputFunctionCall(typinput, strInitVal,
3169 : typioparam, -1);
3170 706 : pfree(strInitVal);
3171 706 : return initVal;
3172 : }
3173 :
3174 : /*
3175 : * are_peers
3176 : * compare two rows to see if they are equal according to the ORDER BY clause
3177 : *
3178 : * NB: this does not consider the window frame mode.
3179 : */
3180 : static bool
3181 597208 : are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
3182 : TupleTableSlot *slot2)
3183 : {
3184 597208 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
3185 597208 : ExprContext *econtext = winstate->tmpcontext;
3186 :
3187 : /* If no ORDER BY, all rows are peers with each other */
3188 597208 : if (node->ordNumCols == 0)
3189 31052 : return true;
3190 :
3191 566156 : econtext->ecxt_outertuple = slot1;
3192 566156 : econtext->ecxt_innertuple = slot2;
3193 566156 : return ExecQualAndReset(winstate->ordEqfunction, econtext);
3194 : }
3195 :
3196 : /*
3197 : * window_gettupleslot
3198 : * Fetch the pos'th tuple of the current partition into the slot,
3199 : * using the winobj's read pointer
3200 : *
3201 : * Returns true if successful, false if no such row
3202 : */
3203 : static bool
3204 761894 : window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
3205 : {
3206 761894 : WindowAggState *winstate = winobj->winstate;
3207 : MemoryContext oldcontext;
3208 :
3209 : /* often called repeatedly in a row */
3210 761894 : CHECK_FOR_INTERRUPTS();
3211 :
3212 : /* Don't allow passing -1 to spool_tuples here */
3213 761894 : if (pos < 0)
3214 408 : return false;
3215 :
3216 : /* If necessary, fetch the tuple into the spool */
3217 761486 : spool_tuples(winstate, pos);
3218 :
3219 761486 : if (pos >= winstate->spooled_rows)
3220 4748 : return false;
3221 :
3222 756738 : if (pos < winobj->markpos)
3223 0 : elog(ERROR, "cannot fetch row before WindowObject's mark position");
3224 :
3225 756738 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3226 :
3227 756738 : tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
3228 :
3229 : /*
3230 : * Advance or rewind until we are within one tuple of the one we want.
3231 : */
3232 756738 : if (winobj->seekpos < pos - 1)
3233 : {
3234 2490 : if (!tuplestore_skiptuples(winstate->buffer,
3235 2490 : pos - 1 - winobj->seekpos,
3236 : true))
3237 0 : elog(ERROR, "unexpected end of tuplestore");
3238 2490 : winobj->seekpos = pos - 1;
3239 : }
3240 754248 : else if (winobj->seekpos > pos + 1)
3241 : {
3242 2748 : if (!tuplestore_skiptuples(winstate->buffer,
3243 2748 : winobj->seekpos - (pos + 1),
3244 : false))
3245 0 : elog(ERROR, "unexpected end of tuplestore");
3246 2748 : winobj->seekpos = pos + 1;
3247 : }
3248 751500 : else if (winobj->seekpos == pos)
3249 : {
3250 : /*
3251 : * There's no API to refetch the tuple at the current position. We
3252 : * have to move one tuple forward, and then one backward. (We don't
3253 : * do it the other way because we might try to fetch the row before
3254 : * our mark, which isn't allowed.) XXX this case could stand to be
3255 : * optimized.
3256 : */
3257 173124 : tuplestore_advance(winstate->buffer, true);
3258 173124 : winobj->seekpos++;
3259 : }
3260 :
3261 : /*
3262 : * Now we should be on the tuple immediately before or after the one we
3263 : * want, so just fetch forwards or backwards as appropriate.
3264 : *
3265 : * Notice that we tell tuplestore_gettupleslot to make a physical copy of
3266 : * the fetched tuple. This ensures that the slot's contents remain valid
3267 : * through manipulations of the tuplestore, which some callers depend on.
3268 : */
3269 756738 : if (winobj->seekpos > pos)
3270 : {
3271 176148 : if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
3272 0 : elog(ERROR, "unexpected end of tuplestore");
3273 176148 : winobj->seekpos--;
3274 : }
3275 : else
3276 : {
3277 580590 : if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
3278 0 : elog(ERROR, "unexpected end of tuplestore");
3279 580590 : winobj->seekpos++;
3280 : }
3281 :
3282 : Assert(winobj->seekpos == pos);
3283 :
3284 756738 : MemoryContextSwitchTo(oldcontext);
3285 :
3286 756738 : return true;
3287 : }
3288 :
3289 : /* gettuple_eval_partition
3290 : * get tuple in a partition and evaluate the window function's argument
3291 : * expression on it.
3292 : */
3293 : static Datum
3294 237540 : gettuple_eval_partition(WindowObject winobj, int argno,
3295 : int64 abs_pos, bool *isnull, bool *isout)
3296 : {
3297 : WindowAggState *winstate;
3298 : ExprContext *econtext;
3299 : TupleTableSlot *slot;
3300 :
3301 237540 : winstate = winobj->winstate;
3302 237540 : slot = winstate->temp_slot_1;
3303 237540 : if (!window_gettupleslot(winobj, abs_pos, slot))
3304 : {
3305 : /* out of partition */
3306 582 : if (isout)
3307 582 : *isout = true;
3308 582 : *isnull = true;
3309 582 : return (Datum) 0;
3310 : }
3311 :
3312 236958 : if (isout)
3313 236958 : *isout = false;
3314 236958 : econtext = winstate->ss.ps.ps_ExprContext;
3315 236958 : econtext->ecxt_outertuple = slot;
3316 236958 : return ExecEvalExpr((ExprState *) list_nth
3317 236958 : (winobj->argstates, argno),
3318 : econtext, isnull);
3319 : }
3320 :
3321 : /*
3322 : * ignorenulls_getfuncarginframe
3323 : * For IGNORE NULLS, get the next nonnull value in the frame, moving forward
3324 : * or backward until we find a value or reach the frame's end.
3325 : */
3326 : static Datum
3327 960 : ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
3328 : int relpos, int seektype, bool set_mark,
3329 : bool *isnull, bool *isout)
3330 : {
3331 : WindowAggState *winstate;
3332 : ExprContext *econtext;
3333 : TupleTableSlot *slot;
3334 : Datum datum;
3335 : int64 abs_pos;
3336 : int64 mark_pos;
3337 : int notnull_offset;
3338 : int notnull_relpos;
3339 : int forward;
3340 :
3341 : Assert(WindowObjectIsValid(winobj));
3342 960 : winstate = winobj->winstate;
3343 960 : econtext = winstate->ss.ps.ps_ExprContext;
3344 960 : slot = winstate->temp_slot_1;
3345 960 : datum = (Datum) 0;
3346 960 : notnull_offset = 0;
3347 960 : notnull_relpos = abs(relpos);
3348 :
3349 960 : switch (seektype)
3350 : {
3351 0 : case WINDOW_SEEK_CURRENT:
3352 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3353 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3354 : break;
3355 660 : case WINDOW_SEEK_HEAD:
3356 : /* rejecting relpos < 0 is easy and simplifies code below */
3357 660 : if (relpos < 0)
3358 0 : goto out_of_frame;
3359 660 : update_frameheadpos(winstate);
3360 660 : abs_pos = winstate->frameheadpos;
3361 660 : mark_pos = winstate->frameheadpos;
3362 660 : forward = 1;
3363 660 : break;
3364 300 : case WINDOW_SEEK_TAIL:
3365 : /* rejecting relpos > 0 is easy and simplifies code below */
3366 300 : if (relpos > 0)
3367 0 : goto out_of_frame;
3368 300 : update_frametailpos(winstate);
3369 300 : abs_pos = winstate->frametailpos - 1;
3370 300 : mark_pos = 0; /* keep compiler quiet */
3371 300 : forward = -1;
3372 300 : break;
3373 0 : default:
3374 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3375 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3376 : break;
3377 : }
3378 :
3379 : /*
3380 : * Get the next nonnull value in the frame, moving forward or backward
3381 : * until we find a value or reach the frame's end.
3382 : */
3383 : do
3384 : {
3385 : int inframe;
3386 : int v;
3387 :
3388 : /*
3389 : * Check apparent out of frame case. We need to do this because we
3390 : * may not call window_gettupleslot before row_is_in_frame, which
3391 : * supposes abs_pos is never negative.
3392 : */
3393 2292 : if (abs_pos < 0)
3394 12 : goto out_of_frame;
3395 :
3396 : /* check whether row is in frame */
3397 2280 : inframe = row_is_in_frame(winobj, abs_pos, slot, true);
3398 2280 : if (inframe == -1)
3399 54 : goto out_of_frame;
3400 2226 : else if (inframe == 0)
3401 78 : goto advance;
3402 :
3403 2148 : if (isout)
3404 0 : *isout = false;
3405 :
3406 2148 : v = get_notnull_info(winobj, abs_pos, argno);
3407 2148 : if (v == NN_NULL) /* this row is known to be NULL */
3408 600 : goto advance;
3409 :
3410 1548 : else if (v == NN_UNKNOWN) /* need to check NULL or not */
3411 : {
3412 786 : if (!window_gettupleslot(winobj, abs_pos, slot))
3413 30 : goto out_of_frame;
3414 :
3415 756 : econtext->ecxt_outertuple = slot;
3416 756 : datum = ExecEvalExpr(
3417 756 : (ExprState *) list_nth(winobj->argstates,
3418 : argno), econtext,
3419 : isnull);
3420 756 : if (!*isnull)
3421 450 : notnull_offset++;
3422 :
3423 : /* record the row status */
3424 756 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3425 : }
3426 : else /* this row is known to be NOT NULL */
3427 : {
3428 762 : notnull_offset++;
3429 762 : if (notnull_offset > notnull_relpos)
3430 : {
3431 : /* to prepare exiting this loop, datum needs to be set */
3432 480 : if (!window_gettupleslot(winobj, abs_pos, slot))
3433 0 : goto out_of_frame;
3434 :
3435 480 : econtext->ecxt_outertuple = slot;
3436 480 : datum = ExecEvalExpr(
3437 480 : (ExprState *) list_nth
3438 480 : (winobj->argstates, argno),
3439 : econtext, isnull);
3440 : }
3441 : }
3442 282 : advance:
3443 2196 : abs_pos += forward;
3444 2196 : } while (notnull_offset <= notnull_relpos);
3445 :
3446 864 : if (set_mark)
3447 864 : WinSetMarkPosition(winobj, mark_pos);
3448 :
3449 864 : return datum;
3450 :
3451 96 : out_of_frame:
3452 96 : if (isout)
3453 0 : *isout = true;
3454 96 : *isnull = true;
3455 96 : return (Datum) 0;
3456 : }
3457 :
3458 :
3459 : /*
3460 : * init_notnull_info
3461 : * Initialize non null map.
3462 : */
3463 : static void
3464 2040 : init_notnull_info(WindowObject winobj, WindowStatePerFunc perfuncstate)
3465 : {
3466 2040 : int numargs = perfuncstate->numArguments;
3467 :
3468 2040 : if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3469 : {
3470 186 : winobj->notnull_info = palloc0(sizeof(uint8 *) * numargs);
3471 186 : winobj->num_notnull_info = palloc0(sizeof(int64) * numargs);
3472 : }
3473 2040 : }
3474 :
3475 : /*
3476 : * grow_notnull_info
3477 : * expand notnull_info if necessary.
3478 : * pos: not null info position
3479 : * argno: argument number
3480 : */
3481 : static void
3482 3948 : grow_notnull_info(WindowObject winobj, int64 pos, int argno)
3483 : {
3484 : /* initial number of notnull info members */
3485 : #define INIT_NOT_NULL_INFO_NUM 128
3486 :
3487 3948 : if (pos >= winobj->num_notnull_info[argno])
3488 : {
3489 : /* We may be called in a short-lived context */
3490 150 : MemoryContext oldcontext = MemoryContextSwitchTo
3491 150 : (winobj->winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3492 :
3493 : for (;;)
3494 0 : {
3495 150 : Size oldsize = NN_POS_TO_BYTES
3496 : (winobj->num_notnull_info[argno]);
3497 : Size newsize;
3498 :
3499 150 : if (oldsize == 0) /* memory has not been allocated yet for this
3500 : * arg */
3501 : {
3502 150 : newsize = NN_POS_TO_BYTES(INIT_NOT_NULL_INFO_NUM);
3503 150 : winobj->notnull_info[argno] = palloc0(newsize);
3504 : }
3505 : else
3506 : {
3507 0 : newsize = oldsize * 2;
3508 0 : winobj->notnull_info[argno] =
3509 0 : repalloc0(winobj->notnull_info[argno], oldsize, newsize);
3510 : }
3511 150 : winobj->num_notnull_info[argno] = NN_BYTES_TO_POS(newsize);
3512 150 : if (winobj->num_notnull_info[argno] > pos)
3513 150 : break;
3514 : }
3515 150 : MemoryContextSwitchTo(oldcontext);
3516 : }
3517 3948 : }
3518 :
3519 : /*
3520 : * get_notnull_info
3521 : * retrieve a map
3522 : * pos: map position
3523 : * argno: argument number
3524 : */
3525 : static uint8
3526 2772 : get_notnull_info(WindowObject winobj, int64 pos, int argno)
3527 : {
3528 : uint8 *mbp;
3529 : uint8 mb;
3530 : int64 bpos;
3531 :
3532 2772 : grow_notnull_info(winobj, pos, argno);
3533 2772 : bpos = NN_POS_TO_BYTES(pos);
3534 2772 : mbp = winobj->notnull_info[argno];
3535 2772 : mb = mbp[bpos];
3536 2772 : return (mb >> (NN_SHIFT(pos))) & NN_MASK;
3537 : }
3538 :
3539 : /*
3540 : * put_notnull_info
3541 : * update map
3542 : * pos: map position
3543 : * argno: argument number
3544 : * isnull: indicate NULL or NOT
3545 : */
3546 : static void
3547 1176 : put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull)
3548 : {
3549 : uint8 *mbp;
3550 : uint8 mb;
3551 : int64 bpos;
3552 1176 : uint8 val = isnull ? NN_NULL : NN_NOTNULL;
3553 : int shift;
3554 :
3555 1176 : grow_notnull_info(winobj, pos, argno);
3556 1176 : bpos = NN_POS_TO_BYTES(pos);
3557 1176 : mbp = winobj->notnull_info[argno];
3558 1176 : mb = mbp[bpos];
3559 1176 : shift = NN_SHIFT(pos);
3560 1176 : mb &= ~(NN_MASK << shift); /* clear map */
3561 1176 : mb |= (val << shift); /* update map */
3562 1176 : mbp[bpos] = mb;
3563 1176 : }
3564 :
3565 : /***********************************************************************
3566 : * API exposed to window functions
3567 : ***********************************************************************/
3568 :
3569 :
3570 : /*
3571 : * WinCheckAndInitializeNullTreatment
3572 : * Check null treatment clause and sets ignore_nulls
3573 : *
3574 : * Window functions should call this to check if they are being called with
3575 : * a null treatment clause when they don't allow it, or to set ignore_nulls.
3576 : */
3577 : void
3578 870928 : WinCheckAndInitializeNullTreatment(WindowObject winobj,
3579 : bool allowNullTreatment,
3580 : FunctionCallInfo fcinfo)
3581 : {
3582 : Assert(WindowObjectIsValid(winobj));
3583 870928 : if (winobj->ignore_nulls != NO_NULLTREATMENT && !allowNullTreatment)
3584 : {
3585 72 : const char *funcname = get_func_name(fcinfo->flinfo->fn_oid);
3586 :
3587 72 : if (!funcname)
3588 0 : elog(ERROR, "could not get function name");
3589 72 : ereport(ERROR,
3590 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3591 : errmsg("function %s does not allow RESPECT/IGNORE NULLS",
3592 : funcname)));
3593 : }
3594 870856 : else if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3595 150 : winobj->ignore_nulls = IGNORE_NULLS;
3596 870856 : }
3597 :
3598 : /*
3599 : * WinGetPartitionLocalMemory
3600 : * Get working memory that lives till end of partition processing
3601 : *
3602 : * On first call within a given partition, this allocates and zeroes the
3603 : * requested amount of space. Subsequent calls just return the same chunk.
3604 : *
3605 : * Memory obtained this way is normally used to hold state that should be
3606 : * automatically reset for each new partition. If a window function wants
3607 : * to hold state across the whole query, fcinfo->fn_extra can be used in the
3608 : * usual way for that.
3609 : */
3610 : void *
3611 331930 : WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
3612 : {
3613 : Assert(WindowObjectIsValid(winobj));
3614 331930 : if (winobj->localmem == NULL)
3615 446 : winobj->localmem =
3616 446 : MemoryContextAllocZero(winobj->winstate->partcontext, sz);
3617 331930 : return winobj->localmem;
3618 : }
3619 :
3620 : /*
3621 : * WinGetCurrentPosition
3622 : * Return the current row's position (counting from 0) within the current
3623 : * partition.
3624 : */
3625 : int64
3626 757918 : WinGetCurrentPosition(WindowObject winobj)
3627 : {
3628 : Assert(WindowObjectIsValid(winobj));
3629 757918 : return winobj->winstate->currentpos;
3630 : }
3631 :
3632 : /*
3633 : * WinGetPartitionRowCount
3634 : * Return total number of rows contained in the current partition.
3635 : *
3636 : * Note: this is a relatively expensive operation because it forces the
3637 : * whole partition to be "spooled" into the tuplestore at once. Once
3638 : * executed, however, additional calls within the same partition are cheap.
3639 : */
3640 : int64
3641 312 : WinGetPartitionRowCount(WindowObject winobj)
3642 : {
3643 : Assert(WindowObjectIsValid(winobj));
3644 312 : spool_tuples(winobj->winstate, -1);
3645 312 : return winobj->winstate->spooled_rows;
3646 : }
3647 :
3648 : /*
3649 : * WinSetMarkPosition
3650 : * Set the "mark" position for the window object, which is the oldest row
3651 : * number (counting from 0) it is allowed to fetch during all subsequent
3652 : * operations within the current partition.
3653 : *
3654 : * Window functions do not have to call this, but are encouraged to move the
3655 : * mark forward when possible to keep the tuplestore size down and prevent
3656 : * having to spill rows to disk.
3657 : */
3658 : void
3659 875250 : WinSetMarkPosition(WindowObject winobj, int64 markpos)
3660 : {
3661 : WindowAggState *winstate;
3662 :
3663 : Assert(WindowObjectIsValid(winobj));
3664 875250 : winstate = winobj->winstate;
3665 :
3666 875250 : if (markpos < winobj->markpos)
3667 0 : elog(ERROR, "cannot move WindowObject's mark position backward");
3668 875250 : tuplestore_select_read_pointer(winstate->buffer, winobj->markptr);
3669 875250 : if (markpos > winobj->markpos)
3670 : {
3671 868932 : tuplestore_skiptuples(winstate->buffer,
3672 868932 : markpos - winobj->markpos,
3673 : true);
3674 868932 : winobj->markpos = markpos;
3675 : }
3676 875250 : tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
3677 875250 : if (markpos > winobj->seekpos)
3678 : {
3679 462464 : tuplestore_skiptuples(winstate->buffer,
3680 462464 : markpos - winobj->seekpos,
3681 : true);
3682 462464 : winobj->seekpos = markpos;
3683 : }
3684 875250 : }
3685 :
3686 : /*
3687 : * WinRowsArePeers
3688 : * Compare two rows (specified by absolute position in partition) to see
3689 : * if they are equal according to the ORDER BY clause.
3690 : *
3691 : * NB: this does not consider the window frame mode.
3692 : */
3693 : bool
3694 165564 : WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
3695 : {
3696 : WindowAggState *winstate;
3697 : WindowAgg *node;
3698 : TupleTableSlot *slot1;
3699 : TupleTableSlot *slot2;
3700 : bool res;
3701 :
3702 : Assert(WindowObjectIsValid(winobj));
3703 165564 : winstate = winobj->winstate;
3704 165564 : node = (WindowAgg *) winstate->ss.ps.plan;
3705 :
3706 : /* If no ORDER BY, all rows are peers; don't bother to fetch them */
3707 165564 : if (node->ordNumCols == 0)
3708 270 : return true;
3709 :
3710 : /*
3711 : * Note: OK to use temp_slot_2 here because we aren't calling any
3712 : * frame-related functions (those tend to clobber temp_slot_2).
3713 : */
3714 165294 : slot1 = winstate->temp_slot_1;
3715 165294 : slot2 = winstate->temp_slot_2;
3716 :
3717 165294 : if (!window_gettupleslot(winobj, pos1, slot1))
3718 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3719 : pos1);
3720 165294 : if (!window_gettupleslot(winobj, pos2, slot2))
3721 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3722 : pos2);
3723 :
3724 165294 : res = are_peers(winstate, slot1, slot2);
3725 :
3726 165294 : ExecClearTuple(slot1);
3727 165294 : ExecClearTuple(slot2);
3728 :
3729 165294 : return res;
3730 : }
3731 :
3732 : /*
3733 : * WinGetFuncArgInPartition
3734 : * Evaluate a window function's argument expression on a specified
3735 : * row of the partition. The row is identified in lseek(2) style,
3736 : * i.e. relative to the current, first, or last row.
3737 : *
3738 : * argno: argument number to evaluate (counted from 0)
3739 : * relpos: signed rowcount offset from the seek position
3740 : * seektype: WINDOW_SEEK_CURRENT, WINDOW_SEEK_HEAD, or WINDOW_SEEK_TAIL
3741 : * set_mark: If the row is found and set_mark is true, the mark is moved to
3742 : * the row as a side-effect.
3743 : * isnull: output argument, receives isnull status of result
3744 : * isout: output argument, set to indicate whether target row position
3745 : * is out of partition (can pass NULL if caller doesn't care about this)
3746 : *
3747 : * Specifying a nonexistent row is not an error, it just causes a null result
3748 : * (plus setting *isout true, if isout isn't NULL).
3749 : */
3750 : Datum
3751 237060 : WinGetFuncArgInPartition(WindowObject winobj, int argno,
3752 : int relpos, int seektype, bool set_mark,
3753 : bool *isnull, bool *isout)
3754 : {
3755 : WindowAggState *winstate;
3756 : int64 abs_pos;
3757 : int64 mark_pos;
3758 : Datum datum;
3759 : bool null_treatment;
3760 : int notnull_offset;
3761 : int notnull_relpos;
3762 : int forward;
3763 : bool myisout;
3764 :
3765 : Assert(WindowObjectIsValid(winobj));
3766 237060 : winstate = winobj->winstate;
3767 :
3768 237060 : null_treatment = (winobj->ignore_nulls == IGNORE_NULLS && relpos != 0);
3769 :
3770 237060 : switch (seektype)
3771 : {
3772 237060 : case WINDOW_SEEK_CURRENT:
3773 237060 : if (null_treatment)
3774 480 : abs_pos = winstate->currentpos;
3775 : else
3776 236580 : abs_pos = winstate->currentpos + relpos;
3777 237060 : break;
3778 0 : case WINDOW_SEEK_HEAD:
3779 0 : if (null_treatment)
3780 0 : abs_pos = 0;
3781 : else
3782 0 : abs_pos = relpos;
3783 0 : break;
3784 0 : case WINDOW_SEEK_TAIL:
3785 0 : spool_tuples(winstate, -1);
3786 0 : abs_pos = winstate->spooled_rows - 1 + relpos;
3787 0 : break;
3788 0 : default:
3789 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3790 : abs_pos = 0; /* keep compiler quiet */
3791 : break;
3792 : }
3793 :
3794 : /* Easy case if IGNORE NULLS is not specified */
3795 237060 : if (!null_treatment)
3796 : {
3797 : /* get tuple and evaluate in partition */
3798 236580 : datum = gettuple_eval_partition(winobj, argno,
3799 : abs_pos, isnull, &myisout);
3800 236580 : if (!myisout && set_mark)
3801 236040 : WinSetMarkPosition(winobj, abs_pos);
3802 236580 : if (isout)
3803 236580 : *isout = myisout;
3804 236580 : return datum;
3805 : }
3806 :
3807 : /* Prepare for loop */
3808 480 : notnull_offset = 0;
3809 480 : notnull_relpos = abs(relpos);
3810 480 : forward = relpos > 0 ? 1 : -1;
3811 480 : myisout = false;
3812 480 : datum = 0;
3813 :
3814 : /*
3815 : * IGNORE NULLS + WINDOW_SEEK_CURRENT + relpos > 0 case, we would fetch
3816 : * beyond the current row + relpos to find out the target row. If we mark
3817 : * at abs_pos, next call to WinGetFuncArgInPartition or
3818 : * WinGetFuncArgInFrame (in case when a window function have multiple
3819 : * args) could fail with "cannot fetch row before WindowObject's mark
3820 : * position". So keep the mark position at currentpos.
3821 : */
3822 480 : if (seektype == WINDOW_SEEK_CURRENT && relpos > 0)
3823 240 : mark_pos = winstate->currentpos;
3824 : else
3825 : {
3826 : /*
3827 : * For other cases we have no idea what position of row callers would
3828 : * fetch next time. Also for relpos < 0 case (we go backward), we
3829 : * cannot set mark either. For those cases we always set mark at 0.
3830 : */
3831 240 : mark_pos = 0;
3832 : }
3833 :
3834 : /*
3835 : * Get the next nonnull value in the partition, moving forward or backward
3836 : * until we find a value or reach the partition's end. We cache the
3837 : * nullness status because we may repeat this process many times.
3838 : */
3839 : do
3840 : {
3841 : int nn_info; /* NOT NULL status */
3842 :
3843 702 : abs_pos += forward;
3844 702 : if (abs_pos < 0) /* clearly out of partition */
3845 78 : break;
3846 :
3847 : /* check NOT NULL cached info */
3848 624 : nn_info = get_notnull_info(winobj, abs_pos, argno);
3849 624 : if (nn_info == NN_NOTNULL) /* this row is known to be NOT NULL */
3850 90 : notnull_offset++;
3851 534 : else if (nn_info == NN_NULL) /* this row is known to be NULL */
3852 54 : continue; /* keep on moving forward or backward */
3853 : else /* need to check NULL or not */
3854 : {
3855 : /*
3856 : * NOT NULL info does not exist yet. Get tuple and evaluate func
3857 : * arg in partition. We ignore the return value from
3858 : * gettuple_eval_partition because we are just interested in
3859 : * whether we are inside or outside of partition, NULL or NOT
3860 : * NULL.
3861 : */
3862 480 : (void) gettuple_eval_partition(winobj, argno,
3863 : abs_pos, isnull, &myisout);
3864 480 : if (myisout) /* out of partition? */
3865 60 : break;
3866 420 : if (!*isnull)
3867 252 : notnull_offset++;
3868 : /* record the row status */
3869 420 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3870 : }
3871 564 : } while (notnull_offset < notnull_relpos);
3872 :
3873 : /* get tuple and evaluate func arg in partition */
3874 480 : datum = gettuple_eval_partition(winobj, argno,
3875 : abs_pos, isnull, &myisout);
3876 480 : if (!myisout && set_mark)
3877 342 : WinSetMarkPosition(winobj, mark_pos);
3878 480 : if (isout)
3879 480 : *isout = myisout;
3880 :
3881 480 : return datum;
3882 : }
3883 :
3884 : /*
3885 : * WinGetFuncArgInFrame
3886 : * Evaluate a window function's argument expression on a specified
3887 : * row of the window frame. The row is identified in lseek(2) style,
3888 : * i.e. relative to the first or last row of the frame. (We do not
3889 : * support WINDOW_SEEK_CURRENT here, because it's not very clear what
3890 : * that should mean if the current row isn't part of the frame.)
3891 : *
3892 : * argno: argument number to evaluate (counted from 0)
3893 : * relpos: signed rowcount offset from the seek position
3894 : * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
3895 : * set_mark: If the row is found/in frame and set_mark is true, the mark is
3896 : * moved to the row as a side-effect.
3897 : * isnull: output argument, receives isnull status of result
3898 : * isout: output argument, set to indicate whether target row position
3899 : * is out of frame (can pass NULL if caller doesn't care about this)
3900 : *
3901 : * Specifying a nonexistent or not-in-frame row is not an error, it just
3902 : * causes a null result (plus setting *isout true, if isout isn't NULL).
3903 : *
3904 : * Note that some exclusion-clause options lead to situations where the
3905 : * rows that are in-frame are not consecutive in the partition. But we
3906 : * count only in-frame rows when measuring relpos.
3907 : *
3908 : * The set_mark flag is interpreted as meaning that the caller will specify
3909 : * a constant (or, perhaps, monotonically increasing) relpos in successive
3910 : * calls, so that *if there is no exclusion clause* there will be no need
3911 : * to fetch a row before the previously fetched row. But we do not expect
3912 : * the caller to know how to account for exclusion clauses. Therefore,
3913 : * if there is an exclusion clause we take responsibility for adjusting the
3914 : * mark request to something that will be safe given the above assumption
3915 : * about relpos.
3916 : */
3917 : Datum
3918 9948 : WinGetFuncArgInFrame(WindowObject winobj, int argno,
3919 : int relpos, int seektype, bool set_mark,
3920 : bool *isnull, bool *isout)
3921 : {
3922 : WindowAggState *winstate;
3923 : ExprContext *econtext;
3924 : TupleTableSlot *slot;
3925 : int64 abs_pos;
3926 : int64 mark_pos;
3927 :
3928 : Assert(WindowObjectIsValid(winobj));
3929 9948 : winstate = winobj->winstate;
3930 9948 : econtext = winstate->ss.ps.ps_ExprContext;
3931 9948 : slot = winstate->temp_slot_1;
3932 :
3933 9948 : if (winobj->ignore_nulls == IGNORE_NULLS)
3934 960 : return ignorenulls_getfuncarginframe(winobj, argno, relpos, seektype,
3935 : set_mark, isnull, isout);
3936 :
3937 8988 : switch (seektype)
3938 : {
3939 0 : case WINDOW_SEEK_CURRENT:
3940 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3941 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3942 : break;
3943 4446 : case WINDOW_SEEK_HEAD:
3944 : /* rejecting relpos < 0 is easy and simplifies code below */
3945 4446 : if (relpos < 0)
3946 0 : goto out_of_frame;
3947 4446 : update_frameheadpos(winstate);
3948 4404 : abs_pos = winstate->frameheadpos + relpos;
3949 4404 : mark_pos = abs_pos;
3950 :
3951 : /*
3952 : * Account for exclusion option if one is active, but advance only
3953 : * abs_pos not mark_pos. This prevents changes of the current
3954 : * row's peer group from resulting in trying to fetch a row before
3955 : * some previous mark position.
3956 : *
3957 : * Note that in some corner cases such as current row being
3958 : * outside frame, these calculations are theoretically too simple,
3959 : * but it doesn't matter because we'll end up deciding the row is
3960 : * out of frame. We do not attempt to avoid fetching rows past
3961 : * end of frame; that would happen in some cases anyway.
3962 : */
3963 4404 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
3964 : {
3965 3744 : case 0:
3966 : /* no adjustment needed */
3967 3744 : break;
3968 240 : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
3969 240 : if (abs_pos >= winstate->currentpos &&
3970 186 : winstate->currentpos >= winstate->frameheadpos)
3971 66 : abs_pos++;
3972 240 : break;
3973 120 : case FRAMEOPTION_EXCLUDE_GROUP:
3974 120 : update_grouptailpos(winstate);
3975 120 : if (abs_pos >= winstate->groupheadpos &&
3976 72 : winstate->grouptailpos > winstate->frameheadpos)
3977 : {
3978 72 : int64 overlapstart = Max(winstate->groupheadpos,
3979 : winstate->frameheadpos);
3980 :
3981 72 : abs_pos += winstate->grouptailpos - overlapstart;
3982 : }
3983 120 : break;
3984 300 : case FRAMEOPTION_EXCLUDE_TIES:
3985 300 : update_grouptailpos(winstate);
3986 300 : if (abs_pos >= winstate->groupheadpos &&
3987 204 : winstate->grouptailpos > winstate->frameheadpos)
3988 : {
3989 84 : int64 overlapstart = Max(winstate->groupheadpos,
3990 : winstate->frameheadpos);
3991 :
3992 84 : if (abs_pos == overlapstart)
3993 84 : abs_pos = winstate->currentpos;
3994 : else
3995 0 : abs_pos += winstate->grouptailpos - overlapstart - 1;
3996 : }
3997 300 : break;
3998 0 : default:
3999 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4000 : winstate->frameOptions);
4001 : break;
4002 : }
4003 4404 : break;
4004 4542 : case WINDOW_SEEK_TAIL:
4005 : /* rejecting relpos > 0 is easy and simplifies code below */
4006 4542 : if (relpos > 0)
4007 0 : goto out_of_frame;
4008 4542 : update_frametailpos(winstate);
4009 4536 : abs_pos = winstate->frametailpos - 1 + relpos;
4010 :
4011 : /*
4012 : * Account for exclusion option if one is active. If there is no
4013 : * exclusion, we can safely set the mark at the accessed row. But
4014 : * if there is, we can only mark the frame start, because we can't
4015 : * be sure how far back in the frame the exclusion might cause us
4016 : * to fetch in future. Furthermore, we have to actually check
4017 : * against frameheadpos here, since it's unsafe to try to fetch a
4018 : * row before frame start if the mark might be there already.
4019 : */
4020 4536 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
4021 : {
4022 4056 : case 0:
4023 : /* no adjustment needed */
4024 4056 : mark_pos = abs_pos;
4025 4056 : break;
4026 120 : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
4027 120 : if (abs_pos <= winstate->currentpos &&
4028 12 : winstate->currentpos < winstate->frametailpos)
4029 12 : abs_pos--;
4030 120 : update_frameheadpos(winstate);
4031 120 : if (abs_pos < winstate->frameheadpos)
4032 6 : goto out_of_frame;
4033 114 : mark_pos = winstate->frameheadpos;
4034 114 : break;
4035 240 : case FRAMEOPTION_EXCLUDE_GROUP:
4036 240 : update_grouptailpos(winstate);
4037 240 : if (abs_pos < winstate->grouptailpos &&
4038 54 : winstate->groupheadpos < winstate->frametailpos)
4039 : {
4040 54 : int64 overlapend = Min(winstate->grouptailpos,
4041 : winstate->frametailpos);
4042 :
4043 54 : abs_pos -= overlapend - winstate->groupheadpos;
4044 : }
4045 240 : update_frameheadpos(winstate);
4046 240 : if (abs_pos < winstate->frameheadpos)
4047 54 : goto out_of_frame;
4048 186 : mark_pos = winstate->frameheadpos;
4049 186 : break;
4050 120 : case FRAMEOPTION_EXCLUDE_TIES:
4051 120 : update_grouptailpos(winstate);
4052 120 : if (abs_pos < winstate->grouptailpos &&
4053 36 : winstate->groupheadpos < winstate->frametailpos)
4054 : {
4055 36 : int64 overlapend = Min(winstate->grouptailpos,
4056 : winstate->frametailpos);
4057 :
4058 36 : if (abs_pos == overlapend - 1)
4059 36 : abs_pos = winstate->currentpos;
4060 : else
4061 0 : abs_pos -= overlapend - 1 - winstate->groupheadpos;
4062 : }
4063 120 : update_frameheadpos(winstate);
4064 120 : if (abs_pos < winstate->frameheadpos)
4065 0 : goto out_of_frame;
4066 120 : mark_pos = winstate->frameheadpos;
4067 120 : break;
4068 0 : default:
4069 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4070 : winstate->frameOptions);
4071 : mark_pos = 0; /* keep compiler quiet */
4072 : break;
4073 : }
4074 4476 : break;
4075 0 : default:
4076 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
4077 : abs_pos = mark_pos = 0; /* keep compiler quiet */
4078 : break;
4079 : }
4080 :
4081 8880 : if (!window_gettupleslot(winobj, abs_pos, slot))
4082 396 : goto out_of_frame;
4083 :
4084 : /* The code above does not detect all out-of-frame cases, so check */
4085 8484 : if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0)
4086 300 : goto out_of_frame;
4087 :
4088 8154 : if (isout)
4089 0 : *isout = false;
4090 8154 : if (set_mark)
4091 8112 : WinSetMarkPosition(winobj, mark_pos);
4092 8154 : econtext->ecxt_outertuple = slot;
4093 8154 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4094 : econtext, isnull);
4095 :
4096 756 : out_of_frame:
4097 756 : if (isout)
4098 0 : *isout = true;
4099 756 : *isnull = true;
4100 756 : return (Datum) 0;
4101 : }
4102 :
4103 : /*
4104 : * WinGetFuncArgCurrent
4105 : * Evaluate a window function's argument expression on the current row.
4106 : *
4107 : * argno: argument number to evaluate (counted from 0)
4108 : * isnull: output argument, receives isnull status of result
4109 : *
4110 : * Note: this isn't quite equivalent to WinGetFuncArgInPartition or
4111 : * WinGetFuncArgInFrame targeting the current row, because it will succeed
4112 : * even if the WindowObject's mark has been set beyond the current row.
4113 : * This should generally be used for "ordinary" arguments of a window
4114 : * function, such as the offset argument of lead() or lag().
4115 : */
4116 : Datum
4117 2010 : WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
4118 : {
4119 : WindowAggState *winstate;
4120 : ExprContext *econtext;
4121 :
4122 : Assert(WindowObjectIsValid(winobj));
4123 2010 : winstate = winobj->winstate;
4124 :
4125 2010 : econtext = winstate->ss.ps.ps_ExprContext;
4126 :
4127 2010 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
4128 2010 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4129 : econtext, isnull);
4130 : }
|