Branch data 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-2026, 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 : 681 : initialize_windowaggregate(WindowAggState *winstate,
251 : : WindowStatePerFunc perfuncstate,
252 : : WindowStatePerAgg peraggstate)
253 : : {
254 : 681 : 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 [ + + ]: 681 : if (peraggstate->aggcontext != winstate->aggcontext)
262 : 495 : MemoryContextReset(peraggstate->aggcontext);
263 : :
264 [ + + ]: 681 : if (peraggstate->initValueIsNull)
265 : 266 : peraggstate->transValue = peraggstate->initValue;
266 : : else
267 : : {
268 : 415 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
269 : 830 : peraggstate->transValue = datumCopy(peraggstate->initValue,
270 : 415 : peraggstate->transtypeByVal,
271 : 415 : peraggstate->transtypeLen);
272 : 415 : MemoryContextSwitchTo(oldContext);
273 : : }
274 : 681 : peraggstate->transValueIsNull = peraggstate->initValueIsNull;
275 : 681 : peraggstate->transValueCount = 0;
276 : 681 : peraggstate->resultValue = (Datum) 0;
277 : 681 : peraggstate->resultValueIsNull = true;
278 : 681 : }
279 : :
280 : : /*
281 : : * advance_windowaggregate
282 : : * parallel to advance_aggregates in nodeAgg.c
283 : : */
284 : : static void
285 : 30494 : advance_windowaggregate(WindowAggState *winstate,
286 : : WindowStatePerFunc perfuncstate,
287 : : WindowStatePerAgg peraggstate)
288 : : {
289 : 30494 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
290 : 30494 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
291 : 30494 : int numArguments = perfuncstate->numArguments;
292 : 30494 : Datum newVal;
293 : 30494 : ListCell *arg;
294 : 30494 : int i;
295 : 30494 : MemoryContext oldContext;
296 : 30494 : ExprContext *econtext = winstate->tmpcontext;
297 : 30494 : ExprState *filter = wfuncstate->aggfilter;
298 : :
299 : 30494 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
300 : :
301 : : /* Skip anything FILTERed out */
302 [ + + ]: 30494 : if (filter)
303 : : {
304 : 57 : bool isnull;
305 : 57 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
306 : :
307 [ + + + + ]: 57 : if (isnull || !DatumGetBool(res))
308 : : {
309 : 27 : MemoryContextSwitchTo(oldContext);
310 : 27 : return;
311 : : }
312 [ + + ]: 57 : }
313 : :
314 : : /* We start from 1, since the 0th arg will be the transition value */
315 : 30467 : i = 1;
316 [ + + + + : 50283 : foreach(arg, wfuncstate->args)
+ + ]
317 : : {
318 : 19816 : ExprState *argstate = (ExprState *) lfirst(arg);
319 : :
320 : 39632 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
321 : 19816 : &fcinfo->args[i].isnull);
322 : 19816 : i++;
323 : 19816 : }
324 : :
325 [ + + ]: 30467 : 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 [ + + ]: 18252 : for (i = 1; i <= numArguments; i++)
333 : : {
334 [ + + ]: 4099 : if (fcinfo->args[i].isnull)
335 : : {
336 : 33 : MemoryContextSwitchTo(oldContext);
337 : 33 : return;
338 : : }
339 : 4066 : }
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 [ + + + + ]: 14153 : if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
351 : : {
352 : 84 : MemoryContextSwitchTo(peraggstate->aggcontext);
353 : 168 : peraggstate->transValue = datumCopy(fcinfo->args[1].value,
354 : 84 : peraggstate->transtypeByVal,
355 : 84 : peraggstate->transtypeLen);
356 : 84 : peraggstate->transValueIsNull = false;
357 : 84 : peraggstate->transValueCount = 1;
358 : 84 : MemoryContextSwitchTo(oldContext);
359 : 84 : return;
360 : : }
361 : :
362 [ - + ]: 14069 : 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 [ # # ]: 0 : Assert(!OidIsValid(peraggstate->invtransfn_oid));
374 : 0 : return;
375 : : }
376 : 14069 : }
377 : :
378 : : /*
379 : : * OK to call the transition function. Set winstate->curaggcontext while
380 : : * calling it, for possible use by AggCheckCallContext.
381 : : */
382 : 30350 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
383 : : numArguments + 1,
384 : : perfuncstate->winCollation,
385 : : (Node *) winstate, NULL);
386 : 30350 : fcinfo->args[0].value = peraggstate->transValue;
387 : 30350 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
388 : 30350 : winstate->curaggcontext = peraggstate->aggcontext;
389 : 30350 : newVal = FunctionCallInvoke(fcinfo);
390 : 30350 : winstate->curaggcontext = NULL;
391 : :
392 : : /*
393 : : * Moving-aggregate transition functions must not return null, see
394 : : * advance_windowaggregate_base().
395 : : */
396 [ - + # # ]: 30350 : 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 : 30350 : 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 [ + + + + ]: 30350 : if (!peraggstate->transtypeByVal &&
418 : 1393 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
419 : : {
420 [ + + ]: 460 : if (!fcinfo->isnull)
421 : : {
422 : 160 : MemoryContextSwitchTo(peraggstate->aggcontext);
423 [ + + ]: 160 : if (DatumIsReadWriteExpandedObject(newVal,
424 : : false,
425 [ - + ]: 319 : peraggstate->transtypeLen) &&
426 : 319 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
427 : : /* do nothing */ ;
428 : : else
429 : 318 : newVal = datumCopy(newVal,
430 : 159 : peraggstate->transtypeByVal,
431 : 159 : peraggstate->transtypeLen);
432 : 160 : }
433 [ + + ]: 460 : if (!peraggstate->transValueIsNull)
434 : : {
435 [ + + + + ]: 450 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
436 : : false,
437 : : peraggstate->transtypeLen))
438 : 300 : DeleteExpandedObject(peraggstate->transValue);
439 : : else
440 : 150 : pfree(DatumGetPointer(peraggstate->transValue));
441 : 150 : }
442 : 160 : }
443 : :
444 : 30050 : MemoryContextSwitchTo(oldContext);
445 : 30050 : peraggstate->transValue = newVal;
446 : 30050 : peraggstate->transValueIsNull = fcinfo->isnull;
447 [ - + ]: 30194 : }
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 : 993 : advance_windowaggregate_base(WindowAggState *winstate,
463 : : WindowStatePerFunc perfuncstate,
464 : : WindowStatePerAgg peraggstate)
465 : : {
466 : 993 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
467 : 993 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
468 : 993 : int numArguments = perfuncstate->numArguments;
469 : 993 : Datum newVal;
470 : 993 : ListCell *arg;
471 : 993 : int i;
472 : 993 : MemoryContext oldContext;
473 : 993 : ExprContext *econtext = winstate->tmpcontext;
474 : 993 : ExprState *filter = wfuncstate->aggfilter;
475 : :
476 : 993 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
477 : :
478 : : /* Skip anything FILTERed out */
479 [ + + ]: 993 : if (filter)
480 : : {
481 : 17 : bool isnull;
482 : 17 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
483 : :
484 [ + - + + ]: 17 : if (isnull || !DatumGetBool(res))
485 : : {
486 : 8 : MemoryContextSwitchTo(oldContext);
487 : 8 : return true;
488 : : }
489 [ + + ]: 17 : }
490 : :
491 : : /* We start from 1, since the 0th arg will be the transition value */
492 : 985 : i = 1;
493 [ + + + + : 1523 : foreach(arg, wfuncstate->args)
+ + ]
494 : : {
495 : 538 : ExprState *argstate = (ExprState *) lfirst(arg);
496 : :
497 : 1076 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
498 : 538 : &fcinfo->args[i].isnull);
499 : 538 : i++;
500 : 538 : }
501 : :
502 [ + + ]: 985 : 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 [ + + ]: 934 : for (i = 1; i <= numArguments; i++)
510 : : {
511 [ + + ]: 472 : if (fcinfo->args[i].isnull)
512 : : {
513 : 13 : MemoryContextSwitchTo(oldContext);
514 : 13 : return true;
515 : : }
516 : 459 : }
517 : 462 : }
518 : :
519 : : /* There should still be an added but not yet removed value */
520 [ + - ]: 972 : 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 [ + - ]: 972 : 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 [ + + ]: 972 : if (peraggstate->transValueCount == 1)
540 : : {
541 : 15 : MemoryContextSwitchTo(oldContext);
542 : 30 : initialize_windowaggregate(winstate,
543 : 15 : &winstate->perfunc[peraggstate->wfuncno],
544 : 15 : peraggstate);
545 : 15 : 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 : 957 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
554 : : numArguments + 1,
555 : : perfuncstate->winCollation,
556 : : (Node *) winstate, NULL);
557 : 957 : fcinfo->args[0].value = peraggstate->transValue;
558 : 957 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
559 : 957 : winstate->curaggcontext = peraggstate->aggcontext;
560 : 957 : newVal = FunctionCallInvoke(fcinfo);
561 : 957 : winstate->curaggcontext = NULL;
562 : :
563 : : /*
564 : : * If the function returns NULL, report failure, forcing a restart.
565 : : */
566 [ + + ]: 957 : if (fcinfo->isnull)
567 : : {
568 : 50 : MemoryContextSwitchTo(oldContext);
569 : 50 : return false;
570 : : }
571 : :
572 : : /* Update number of rows included in transValue */
573 : 907 : 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 [ + + + + ]: 907 : if (!peraggstate->transtypeByVal &&
587 : 355 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
588 : : {
589 [ + + ]: 333 : if (!fcinfo->isnull)
590 : : {
591 : 111 : MemoryContextSwitchTo(peraggstate->aggcontext);
592 [ + + ]: 111 : if (DatumIsReadWriteExpandedObject(newVal,
593 : : false,
594 [ - + ]: 222 : peraggstate->transtypeLen) &&
595 : 222 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
596 : : /* do nothing */ ;
597 : : else
598 : 222 : newVal = datumCopy(newVal,
599 : 111 : peraggstate->transtypeByVal,
600 : 111 : peraggstate->transtypeLen);
601 : 111 : }
602 [ - + ]: 333 : if (!peraggstate->transValueIsNull)
603 : : {
604 [ + + + + ]: 333 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
605 : : false,
606 : : peraggstate->transtypeLen))
607 : 222 : DeleteExpandedObject(peraggstate->transValue);
608 : : else
609 : 111 : pfree(DatumGetPointer(peraggstate->transValue));
610 : 111 : }
611 : 111 : }
612 : :
613 : 685 : MemoryContextSwitchTo(oldContext);
614 : 685 : peraggstate->transValue = newVal;
615 : 685 : peraggstate->transValueIsNull = fcinfo->isnull;
616 : :
617 : 685 : return true;
618 : 771 : }
619 : :
620 : : /*
621 : : * finalize_windowaggregate
622 : : * parallel to finalize_aggregate in nodeAgg.c
623 : : */
624 : : static void
625 : 1782 : finalize_windowaggregate(WindowAggState *winstate,
626 : : WindowStatePerFunc perfuncstate,
627 : : WindowStatePerAgg peraggstate,
628 : : Datum *result, bool *isnull)
629 : : {
630 : 1782 : MemoryContext oldContext;
631 : :
632 : 1782 : 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 [ + + ]: 1782 : if (OidIsValid(peraggstate->finalfn_oid))
638 : : {
639 : 992 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
640 : 992 : int numFinalArgs = peraggstate->numFinalArgs;
641 : 992 : bool anynull;
642 : 992 : int i;
643 : :
644 : 992 : InitFunctionCallInfoData(fcinfodata.fcinfo, &(peraggstate->finalfn),
645 : : numFinalArgs,
646 : : perfuncstate->winCollation,
647 : : (Node *) winstate, NULL);
648 : 992 : fcinfo->args[0].value =
649 [ + - + + ]: 992 : MakeExpandedObjectReadOnly(peraggstate->transValue,
650 : : peraggstate->transValueIsNull,
651 : : peraggstate->transtypeLen);
652 : 992 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
653 : 992 : anynull = peraggstate->transValueIsNull;
654 : :
655 : : /* Fill any remaining argument positions with nulls */
656 [ + + ]: 1002 : for (i = 1; i < numFinalArgs; i++)
657 : : {
658 : 10 : fcinfo->args[i].value = (Datum) 0;
659 : 10 : fcinfo->args[i].isnull = true;
660 : 10 : anynull = true;
661 : 10 : }
662 : :
663 [ + + + - ]: 992 : 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 : 0 : }
669 : : else
670 : : {
671 : 992 : Datum res;
672 : :
673 : 992 : winstate->curaggcontext = peraggstate->aggcontext;
674 : 992 : res = FunctionCallInvoke(fcinfo);
675 : 992 : winstate->curaggcontext = NULL;
676 : 992 : *isnull = fcinfo->isnull;
677 [ + + + + ]: 992 : *result = MakeExpandedObjectReadOnly(res,
678 : : fcinfo->isnull,
679 : : peraggstate->resulttypeLen);
680 : 992 : }
681 : 992 : }
682 : : else
683 : : {
684 : 790 : *result =
685 [ + + + + ]: 790 : MakeExpandedObjectReadOnly(peraggstate->transValue,
686 : : peraggstate->transValueIsNull,
687 : : peraggstate->transtypeLen);
688 : 790 : *isnull = peraggstate->transValueIsNull;
689 : : }
690 : :
691 : 1782 : MemoryContextSwitchTo(oldContext);
692 : 1782 : }
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 : 26719 : eval_windowaggregates(WindowAggState *winstate)
707 : : {
708 : 26719 : WindowStatePerAgg peraggstate;
709 : 26719 : int wfuncno,
710 : : numaggs,
711 : : numaggs_restart,
712 : : i;
713 : 26719 : int64 aggregatedupto_nonrestarted;
714 : 26719 : MemoryContext oldContext;
715 : 26719 : ExprContext *econtext;
716 : 26719 : WindowObject agg_winobj;
717 : 26719 : TupleTableSlot *agg_row_slot;
718 : 26719 : TupleTableSlot *temp_slot;
719 : :
720 : 26719 : numaggs = winstate->numaggs;
721 [ + - ]: 26719 : if (numaggs == 0)
722 : 0 : return; /* nothing to do */
723 : :
724 : : /* final output execution is in ps_ExprContext */
725 : 26719 : econtext = winstate->ss.ps.ps_ExprContext;
726 : 26719 : agg_winobj = winstate->agg_winobj;
727 : 26719 : agg_row_slot = winstate->agg_row_slot;
728 : 26719 : 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 : 26719 : update_frameheadpos(winstate);
780 [ + - ]: 26719 : 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 [ + + ]: 26719 : if (winstate->aggregatedbase == winstate->frameheadpos &&
795 : 26088 : (winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
796 [ + + ]: 26088 : FRAMEOPTION_END_CURRENT_ROW)) &&
797 [ + + ]: 25768 : !(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
798 [ + + + + ]: 25738 : winstate->aggregatedbase <= winstate->currentpos &&
799 : 25732 : winstate->aggregatedupto > winstate->currentpos)
800 : : {
801 [ + + ]: 50450 : for (i = 0; i < numaggs; i++)
802 : : {
803 : 25226 : peraggstate = &winstate->peragg[i];
804 : 25226 : wfuncno = peraggstate->wfuncno;
805 : 25226 : econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
806 : 25226 : econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
807 : 25226 : }
808 : 25224 : 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 : 1495 : numaggs_restart = 0;
827 [ + + ]: 3283 : for (i = 0; i < numaggs; i++)
828 : : {
829 : 1788 : peraggstate = &winstate->peragg[i];
830 [ + + ]: 1788 : if (winstate->currentpos == 0 ||
831 [ + + ]: 1446 : (winstate->aggregatedbase != winstate->frameheadpos &&
832 : 865 : !OidIsValid(peraggstate->invtransfn_oid)) ||
833 [ + + + + ]: 1446 : (winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
834 : 1250 : winstate->aggregatedupto <= winstate->frameheadpos)
835 : : {
836 : 616 : peraggstate->restart = true;
837 : 616 : numaggs_restart++;
838 : 616 : }
839 : : else
840 : 1172 : peraggstate->restart = false;
841 : 1788 : }
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 [ + + + + ]: 3460 : while (numaggs_restart < numaggs &&
851 : 1437 : 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 [ + - + - ]: 1056 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
858 : 528 : 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 : 528 : 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 [ + + ]: 1301 : for (i = 0; i < numaggs; i++)
869 : : {
870 : 773 : bool ok;
871 : :
872 : 773 : peraggstate = &winstate->peragg[i];
873 [ + + ]: 773 : if (peraggstate->restart)
874 : 2 : continue;
875 : :
876 : 771 : wfuncno = peraggstate->wfuncno;
877 : 1542 : ok = advance_windowaggregate_base(winstate,
878 : 771 : &winstate->perfunc[wfuncno],
879 : 771 : peraggstate);
880 [ + + ]: 771 : if (!ok)
881 : : {
882 : : /* Inverse transition function has failed, must restart */
883 : 50 : peraggstate->restart = true;
884 : 50 : numaggs_restart++;
885 : 50 : }
886 [ + + ]: 773 : }
887 : :
888 : : /* Reset per-input-tuple context after each tuple */
889 : 528 : ResetExprContext(winstate->tmpcontext);
890 : :
891 : : /* And advance the aggregated-row state */
892 : 528 : winstate->aggregatedbase++;
893 : 528 : 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 : 1495 : 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 [ + + ]: 1495 : if (agg_winobj->markptr >= 0)
908 : 1034 : 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 [ + + ]: 1495 : if (numaggs_restart > 0)
921 : 626 : MemoryContextReset(winstate->aggcontext);
922 [ + + ]: 3283 : for (i = 0; i < numaggs; i++)
923 : : {
924 : 1788 : peraggstate = &winstate->peragg[i];
925 : :
926 : : /* Aggregates using the shared ctx must restart if *any* agg does */
927 [ + + + + : 1788 : Assert(peraggstate->aggcontext != winstate->aggcontext ||
- + ]
928 : : numaggs_restart == 0 ||
929 : : peraggstate->restart);
930 : :
931 [ + + ]: 1788 : if (peraggstate->restart)
932 : : {
933 : 666 : wfuncno = peraggstate->wfuncno;
934 : 1332 : initialize_windowaggregate(winstate,
935 : 666 : &winstate->perfunc[wfuncno],
936 : 666 : peraggstate);
937 : 666 : }
938 [ + + ]: 1122 : else if (!peraggstate->resultValueIsNull)
939 : : {
940 [ + + ]: 1083 : if (!peraggstate->resulttypeByVal)
941 : 374 : pfree(DatumGetPointer(peraggstate->resultValue));
942 : 1083 : peraggstate->resultValue = (Datum) 0;
943 : 1083 : peraggstate->resultValueIsNull = true;
944 : 1083 : }
945 : 1788 : }
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 : 1495 : aggregatedupto_nonrestarted = winstate->aggregatedupto;
958 [ + + + + ]: 1495 : if (numaggs_restart > 0 &&
959 : 626 : winstate->aggregatedupto != winstate->frameheadpos)
960 : : {
961 : 239 : winstate->aggregatedupto = winstate->frameheadpos;
962 : 239 : ExecClearTuple(agg_row_slot);
963 : 239 : }
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 : 31310 : for (;;)
973 : : {
974 : 31310 : int ret;
975 : :
976 : : /* Fetch next row if we didn't already */
977 [ + + + + ]: 31310 : if (TupIsNull(agg_row_slot))
978 : : {
979 [ + + + + ]: 61310 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
980 : 30655 : agg_row_slot))
981 : 688 : break; /* must be end of partition */
982 : 29959 : }
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 : 61228 : ret = row_is_in_frame(agg_winobj, winstate->aggregatedupto,
989 : 30614 : agg_row_slot, false);
990 [ + + ]: 30614 : if (ret < 0)
991 : 799 : break;
992 [ + + ]: 29815 : if (ret == 0)
993 : 316 : goto next_tuple;
994 : :
995 : : /* Set tuple context for evaluation of aggregate arguments */
996 : 29499 : winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
997 : :
998 : : /* Accumulate row into the aggregates */
999 [ + + ]: 63559 : for (i = 0; i < numaggs; i++)
1000 : : {
1001 : 34060 : peraggstate = &winstate->peragg[i];
1002 : :
1003 : : /* Non-restarted aggs skip until aggregatedupto_nonrestarted */
1004 [ + + + + ]: 34060 : if (!peraggstate->restart &&
1005 : 20488 : winstate->aggregatedupto < aggregatedupto_nonrestarted)
1006 : 3864 : continue;
1007 : :
1008 : 30196 : wfuncno = peraggstate->wfuncno;
1009 : 60392 : advance_windowaggregate(winstate,
1010 : 30196 : &winstate->perfunc[wfuncno],
1011 : 30196 : peraggstate);
1012 : 59695 : }
1013 : :
1014 : : next_tuple:
1015 : : /* Reset per-input-tuple context after each tuple */
1016 : 29815 : ResetExprContext(winstate->tmpcontext);
1017 : :
1018 : : /* And advance the aggregated-row state */
1019 : 29815 : winstate->aggregatedupto++;
1020 : 29815 : ExecClearTuple(agg_row_slot);
1021 [ + + ]: 31302 : }
1022 : :
1023 : : /* The frame's end is not supposed to move backwards, ever */
1024 [ + - ]: 1487 : Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
1025 : :
1026 : : /*
1027 : : * finalize aggregates and fill result/isnull fields.
1028 : : */
1029 [ + + ]: 3269 : for (i = 0; i < numaggs; i++)
1030 : : {
1031 : 1782 : Datum *result;
1032 : 1782 : bool *isnull;
1033 : :
1034 : 1782 : peraggstate = &winstate->peragg[i];
1035 : 1782 : wfuncno = peraggstate->wfuncno;
1036 : 1782 : result = &econtext->ecxt_aggvalues[wfuncno];
1037 : 1782 : isnull = &econtext->ecxt_aggnulls[wfuncno];
1038 : 3564 : finalize_windowaggregate(winstate,
1039 : 1782 : &winstate->perfunc[wfuncno],
1040 : 1782 : peraggstate,
1041 : 1782 : 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 [ + + + + ]: 1782 : if (!peraggstate->resulttypeByVal && !*isnull)
1051 : : {
1052 : 472 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
1053 : 472 : peraggstate->resultValue =
1054 : 944 : datumCopy(*result,
1055 : 472 : peraggstate->resulttypeByVal,
1056 : 472 : peraggstate->resulttypeLen);
1057 : 472 : MemoryContextSwitchTo(oldContext);
1058 : 472 : }
1059 : : else
1060 : : {
1061 : 1310 : peraggstate->resultValue = *result;
1062 : : }
1063 : 1782 : peraggstate->resultValueIsNull = *isnull;
1064 : 1782 : }
1065 : 26711 : }
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 : 145151 : eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
1078 : : Datum *result, bool *isnull)
1079 : : {
1080 : 145151 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
1081 : 145151 : MemoryContext oldContext;
1082 : :
1083 : 145151 : 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 : 145151 : 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 [ + + ]: 186702 : for (int argno = 0; argno < perfuncstate->numArguments; argno++)
1097 : 41551 : fcinfo->args[argno].isnull = true;
1098 : : /* Window functions don't have a current aggregate context, either */
1099 : 145151 : winstate->curaggcontext = NULL;
1100 : :
1101 : 145151 : *result = FunctionCallInvoke(fcinfo);
1102 : 145151 : *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 [ + + + + : 145151 : if (!perfuncstate->resulttypeByVal && !fcinfo->isnull &&
+ + ]
1112 : 170 : winstate->numfuncs > 1)
1113 : 36 : *result = datumCopy(*result,
1114 : 18 : perfuncstate->resulttypeByVal,
1115 : 18 : perfuncstate->resulttypeLen);
1116 : :
1117 : 145151 : MemoryContextSwitchTo(oldContext);
1118 : 145151 : }
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 : 381 : prepare_tuplestore(WindowAggState *winstate)
1130 : : {
1131 : 381 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1132 : 381 : int frameOptions = winstate->frameOptions;
1133 : 381 : int numfuncs = winstate->numfuncs;
1134 : :
1135 : : /* we shouldn't be called if this was done already */
1136 [ + - ]: 381 : Assert(winstate->buffer == NULL);
1137 : :
1138 : : /* Create new tuplestore */
1139 : 381 : 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 : 381 : winstate->current_ptr = 0; /* read pointer 0 is pre-allocated */
1147 : :
1148 : : /* reset default REWIND capability bit for current ptr */
1149 : 381 : tuplestore_set_eflags(winstate->buffer, 0);
1150 : :
1151 : : /* create read pointers for aggregates, if needed */
1152 [ + + ]: 381 : if (winstate->numaggs > 0)
1153 : : {
1154 : 188 : WindowObject agg_winobj = winstate->agg_winobj;
1155 : 188 : 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 [ + + + + ]: 188 : if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
1162 : 66 : (frameOptions & FRAMEOPTION_EXCLUSION))
1163 : : {
1164 : : /* ... so create a mark pointer to track the frame head */
1165 : 125 : agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
1166 : : /* and the read pointer will need BACKWARD capability */
1167 : 125 : readptr_flags |= EXEC_FLAG_BACKWARD;
1168 : 125 : }
1169 : :
1170 : 376 : agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1171 : 188 : readptr_flags);
1172 : 188 : }
1173 : :
1174 : : /* create mark and read pointers for each real window function */
1175 [ + + ]: 889 : for (int i = 0; i < numfuncs; i++)
1176 : : {
1177 : 508 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1178 : :
1179 [ + + ]: 508 : if (!perfuncstate->plain_agg)
1180 : : {
1181 : 304 : WindowObject winobj = perfuncstate->winobj;
1182 : :
1183 : 304 : winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer,
1184 : : 0);
1185 : 304 : winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1186 : : EXEC_FLAG_BACKWARD);
1187 : 304 : }
1188 : 508 : }
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 : 381 : winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */
1199 : :
1200 [ + + ]: 381 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1201 : : {
1202 [ + + ]: 211 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
1203 [ + + ]: 211 : node->ordNumCols != 0) ||
1204 : 200 : (frameOptions & FRAMEOPTION_START_OFFSET))
1205 : 120 : winstate->framehead_ptr =
1206 : 120 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1207 [ + + ]: 211 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
1208 [ + + ]: 211 : node->ordNumCols != 0) ||
1209 : 128 : (frameOptions & FRAMEOPTION_END_OFFSET))
1210 : 175 : winstate->frametail_ptr =
1211 : 175 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1212 : 211 : }
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 : 381 : winstate->grouptail_ptr = -1;
1222 : :
1223 : 381 : if ((frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
1224 [ + + + + ]: 381 : FRAMEOPTION_EXCLUDE_TIES)) &&
1225 : 30 : node->ordNumCols != 0)
1226 : : {
1227 : 28 : winstate->grouptail_ptr =
1228 : 28 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1229 : 28 : }
1230 : 381 : }
1231 : :
1232 : : /*
1233 : : * begin_partition
1234 : : * Start buffering rows of the next partition.
1235 : : */
1236 : : static void
1237 : 598 : begin_partition(WindowAggState *winstate)
1238 : : {
1239 : 598 : PlanState *outerPlan = outerPlanState(winstate);
1240 : 598 : int numfuncs = winstate->numfuncs;
1241 : :
1242 : 598 : winstate->partition_spooled = false;
1243 : 598 : winstate->framehead_valid = false;
1244 : 598 : winstate->frametail_valid = false;
1245 : 598 : winstate->grouptail_valid = false;
1246 : 598 : winstate->spooled_rows = 0;
1247 : 598 : winstate->currentpos = 0;
1248 : 598 : winstate->frameheadpos = 0;
1249 : 598 : winstate->frametailpos = 0;
1250 : 598 : winstate->currentgroup = 0;
1251 : 598 : winstate->frameheadgroup = 0;
1252 : 598 : winstate->frametailgroup = 0;
1253 : 598 : winstate->groupheadpos = 0;
1254 : 598 : winstate->grouptailpos = -1; /* see update_grouptailpos */
1255 : 598 : ExecClearTuple(winstate->agg_row_slot);
1256 [ + + ]: 598 : if (winstate->framehead_slot)
1257 : 170 : ExecClearTuple(winstate->framehead_slot);
1258 [ + + ]: 598 : if (winstate->frametail_slot)
1259 : 287 : 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 [ + - + + ]: 598 : if (TupIsNull(winstate->first_part_slot))
1266 : : {
1267 : 394 : TupleTableSlot *outerslot = ExecProcNode(outerPlan);
1268 : :
1269 [ + + + + ]: 394 : if (!TupIsNull(outerslot))
1270 : 391 : ExecCopySlot(winstate->first_part_slot, outerslot);
1271 : : else
1272 : : {
1273 : : /* outer plan is empty, so we have nothing to do */
1274 : 3 : winstate->partition_spooled = true;
1275 : 3 : winstate->more_partitions = false;
1276 : 3 : return;
1277 : : }
1278 [ + + ]: 394 : }
1279 : :
1280 : : /* Create new tuplestore if not done already. */
1281 [ + + ]: 595 : if (unlikely(winstate->buffer == NULL))
1282 : 381 : prepare_tuplestore(winstate);
1283 : :
1284 : 595 : winstate->next_partition = false;
1285 : :
1286 [ + + ]: 595 : if (winstate->numaggs > 0)
1287 : : {
1288 : 310 : WindowObject agg_winobj = winstate->agg_winobj;
1289 : :
1290 : : /* reset mark and see positions for aggregate functions */
1291 : 310 : agg_winobj->markpos = -1;
1292 : 310 : agg_winobj->seekpos = -1;
1293 : :
1294 : : /* Also reset the row counters for aggregates */
1295 : 310 : winstate->aggregatedbase = 0;
1296 : 310 : winstate->aggregatedupto = 0;
1297 : 310 : }
1298 : :
1299 : : /* reset mark and seek positions for each real window function */
1300 [ + + ]: 1368 : for (int i = 0; i < numfuncs; i++)
1301 : : {
1302 : 773 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1303 : :
1304 [ + + ]: 773 : if (!perfuncstate->plain_agg)
1305 : : {
1306 : 430 : WindowObject winobj = perfuncstate->winobj;
1307 : :
1308 : 430 : winobj->markpos = -1;
1309 : 430 : winobj->seekpos = -1;
1310 : :
1311 : : /* reset null map */
1312 [ + + + + ]: 430 : if (winobj->ignore_nulls == IGNORE_NULLS ||
1313 : 425 : winobj->ignore_nulls == PARSER_IGNORE_NULLS)
1314 : : {
1315 : 36 : int numargs = perfuncstate->numArguments;
1316 : :
1317 [ + + ]: 83 : for (int j = 0; j < numargs; j++)
1318 : : {
1319 : 47 : int n = winobj->num_notnull_info[j];
1320 : :
1321 [ + + ]: 47 : if (n > 0)
1322 : 5 : memset(winobj->notnull_info[j], 0,
1323 : : NN_POS_TO_BYTES(n));
1324 : 47 : }
1325 : 36 : }
1326 : 430 : }
1327 : 773 : }
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 : 595 : tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
1334 : 595 : winstate->spooled_rows++;
1335 [ - + ]: 598 : }
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 : 310440 : spool_tuples(WindowAggState *winstate, int64 pos)
1343 : : {
1344 : 310440 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1345 : 310440 : PlanState *outerPlan;
1346 : 310440 : TupleTableSlot *outerslot;
1347 : 310440 : MemoryContext oldcontext;
1348 : :
1349 [ + + ]: 310440 : if (!winstate->buffer)
1350 : 1 : return; /* just a safety check */
1351 [ + + ]: 310439 : if (winstate->partition_spooled)
1352 : 21879 : 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 [ + + ]: 288560 : if (winstate->status != WINDOWAGG_RUN)
1361 : : {
1362 [ + - + - ]: 5 : Assert(winstate->status == WINDOWAGG_PASSTHROUGH ||
1363 : : winstate->status == WINDOWAGG_PASSTHROUGH_STRICT);
1364 : :
1365 : 5 : pos = -1;
1366 : 5 : }
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 [ + + ]: 288555 : else if (!tuplestore_in_memory(winstate->buffer))
1377 : 2 : pos = -1;
1378 : :
1379 : 288560 : outerPlan = outerPlanState(winstate);
1380 : :
1381 : : /* Must be in query context to call outerplan */
1382 : 288560 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1383 : :
1384 [ + + + + ]: 458883 : while (winstate->spooled_rows <= pos || pos == -1)
1385 : : {
1386 : 170885 : outerslot = ExecProcNode(outerPlan);
1387 [ + + + + ]: 170885 : if (TupIsNull(outerslot))
1388 : : {
1389 : : /* reached the end of the last partition */
1390 : 358 : winstate->partition_spooled = true;
1391 : 358 : winstate->more_partitions = false;
1392 : 358 : break;
1393 : : }
1394 : :
1395 [ + + ]: 170527 : if (node->partNumCols > 0)
1396 : : {
1397 : 23104 : ExprContext *econtext = winstate->tmpcontext;
1398 : :
1399 : 23104 : econtext->ecxt_innertuple = winstate->first_part_slot;
1400 : 23104 : econtext->ecxt_outertuple = outerslot;
1401 : :
1402 : : /* Check if this tuple still belongs to the current partition */
1403 [ + + ]: 23104 : if (!ExecQualAndReset(winstate->partEqfunction, econtext))
1404 : : {
1405 : : /*
1406 : : * end of partition; copy the tuple for the next cycle.
1407 : : */
1408 : 204 : ExecCopySlot(winstate->first_part_slot, outerslot);
1409 : 204 : winstate->partition_spooled = true;
1410 : 204 : winstate->more_partitions = true;
1411 : 204 : break;
1412 : : }
1413 [ + + ]: 23104 : }
1414 : :
1415 : : /*
1416 : : * Remember the tuple unless we're the top-level window and we're in
1417 : : * pass-through mode.
1418 : : */
1419 [ + + ]: 170323 : if (winstate->status != WINDOWAGG_PASSTHROUGH_STRICT)
1420 : : {
1421 : : /* Still in partition, so save it into the tuplestore */
1422 : 170321 : tuplestore_puttupleslot(winstate->buffer, outerslot);
1423 : 170321 : winstate->spooled_rows++;
1424 : 170321 : }
1425 : : }
1426 : :
1427 : 288560 : MemoryContextSwitchTo(oldcontext);
1428 : 310440 : }
1429 : :
1430 : : /*
1431 : : * release_partition
1432 : : * clear information kept within a partition, including
1433 : : * tuplestore and aggregate results.
1434 : : */
1435 : : static void
1436 : 992 : release_partition(WindowAggState *winstate)
1437 : : {
1438 : 992 : int i;
1439 : :
1440 [ + + ]: 2275 : for (i = 0; i < winstate->numfuncs; i++)
1441 : : {
1442 : 1283 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1443 : :
1444 : : /* Release any partition-local state of this window function */
1445 [ + + ]: 1283 : if (perfuncstate->winobj)
1446 : 684 : perfuncstate->winobj->localmem = NULL;
1447 : 1283 : }
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 : 992 : MemoryContextReset(winstate->partcontext);
1456 : 992 : MemoryContextReset(winstate->aggcontext);
1457 [ + + ]: 1591 : for (i = 0; i < winstate->numaggs; i++)
1458 : : {
1459 [ + + ]: 599 : if (winstate->peragg[i].aggcontext != winstate->aggcontext)
1460 : 322 : MemoryContextReset(winstate->peragg[i].aggcontext);
1461 : 599 : }
1462 : :
1463 [ + + ]: 992 : if (winstate->buffer)
1464 : 567 : tuplestore_clear(winstate->buffer);
1465 : 992 : winstate->partition_spooled = false;
1466 : 992 : winstate->next_partition = true;
1467 : 992 : }
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 : 32405 : row_is_in_frame(WindowObject winobj, int64 pos, TupleTableSlot *slot,
1487 : : bool fetch_tuple)
1488 : : {
1489 : 32405 : WindowAggState *winstate = winobj->winstate;
1490 : 32405 : int frameOptions = winstate->frameOptions;
1491 : :
1492 [ + - ]: 32405 : 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 : 32405 : update_frameheadpos(winstate);
1499 [ + + ]: 32405 : if (pos < winstate->frameheadpos)
1500 : 24 : 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 [ + + ]: 32381 : if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1508 : : {
1509 [ + + ]: 26381 : if (frameOptions & FRAMEOPTION_ROWS)
1510 : : {
1511 : : /* rows after current row are out of frame */
1512 [ + + ]: 368 : if (pos > winstate->currentpos)
1513 : 162 : return -1;
1514 : 206 : }
1515 [ + - ]: 26013 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1516 : : {
1517 : : /* following row that is not peer is out of frame */
1518 [ + + ]: 26013 : if (pos > winstate->currentpos)
1519 : : {
1520 [ + - ]: 25436 : if (fetch_tuple) /* need to fetch tuple? */
1521 [ # # ]: 0 : if (!window_gettupleslot(winobj, pos, slot))
1522 : 0 : return -1;
1523 [ + + ]: 25436 : if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
1524 : 226 : return -1;
1525 : 25210 : }
1526 : 25787 : }
1527 : : else
1528 : 0 : Assert(false);
1529 : 25993 : }
1530 [ + + ]: 6000 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1531 : : {
1532 [ + + ]: 3312 : if (frameOptions & FRAMEOPTION_ROWS)
1533 : : {
1534 : 988 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1535 : :
1536 : : /* rows after current row + offset are out of frame */
1537 [ + + ]: 988 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1538 : 19 : offset = -offset;
1539 : :
1540 [ + + ]: 988 : if (pos > winstate->currentpos + offset)
1541 : 201 : return -1;
1542 [ + + ]: 988 : }
1543 [ + - ]: 2324 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1544 : : {
1545 : : /* hard cases, so delegate to update_frametailpos */
1546 : 2324 : update_frametailpos(winstate);
1547 [ + + ]: 2324 : if (pos >= winstate->frametailpos)
1548 : 245 : return -1;
1549 : 2079 : }
1550 : : else
1551 : 0 : Assert(false);
1552 : 2866 : }
1553 : :
1554 : : /* Check exclusion clause */
1555 [ + + ]: 31547 : if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
1556 : : {
1557 [ + + ]: 491 : if (pos == winstate->currentpos)
1558 : 83 : return 0;
1559 : 408 : }
1560 [ + + + + ]: 31551 : else if ((frameOptions & FRAMEOPTION_EXCLUDE_GROUP) ||
1561 [ + + ]: 30579 : ((frameOptions & FRAMEOPTION_EXCLUDE_TIES) &&
1562 : 495 : pos != winstate->currentpos))
1563 : : {
1564 : 882 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1565 : :
1566 : : /* If no ORDER BY, all rows are peers with each other */
1567 [ + + ]: 882 : if (node->ordNumCols == 0)
1568 : 78 : return 0;
1569 : : /* Otherwise, check the group boundaries */
1570 [ + + ]: 804 : if (pos >= winstate->groupheadpos)
1571 : : {
1572 : 432 : update_grouptailpos(winstate);
1573 [ + + ]: 432 : if (pos < winstate->grouptailpos)
1574 : 168 : return 0;
1575 : 264 : }
1576 [ + + ]: 882 : }
1577 : :
1578 : : /* If we get here, it's in frame */
1579 : 31218 : return 1;
1580 : 32405 : }
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 : 61095 : update_frameheadpos(WindowAggState *winstate)
1594 : : {
1595 : 61095 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1596 : 61095 : int frameOptions = winstate->frameOptions;
1597 : 61095 : MemoryContext oldcontext;
1598 : :
1599 [ + + ]: 61095 : if (winstate->framehead_valid)
1600 : 33490 : return; /* already known for current row */
1601 : :
1602 : : /* We may be called in a short-lived context */
1603 : 27605 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1604 : :
1605 [ + + ]: 27605 : if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
1606 : : {
1607 : : /* In UNBOUNDED PRECEDING mode, frame head is always row 0 */
1608 : 25825 : winstate->frameheadpos = 0;
1609 : 25825 : winstate->framehead_valid = true;
1610 : 25825 : }
1611 [ + + ]: 1780 : else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
1612 : : {
1613 [ + + ]: 464 : if (frameOptions & FRAMEOPTION_ROWS)
1614 : : {
1615 : : /* In ROWS mode, frame head is the same as current */
1616 : 396 : winstate->frameheadpos = winstate->currentpos;
1617 : 396 : winstate->framehead_valid = true;
1618 : 396 : }
1619 [ + - ]: 68 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1620 : : {
1621 : : /* If no ORDER BY, all rows are peers with each other */
1622 [ + - ]: 68 : 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 : 136 : tuplestore_select_read_pointer(winstate->buffer,
1638 : 68 : winstate->framehead_ptr);
1639 [ + + + + ]: 102 : if (winstate->frameheadpos == 0 &&
1640 [ + - ]: 34 : TupIsNull(winstate->framehead_slot))
1641 : : {
1642 : : /* fetch first row into framehead_slot, if we didn't already */
1643 [ + - + - ]: 26 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1644 : 13 : winstate->framehead_slot))
1645 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
1646 : 13 : }
1647 : :
1648 [ + - - + ]: 118 : while (!TupIsNull(winstate->framehead_slot))
1649 : : {
1650 [ + + + + ]: 236 : if (are_peers(winstate, winstate->framehead_slot,
1651 : 118 : winstate->ss.ss_ScanTupleSlot))
1652 : 68 : break; /* this row is the correct frame head */
1653 : : /* Note we advance frameheadpos even if the fetch fails */
1654 : 50 : winstate->frameheadpos++;
1655 : 50 : spool_tuples(winstate, winstate->frameheadpos);
1656 [ - + - + ]: 100 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1657 : 50 : winstate->framehead_slot))
1658 : 0 : break; /* end of partition */
1659 : : }
1660 : 68 : winstate->framehead_valid = true;
1661 : 68 : }
1662 : : else
1663 : 0 : Assert(false);
1664 : 464 : }
1665 [ + - ]: 1316 : else if (frameOptions & FRAMEOPTION_START_OFFSET)
1666 : : {
1667 [ + + ]: 1316 : if (frameOptions & FRAMEOPTION_ROWS)
1668 : : {
1669 : : /* In ROWS mode, bound is physically n before/after current */
1670 : 332 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1671 : :
1672 [ + + ]: 332 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1673 : 322 : offset = -offset;
1674 : :
1675 : 332 : winstate->frameheadpos = winstate->currentpos + offset;
1676 : : /* frame head can't go before first row */
1677 [ + + ]: 332 : if (winstate->frameheadpos < 0)
1678 : 56 : winstate->frameheadpos = 0;
1679 [ + - ]: 276 : 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 : 0 : }
1686 : 332 : winstate->framehead_valid = true;
1687 : 332 : }
1688 [ + + ]: 984 : 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 : 754 : int sortCol = node->ordColIdx[0];
1699 : 754 : bool sub,
1700 : : less;
1701 : :
1702 : : /* We must have an ordering column */
1703 [ + - ]: 754 : Assert(node->ordNumCols == 1);
1704 : :
1705 : : /* Precompute flags for in_range checks */
1706 [ + + ]: 754 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1707 : 617 : sub = true; /* subtract startOffset from current row */
1708 : : else
1709 : 137 : sub = false; /* add it */
1710 : 754 : less = false; /* normally, we want frame head >= sum */
1711 : : /* If sort order is descending, flip both flags */
1712 [ + + ]: 754 : if (!winstate->inRangeAsc)
1713 : : {
1714 : 109 : sub = !sub;
1715 : 109 : less = true;
1716 : 109 : }
1717 : :
1718 : 1508 : tuplestore_select_read_pointer(winstate->buffer,
1719 : 754 : winstate->framehead_ptr);
1720 [ + + + + ]: 1171 : if (winstate->frameheadpos == 0 &&
1721 [ + - ]: 417 : TupIsNull(winstate->framehead_slot))
1722 : : {
1723 : : /* fetch first row into framehead_slot, if we didn't already */
1724 [ + - + - ]: 190 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1725 : 95 : winstate->framehead_slot))
1726 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
1727 : 95 : }
1728 : :
1729 [ - + + + ]: 1211 : while (!TupIsNull(winstate->framehead_slot))
1730 : : {
1731 : 1169 : Datum headval,
1732 : : currval;
1733 : 1169 : bool headisnull,
1734 : : currisnull;
1735 : :
1736 : 1169 : headval = slot_getattr(winstate->framehead_slot, sortCol,
1737 : : &headisnull);
1738 : 1169 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1739 : : &currisnull);
1740 [ + + + + ]: 1169 : if (headisnull || currisnull)
1741 : : {
1742 : : /* order of the rows depends only on nulls_first */
1743 [ + + ]: 18 : if (winstate->inRangeNullsFirst)
1744 : : {
1745 : : /* advance head if head is null and curr is not */
1746 [ + - + + ]: 8 : if (!headisnull || currisnull)
1747 : 4 : break;
1748 : 4 : }
1749 : : else
1750 : : {
1751 : : /* advance head if head is not null and curr is null */
1752 [ + + - + ]: 10 : if (headisnull || !currisnull)
1753 : 4 : break;
1754 : : }
1755 : 10 : }
1756 : : else
1757 : : {
1758 [ + + + + ]: 2302 : if (DatumGetBool(FunctionCall5Coll(&winstate->startInRangeFunc,
1759 : 1151 : winstate->inRangeColl,
1760 : 1151 : headval,
1761 : 1151 : currval,
1762 : 1151 : winstate->startOffsetValue,
1763 : 1151 : BoolGetDatum(sub),
1764 : 1151 : BoolGetDatum(less))))
1765 : 695 : break; /* this row is the correct frame head */
1766 : : }
1767 : : /* Note we advance frameheadpos even if the fetch fails */
1768 : 466 : winstate->frameheadpos++;
1769 : 466 : spool_tuples(winstate, winstate->frameheadpos);
1770 [ + + + + ]: 932 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1771 : 466 : winstate->framehead_slot))
1772 : 9 : break; /* end of partition */
1773 [ + + ]: 1169 : }
1774 : 754 : winstate->framehead_valid = true;
1775 : 754 : }
1776 [ + - ]: 230 : 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 : 230 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1787 : 230 : int64 minheadgroup;
1788 : :
1789 [ + + ]: 230 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1790 : 188 : minheadgroup = winstate->currentgroup - offset;
1791 : : else
1792 : 42 : minheadgroup = winstate->currentgroup + offset;
1793 : :
1794 : 460 : tuplestore_select_read_pointer(winstate->buffer,
1795 : 230 : winstate->framehead_ptr);
1796 [ + + + + ]: 355 : if (winstate->frameheadpos == 0 &&
1797 [ + - ]: 125 : TupIsNull(winstate->framehead_slot))
1798 : : {
1799 : : /* fetch first row into framehead_slot, if we didn't already */
1800 [ + - + - ]: 124 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1801 : 62 : winstate->framehead_slot))
1802 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
1803 : 62 : }
1804 : :
1805 [ - + + + ]: 357 : while (!TupIsNull(winstate->framehead_slot))
1806 : : {
1807 [ + + ]: 353 : if (winstate->frameheadgroup >= minheadgroup)
1808 : 220 : break; /* this row is the correct frame head */
1809 : 133 : ExecCopySlot(winstate->temp_slot_2, winstate->framehead_slot);
1810 : : /* Note we advance frameheadpos even if the fetch fails */
1811 : 133 : winstate->frameheadpos++;
1812 : 133 : spool_tuples(winstate, winstate->frameheadpos);
1813 [ + + + + ]: 266 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1814 : 133 : winstate->framehead_slot))
1815 : 6 : break; /* end of partition */
1816 [ + + + + ]: 254 : if (!are_peers(winstate, winstate->temp_slot_2,
1817 : 127 : winstate->framehead_slot))
1818 : 87 : winstate->frameheadgroup++;
1819 : : }
1820 : 230 : ExecClearTuple(winstate->temp_slot_2);
1821 : 230 : winstate->framehead_valid = true;
1822 : 230 : }
1823 : : else
1824 : 0 : Assert(false);
1825 : 1316 : }
1826 : : else
1827 : 0 : Assert(false);
1828 : :
1829 : 27605 : MemoryContextSwitchTo(oldcontext);
1830 : 61095 : }
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 : 33807 : update_frametailpos(WindowAggState *winstate)
1844 : : {
1845 : 33807 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1846 : 33807 : int frameOptions = winstate->frameOptions;
1847 : 33807 : MemoryContext oldcontext;
1848 : :
1849 [ + + ]: 33807 : if (winstate->frametail_valid)
1850 : 2990 : return; /* already known for current row */
1851 : :
1852 : : /* We may be called in a short-lived context */
1853 : 30817 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1854 : :
1855 [ + + ]: 30817 : if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
1856 : : {
1857 : : /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
1858 : 40 : spool_tuples(winstate, -1);
1859 : 40 : winstate->frametailpos = winstate->spooled_rows;
1860 : 40 : winstate->frametail_valid = true;
1861 : 40 : }
1862 [ + + ]: 30777 : else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1863 : : {
1864 [ + + ]: 29673 : if (frameOptions & FRAMEOPTION_ROWS)
1865 : : {
1866 : : /* In ROWS mode, exactly the rows up to current are in frame */
1867 : 20 : winstate->frametailpos = winstate->currentpos + 1;
1868 : 20 : winstate->frametail_valid = true;
1869 : 20 : }
1870 [ + - ]: 29653 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1871 : : {
1872 : : /* If no ORDER BY, all rows are peers with each other */
1873 [ + + ]: 29653 : if (node->ordNumCols == 0)
1874 : : {
1875 : 10 : spool_tuples(winstate, -1);
1876 : 10 : winstate->frametailpos = winstate->spooled_rows;
1877 : 10 : winstate->frametail_valid = true;
1878 : 10 : MemoryContextSwitchTo(oldcontext);
1879 : 10 : 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 : 59286 : tuplestore_select_read_pointer(winstate->buffer,
1891 : 29643 : winstate->frametail_ptr);
1892 [ + + + - ]: 29760 : if (winstate->frametailpos == 0 &&
1893 [ + - ]: 117 : TupIsNull(winstate->frametail_slot))
1894 : : {
1895 : : /* fetch first row into frametail_slot, if we didn't already */
1896 [ + - + - ]: 234 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1897 : 117 : winstate->frametail_slot))
1898 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
1899 : 117 : }
1900 : :
1901 [ - + + + ]: 59171 : while (!TupIsNull(winstate->frametail_slot))
1902 : : {
1903 [ + + + + ]: 55151 : if (winstate->frametailpos > winstate->currentpos &&
1904 : 91136 : !are_peers(winstate, winstate->frametail_slot,
1905 : 45568 : winstate->ss.ss_ScanTupleSlot))
1906 : 25508 : break; /* this row is the frame tail */
1907 : : /* Note we advance frametailpos even if the fetch fails */
1908 : 29643 : winstate->frametailpos++;
1909 : 29643 : spool_tuples(winstate, winstate->frametailpos);
1910 [ + + + + ]: 59286 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1911 : 29643 : winstate->frametail_slot))
1912 : 115 : break; /* end of partition */
1913 : : }
1914 : 29643 : winstate->frametail_valid = true;
1915 : 29643 : }
1916 : : else
1917 : 0 : Assert(false);
1918 : 29663 : }
1919 [ + - ]: 1104 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1920 : : {
1921 [ + + ]: 1104 : if (frameOptions & FRAMEOPTION_ROWS)
1922 : : {
1923 : : /* In ROWS mode, bound is physically n before/after current */
1924 : 70 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1925 : :
1926 [ + - ]: 70 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1927 : 0 : offset = -offset;
1928 : :
1929 : 70 : winstate->frametailpos = winstate->currentpos + offset + 1;
1930 : : /* smallest allowable value of frametailpos is 0 */
1931 [ + - ]: 70 : if (winstate->frametailpos < 0)
1932 : 0 : winstate->frametailpos = 0;
1933 [ - + ]: 70 : else if (winstate->frametailpos > winstate->currentpos + 1)
1934 : : {
1935 : : /* make sure frametailpos is not past end of partition */
1936 : 70 : spool_tuples(winstate, winstate->frametailpos - 1);
1937 [ + + ]: 70 : if (winstate->frametailpos > winstate->spooled_rows)
1938 : 16 : winstate->frametailpos = winstate->spooled_rows;
1939 : 70 : }
1940 : 70 : winstate->frametail_valid = true;
1941 : 70 : }
1942 [ + + ]: 1034 : 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 : 814 : int sortCol = node->ordColIdx[0];
1953 : 814 : bool sub,
1954 : : less;
1955 : :
1956 : : /* We must have an ordering column */
1957 [ + - ]: 814 : Assert(node->ordNumCols == 1);
1958 : :
1959 : : /* Precompute flags for in_range checks */
1960 [ + + ]: 814 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1961 : 152 : sub = true; /* subtract endOffset from current row */
1962 : : else
1963 : 662 : sub = false; /* add it */
1964 : 814 : less = true; /* normally, we want frame tail <= sum */
1965 : : /* If sort order is descending, flip both flags */
1966 [ + + ]: 814 : if (!winstate->inRangeAsc)
1967 : : {
1968 : 115 : sub = !sub;
1969 : 115 : less = false;
1970 : 115 : }
1971 : :
1972 : 1628 : tuplestore_select_read_pointer(winstate->buffer,
1973 : 814 : winstate->frametail_ptr);
1974 [ + + + + ]: 951 : if (winstate->frametailpos == 0 &&
1975 [ + - ]: 137 : TupIsNull(winstate->frametail_slot))
1976 : : {
1977 : : /* fetch first row into frametail_slot, if we didn't already */
1978 [ + - + - ]: 196 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1979 : 98 : winstate->frametail_slot))
1980 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
1981 : 98 : }
1982 : :
1983 [ - + + + ]: 1501 : while (!TupIsNull(winstate->frametail_slot))
1984 : : {
1985 : 1232 : Datum tailval,
1986 : : currval;
1987 : 1232 : bool tailisnull,
1988 : : currisnull;
1989 : :
1990 : 1232 : tailval = slot_getattr(winstate->frametail_slot, sortCol,
1991 : : &tailisnull);
1992 : 1232 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1993 : : &currisnull);
1994 [ + + + + ]: 1232 : if (tailisnull || currisnull)
1995 : : {
1996 : : /* order of the rows depends only on nulls_first */
1997 [ + + ]: 18 : if (winstate->inRangeNullsFirst)
1998 : : {
1999 : : /* advance tail if tail is null or curr is not */
2000 [ + + ]: 8 : if (!tailisnull)
2001 : 4 : break;
2002 : 4 : }
2003 : : else
2004 : : {
2005 : : /* advance tail if tail is not null or curr is null */
2006 [ + + ]: 10 : if (!currisnull)
2007 : 6 : break;
2008 : : }
2009 : 8 : }
2010 : : else
2011 : : {
2012 [ + + + + ]: 2428 : if (!DatumGetBool(FunctionCall5Coll(&winstate->endInRangeFunc,
2013 : 1214 : winstate->inRangeColl,
2014 : 1214 : tailval,
2015 : 1214 : currval,
2016 : 1214 : winstate->endOffsetValue,
2017 : 1214 : BoolGetDatum(sub),
2018 : 1214 : BoolGetDatum(less))))
2019 : 455 : break; /* this row is the correct frame tail */
2020 : : }
2021 : : /* Note we advance frametailpos even if the fetch fails */
2022 : 767 : winstate->frametailpos++;
2023 : 767 : spool_tuples(winstate, winstate->frametailpos);
2024 [ + + + + ]: 1534 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2025 : 767 : winstate->frametail_slot))
2026 : 80 : break; /* end of partition */
2027 [ + + ]: 1232 : }
2028 : 814 : winstate->frametail_valid = true;
2029 : 814 : }
2030 [ + - ]: 220 : 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 : 220 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
2041 : 220 : int64 maxtailgroup;
2042 : :
2043 [ + + ]: 220 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
2044 : 12 : maxtailgroup = winstate->currentgroup - offset;
2045 : : else
2046 : 208 : maxtailgroup = winstate->currentgroup + offset;
2047 : :
2048 : 440 : tuplestore_select_read_pointer(winstate->buffer,
2049 : 220 : winstate->frametail_ptr);
2050 [ + + + + ]: 284 : if (winstate->frametailpos == 0 &&
2051 [ + - ]: 64 : TupIsNull(winstate->frametail_slot))
2052 : : {
2053 : : /* fetch first row into frametail_slot, if we didn't already */
2054 [ + - + - ]: 122 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2055 : 61 : winstate->frametail_slot))
2056 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
2057 : 61 : }
2058 : :
2059 [ - + + + ]: 378 : while (!TupIsNull(winstate->frametail_slot))
2060 : : {
2061 [ + + ]: 340 : if (winstate->frametailgroup > maxtailgroup)
2062 : 124 : break; /* this row is the correct frame tail */
2063 : 216 : ExecCopySlot(winstate->temp_slot_2, winstate->frametail_slot);
2064 : : /* Note we advance frametailpos even if the fetch fails */
2065 : 216 : winstate->frametailpos++;
2066 : 216 : spool_tuples(winstate, winstate->frametailpos);
2067 [ + + + + ]: 432 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2068 : 216 : winstate->frametail_slot))
2069 : 58 : break; /* end of partition */
2070 [ + + + + ]: 316 : if (!are_peers(winstate, winstate->temp_slot_2,
2071 : 158 : winstate->frametail_slot))
2072 : 100 : winstate->frametailgroup++;
2073 : : }
2074 : 220 : ExecClearTuple(winstate->temp_slot_2);
2075 : 220 : winstate->frametail_valid = true;
2076 : 220 : }
2077 : : else
2078 : 0 : Assert(false);
2079 : 1104 : }
2080 : : else
2081 : 0 : Assert(false);
2082 : :
2083 : 30807 : MemoryContextSwitchTo(oldcontext);
2084 : 33807 : }
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 : 812 : update_grouptailpos(WindowAggState *winstate)
2094 : : {
2095 : 812 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
2096 : 812 : MemoryContext oldcontext;
2097 : :
2098 [ + + ]: 812 : if (winstate->grouptail_valid)
2099 : 659 : return; /* already known for current row */
2100 : :
2101 : : /* We may be called in a short-lived context */
2102 : 153 : 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 [ + - ]: 153 : 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 [ + - ]: 153 : Assert(winstate->grouptailpos <= winstate->currentpos);
2122 : 306 : tuplestore_select_read_pointer(winstate->buffer,
2123 : 153 : winstate->grouptail_ptr);
2124 : 293 : for (;;)
2125 : : {
2126 : : /* Note we advance grouptailpos even if the fetch fails */
2127 : 293 : winstate->grouptailpos++;
2128 : 293 : spool_tuples(winstate, winstate->grouptailpos);
2129 [ + + + + ]: 586 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2130 : 293 : winstate->temp_slot_2))
2131 : 43 : break; /* end of partition */
2132 [ + + + + ]: 250 : if (winstate->grouptailpos > winstate->currentpos &&
2133 : 414 : !are_peers(winstate, winstate->temp_slot_2,
2134 : 207 : winstate->ss.ss_ScanTupleSlot))
2135 : 110 : break; /* this row is the group tail */
2136 : : }
2137 : 153 : ExecClearTuple(winstate->temp_slot_2);
2138 : 153 : winstate->grouptail_valid = true;
2139 : :
2140 : 153 : MemoryContextSwitchTo(oldcontext);
2141 [ - + ]: 812 : }
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 : 394 : calculate_frame_offsets(PlanState *pstate)
2150 : : {
2151 : 394 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2152 : 394 : ExprContext *econtext;
2153 : 394 : int frameOptions = winstate->frameOptions;
2154 : 394 : Datum value;
2155 : 394 : bool isnull;
2156 : 394 : int16 len;
2157 : 394 : bool byval;
2158 : :
2159 : : /* Ensure we've not been called before for this scan */
2160 [ + - ]: 394 : Assert(winstate->all_first);
2161 : :
2162 : 394 : econtext = winstate->ss.ps.ps_ExprContext;
2163 : :
2164 [ + + ]: 394 : if (frameOptions & FRAMEOPTION_START_OFFSET)
2165 : : {
2166 [ + - ]: 144 : Assert(winstate->startOffset != NULL);
2167 : 288 : value = ExecEvalExprSwitchContext(winstate->startOffset,
2168 : 144 : econtext,
2169 : : &isnull);
2170 [ + - ]: 144 : 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 : 144 : get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
2176 : : &len,
2177 : : &byval);
2178 : 144 : winstate->startOffsetValue = datumCopy(value, byval, len);
2179 [ + + ]: 144 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2180 : : {
2181 : : /* value is known to be int8 */
2182 : 58 : int64 offset = DatumGetInt64(value);
2183 : :
2184 [ + - ]: 58 : 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 : 58 : }
2189 : 144 : }
2190 : :
2191 [ + + ]: 394 : if (frameOptions & FRAMEOPTION_END_OFFSET)
2192 : : {
2193 [ + - ]: 160 : Assert(winstate->endOffset != NULL);
2194 : 320 : value = ExecEvalExprSwitchContext(winstate->endOffset,
2195 : 160 : econtext,
2196 : : &isnull);
2197 [ + - ]: 160 : 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 : 160 : get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
2203 : : &len,
2204 : : &byval);
2205 : 160 : winstate->endOffsetValue = datumCopy(value, byval, len);
2206 [ + + ]: 160 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2207 : : {
2208 : : /* value is known to be int8 */
2209 : 63 : int64 offset = DatumGetInt64(value);
2210 : :
2211 [ + - ]: 63 : 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 : 63 : }
2216 : 160 : }
2217 : 394 : winstate->all_first = false;
2218 : 394 : }
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 : 151160 : ExecWindowAgg(PlanState *pstate)
2231 : : {
2232 : 151160 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2233 : 151160 : TupleTableSlot *slot;
2234 : 151160 : ExprContext *econtext;
2235 : 151160 : int i;
2236 : 151160 : int numfuncs;
2237 : :
2238 [ + - ]: 151160 : CHECK_FOR_INTERRUPTS();
2239 : :
2240 [ + - ]: 151160 : 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 [ + + ]: 151160 : if (unlikely(winstate->all_first))
2249 : 394 : calculate_frame_offsets(pstate);
2250 : :
2251 : : /* We need to loop as the runCondition or qual may filter out tuples */
2252 : 151167 : for (;;)
2253 : : {
2254 [ + + ]: 151182 : if (winstate->next_partition)
2255 : : {
2256 : : /* Initialize for first partition and set current row = 0 */
2257 : 360 : begin_partition(winstate);
2258 : : /* If there are no input rows, we'll detect that and exit below */
2259 : 360 : }
2260 : : else
2261 : : {
2262 : : /* Advance current row within partition */
2263 : 150822 : winstate->currentpos++;
2264 : : /* This might mean that the frame moves, too */
2265 : 150822 : winstate->framehead_valid = false;
2266 : 150822 : 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 : 151182 : spool_tuples(winstate, winstate->currentpos);
2275 : :
2276 : : /* Move to the next partition if we reached the end of this partition */
2277 [ + + + + ]: 151182 : if (winstate->partition_spooled &&
2278 : 10493 : winstate->currentpos >= winstate->spooled_rows)
2279 : : {
2280 : 556 : release_partition(winstate);
2281 : :
2282 [ + + ]: 556 : if (winstate->more_partitions)
2283 : : {
2284 : 204 : begin_partition(winstate);
2285 [ - + ]: 204 : Assert(winstate->spooled_rows > 0);
2286 : :
2287 : : /* Come out of pass-through mode when changing partition */
2288 : 204 : winstate->status = WINDOWAGG_RUN;
2289 : 204 : }
2290 : : else
2291 : : {
2292 : : /* No further partitions? We're done */
2293 : 352 : winstate->status = WINDOWAGG_DONE;
2294 : 352 : return NULL;
2295 : : }
2296 : 204 : }
2297 : :
2298 : : /* final output execution is in ps_ExprContext */
2299 : 150830 : econtext = winstate->ss.ps.ps_ExprContext;
2300 : :
2301 : : /* Clear the per-output-tuple context for current row */
2302 : 150830 : 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 : 150830 : tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);
2320 : 150830 : if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |
2321 : : FRAMEOPTION_EXCLUDE_GROUP |
2322 [ + + + + ]: 150830 : FRAMEOPTION_EXCLUDE_TIES)) &&
2323 : 483 : winstate->currentpos > 0)
2324 : : {
2325 : 393 : ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot);
2326 [ - + - + ]: 786 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2327 : 393 : winstate->ss.ss_ScanTupleSlot))
2328 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
2329 [ + + + + ]: 786 : if (!are_peers(winstate, winstate->temp_slot_2,
2330 : 393 : winstate->ss.ss_ScanTupleSlot))
2331 : : {
2332 : 207 : winstate->currentgroup++;
2333 : 207 : winstate->groupheadpos = winstate->currentpos;
2334 : 207 : winstate->grouptail_valid = false;
2335 : 207 : }
2336 : 393 : ExecClearTuple(winstate->temp_slot_2);
2337 : 393 : }
2338 : : else
2339 : : {
2340 [ + - + - ]: 300874 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2341 : 150437 : 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 [ + + ]: 150830 : if (winstate->status == WINDOWAGG_RUN)
2347 : : {
2348 : : /*
2349 : : * Evaluate true window functions
2350 : : */
2351 : 150819 : numfuncs = winstate->numfuncs;
2352 [ + + ]: 322985 : for (i = 0; i < numfuncs; i++)
2353 : : {
2354 : 172166 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
2355 : :
2356 [ + + ]: 172166 : if (perfuncstate->plain_agg)
2357 : 27015 : continue;
2358 : 290302 : eval_windowfunction(winstate, perfuncstate,
2359 : 145151 : &(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),
2360 : 145151 : &(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));
2361 [ - + + ]: 172166 : }
2362 : :
2363 : : /*
2364 : : * Evaluate aggregates
2365 : : */
2366 [ + + ]: 150819 : if (winstate->numaggs > 0)
2367 : 26716 : eval_windowaggregates(winstate);
2368 : 150819 : }
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 [ + + ]: 150830 : if (winstate->framehead_ptr >= 0)
2381 : 1036 : update_frameheadpos(winstate);
2382 [ + + ]: 150830 : if (winstate->frametail_ptr >= 0)
2383 : 30669 : update_frametailpos(winstate);
2384 [ + + ]: 150830 : if (winstate->grouptail_ptr >= 0)
2385 : 250 : update_grouptailpos(winstate);
2386 : :
2387 : : /*
2388 : : * Truncate any no-longer-needed rows from the tuplestore.
2389 : : */
2390 : 150830 : 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 : 150830 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
2398 : :
2399 : 150830 : slot = ExecProject(winstate->ss.ps.ps_ProjInfo);
2400 : :
2401 [ + + ]: 150830 : if (winstate->status == WINDOWAGG_RUN)
2402 : : {
2403 : 150819 : 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 [ + + ]: 150819 : 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 [ + + ]: 22 : 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 : 15 : numfuncs = winstate->numfuncs;
2434 [ + + ]: 44 : for (i = 0; i < numfuncs; i++)
2435 : : {
2436 : 29 : econtext->ecxt_aggvalues[i] = (Datum) 0;
2437 : 29 : econtext->ecxt_aggnulls[i] = true;
2438 : 29 : }
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 [ + + ]: 15 : if (winstate->top_window)
2447 : : {
2448 : 12 : winstate->status = WINDOWAGG_PASSTHROUGH_STRICT;
2449 : 12 : continue;
2450 : : }
2451 : : else
2452 : : {
2453 : 3 : winstate->status = WINDOWAGG_PASSTHROUGH;
2454 : : }
2455 : 3 : }
2456 : : else
2457 : : {
2458 : : /*
2459 : : * Pass-through not required. We can just return NULL.
2460 : : * Nothing else will match the runcondition.
2461 : : */
2462 : 7 : winstate->status = WINDOWAGG_DONE;
2463 : 7 : return NULL;
2464 : : }
2465 : 3 : }
2466 : :
2467 : : /*
2468 : : * Filter out any tuples we don't need in the top-level WindowAgg.
2469 : : */
2470 [ + + ]: 150800 : if (!ExecQual(winstate->ss.ps.qual, econtext))
2471 : : {
2472 [ + - ]: 3 : InstrCountFiltered1(winstate, 1);
2473 : 3 : continue;
2474 : : }
2475 : :
2476 : 150797 : 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 [ + + ]: 11 : else if (!winstate->top_window)
2484 : 4 : break;
2485 : : }
2486 : :
2487 : 150801 : return slot;
2488 : 151160 : }
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 : 457 : ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
2499 : : {
2500 : 457 : WindowAggState *winstate;
2501 : 457 : Plan *outerPlan;
2502 : 457 : ExprContext *econtext;
2503 : 457 : ExprContext *tmpcontext;
2504 : 457 : WindowStatePerFunc perfunc;
2505 : 457 : WindowStatePerAgg peragg;
2506 : 457 : int frameOptions = node->frameOptions;
2507 : 457 : int numfuncs,
2508 : : wfuncno,
2509 : : numaggs,
2510 : : aggno;
2511 : 457 : TupleDesc scanDesc;
2512 : 457 : ListCell *l;
2513 : :
2514 : : /* check for unsupported flags */
2515 [ + - ]: 457 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
2516 : :
2517 : : /*
2518 : : * create state structure
2519 : : */
2520 : 457 : winstate = makeNode(WindowAggState);
2521 : 457 : winstate->ss.ps.plan = (Plan *) node;
2522 : 457 : winstate->ss.ps.state = estate;
2523 : 457 : winstate->ss.ps.ExecProcNode = ExecWindowAgg;
2524 : :
2525 : : /* copy frame options to state node for easy access */
2526 : 457 : 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 : 457 : ExecAssignExprContext(estate, &winstate->ss.ps);
2534 : 457 : tmpcontext = winstate->ss.ps.ps_ExprContext;
2535 : 457 : winstate->tmpcontext = tmpcontext;
2536 : 457 : ExecAssignExprContext(estate, &winstate->ss.ps);
2537 : :
2538 : : /* Create long-lived context for storage of partition-local memory etc */
2539 : 457 : winstate->partcontext =
2540 : 457 : 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 : 457 : winstate->aggcontext =
2551 : 457 : AllocSetContextCreate(CurrentMemoryContext,
2552 : : "WindowAgg Aggregates",
2553 : : ALLOCSET_DEFAULT_SIZES);
2554 : :
2555 : : /* Only the top-level WindowAgg may have a qual */
2556 [ + + + - ]: 457 : Assert(node->plan.qual == NIL || node->topWindow);
2557 : :
2558 : : /* Initialize the qual */
2559 : 914 : winstate->ss.ps.qual = ExecInitQual(node->plan.qual,
2560 : 457 : (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 : 914 : winstate->runcondition = ExecInitQual(node->runCondition,
2570 : 457 : (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 [ + + ]: 457 : 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 : 457 : winstate->top_window = node->topWindow;
2581 : :
2582 : : /*
2583 : : * initialize child nodes
2584 : : */
2585 : 457 : outerPlan = outerPlan(node);
2586 : 457 : 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 : 457 : ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple);
2593 : 457 : scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
2594 : :
2595 : : /* the outer tuple isn't the child's tuple, but always a minimal tuple */
2596 : 457 : winstate->ss.ps.outeropsset = true;
2597 : 457 : winstate->ss.ps.outerops = &TTSOpsMinimalTuple;
2598 : 457 : winstate->ss.ps.outeropsfixed = true;
2599 : :
2600 : : /*
2601 : : * tuple table initialization
2602 : : */
2603 : 457 : winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2604 : : &TTSOpsMinimalTuple);
2605 : 457 : winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2606 : : &TTSOpsMinimalTuple);
2607 : 457 : winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,
2608 : : &TTSOpsMinimalTuple);
2609 : 457 : 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 : 457 : winstate->framehead_slot = winstate->frametail_slot = NULL;
2618 : :
2619 [ + + ]: 457 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
2620 : : {
2621 [ + + ]: 258 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
2622 [ + + ]: 258 : node->ordNumCols != 0) ||
2623 : 246 : (frameOptions & FRAMEOPTION_START_OFFSET))
2624 : 123 : winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2625 : : &TTSOpsMinimalTuple);
2626 [ + + ]: 258 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
2627 [ + + ]: 258 : node->ordNumCols != 0) ||
2628 : 130 : (frameOptions & FRAMEOPTION_END_OFFSET))
2629 : 203 : winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2630 : : &TTSOpsMinimalTuple);
2631 : 258 : }
2632 : :
2633 : : /*
2634 : : * Initialize result slot, type and projection.
2635 : : */
2636 : 457 : ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual);
2637 : 457 : ExecAssignProjectionInfo(&winstate->ss.ps, NULL);
2638 : :
2639 : : /* Set up data for comparing tuples */
2640 [ + + ]: 457 : if (node->partNumCols > 0)
2641 : 111 : winstate->partEqfunction =
2642 : 222 : execTuplesMatchPrepare(scanDesc,
2643 : 111 : node->partNumCols,
2644 : 111 : node->partColIdx,
2645 : 111 : node->partOperators,
2646 : 111 : node->partCollations,
2647 : 111 : &winstate->ss.ps);
2648 : :
2649 [ + + ]: 457 : if (node->ordNumCols > 0)
2650 : 366 : winstate->ordEqfunction =
2651 : 732 : execTuplesMatchPrepare(scanDesc,
2652 : 366 : node->ordNumCols,
2653 : 366 : node->ordColIdx,
2654 : 366 : node->ordOperators,
2655 : 366 : node->ordCollations,
2656 : 366 : &winstate->ss.ps);
2657 : :
2658 : : /*
2659 : : * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes.
2660 : : */
2661 : 457 : numfuncs = winstate->numfuncs;
2662 : 457 : numaggs = winstate->numaggs;
2663 : 457 : econtext = winstate->ss.ps.ps_ExprContext;
2664 : 457 : econtext->ecxt_aggvalues = palloc0_array(Datum, numfuncs);
2665 : 457 : econtext->ecxt_aggnulls = palloc0_array(bool, numfuncs);
2666 : :
2667 : : /*
2668 : : * allocate per-wfunc/per-agg state information.
2669 : : */
2670 : 457 : perfunc = palloc0_array(WindowStatePerFuncData, numfuncs);
2671 : 457 : peragg = palloc0_array(WindowStatePerAggData, numaggs);
2672 : 457 : winstate->perfunc = perfunc;
2673 : 457 : winstate->peragg = peragg;
2674 : :
2675 : 457 : wfuncno = -1;
2676 : 457 : aggno = -1;
2677 [ + - + + : 1055 : foreach(l, winstate->funcs)
+ + ]
2678 : : {
2679 : 598 : WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
2680 : 598 : WindowFunc *wfunc = wfuncstate->wfunc;
2681 : 598 : WindowStatePerFunc perfuncstate;
2682 : 598 : AclResult aclresult;
2683 : 598 : int i;
2684 : :
2685 [ + - ]: 598 : 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 [ + + ]: 782 : for (i = 0; i <= wfuncno; i++)
2694 : : {
2695 [ + + + - ]: 186 : if (equal(wfunc, perfunc[i].wfunc) &&
2696 : 2 : !contain_volatile_functions((Node *) wfunc))
2697 : 2 : break;
2698 : 184 : }
2699 [ + + - + ]: 598 : if (i <= wfuncno && wfunc->ignore_nulls == perfunc[i].ignore_nulls)
2700 : : {
2701 : : /* Found a match to an existing entry, so just mark it */
2702 : 2 : wfuncstate->wfuncno = i;
2703 : 2 : continue;
2704 : : }
2705 : :
2706 : : /* Nope, so assign a new PerAgg record */
2707 : 596 : perfuncstate = &perfunc[++wfuncno];
2708 : :
2709 : : /* Mark WindowFunc state node with assigned index in the result array */
2710 : 596 : wfuncstate->wfuncno = wfuncno;
2711 : :
2712 : : /* Check permission to call window function */
2713 : 596 : aclresult = object_aclcheck(ProcedureRelationId, wfunc->winfnoid, GetUserId(),
2714 : : ACL_EXECUTE);
2715 [ + - ]: 596 : if (aclresult != ACLCHECK_OK)
2716 : 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
2717 : 0 : get_func_name(wfunc->winfnoid));
2718 [ + - ]: 596 : InvokeFunctionExecuteHook(wfunc->winfnoid);
2719 : :
2720 : : /* Fill in the perfuncstate data */
2721 : 596 : perfuncstate->wfuncstate = wfuncstate;
2722 : 596 : perfuncstate->wfunc = wfunc;
2723 : 596 : perfuncstate->numArguments = list_length(wfuncstate->args);
2724 : 596 : perfuncstate->winCollation = wfunc->inputcollid;
2725 : :
2726 : 1192 : get_typlenbyval(wfunc->wintype,
2727 : 596 : &perfuncstate->resulttypeLen,
2728 : 596 : &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 : 596 : perfuncstate->plain_agg = wfunc->winagg;
2735 [ + + ]: 596 : if (wfunc->winagg)
2736 : : {
2737 : 257 : WindowStatePerAgg peraggstate;
2738 : :
2739 : 257 : perfuncstate->aggno = ++aggno;
2740 : 257 : peraggstate = &winstate->peragg[aggno];
2741 : 257 : initialize_peragg(winstate, wfunc, peraggstate);
2742 : 257 : peraggstate->wfuncno = wfuncno;
2743 : 257 : }
2744 : : else
2745 : : {
2746 : 339 : WindowObject winobj = makeNode(WindowObjectData);
2747 : :
2748 : 339 : winobj->winstate = winstate;
2749 : 339 : winobj->argstates = wfuncstate->args;
2750 : 339 : winobj->localmem = NULL;
2751 : 339 : perfuncstate->winobj = winobj;
2752 : 339 : winobj->ignore_nulls = wfunc->ignore_nulls;
2753 : 339 : init_notnull_info(winobj, perfuncstate);
2754 : :
2755 : : /* It's a real window function, so set up to call it. */
2756 : 678 : fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
2757 : 339 : econtext->ecxt_per_query_memory);
2758 : 339 : fmgr_info_set_expr((Node *) wfunc, &perfuncstate->flinfo);
2759 : 339 : }
2760 [ - + + ]: 598 : }
2761 : :
2762 : : /* Update numfuncs, numaggs to match number of unique functions found */
2763 : 457 : winstate->numfuncs = wfuncno + 1;
2764 : 457 : winstate->numaggs = aggno + 1;
2765 : :
2766 : : /* Set up WindowObject for aggregates, if needed */
2767 [ + + ]: 457 : if (winstate->numaggs > 0)
2768 : : {
2769 : 238 : WindowObject agg_winobj = makeNode(WindowObjectData);
2770 : :
2771 : 238 : agg_winobj->winstate = winstate;
2772 : 238 : agg_winobj->argstates = NIL;
2773 : 238 : agg_winobj->localmem = NULL;
2774 : : /* make sure markptr = -1 to invalidate. It may not get used */
2775 : 238 : agg_winobj->markptr = -1;
2776 : 238 : agg_winobj->readptr = -1;
2777 : 238 : winstate->agg_winobj = agg_winobj;
2778 : 238 : }
2779 : :
2780 : : /* Set the status to running */
2781 : 457 : winstate->status = WINDOWAGG_RUN;
2782 : :
2783 : : /* initialize frame bound offset expressions */
2784 : 914 : winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
2785 : 457 : (PlanState *) winstate);
2786 : 914 : winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
2787 : 457 : (PlanState *) winstate);
2788 : :
2789 : : /* Lookup in_range support functions if needed */
2790 [ + + ]: 457 : if (OidIsValid(node->startInRangeFunc))
2791 : 87 : fmgr_info(node->startInRangeFunc, &winstate->startInRangeFunc);
2792 [ + + ]: 457 : if (OidIsValid(node->endInRangeFunc))
2793 : 98 : fmgr_info(node->endInRangeFunc, &winstate->endInRangeFunc);
2794 : 457 : winstate->inRangeColl = node->inRangeColl;
2795 : 457 : winstate->inRangeAsc = node->inRangeAsc;
2796 : 457 : winstate->inRangeNullsFirst = node->inRangeNullsFirst;
2797 : :
2798 : 457 : winstate->all_first = true;
2799 : 457 : winstate->partition_spooled = false;
2800 : 457 : winstate->more_partitions = false;
2801 : 457 : winstate->next_partition = true;
2802 : :
2803 : 914 : return winstate;
2804 : 457 : }
2805 : :
2806 : : /* -----------------
2807 : : * ExecEndWindowAgg
2808 : : * -----------------
2809 : : */
2810 : : void
2811 : 423 : ExecEndWindowAgg(WindowAggState *node)
2812 : : {
2813 : 423 : PlanState *outerPlan;
2814 : 423 : int i;
2815 : :
2816 [ + + ]: 423 : if (node->buffer != NULL)
2817 : : {
2818 : 347 : tuplestore_end(node->buffer);
2819 : :
2820 : : /* nullify so that release_partition skips the tuplestore_clear() */
2821 : 347 : node->buffer = NULL;
2822 : 347 : }
2823 : :
2824 : 423 : release_partition(node);
2825 : :
2826 [ + + ]: 673 : for (i = 0; i < node->numaggs; i++)
2827 : : {
2828 [ + + ]: 250 : if (node->peragg[i].aggcontext != node->aggcontext)
2829 : 131 : MemoryContextDelete(node->peragg[i].aggcontext);
2830 : 250 : }
2831 : 423 : MemoryContextDelete(node->partcontext);
2832 : 423 : MemoryContextDelete(node->aggcontext);
2833 : :
2834 : 423 : pfree(node->perfunc);
2835 : 423 : pfree(node->peragg);
2836 : :
2837 : 423 : outerPlan = outerPlanState(node);
2838 : 423 : ExecEndNode(outerPlan);
2839 : 423 : }
2840 : :
2841 : : /* -----------------
2842 : : * ExecReScanWindowAgg
2843 : : * -----------------
2844 : : */
2845 : : void
2846 : 13 : ExecReScanWindowAgg(WindowAggState *node)
2847 : : {
2848 : 13 : PlanState *outerPlan = outerPlanState(node);
2849 : 13 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
2850 : :
2851 : 13 : node->status = WINDOWAGG_RUN;
2852 : 13 : node->all_first = true;
2853 : :
2854 : : /* release tuplestore et al */
2855 : 13 : release_partition(node);
2856 : :
2857 : : /* release all temp tuples, but especially first_part_slot */
2858 : 13 : ExecClearTuple(node->ss.ss_ScanTupleSlot);
2859 : 13 : ExecClearTuple(node->first_part_slot);
2860 : 13 : ExecClearTuple(node->agg_row_slot);
2861 : 13 : ExecClearTuple(node->temp_slot_1);
2862 : 13 : ExecClearTuple(node->temp_slot_2);
2863 [ + - ]: 13 : if (node->framehead_slot)
2864 : 0 : ExecClearTuple(node->framehead_slot);
2865 [ + + ]: 13 : if (node->frametail_slot)
2866 : 1 : ExecClearTuple(node->frametail_slot);
2867 : :
2868 : : /* Forget current wfunc values */
2869 [ + - + - : 26 : MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numfuncs);
+ - - + +
+ ]
2870 [ + - - + : 13 : 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 [ + + ]: 13 : if (outerPlan->chgParam == NULL)
2877 : 1 : ExecReScan(outerPlan);
2878 : 13 : }
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 : 257 : initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
2887 : : WindowStatePerAgg peraggstate)
2888 : : {
2889 : 257 : Oid inputTypes[FUNC_MAX_ARGS];
2890 : 257 : int numArguments;
2891 : 257 : HeapTuple aggTuple;
2892 : 257 : Form_pg_aggregate aggform;
2893 : 257 : Oid aggtranstype;
2894 : 257 : AttrNumber initvalAttNo;
2895 : 257 : AclResult aclresult;
2896 : 257 : bool use_ma_code;
2897 : 257 : Oid transfn_oid,
2898 : : invtransfn_oid,
2899 : : finalfn_oid;
2900 : 257 : bool finalextra;
2901 : 257 : char finalmodify;
2902 : 257 : Expr *transfnexpr,
2903 : : *invtransfnexpr,
2904 : : *finalfnexpr;
2905 : 257 : Datum textInitVal;
2906 : 257 : int i;
2907 : 257 : ListCell *lc;
2908 : :
2909 : 257 : numArguments = list_length(wfunc->args);
2910 : :
2911 : 257 : i = 0;
2912 [ + + + + : 495 : foreach(lc, wfunc->args)
+ + ]
2913 : : {
2914 : 238 : inputTypes[i++] = exprType((Node *) lfirst(lc));
2915 : 238 : }
2916 : :
2917 : 257 : aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(wfunc->winfnoid));
2918 [ + - ]: 257 : if (!HeapTupleIsValid(aggTuple))
2919 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for aggregate %u",
2920 : : wfunc->winfnoid);
2921 : 257 : 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 [ + + ]: 257 : if (!OidIsValid(aggform->aggminvtransfn))
2942 : 33 : use_ma_code = false; /* sine qua non */
2943 [ + - + - ]: 224 : else if (aggform->aggmfinalmodify == AGGMODIFY_READ_ONLY &&
2944 : 224 : aggform->aggfinalmodify != AGGMODIFY_READ_ONLY)
2945 : 0 : use_ma_code = true; /* decision forced by safety */
2946 [ + + ]: 224 : else if (winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
2947 : 89 : use_ma_code = false; /* non-moving frame head */
2948 [ + + ]: 135 : else if (contain_volatile_functions((Node *) wfunc))
2949 : 2 : use_ma_code = false; /* avoid possible behavioral change */
2950 [ - + ]: 133 : else if (contain_subplans((Node *) wfunc))
2951 : 0 : use_ma_code = false; /* subplans might contain volatile functions */
2952 : : else
2953 : 133 : use_ma_code = true; /* yes, let's use it */
2954 [ + + ]: 257 : if (use_ma_code)
2955 : : {
2956 : 133 : peraggstate->transfn_oid = transfn_oid = aggform->aggmtransfn;
2957 : 133 : peraggstate->invtransfn_oid = invtransfn_oid = aggform->aggminvtransfn;
2958 : 133 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggmfinalfn;
2959 : 133 : finalextra = aggform->aggmfinalextra;
2960 : 133 : finalmodify = aggform->aggmfinalmodify;
2961 : 133 : aggtranstype = aggform->aggmtranstype;
2962 : 133 : initvalAttNo = Anum_pg_aggregate_aggminitval;
2963 : 133 : }
2964 : : else
2965 : : {
2966 : 124 : peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
2967 : 124 : peraggstate->invtransfn_oid = invtransfn_oid = InvalidOid;
2968 : 124 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
2969 : 124 : finalextra = aggform->aggfinalextra;
2970 : 124 : finalmodify = aggform->aggfinalmodify;
2971 : 124 : aggtranstype = aggform->aggtranstype;
2972 : 124 : 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 : 257 : HeapTuple procTuple;
2983 : 257 : Oid aggOwner;
2984 : :
2985 : 257 : procTuple = SearchSysCache1(PROCOID,
2986 : 257 : ObjectIdGetDatum(wfunc->winfnoid));
2987 [ + - ]: 257 : if (!HeapTupleIsValid(procTuple))
2988 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for function %u",
2989 : : wfunc->winfnoid);
2990 : 257 : aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
2991 : 257 : ReleaseSysCache(procTuple);
2992 : :
2993 : 257 : aclresult = object_aclcheck(ProcedureRelationId, transfn_oid, aggOwner,
2994 : : ACL_EXECUTE);
2995 [ + - ]: 257 : if (aclresult != ACLCHECK_OK)
2996 : 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
2997 : 0 : get_func_name(transfn_oid));
2998 [ + - ]: 257 : InvokeFunctionExecuteHook(transfn_oid);
2999 : :
3000 [ + + ]: 257 : if (OidIsValid(invtransfn_oid))
3001 : : {
3002 : 133 : aclresult = object_aclcheck(ProcedureRelationId, invtransfn_oid, aggOwner,
3003 : : ACL_EXECUTE);
3004 [ + - ]: 133 : if (aclresult != ACLCHECK_OK)
3005 : 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
3006 : 0 : get_func_name(invtransfn_oid));
3007 [ + - ]: 133 : InvokeFunctionExecuteHook(invtransfn_oid);
3008 : 133 : }
3009 : :
3010 [ + + ]: 257 : if (OidIsValid(finalfn_oid))
3011 : : {
3012 : 141 : aclresult = object_aclcheck(ProcedureRelationId, finalfn_oid, aggOwner,
3013 : : ACL_EXECUTE);
3014 [ + - ]: 141 : if (aclresult != ACLCHECK_OK)
3015 : 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
3016 : 0 : get_func_name(finalfn_oid));
3017 [ + - ]: 141 : InvokeFunctionExecuteHook(finalfn_oid);
3018 : 141 : }
3019 : 257 : }
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 [ + - ]: 257 : 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 [ + + ]: 257 : if (finalextra)
3034 : 3 : peraggstate->numFinalArgs = numArguments + 1;
3035 : : else
3036 : 254 : peraggstate->numFinalArgs = 1;
3037 : :
3038 : : /* resolve actual type of transition state, if polymorphic */
3039 : 514 : aggtranstype = resolve_aggregate_transtype(wfunc->winfnoid,
3040 : 257 : aggtranstype,
3041 : 257 : inputTypes,
3042 : 257 : numArguments);
3043 : :
3044 : : /* build expression trees using actual argument & result types */
3045 : 514 : build_aggregate_transfn_expr(inputTypes,
3046 : 257 : numArguments,
3047 : : 0, /* no ordered-set window functions yet */
3048 : : false, /* no variadic window functions yet */
3049 : 257 : aggtranstype,
3050 : 257 : wfunc->inputcollid,
3051 : 257 : transfn_oid,
3052 : 257 : invtransfn_oid,
3053 : : &transfnexpr,
3054 : : &invtransfnexpr);
3055 : :
3056 : : /* set up infrastructure for calling the transfn(s) and finalfn */
3057 : 257 : fmgr_info(transfn_oid, &peraggstate->transfn);
3058 : 257 : fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
3059 : :
3060 [ + + ]: 257 : if (OidIsValid(invtransfn_oid))
3061 : : {
3062 : 133 : fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
3063 : 133 : fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
3064 : 133 : }
3065 : :
3066 [ + + ]: 257 : if (OidIsValid(finalfn_oid))
3067 : : {
3068 : 282 : build_aggregate_finalfn_expr(inputTypes,
3069 : 141 : peraggstate->numFinalArgs,
3070 : 141 : aggtranstype,
3071 : 141 : wfunc->wintype,
3072 : 141 : wfunc->inputcollid,
3073 : 141 : finalfn_oid,
3074 : : &finalfnexpr);
3075 : 141 : fmgr_info(finalfn_oid, &peraggstate->finalfn);
3076 : 141 : fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
3077 : 141 : }
3078 : :
3079 : : /* get info about relevant datatypes */
3080 : 514 : get_typlenbyval(wfunc->wintype,
3081 : 257 : &peraggstate->resulttypeLen,
3082 : 257 : &peraggstate->resulttypeByVal);
3083 : 514 : get_typlenbyval(aggtranstype,
3084 : 257 : &peraggstate->transtypeLen,
3085 : 257 : &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 : 514 : textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple, initvalAttNo,
3092 : 257 : &peraggstate->initValueIsNull);
3093 : :
3094 [ + + ]: 257 : if (peraggstate->initValueIsNull)
3095 : 140 : peraggstate->initValue = (Datum) 0;
3096 : : else
3097 : 234 : peraggstate->initValue = GetAggInitVal(textInitVal,
3098 : 117 : 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 [ + + + + ]: 257 : if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
3108 : : {
3109 [ + - ]: 27 : if (numArguments < 1 ||
3110 : 27 : !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 : 27 : }
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 [ + + + - ]: 257 : if (OidIsValid(invtransfn_oid) &&
3126 : 133 : 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 [ + + ]: 257 : if (OidIsValid(invtransfn_oid))
3146 : 133 : peraggstate->aggcontext =
3147 : 133 : AllocSetContextCreate(CurrentMemoryContext,
3148 : : "WindowAgg Per Aggregate",
3149 : : ALLOCSET_DEFAULT_SIZES);
3150 : : else
3151 : 124 : peraggstate->aggcontext = winstate->aggcontext;
3152 : :
3153 : 257 : ReleaseSysCache(aggTuple);
3154 : :
3155 : 514 : return peraggstate;
3156 : 257 : }
3157 : :
3158 : : static Datum
3159 : 117 : GetAggInitVal(Datum textInitVal, Oid transtype)
3160 : : {
3161 : 117 : Oid typinput,
3162 : : typioparam;
3163 : 117 : char *strInitVal;
3164 : 117 : Datum initVal;
3165 : :
3166 : 117 : getTypeInputInfo(transtype, &typinput, &typioparam);
3167 : 117 : strInitVal = TextDatumGetCString(textInitVal);
3168 : 234 : initVal = OidInputFunctionCall(typinput, strInitVal,
3169 : 117 : typioparam, -1);
3170 : 117 : pfree(strInitVal);
3171 : 234 : return initVal;
3172 : 117 : }
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 : 99555 : are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
3182 : : TupleTableSlot *slot2)
3183 : : {
3184 : 99555 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
3185 : 99555 : ExprContext *econtext = winstate->tmpcontext;
3186 : :
3187 : : /* If no ORDER BY, all rows are peers with each other */
3188 [ + + ]: 99555 : if (node->ordNumCols == 0)
3189 : 5172 : return true;
3190 : :
3191 : 94383 : econtext->ecxt_outertuple = slot1;
3192 : 94383 : econtext->ecxt_innertuple = slot2;
3193 : 94383 : return ExecQualAndReset(winstate->ordEqfunction, econtext);
3194 : 99555 : }
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 : 127552 : window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
3205 : : {
3206 : 127552 : WindowAggState *winstate = winobj->winstate;
3207 : 127552 : MemoryContext oldcontext;
3208 : :
3209 : : /* often called repeatedly in a row */
3210 [ + - ]: 127552 : CHECK_FOR_INTERRUPTS();
3211 : :
3212 : : /* Don't allow passing -1 to spool_tuples here */
3213 [ + + ]: 127552 : if (pos < 0)
3214 : 68 : return false;
3215 : :
3216 : : /* If necessary, fetch the tuple into the spool */
3217 : 127484 : spool_tuples(winstate, pos);
3218 : :
3219 [ + + ]: 127484 : if (pos >= winstate->spooled_rows)
3220 : 788 : return false;
3221 : :
3222 [ + - ]: 126696 : if (pos < winobj->markpos)
3223 [ # # # # ]: 0 : elog(ERROR, "cannot fetch row before WindowObject's mark position");
3224 : :
3225 : 126696 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3226 : :
3227 : 126696 : 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 [ + + ]: 126696 : if (winobj->seekpos < pos - 1)
3233 : : {
3234 [ + - + - ]: 830 : if (!tuplestore_skiptuples(winstate->buffer,
3235 : 415 : pos - 1 - winobj->seekpos,
3236 : : true))
3237 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
3238 : 415 : winobj->seekpos = pos - 1;
3239 : 415 : }
3240 [ + + ]: 126281 : else if (winobj->seekpos > pos + 1)
3241 : : {
3242 [ + - + - ]: 930 : if (!tuplestore_skiptuples(winstate->buffer,
3243 : 465 : winobj->seekpos - (pos + 1),
3244 : : false))
3245 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
3246 : 465 : winobj->seekpos = pos + 1;
3247 : 465 : }
3248 [ + + ]: 125816 : 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 : 28852 : tuplestore_advance(winstate->buffer, true);
3258 : 28852 : winobj->seekpos++;
3259 : 28852 : }
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 [ + + ]: 126696 : if (winobj->seekpos > pos)
3270 : : {
3271 [ + - ]: 29362 : if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
3272 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
3273 : 29362 : winobj->seekpos--;
3274 : 29362 : }
3275 : : else
3276 : : {
3277 [ + - ]: 97334 : if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
3278 [ # # # # ]: 0 : elog(ERROR, "unexpected end of tuplestore");
3279 : 97334 : winobj->seekpos++;
3280 : : }
3281 : :
3282 [ + - ]: 126696 : Assert(winobj->seekpos == pos);
3283 : :
3284 : 126696 : MemoryContextSwitchTo(oldcontext);
3285 : :
3286 : 126696 : return true;
3287 : 127552 : }
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 : 39590 : gettuple_eval_partition(WindowObject winobj, int argno,
3295 : : int64 abs_pos, bool *isnull, bool *isout)
3296 : : {
3297 : 39590 : WindowAggState *winstate;
3298 : 39590 : ExprContext *econtext;
3299 : 39590 : TupleTableSlot *slot;
3300 : :
3301 : 39590 : winstate = winobj->winstate;
3302 : 39590 : slot = winstate->temp_slot_1;
3303 [ + + ]: 39590 : if (!window_gettupleslot(winobj, abs_pos, slot))
3304 : : {
3305 : : /* out of partition */
3306 [ - + ]: 97 : if (isout)
3307 : 97 : *isout = true;
3308 : 97 : *isnull = true;
3309 : 97 : return (Datum) 0;
3310 : : }
3311 : :
3312 [ - + ]: 39493 : if (isout)
3313 : 39493 : *isout = false;
3314 : 39493 : econtext = winstate->ss.ps.ps_ExprContext;
3315 : 39493 : econtext->ecxt_outertuple = slot;
3316 : 78986 : return ExecEvalExpr((ExprState *) list_nth
3317 : 39493 : (winobj->argstates, argno),
3318 : 39493 : econtext, isnull);
3319 : 39590 : }
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 : 160 : ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
3328 : : int relpos, int seektype, bool set_mark,
3329 : : bool *isnull, bool *isout)
3330 : : {
3331 : 160 : WindowAggState *winstate;
3332 : 160 : ExprContext *econtext;
3333 : 160 : TupleTableSlot *slot;
3334 : 160 : Datum datum;
3335 : 160 : int64 abs_pos;
3336 : 160 : int64 mark_pos;
3337 : 160 : int notnull_offset;
3338 : 160 : int notnull_relpos;
3339 : 160 : int forward;
3340 : :
3341 [ + - ]: 160 : Assert(WindowObjectIsValid(winobj));
3342 : 160 : winstate = winobj->winstate;
3343 : 160 : econtext = winstate->ss.ps.ps_ExprContext;
3344 : 160 : slot = winstate->temp_slot_1;
3345 : 160 : datum = (Datum) 0;
3346 : 160 : notnull_offset = 0;
3347 : 160 : notnull_relpos = abs(relpos);
3348 : :
3349 [ + + - - ]: 160 : switch (seektype)
3350 : : {
3351 : : case WINDOW_SEEK_CURRENT:
3352 [ # # # # ]: 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3353 : 0 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3354 : 0 : break;
3355 : : case WINDOW_SEEK_HEAD:
3356 : : /* rejecting relpos < 0 is easy and simplifies code below */
3357 [ + - ]: 110 : if (relpos < 0)
3358 : 0 : goto out_of_frame;
3359 : 110 : update_frameheadpos(winstate);
3360 : 110 : abs_pos = winstate->frameheadpos;
3361 : 110 : mark_pos = winstate->frameheadpos;
3362 : 110 : forward = 1;
3363 : 110 : break;
3364 : : case WINDOW_SEEK_TAIL:
3365 : : /* rejecting relpos > 0 is easy and simplifies code below */
3366 [ - + ]: 50 : if (relpos > 0)
3367 : 0 : goto out_of_frame;
3368 : 50 : update_frametailpos(winstate);
3369 : 50 : abs_pos = winstate->frametailpos - 1;
3370 : 50 : mark_pos = 0; /* keep compiler quiet */
3371 : 50 : forward = -1;
3372 : 50 : break;
3373 : : default:
3374 [ # # # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3375 : 0 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3376 : 0 : 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 : 160 : do
3384 : : {
3385 : 382 : int inframe;
3386 : 382 : 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 [ + + ]: 382 : if (abs_pos < 0)
3394 : 2 : goto out_of_frame;
3395 : :
3396 : : /* check whether row is in frame */
3397 : 380 : inframe = row_is_in_frame(winobj, abs_pos, slot, true);
3398 [ + + ]: 380 : if (inframe == -1)
3399 : 9 : goto out_of_frame;
3400 [ + + ]: 371 : else if (inframe == 0)
3401 : 13 : goto advance;
3402 : :
3403 [ + - ]: 358 : if (isout)
3404 : 0 : *isout = false;
3405 : :
3406 : 358 : v = get_notnull_info(winobj, abs_pos, argno);
3407 [ + + ]: 611 : if (v == NN_NULL) /* this row is known to be NULL */
3408 : 100 : goto advance;
3409 : :
3410 [ + + ]: 258 : else if (v == NN_UNKNOWN) /* need to check NULL or not */
3411 : : {
3412 [ + + ]: 131 : if (!window_gettupleslot(winobj, abs_pos, slot))
3413 : 5 : goto out_of_frame;
3414 : :
3415 : 126 : econtext->ecxt_outertuple = slot;
3416 : 126 : datum = ExecEvalExpr(
3417 : 252 : (ExprState *) list_nth(winobj->argstates,
3418 : 252 : argno), econtext,
3419 : 126 : isnull);
3420 [ + + ]: 126 : if (!*isnull)
3421 : 75 : notnull_offset++;
3422 : :
3423 : : /* record the row status */
3424 : 126 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3425 : 126 : }
3426 : : else /* this row is known to be NOT NULL */
3427 : : {
3428 : 127 : notnull_offset++;
3429 [ + + ]: 127 : if (notnull_offset > notnull_relpos)
3430 : : {
3431 : : /* to prepare exiting this loop, datum needs to be set */
3432 [ + - ]: 80 : if (!window_gettupleslot(winobj, abs_pos, slot))
3433 : 0 : goto out_of_frame;
3434 : :
3435 : 80 : econtext->ecxt_outertuple = slot;
3436 : 80 : datum = ExecEvalExpr(
3437 : 80 : (ExprState *) list_nth
3438 : 80 : (winobj->argstates, argno),
3439 : 80 : econtext, isnull);
3440 : 80 : }
3441 : : }
3442 : : advance:
3443 : 366 : abs_pos += forward;
3444 [ - + + + : 382 : } while (notnull_offset <= notnull_relpos);
+ ]
3445 : :
3446 [ - + ]: 144 : if (set_mark)
3447 : 144 : WinSetMarkPosition(winobj, mark_pos);
3448 : :
3449 : 144 : return datum;
3450 : :
3451 : : out_of_frame:
3452 [ - + ]: 16 : if (isout)
3453 : 0 : *isout = true;
3454 : 16 : *isnull = true;
3455 : 16 : return (Datum) 0;
3456 : 160 : }
3457 : :
3458 : :
3459 : : /*
3460 : : * init_notnull_info
3461 : : * Initialize non null map.
3462 : : */
3463 : : static void
3464 : 339 : init_notnull_info(WindowObject winobj, WindowStatePerFunc perfuncstate)
3465 : : {
3466 : 339 : int numargs = perfuncstate->numArguments;
3467 : :
3468 [ + + ]: 339 : if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3469 : : {
3470 : 31 : winobj->notnull_info = palloc0_array(uint8 *, numargs);
3471 : 31 : winobj->num_notnull_info = palloc0_array(int64, numargs);
3472 : 31 : }
3473 : 339 : }
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 : 658 : 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 [ + + ]: 658 : if (pos >= winobj->num_notnull_info[argno])
3488 : : {
3489 : : /* We may be called in a short-lived context */
3490 : 50 : MemoryContext oldcontext = MemoryContextSwitchTo
3491 : 25 : (winobj->winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3492 : :
3493 : 25 : for (;;)
3494 : : {
3495 : 25 : Size oldsize = NN_POS_TO_BYTES
3496 : : (winobj->num_notnull_info[argno]);
3497 : 25 : Size newsize;
3498 : :
3499 [ - + ]: 25 : if (oldsize == 0) /* memory has not been allocated yet for this
3500 : : * arg */
3501 : : {
3502 : 25 : newsize = NN_POS_TO_BYTES(INIT_NOT_NULL_INFO_NUM);
3503 : 25 : winobj->notnull_info[argno] = palloc0(newsize);
3504 : 25 : }
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 : 25 : winobj->num_notnull_info[argno] = NN_BYTES_TO_POS(newsize);
3512 [ + - ]: 25 : if (winobj->num_notnull_info[argno] > pos)
3513 : 25 : break;
3514 [ - - + ]: 25 : }
3515 : 25 : MemoryContextSwitchTo(oldcontext);
3516 : 25 : }
3517 : 658 : }
3518 : :
3519 : : /*
3520 : : * get_notnull_info
3521 : : * retrieve a map
3522 : : * pos: map position
3523 : : * argno: argument number
3524 : : */
3525 : : static uint8
3526 : 462 : get_notnull_info(WindowObject winobj, int64 pos, int argno)
3527 : : {
3528 : 462 : uint8 *mbp;
3529 : 462 : uint8 mb;
3530 : 462 : int64 bpos;
3531 : :
3532 : 462 : grow_notnull_info(winobj, pos, argno);
3533 : 462 : bpos = NN_POS_TO_BYTES(pos);
3534 : 462 : mbp = winobj->notnull_info[argno];
3535 : 462 : mb = mbp[bpos];
3536 : 924 : return (mb >> (NN_SHIFT(pos))) & NN_MASK;
3537 : 462 : }
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 : 196 : put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull)
3548 : : {
3549 : 196 : uint8 *mbp;
3550 : 196 : uint8 mb;
3551 : 196 : int64 bpos;
3552 : 196 : uint8 val = isnull ? NN_NULL : NN_NOTNULL;
3553 : 196 : int shift;
3554 : :
3555 : 196 : grow_notnull_info(winobj, pos, argno);
3556 : 196 : bpos = NN_POS_TO_BYTES(pos);
3557 : 196 : mbp = winobj->notnull_info[argno];
3558 : 196 : mb = mbp[bpos];
3559 : 196 : shift = NN_SHIFT(pos);
3560 : 196 : mb &= ~(NN_MASK << shift); /* clear map */
3561 : 196 : mb |= (val << shift); /* update map */
3562 : 196 : mbp[bpos] = mb;
3563 : 196 : }
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 : 145151 : WinCheckAndInitializeNullTreatment(WindowObject winobj,
3579 : : bool allowNullTreatment,
3580 : : FunctionCallInfo fcinfo)
3581 : : {
3582 [ + - ]: 145151 : Assert(WindowObjectIsValid(winobj));
3583 [ + + + + ]: 145151 : if (winobj->ignore_nulls != NO_NULLTREATMENT && !allowNullTreatment)
3584 : : {
3585 : 12 : const char *funcname = get_func_name(fcinfo->flinfo->fn_oid);
3586 : :
3587 [ + - ]: 12 : if (!funcname)
3588 [ # # # # ]: 0 : elog(ERROR, "could not get function name");
3589 [ + - + - ]: 12 : ereport(ERROR,
3590 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3591 : : errmsg("function %s does not allow RESPECT/IGNORE NULLS",
3592 : : funcname)));
3593 : 0 : }
3594 [ + + ]: 145139 : else if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3595 : 25 : winobj->ignore_nulls = IGNORE_NULLS;
3596 : 145139 : }
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 : 55315 : WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
3612 : : {
3613 [ + - ]: 55315 : Assert(WindowObjectIsValid(winobj));
3614 [ + + ]: 55315 : if (winobj->localmem == NULL)
3615 : 72 : winobj->localmem =
3616 : 72 : MemoryContextAllocZero(winobj->winstate->partcontext, sz);
3617 : 55315 : 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 : 125373 : WinGetCurrentPosition(WindowObject winobj)
3627 : : {
3628 [ + - ]: 125373 : Assert(WindowObjectIsValid(winobj));
3629 : 125373 : 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 : 52 : WinGetPartitionRowCount(WindowObject winobj)
3642 : : {
3643 [ + - ]: 52 : Assert(WindowObjectIsValid(winobj));
3644 : 52 : spool_tuples(winobj->winstate, -1);
3645 : 52 : 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 : 145868 : WinSetMarkPosition(WindowObject winobj, int64 markpos)
3660 : : {
3661 : 145868 : WindowAggState *winstate;
3662 : :
3663 [ + - ]: 145868 : Assert(WindowObjectIsValid(winobj));
3664 : 145868 : winstate = winobj->winstate;
3665 : :
3666 [ + - ]: 145868 : if (markpos < winobj->markpos)
3667 [ # # # # ]: 0 : elog(ERROR, "cannot move WindowObject's mark position backward");
3668 : 145868 : tuplestore_select_read_pointer(winstate->buffer, winobj->markptr);
3669 [ + + ]: 145868 : if (markpos > winobj->markpos)
3670 : : {
3671 : 289630 : tuplestore_skiptuples(winstate->buffer,
3672 : 144815 : markpos - winobj->markpos,
3673 : : true);
3674 : 144815 : winobj->markpos = markpos;
3675 : 144815 : }
3676 : 145868 : tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
3677 [ + + ]: 145868 : if (markpos > winobj->seekpos)
3678 : : {
3679 : 154148 : tuplestore_skiptuples(winstate->buffer,
3680 : 77074 : markpos - winobj->seekpos,
3681 : : true);
3682 : 77074 : winobj->seekpos = markpos;
3683 : 77074 : }
3684 : 145868 : }
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 : 27593 : WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
3695 : : {
3696 : 27593 : WindowAggState *winstate;
3697 : 27593 : WindowAgg *node;
3698 : 27593 : TupleTableSlot *slot1;
3699 : 27593 : TupleTableSlot *slot2;
3700 : 27593 : bool res;
3701 : :
3702 [ + - ]: 27593 : Assert(WindowObjectIsValid(winobj));
3703 : 27593 : winstate = winobj->winstate;
3704 : 27593 : node = (WindowAgg *) winstate->ss.ps.plan;
3705 : :
3706 : : /* If no ORDER BY, all rows are peers; don't bother to fetch them */
3707 [ + + ]: 27593 : if (node->ordNumCols == 0)
3708 : 45 : 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 : 27548 : slot1 = winstate->temp_slot_1;
3715 : 27548 : slot2 = winstate->temp_slot_2;
3716 : :
3717 [ + - ]: 27548 : if (!window_gettupleslot(winobj, pos1, slot1))
3718 [ # # # # ]: 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3719 : : pos1);
3720 [ + - ]: 27548 : if (!window_gettupleslot(winobj, pos2, slot2))
3721 [ # # # # ]: 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3722 : : pos2);
3723 : :
3724 : 27548 : res = are_peers(winstate, slot1, slot2);
3725 : :
3726 : 27548 : ExecClearTuple(slot1);
3727 : 27548 : ExecClearTuple(slot2);
3728 : :
3729 : 27548 : return res;
3730 : 27593 : }
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 : 39510 : WinGetFuncArgInPartition(WindowObject winobj, int argno,
3752 : : int relpos, int seektype, bool set_mark,
3753 : : bool *isnull, bool *isout)
3754 : : {
3755 : 39510 : WindowAggState *winstate;
3756 : 39510 : int64 abs_pos;
3757 : 39510 : int64 mark_pos;
3758 : 39510 : Datum datum;
3759 : 39510 : bool null_treatment;
3760 : 39510 : int notnull_offset;
3761 : 39510 : int notnull_relpos;
3762 : 39510 : int forward;
3763 : 39510 : bool myisout;
3764 : :
3765 [ + - ]: 39510 : Assert(WindowObjectIsValid(winobj));
3766 : 39510 : winstate = winobj->winstate;
3767 : :
3768 [ + + ]: 39510 : null_treatment = (winobj->ignore_nulls == IGNORE_NULLS && relpos != 0);
3769 : :
3770 [ + - - - ]: 39510 : switch (seektype)
3771 : : {
3772 : : case WINDOW_SEEK_CURRENT:
3773 [ + + ]: 39510 : if (null_treatment)
3774 : 80 : abs_pos = winstate->currentpos;
3775 : : else
3776 : 39430 : abs_pos = winstate->currentpos + relpos;
3777 : 39510 : break;
3778 : : 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 : : case WINDOW_SEEK_TAIL:
3785 : 0 : spool_tuples(winstate, -1);
3786 : 0 : abs_pos = winstate->spooled_rows - 1 + relpos;
3787 : 0 : break;
3788 : : default:
3789 [ # # # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3790 : 0 : abs_pos = 0; /* keep compiler quiet */
3791 : 0 : break;
3792 : : }
3793 : :
3794 : : /* Easy case if IGNORE NULLS is not specified */
3795 [ + + ]: 39510 : if (!null_treatment)
3796 : : {
3797 : : /* get tuple and evaluate in partition */
3798 : 78860 : datum = gettuple_eval_partition(winobj, argno,
3799 : 39430 : abs_pos, isnull, &myisout);
3800 [ + + + + ]: 39430 : if (!myisout && set_mark)
3801 : 39340 : WinSetMarkPosition(winobj, abs_pos);
3802 [ - + ]: 39430 : if (isout)
3803 : 39430 : *isout = myisout;
3804 : 39430 : return datum;
3805 : : }
3806 : :
3807 : : /* Prepare for loop */
3808 : 80 : notnull_offset = 0;
3809 : 80 : notnull_relpos = abs(relpos);
3810 : 80 : forward = relpos > 0 ? 1 : -1;
3811 : 80 : myisout = false;
3812 : 80 : 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 [ + - + + ]: 80 : if (seektype == WINDOW_SEEK_CURRENT && relpos > 0)
3823 : 40 : 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 : 40 : 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 : 80 : do
3840 : : {
3841 : 117 : int nn_info; /* NOT NULL status */
3842 : :
3843 : 117 : abs_pos += forward;
3844 [ + + ]: 117 : if (abs_pos < 0) /* clearly out of partition */
3845 : 13 : break;
3846 : :
3847 : : /* check NOT NULL cached info */
3848 : 104 : nn_info = get_notnull_info(winobj, abs_pos, argno);
3849 [ + + ]: 104 : if (nn_info == NN_NOTNULL) /* this row is known to be NOT NULL */
3850 : 15 : notnull_offset++;
3851 [ + + ]: 89 : else if (nn_info == NN_NULL) /* this row is known to be NULL */
3852 : 9 : 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 : 160 : (void) gettuple_eval_partition(winobj, argno,
3863 : 80 : abs_pos, isnull, &myisout);
3864 [ + + ]: 80 : if (myisout) /* out of partition? */
3865 : 10 : break;
3866 [ + + ]: 70 : if (!*isnull)
3867 : 42 : notnull_offset++;
3868 : : /* record the row status */
3869 : 70 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3870 : : }
3871 [ - + + + : 117 : } while (notnull_offset < notnull_relpos);
+ ]
3872 : :
3873 : : /* get tuple and evaluate func arg in partition */
3874 : 160 : datum = gettuple_eval_partition(winobj, argno,
3875 : 80 : abs_pos, isnull, &myisout);
3876 [ + + - + ]: 80 : if (!myisout && set_mark)
3877 : 57 : WinSetMarkPosition(winobj, mark_pos);
3878 [ - + ]: 80 : if (isout)
3879 : 80 : *isout = myisout;
3880 : :
3881 : 80 : return datum;
3882 : 39510 : }
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 : 1645 : WinGetFuncArgInFrame(WindowObject winobj, int argno,
3919 : : int relpos, int seektype, bool set_mark,
3920 : : bool *isnull, bool *isout)
3921 : : {
3922 : 1645 : WindowAggState *winstate;
3923 : 1645 : ExprContext *econtext;
3924 : 1645 : TupleTableSlot *slot;
3925 : 1645 : int64 abs_pos;
3926 : 1645 : int64 mark_pos;
3927 : :
3928 [ + - ]: 1645 : Assert(WindowObjectIsValid(winobj));
3929 : 1645 : winstate = winobj->winstate;
3930 : 1645 : econtext = winstate->ss.ps.ps_ExprContext;
3931 : 1645 : slot = winstate->temp_slot_1;
3932 : :
3933 [ + + ]: 1645 : if (winobj->ignore_nulls == IGNORE_NULLS)
3934 : 320 : return ignorenulls_getfuncarginframe(winobj, argno, relpos, seektype,
3935 : 160 : set_mark, isnull, isout);
3936 : :
3937 [ + + - - ]: 1485 : switch (seektype)
3938 : : {
3939 : : case WINDOW_SEEK_CURRENT:
3940 [ # # # # ]: 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3941 : 0 : abs_pos = mark_pos = 0; /* keep compiler quiet */
3942 : 0 : break;
3943 : : case WINDOW_SEEK_HEAD:
3944 : : /* rejecting relpos < 0 is easy and simplifies code below */
3945 [ + - ]: 729 : if (relpos < 0)
3946 : 0 : goto out_of_frame;
3947 : 729 : update_frameheadpos(winstate);
3948 : 729 : abs_pos = winstate->frameheadpos + relpos;
3949 : 729 : 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 [ + + + + : 729 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
- ]
3964 : : {
3965 : : case 0:
3966 : : /* no adjustment needed */
3967 : : break;
3968 : : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
3969 [ + + + + ]: 40 : if (abs_pos >= winstate->currentpos &&
3970 : 31 : winstate->currentpos >= winstate->frameheadpos)
3971 : 11 : abs_pos++;
3972 : 40 : break;
3973 : : case FRAMEOPTION_EXCLUDE_GROUP:
3974 : 20 : update_grouptailpos(winstate);
3975 [ + + - + ]: 20 : if (abs_pos >= winstate->groupheadpos &&
3976 : 12 : winstate->grouptailpos > winstate->frameheadpos)
3977 : : {
3978 [ - + ]: 12 : int64 overlapstart = Max(winstate->groupheadpos,
3979 : : winstate->frameheadpos);
3980 : :
3981 : 12 : abs_pos += winstate->grouptailpos - overlapstart;
3982 : 12 : }
3983 : 20 : break;
3984 : : case FRAMEOPTION_EXCLUDE_TIES:
3985 : 50 : update_grouptailpos(winstate);
3986 [ + + + + ]: 50 : if (abs_pos >= winstate->groupheadpos &&
3987 : 34 : winstate->grouptailpos > winstate->frameheadpos)
3988 : : {
3989 [ - + ]: 14 : int64 overlapstart = Max(winstate->groupheadpos,
3990 : : winstate->frameheadpos);
3991 : :
3992 [ + - ]: 14 : if (abs_pos == overlapstart)
3993 : 14 : abs_pos = winstate->currentpos;
3994 : : else
3995 : 0 : abs_pos += winstate->grouptailpos - overlapstart - 1;
3996 : 14 : }
3997 : 50 : break;
3998 : : default:
3999 [ # # # # ]: 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4000 : : winstate->frameOptions);
4001 : 0 : break;
4002 : : }
4003 : 729 : break;
4004 : : case WINDOW_SEEK_TAIL:
4005 : : /* rejecting relpos > 0 is easy and simplifies code below */
4006 [ - + ]: 756 : if (relpos > 0)
4007 : 0 : goto out_of_frame;
4008 : 756 : update_frametailpos(winstate);
4009 : 756 : 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 [ + + + + : 756 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
- ]
4021 : : {
4022 : : case 0:
4023 : : /* no adjustment needed */
4024 : 676 : mark_pos = abs_pos;
4025 : 676 : break;
4026 : : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
4027 [ + + - + ]: 20 : if (abs_pos <= winstate->currentpos &&
4028 : 2 : winstate->currentpos < winstate->frametailpos)
4029 : 2 : abs_pos--;
4030 : 20 : update_frameheadpos(winstate);
4031 [ + + ]: 20 : if (abs_pos < winstate->frameheadpos)
4032 : 1 : goto out_of_frame;
4033 : 19 : mark_pos = winstate->frameheadpos;
4034 : 19 : break;
4035 : : case FRAMEOPTION_EXCLUDE_GROUP:
4036 : 40 : update_grouptailpos(winstate);
4037 [ + + - + ]: 40 : if (abs_pos < winstate->grouptailpos &&
4038 : 9 : winstate->groupheadpos < winstate->frametailpos)
4039 : : {
4040 [ - + ]: 9 : int64 overlapend = Min(winstate->grouptailpos,
4041 : : winstate->frametailpos);
4042 : :
4043 : 9 : abs_pos -= overlapend - winstate->groupheadpos;
4044 : 9 : }
4045 : 40 : update_frameheadpos(winstate);
4046 [ + + ]: 40 : if (abs_pos < winstate->frameheadpos)
4047 : 9 : goto out_of_frame;
4048 : 31 : mark_pos = winstate->frameheadpos;
4049 : 31 : break;
4050 : : case FRAMEOPTION_EXCLUDE_TIES:
4051 : 20 : update_grouptailpos(winstate);
4052 [ + + - + ]: 20 : if (abs_pos < winstate->grouptailpos &&
4053 : 6 : winstate->groupheadpos < winstate->frametailpos)
4054 : : {
4055 [ - + ]: 6 : int64 overlapend = Min(winstate->grouptailpos,
4056 : : winstate->frametailpos);
4057 : :
4058 [ + - ]: 6 : if (abs_pos == overlapend - 1)
4059 : 6 : abs_pos = winstate->currentpos;
4060 : : else
4061 : 0 : abs_pos -= overlapend - 1 - winstate->groupheadpos;
4062 : 6 : }
4063 : 20 : update_frameheadpos(winstate);
4064 [ - + ]: 20 : if (abs_pos < winstate->frameheadpos)
4065 : 0 : goto out_of_frame;
4066 : 20 : mark_pos = winstate->frameheadpos;
4067 : 20 : break;
4068 : : default:
4069 [ # # # # ]: 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4070 : : winstate->frameOptions);
4071 : 0 : mark_pos = 0; /* keep compiler quiet */
4072 : 0 : break;
4073 : : }
4074 : 746 : break;
4075 : : default:
4076 [ # # # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
4077 : 0 : abs_pos = mark_pos = 0; /* keep compiler quiet */
4078 : 0 : break;
4079 : : }
4080 : :
4081 [ + + ]: 1475 : if (!window_gettupleslot(winobj, abs_pos, slot))
4082 : 66 : goto out_of_frame;
4083 : :
4084 : : /* The code above does not detect all out-of-frame cases, so check */
4085 [ + + ]: 1409 : if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0)
4086 : 50 : goto out_of_frame;
4087 : :
4088 [ + - ]: 1359 : if (isout)
4089 : 0 : *isout = false;
4090 [ + + ]: 1359 : if (set_mark)
4091 : 1352 : WinSetMarkPosition(winobj, mark_pos);
4092 : 1359 : econtext->ecxt_outertuple = slot;
4093 : 2718 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4094 : 1359 : econtext, isnull);
4095 : :
4096 : : out_of_frame:
4097 [ + - ]: 126 : if (isout)
4098 : 0 : *isout = true;
4099 : 126 : *isnull = true;
4100 : 126 : return (Datum) 0;
4101 : 1645 : }
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 : 335 : WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
4118 : : {
4119 : 335 : WindowAggState *winstate;
4120 : 335 : ExprContext *econtext;
4121 : :
4122 [ + - ]: 335 : Assert(WindowObjectIsValid(winobj));
4123 : 335 : winstate = winobj->winstate;
4124 : :
4125 : 335 : econtext = winstate->ss.ps.ps_ExprContext;
4126 : :
4127 : 335 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
4128 : 1005 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4129 : 335 : econtext, isnull);
4130 : 335 : }
|