Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeWindowAgg.c
4 : : * routines to handle WindowAgg nodes.
5 : : *
6 : : * A WindowAgg node evaluates "window functions" across suitable partitions
7 : : * of the input tuple set. Any one WindowAgg works for just a single window
8 : : * specification, though it can evaluate multiple window functions sharing
9 : : * identical window specifications. The input tuples are required to be
10 : : * delivered in sorted order, with the PARTITION BY columns (if any) as
11 : : * major sort keys and the ORDER BY columns (if any) as minor sort keys.
12 : : * (The planner generates a stack of WindowAggs with intervening Sort nodes
13 : : * as needed, if a query involves more than one window specification.)
14 : : *
15 : : * Since window functions can require access to any or all of the rows in
16 : : * the current partition, we accumulate rows of the partition into a
17 : : * tuplestore. The window functions are called using the WindowObject API
18 : : * so that they can access those rows as needed.
19 : : *
20 : : * We also support using plain aggregate functions as window functions.
21 : : * For these, the regular Agg-node environment is emulated for each partition.
22 : : * As required by the SQL spec, the output represents the value of the
23 : : * aggregate function over all rows in the current row's window frame.
24 : : *
25 : : *
26 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
27 : : * Portions Copyright (c) 1994, Regents of the University of California
28 : : *
29 : : * IDENTIFICATION
30 : : * src/backend/executor/nodeWindowAgg.c
31 : : *
32 : : *-------------------------------------------------------------------------
33 : : */
34 : : #include "postgres.h"
35 : :
36 : : #include "access/htup_details.h"
37 : : #include "catalog/objectaccess.h"
38 : : #include "catalog/pg_aggregate.h"
39 : : #include "catalog/pg_proc.h"
40 : : #include "executor/executor.h"
41 : : #include "executor/nodeWindowAgg.h"
42 : : #include "miscadmin.h"
43 : : #include "nodes/nodeFuncs.h"
44 : : #include "optimizer/clauses.h"
45 : : #include "optimizer/optimizer.h"
46 : : #include "parser/parse_agg.h"
47 : : #include "parser/parse_coerce.h"
48 : : #include "utils/acl.h"
49 : : #include "utils/builtins.h"
50 : : #include "utils/datum.h"
51 : : #include "utils/expandeddatum.h"
52 : : #include "utils/lsyscache.h"
53 : : #include "utils/memutils.h"
54 : : #include "utils/regproc.h"
55 : : #include "utils/syscache.h"
56 : : #include "windowapi.h"
57 : :
58 : : /*
59 : : * All the window function APIs are called with this object, which is passed
60 : : * to window functions as fcinfo->context.
61 : : */
62 : : typedef struct WindowObjectData
63 : : {
64 : : NodeTag type;
65 : : WindowAggState *winstate; /* parent WindowAggState */
66 : : List *argstates; /* ExprState trees for fn's arguments */
67 : : void *localmem; /* WinGetPartitionLocalMemory's chunk */
68 : : int markptr; /* tuplestore mark pointer for this fn */
69 : : int readptr; /* tuplestore read pointer for this fn */
70 : : int64 markpos; /* row that markptr is positioned on */
71 : : int64 seekpos; /* row that readptr is positioned on */
72 : : uint8 **notnull_info; /* not null info for each func args */
73 : : int64 *num_notnull_info; /* track size (number of tuples in
74 : : * partition) of the notnull_info array
75 : : * for each func args */
76 : :
77 : : /*
78 : : * Null treatment options. One of: NO_NULLTREATMENT, PARSER_IGNORE_NULLS,
79 : : * PARSER_RESPECT_NULLS or IGNORE_NULLS.
80 : : */
81 : : int ignore_nulls;
82 : : } WindowObjectData;
83 : :
84 : : /*
85 : : * We have one WindowStatePerFunc struct for each window function and
86 : : * window aggregate handled by this node.
87 : : */
88 : : typedef struct WindowStatePerFuncData
89 : : {
90 : : /* Links to WindowFunc expr and state nodes this working state is for */
91 : : WindowFuncExprState *wfuncstate;
92 : : WindowFunc *wfunc;
93 : :
94 : : int numArguments; /* number of arguments */
95 : :
96 : : FmgrInfo flinfo; /* fmgr lookup data for window function */
97 : :
98 : : Oid winCollation; /* collation derived for window function */
99 : :
100 : : /*
101 : : * We need the len and byval info for the result of each function in order
102 : : * to know how to copy/delete values.
103 : : */
104 : : int16 resulttypeLen;
105 : : bool resulttypeByVal;
106 : :
107 : : bool plain_agg; /* is it just a plain aggregate function? */
108 : : int aggno; /* if so, index of its WindowStatePerAggData */
109 : : uint8 ignore_nulls; /* ignore nulls */
110 : :
111 : : WindowObject winobj; /* object used in window function API */
112 : : } WindowStatePerFuncData;
113 : :
114 : : /*
115 : : * For plain aggregate window functions, we also have one of these.
116 : : */
117 : : typedef struct WindowStatePerAggData
118 : : {
119 : : /* Oids of transition functions */
120 : : Oid transfn_oid;
121 : : Oid invtransfn_oid; /* may be InvalidOid */
122 : : Oid finalfn_oid; /* may be InvalidOid */
123 : :
124 : : /*
125 : : * fmgr lookup data for transition functions --- only valid when
126 : : * corresponding oid is not InvalidOid. Note in particular that fn_strict
127 : : * flags are kept here.
128 : : */
129 : : FmgrInfo transfn;
130 : : FmgrInfo invtransfn;
131 : : FmgrInfo finalfn;
132 : :
133 : : int numFinalArgs; /* number of arguments to pass to finalfn */
134 : :
135 : : /*
136 : : * initial value from pg_aggregate entry
137 : : */
138 : : Datum initValue;
139 : : bool initValueIsNull;
140 : :
141 : : /*
142 : : * cached value for current frame boundaries
143 : : */
144 : : Datum resultValue;
145 : : bool resultValueIsNull;
146 : :
147 : : /*
148 : : * We need the len and byval info for the agg's input, result, and
149 : : * transition data types in order to know how to copy/delete values.
150 : : */
151 : : int16 inputtypeLen,
152 : : resulttypeLen,
153 : : transtypeLen;
154 : : bool inputtypeByVal,
155 : : resulttypeByVal,
156 : : transtypeByVal;
157 : :
158 : : int wfuncno; /* index of associated WindowStatePerFuncData */
159 : :
160 : : /* Context holding transition value and possibly other subsidiary data */
161 : : MemoryContext aggcontext; /* may be private, or winstate->aggcontext */
162 : :
163 : : /* Current transition value */
164 : : Datum transValue; /* current transition value */
165 : : bool transValueIsNull;
166 : :
167 : : int64 transValueCount; /* number of currently-aggregated rows */
168 : :
169 : : /* Data local to eval_windowaggregates() */
170 : : bool restart; /* need to restart this agg in this cycle? */
171 : : } WindowStatePerAggData;
172 : :
173 : : static void initialize_windowaggregate(WindowAggState *winstate,
174 : : WindowStatePerFunc perfuncstate,
175 : : WindowStatePerAgg peraggstate);
176 : : static void advance_windowaggregate(WindowAggState *winstate,
177 : : WindowStatePerFunc perfuncstate,
178 : : WindowStatePerAgg peraggstate);
179 : : static bool advance_windowaggregate_base(WindowAggState *winstate,
180 : : WindowStatePerFunc perfuncstate,
181 : : WindowStatePerAgg peraggstate);
182 : : static void finalize_windowaggregate(WindowAggState *winstate,
183 : : WindowStatePerFunc perfuncstate,
184 : : WindowStatePerAgg peraggstate,
185 : : Datum *result, bool *isnull);
186 : :
187 : : static void eval_windowaggregates(WindowAggState *winstate);
188 : : static void eval_windowfunction(WindowAggState *winstate,
189 : : WindowStatePerFunc perfuncstate,
190 : : Datum *result, bool *isnull);
191 : :
192 : : static void begin_partition(WindowAggState *winstate);
193 : : static void spool_tuples(WindowAggState *winstate, int64 pos);
194 : : static void release_partition(WindowAggState *winstate);
195 : :
196 : : static int row_is_in_frame(WindowObject winobj, int64 pos,
197 : : TupleTableSlot *slot, bool fetch_tuple);
198 : : static void update_frameheadpos(WindowAggState *winstate);
199 : : static void update_frametailpos(WindowAggState *winstate);
200 : : static void update_grouptailpos(WindowAggState *winstate);
201 : :
202 : : static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
203 : : WindowFunc *wfunc,
204 : : WindowStatePerAgg peraggstate);
205 : : static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
206 : :
207 : : static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
208 : : TupleTableSlot *slot2);
209 : : static bool window_gettupleslot(WindowObject winobj, int64 pos,
210 : : TupleTableSlot *slot);
211 : :
212 : : static Datum ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
213 : : int relpos, int seektype,
214 : : bool set_mark, bool *isnull,
215 : : bool *isout);
216 : : static Datum gettuple_eval_partition(WindowObject winobj, int argno,
217 : : int64 abs_pos, bool *isnull,
218 : : bool *isout);
219 : : static void init_notnull_info(WindowObject winobj,
220 : : WindowStatePerFunc perfuncstate);
221 : : static void grow_notnull_info(WindowObject winobj,
222 : : int64 pos, int argno);
223 : : static uint8 get_notnull_info(WindowObject winobj,
224 : : int64 pos, int argno);
225 : : static void put_notnull_info(WindowObject winobj,
226 : : int64 pos, int argno, bool isnull);
227 : :
228 : : /*
229 : : * Not null info bit array consists of 2-bit items
230 : : */
231 : : #define NN_UNKNOWN 0x00 /* value not calculated yet */
232 : : #define NN_NULL 0x01 /* NULL */
233 : : #define NN_NOTNULL 0x02 /* NOT NULL */
234 : : #define NN_MASK 0x03 /* mask for NOT NULL MAP */
235 : : #define NN_BITS_PER_MEMBER 2 /* number of bits in not null map */
236 : : /* number of items per variable */
237 : : #define NN_ITEM_PER_VAR (BITS_PER_BYTE / NN_BITS_PER_MEMBER)
238 : : /* convert map position to byte offset */
239 : : #define NN_POS_TO_BYTES(pos) ((pos) / NN_ITEM_PER_VAR)
240 : : /* bytes offset to map position */
241 : : #define NN_BYTES_TO_POS(bytes) ((bytes) * NN_ITEM_PER_VAR)
242 : : /* caculate 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
6147 tgl@sss.pgh.pa.us 250 :CBC 2015 : initialize_windowaggregate(WindowAggState *winstate,
251 : : WindowStatePerFunc perfuncstate,
252 : : WindowStatePerAgg peraggstate)
253 : : {
254 : : MemoryContext oldContext;
255 : :
256 : : /*
257 : : * If we're using a private aggcontext, we may reset it here. But if the
258 : : * context is shared, we don't know which other aggregates may still need
259 : : * it, so we must leave it to the caller to reset at an appropriate time.
260 : : */
4216 261 [ + + ]: 2015 : if (peraggstate->aggcontext != winstate->aggcontext)
712 nathan@postgresql.or 262 : 1447 : MemoryContextReset(peraggstate->aggcontext);
263 : :
6147 tgl@sss.pgh.pa.us 264 [ + + ]: 2015 : if (peraggstate->initValueIsNull)
265 : 768 : peraggstate->transValue = peraggstate->initValue;
266 : : else
267 : : {
4216 268 : 1247 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
6147 269 : 2494 : peraggstate->transValue = datumCopy(peraggstate->initValue,
270 : 1247 : peraggstate->transtypeByVal,
271 : 1247 : peraggstate->transtypeLen);
272 : 1247 : MemoryContextSwitchTo(oldContext);
273 : : }
274 : 2015 : peraggstate->transValueIsNull = peraggstate->initValueIsNull;
4216 275 : 2015 : peraggstate->transValueCount = 0;
276 : 2015 : peraggstate->resultValue = (Datum) 0;
6144 277 : 2015 : peraggstate->resultValueIsNull = true;
6147 278 : 2015 : }
279 : :
280 : : /*
281 : : * advance_windowaggregate
282 : : * parallel to advance_aggregates in nodeAgg.c
283 : : */
284 : : static void
285 : 88079 : advance_windowaggregate(WindowAggState *winstate,
286 : : WindowStatePerFunc perfuncstate,
287 : : WindowStatePerAgg peraggstate)
288 : : {
2466 andres@anarazel.de 289 : 88079 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
5982 bruce@momjian.us 290 : 88079 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
291 : 88079 : int numArguments = perfuncstate->numArguments;
292 : : Datum newVal;
293 : : ListCell *arg;
294 : : int i;
295 : : MemoryContext oldContext;
6147 tgl@sss.pgh.pa.us 296 : 88079 : ExprContext *econtext = winstate->tmpcontext;
4486 noah@leadboat.com 297 : 88079 : ExprState *filter = wfuncstate->aggfilter;
298 : :
6147 tgl@sss.pgh.pa.us 299 : 88079 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
300 : :
301 : : /* Skip anything FILTERed out */
4486 noah@leadboat.com 302 [ + + ]: 88079 : if (filter)
303 : : {
304 : : bool isnull;
3203 andres@anarazel.de 305 : 171 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
306 : :
4486 noah@leadboat.com 307 [ + + + + ]: 171 : if (isnull || !DatumGetBool(res))
308 : : {
309 : 81 : MemoryContextSwitchTo(oldContext);
310 : 81 : return;
311 : : }
312 : : }
313 : :
314 : : /* We start from 1, since the 0th arg will be the transition value */
6147 tgl@sss.pgh.pa.us 315 : 87998 : i = 1;
316 [ + + + + : 145771 : foreach(arg, wfuncstate->args)
+ + ]
317 : : {
5982 bruce@momjian.us 318 : 57773 : ExprState *argstate = (ExprState *) lfirst(arg);
319 : :
2466 andres@anarazel.de 320 : 57773 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
321 : : &fcinfo->args[i].isnull);
6147 tgl@sss.pgh.pa.us 322 : 57773 : i++;
323 : : }
324 : :
325 [ + + ]: 87998 : 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 [ + + ]: 49778 : for (i = 1; i <= numArguments; i++)
333 : : {
2466 andres@anarazel.de 334 [ + + ]: 9808 : if (fcinfo->args[i].isnull)
335 : : {
6147 tgl@sss.pgh.pa.us 336 : 99 : MemoryContextSwitchTo(oldContext);
337 : 99 : return;
338 : : }
339 : : }
340 : :
341 : : /*
342 : : * For strict transition functions with initial value NULL we use the
343 : : * first non-NULL input as the initial state. (We already checked
344 : : * that the agg's input type is binary-compatible with its transtype,
345 : : * so straight copy here is OK.)
346 : : *
347 : : * We must copy the datum into aggcontext if it is pass-by-ref. We do
348 : : * not need to pfree the old transValue, since it's NULL.
349 : : */
4216 350 [ + + + + ]: 39970 : if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
351 : : {
352 : 216 : MemoryContextSwitchTo(peraggstate->aggcontext);
2466 andres@anarazel.de 353 : 432 : peraggstate->transValue = datumCopy(fcinfo->args[1].value,
5982 bruce@momjian.us 354 : 216 : peraggstate->transtypeByVal,
355 : 216 : peraggstate->transtypeLen);
6147 tgl@sss.pgh.pa.us 356 : 216 : peraggstate->transValueIsNull = false;
4216 357 : 216 : peraggstate->transValueCount = 1;
6147 358 : 216 : MemoryContextSwitchTo(oldContext);
359 : 216 : return;
360 : : }
361 : :
362 [ - + ]: 39754 : 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 : : */
6147 tgl@sss.pgh.pa.us 372 :UBC 0 : MemoryContextSwitchTo(oldContext);
4216 373 [ # # ]: 0 : Assert(!OidIsValid(peraggstate->invtransfn_oid));
6147 374 : 0 : return;
375 : : }
376 : : }
377 : :
378 : : /*
379 : : * OK to call the transition function. Set winstate->curaggcontext while
380 : : * calling it, for possible use by AggCheckCallContext.
381 : : */
6147 tgl@sss.pgh.pa.us 382 :CBC 87683 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
383 : : numArguments + 1,
384 : : perfuncstate->winCollation,
385 : : (Node *) winstate, NULL);
2466 andres@anarazel.de 386 : 87683 : fcinfo->args[0].value = peraggstate->transValue;
387 : 87683 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
4216 tgl@sss.pgh.pa.us 388 : 87683 : winstate->curaggcontext = peraggstate->aggcontext;
6147 389 : 87683 : newVal = FunctionCallInvoke(fcinfo);
4216 390 : 87677 : winstate->curaggcontext = NULL;
391 : :
392 : : /*
393 : : * Moving-aggregate transition functions must not return null, see
394 : : * advance_windowaggregate_base().
395 : : */
396 [ - + - - ]: 87677 : if (fcinfo->isnull && OidIsValid(peraggstate->invtransfn_oid))
4216 tgl@sss.pgh.pa.us 397 [ # # ]:UBC 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 : : */
4216 tgl@sss.pgh.pa.us 407 :CBC 87677 : 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 : : */
6147 417 [ + + + + ]: 91856 : if (!peraggstate->transtypeByVal &&
418 : 4179 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
419 : : {
420 [ + - ]: 480 : if (!fcinfo->isnull)
421 : : {
4216 422 : 480 : MemoryContextSwitchTo(peraggstate->aggcontext);
3284 423 [ + - + - ]: 480 : if (DatumIsReadWriteExpandedObject(newVal,
424 : : false,
425 [ + + ]: 483 : peraggstate->transtypeLen) &&
426 [ + - ]: 3 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
427 : : /* do nothing */ ;
428 : : else
429 : 477 : newVal = datumCopy(newVal,
430 : 477 : peraggstate->transtypeByVal,
431 : 477 : peraggstate->transtypeLen);
432 : : }
6147 433 [ + + ]: 480 : if (!peraggstate->transValueIsNull)
434 : : {
3284 435 [ + - - + : 450 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
- - ]
436 : : false,
437 : : peraggstate->transtypeLen))
3284 tgl@sss.pgh.pa.us 438 :UBC 0 : DeleteExpandedObject(peraggstate->transValue);
439 : : else
3284 tgl@sss.pgh.pa.us 440 :CBC 450 : pfree(DatumGetPointer(peraggstate->transValue));
441 : : }
442 : : }
443 : :
6147 444 : 87677 : MemoryContextSwitchTo(oldContext);
445 : 87677 : peraggstate->transValue = newVal;
446 : 87677 : peraggstate->transValueIsNull = fcinfo->isnull;
447 : : }
448 : :
449 : : /*
450 : : * advance_windowaggregate_base
451 : : * Remove the oldest tuple from an aggregation.
452 : : *
453 : : * This is very much like advance_windowaggregate, except that we will call
454 : : * the inverse transition function (which caller must have checked is
455 : : * available).
456 : : *
457 : : * Returns true if we successfully removed the current row from this
458 : : * aggregate, false if not (in the latter case, caller is responsible
459 : : * for cleaning up by restarting the aggregation).
460 : : */
461 : : static bool
4216 462 : 2313 : advance_windowaggregate_base(WindowAggState *winstate,
463 : : WindowStatePerFunc perfuncstate,
464 : : WindowStatePerAgg peraggstate)
465 : : {
2466 andres@anarazel.de 466 : 2313 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
4216 tgl@sss.pgh.pa.us 467 : 2313 : WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
468 : 2313 : int numArguments = perfuncstate->numArguments;
469 : : Datum newVal;
470 : : ListCell *arg;
471 : : int i;
472 : : MemoryContext oldContext;
473 : 2313 : ExprContext *econtext = winstate->tmpcontext;
474 : 2313 : ExprState *filter = wfuncstate->aggfilter;
475 : :
476 : 2313 : oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
477 : :
478 : : /* Skip anything FILTERed out */
479 [ + + ]: 2313 : if (filter)
480 : : {
481 : : bool isnull;
3203 andres@anarazel.de 482 : 51 : Datum res = ExecEvalExpr(filter, econtext, &isnull);
483 : :
4216 tgl@sss.pgh.pa.us 484 [ + - + + ]: 51 : if (isnull || !DatumGetBool(res))
485 : : {
486 : 24 : MemoryContextSwitchTo(oldContext);
487 : 24 : return true;
488 : : }
489 : : }
490 : :
491 : : /* We start from 1, since the 0th arg will be the transition value */
492 : 2289 : i = 1;
493 [ + + + + : 4569 : foreach(arg, wfuncstate->args)
+ + ]
494 : : {
495 : 2280 : ExprState *argstate = (ExprState *) lfirst(arg);
496 : :
2466 andres@anarazel.de 497 : 2280 : fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
498 : : &fcinfo->args[i].isnull);
4216 tgl@sss.pgh.pa.us 499 : 2280 : i++;
500 : : }
501 : :
502 [ + + ]: 2289 : 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 [ + + ]: 2802 : for (i = 1; i <= numArguments; i++)
510 : : {
2466 andres@anarazel.de 511 [ + + ]: 1416 : if (fcinfo->args[i].isnull)
512 : : {
4216 tgl@sss.pgh.pa.us 513 : 39 : MemoryContextSwitchTo(oldContext);
514 : 39 : return true;
515 : : }
516 : : }
517 : : }
518 : :
519 : : /* There should still be an added but not yet removed value */
520 [ - + ]: 2250 : 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 [ - + ]: 2250 : if (peraggstate->transValueIsNull)
4216 tgl@sss.pgh.pa.us 531 [ # # ]:UBC 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 : : */
4216 tgl@sss.pgh.pa.us 539 [ + + ]:CBC 2250 : if (peraggstate->transValueCount == 1)
540 : : {
541 : 45 : MemoryContextSwitchTo(oldContext);
542 : 45 : initialize_windowaggregate(winstate,
543 : 45 : &winstate->perfunc[peraggstate->wfuncno],
544 : : peraggstate);
545 : 45 : 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 : 2205 : InitFunctionCallInfoData(*fcinfo, &(peraggstate->invtransfn),
554 : : numArguments + 1,
555 : : perfuncstate->winCollation,
556 : : (Node *) winstate, NULL);
2466 andres@anarazel.de 557 : 2205 : fcinfo->args[0].value = peraggstate->transValue;
558 : 2205 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
4216 tgl@sss.pgh.pa.us 559 : 2205 : winstate->curaggcontext = peraggstate->aggcontext;
560 : 2205 : newVal = FunctionCallInvoke(fcinfo);
561 : 2205 : winstate->curaggcontext = NULL;
562 : :
563 : : /*
564 : : * If the function returns NULL, report failure, forcing a restart.
565 : : */
566 [ + + ]: 2205 : if (fcinfo->isnull)
567 : : {
568 : 112 : MemoryContextSwitchTo(oldContext);
569 : 112 : return false;
570 : : }
571 : :
572 : : /* Update number of rows included in transValue */
573 : 2093 : 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 [ + + + + ]: 3158 : if (!peraggstate->transtypeByVal &&
587 : 1065 : DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
588 : : {
589 [ + - ]: 333 : if (!fcinfo->isnull)
590 : : {
591 : 333 : MemoryContextSwitchTo(peraggstate->aggcontext);
3284 592 [ + - - - ]: 333 : if (DatumIsReadWriteExpandedObject(newVal,
593 : : false,
594 [ - + ]: 333 : peraggstate->transtypeLen) &&
3284 tgl@sss.pgh.pa.us 595 [ # # ]:UBC 0 : MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
596 : : /* do nothing */ ;
597 : : else
3284 tgl@sss.pgh.pa.us 598 :CBC 333 : newVal = datumCopy(newVal,
599 : 333 : peraggstate->transtypeByVal,
600 : 333 : peraggstate->transtypeLen);
601 : : }
4216 602 [ + - ]: 333 : if (!peraggstate->transValueIsNull)
603 : : {
3284 604 [ + - - + : 333 : if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
- - ]
605 : : false,
606 : : peraggstate->transtypeLen))
3284 tgl@sss.pgh.pa.us 607 :UBC 0 : DeleteExpandedObject(peraggstate->transValue);
608 : : else
3284 tgl@sss.pgh.pa.us 609 :CBC 333 : pfree(DatumGetPointer(peraggstate->transValue));
610 : : }
611 : : }
612 : :
4216 613 : 2093 : MemoryContextSwitchTo(oldContext);
614 : 2093 : peraggstate->transValue = newVal;
615 : 2093 : peraggstate->transValueIsNull = fcinfo->isnull;
616 : :
617 : 2093 : return true;
618 : : }
619 : :
620 : : /*
621 : : * finalize_windowaggregate
622 : : * parallel to finalize_aggregate in nodeAgg.c
623 : : */
624 : : static void
6147 625 : 5316 : finalize_windowaggregate(WindowAggState *winstate,
626 : : WindowStatePerFunc perfuncstate,
627 : : WindowStatePerAgg peraggstate,
628 : : Datum *result, bool *isnull)
629 : : {
630 : : MemoryContext oldContext;
631 : :
632 : 5316 : 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 [ + + ]: 5316 : if (OidIsValid(peraggstate->finalfn_oid))
638 : : {
2466 andres@anarazel.de 639 : 2942 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
4205 tgl@sss.pgh.pa.us 640 : 2942 : int numFinalArgs = peraggstate->numFinalArgs;
641 : : bool anynull;
642 : : int i;
643 : :
2466 andres@anarazel.de 644 : 2942 : InitFunctionCallInfoData(fcinfodata.fcinfo, &(peraggstate->finalfn),
645 : : numFinalArgs,
646 : : perfuncstate->winCollation,
647 : : (Node *) winstate, NULL);
648 : 2942 : fcinfo->args[0].value =
649 [ + + + + ]: 2942 : MakeExpandedObjectReadOnly(peraggstate->transValue,
650 : : peraggstate->transValueIsNull,
651 : : peraggstate->transtypeLen);
652 : 2942 : fcinfo->args[0].isnull = peraggstate->transValueIsNull;
4205 tgl@sss.pgh.pa.us 653 : 2942 : anynull = peraggstate->transValueIsNull;
654 : :
655 : : /* Fill any remaining argument positions with nulls */
656 [ + + ]: 2992 : for (i = 1; i < numFinalArgs; i++)
657 : : {
2466 andres@anarazel.de 658 : 50 : fcinfo->args[i].value = (Datum) 0;
659 : 50 : fcinfo->args[i].isnull = true;
4205 tgl@sss.pgh.pa.us 660 : 50 : anynull = true;
661 : : }
662 : :
2466 andres@anarazel.de 663 [ + + - + ]: 2942 : if (fcinfo->flinfo->fn_strict && anynull)
664 : : {
665 : : /* don't call a strict function with NULL inputs */
6147 tgl@sss.pgh.pa.us 666 :UBC 0 : *result = (Datum) 0;
667 : 0 : *isnull = true;
668 : : }
669 : : else
670 : : {
671 : : Datum res;
672 : :
4216 tgl@sss.pgh.pa.us 673 :CBC 2942 : winstate->curaggcontext = peraggstate->aggcontext;
925 674 : 2942 : res = FunctionCallInvoke(fcinfo);
4216 675 : 2936 : winstate->curaggcontext = NULL;
2466 andres@anarazel.de 676 : 2936 : *isnull = fcinfo->isnull;
925 tgl@sss.pgh.pa.us 677 [ + + + + ]: 2936 : *result = MakeExpandedObjectReadOnly(res,
678 : : fcinfo->isnull,
679 : : peraggstate->resulttypeLen);
680 : : }
681 : : }
682 : : else
683 : : {
1117 684 : 2374 : *result =
685 [ + + + + ]: 2374 : MakeExpandedObjectReadOnly(peraggstate->transValue,
686 : : peraggstate->transValueIsNull,
687 : : peraggstate->transtypeLen);
6147 688 : 2374 : *isnull = peraggstate->transValueIsNull;
689 : : }
690 : :
691 : 5310 : MemoryContextSwitchTo(oldContext);
692 : 5310 : }
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 : 80122 : eval_windowaggregates(WindowAggState *winstate)
707 : : {
708 : : WindowStatePerAgg peraggstate;
709 : : int wfuncno,
710 : : numaggs,
711 : : numaggs_restart,
712 : : i;
713 : : int64 aggregatedupto_nonrestarted;
714 : : MemoryContext oldContext;
715 : : ExprContext *econtext;
716 : : WindowObject agg_winobj;
717 : : TupleTableSlot *agg_row_slot;
718 : : TupleTableSlot *temp_slot;
719 : :
720 : 80122 : numaggs = winstate->numaggs;
721 [ - + ]: 80122 : if (numaggs == 0)
6147 tgl@sss.pgh.pa.us 722 :UBC 0 : return; /* nothing to do */
723 : :
724 : : /* final output execution is in ps_ExprContext */
6147 tgl@sss.pgh.pa.us 725 :CBC 80122 : econtext = winstate->ss.ps.ps_ExprContext;
5736 726 : 80122 : agg_winobj = winstate->agg_winobj;
727 : 80122 : agg_row_slot = winstate->agg_row_slot;
4216 728 : 80122 : 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 : : */
2819 779 : 80122 : update_frameheadpos(winstate);
4216 780 [ - + ]: 80119 : if (winstate->frameheadpos < winstate->aggregatedbase)
4216 tgl@sss.pgh.pa.us 781 [ # # ]:UBC 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 : : */
4216 tgl@sss.pgh.pa.us 794 [ + + ]:CBC 80119 : if (winstate->aggregatedbase == winstate->frameheadpos &&
795 [ + + ]: 78230 : (winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
796 : 77270 : FRAMEOPTION_END_CURRENT_ROW)) &&
2819 797 [ + + ]: 77270 : !(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
4216 798 [ + + ]: 77180 : winstate->aggregatedbase <= winstate->currentpos &&
799 [ + + ]: 77162 : winstate->aggregatedupto > winstate->currentpos)
800 : : {
6147 801 [ + + ]: 151370 : for (i = 0; i < numaggs; i++)
802 : : {
803 : 75688 : peraggstate = &winstate->peragg[i];
804 : 75688 : wfuncno = peraggstate->wfuncno;
4216 805 : 75688 : econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
806 : 75688 : econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
807 : : }
808 : 75682 : 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 : 4437 : numaggs_restart = 0;
827 [ + + ]: 9765 : for (i = 0; i < numaggs; i++)
828 : : {
829 : 5328 : peraggstate = &winstate->peragg[i];
830 [ + + ]: 5328 : if (winstate->currentpos == 0 ||
831 [ + + ]: 4300 : (winstate->aggregatedbase != winstate->frameheadpos &&
832 [ + + ]: 2603 : !OidIsValid(peraggstate->invtransfn_oid)) ||
2819 833 [ + + ]: 4262 : (winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
4216 834 [ + + ]: 3704 : winstate->aggregatedupto <= winstate->frameheadpos)
835 : : {
836 : 1858 : peraggstate->restart = true;
837 : 1858 : numaggs_restart++;
838 : : }
839 : : else
840 : 3470 : peraggstate->restart = false;
841 : : }
842 : :
843 : : /*
844 : : * If we have any possibly-moving aggregates, attempt to advance
845 : : * aggregatedbase to match the frame's head by removing input rows that
846 : : * fell off the top of the frame from the aggregations. This can fail,
847 : : * i.e. advance_windowaggregate_base() can return false, in which case
848 : : * we'll restart that aggregate below.
849 : : */
850 [ + + ]: 6021 : while (numaggs_restart < numaggs &&
851 [ + + ]: 4265 : 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 [ - + ]: 1584 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
858 : : temp_slot))
4216 tgl@sss.pgh.pa.us 859 [ # # ]:UBC 0 : elog(ERROR, "could not re-fetch previously fetched frame row");
860 : :
861 : : /* Set tuple context for evaluation of aggregate arguments */
4216 tgl@sss.pgh.pa.us 862 :CBC 1584 : 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 [ + + ]: 3903 : for (i = 0; i < numaggs; i++)
869 : : {
870 : : bool ok;
871 : :
872 : 2319 : peraggstate = &winstate->peragg[i];
873 [ + + ]: 2319 : if (peraggstate->restart)
874 : 6 : continue;
875 : :
876 : 2313 : wfuncno = peraggstate->wfuncno;
877 : 2313 : ok = advance_windowaggregate_base(winstate,
878 : 2313 : &winstate->perfunc[wfuncno],
879 : : peraggstate);
880 [ + + ]: 2313 : if (!ok)
881 : : {
882 : : /* Inverse transition function has failed, must restart */
883 : 112 : peraggstate->restart = true;
884 : 112 : numaggs_restart++;
885 : : }
886 : : }
887 : :
888 : : /* Reset per-input-tuple context after each tuple */
889 : 1584 : ResetExprContext(winstate->tmpcontext);
890 : :
891 : : /* And advance the aggregated-row state */
892 : 1584 : winstate->aggregatedbase++;
893 : 1584 : 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 : 4437 : 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 [ + + ]: 4437 : if (agg_winobj->markptr >= 0)
908 : 3112 : 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 [ + + ]: 4437 : if (numaggs_restart > 0)
712 nathan@postgresql.or 921 : 1861 : MemoryContextReset(winstate->aggcontext);
4216 tgl@sss.pgh.pa.us 922 [ + + ]: 9765 : for (i = 0; i < numaggs; i++)
923 : : {
924 : 5328 : peraggstate = &winstate->peragg[i];
925 : :
926 : : /* Aggregates using the shared ctx must restart if *any* agg does */
927 [ + + + + : 5328 : Assert(peraggstate->aggcontext != winstate->aggcontext ||
- + ]
928 : : numaggs_restart == 0 ||
929 : : peraggstate->restart);
930 : :
931 [ + + ]: 5328 : if (peraggstate->restart)
932 : : {
6144 933 : 1970 : wfuncno = peraggstate->wfuncno;
4216 934 : 1970 : initialize_windowaggregate(winstate,
935 : 1970 : &winstate->perfunc[wfuncno],
936 : : peraggstate);
937 : : }
938 [ + + ]: 3358 : else if (!peraggstate->resultValueIsNull)
939 : : {
940 [ + + ]: 3241 : if (!peraggstate->resulttypeByVal)
941 : 1076 : pfree(DatumGetPointer(peraggstate->resultValue));
942 : 3241 : peraggstate->resultValue = (Datum) 0;
943 : 3241 : peraggstate->resultValueIsNull = true;
944 : : }
945 : : }
946 : :
947 : : /*
948 : : * Non-restarted aggregates now contain the rows between aggregatedbase
949 : : * (i.e., frameheadpos) and aggregatedupto, while restarted aggregates
950 : : * contain no rows. If there are any restarted aggregates, we must thus
951 : : * begin aggregating anew at frameheadpos, otherwise we may simply
952 : : * continue at aggregatedupto. We must remember the old value of
953 : : * aggregatedupto to know how long to skip advancing non-restarted
954 : : * aggregates. If we modify aggregatedupto, we must also clear
955 : : * agg_row_slot, per the loop invariant below.
956 : : */
957 : 4437 : aggregatedupto_nonrestarted = winstate->aggregatedupto;
958 [ + + ]: 4437 : if (numaggs_restart > 0 &&
959 [ + + ]: 1861 : winstate->aggregatedupto != winstate->frameheadpos)
960 : : {
961 : 698 : winstate->aggregatedupto = winstate->frameheadpos;
962 : 698 : ExecClearTuple(agg_row_slot);
963 : : }
964 : :
965 : : /*
966 : : * Advance until we reach a row not in frame (or end of partition).
967 : : *
968 : : * Note the loop invariant: agg_row_slot is either empty or holds the row
969 : : * at position aggregatedupto. We advance aggregatedupto after processing
970 : : * a row.
971 : : */
972 : : for (;;)
6147 973 : 87758 : {
974 : : int ret;
975 : :
976 : : /* Fetch next row if we didn't already */
6144 977 [ + - + + ]: 92195 : if (TupIsNull(agg_row_slot))
978 : : {
5736 979 [ + + ]: 90264 : if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
980 : : agg_row_slot))
6144 981 : 2074 : break; /* must be end of partition */
982 : : }
983 : :
984 : : /*
985 : : * Exit loop if no more rows can be in frame. Skip aggregation if
986 : : * current row is not in frame but there might be more in the frame.
987 : : */
24 ishii@postgresql.org 988 :GNC 90121 : ret = row_is_in_frame(agg_winobj, winstate->aggregatedupto,
989 : : agg_row_slot, false);
2819 tgl@sss.pgh.pa.us 990 [ + + ]:CBC 90115 : if (ret < 0)
6144 991 : 2351 : break;
2819 992 [ + + ]: 87764 : if (ret == 0)
993 : 948 : goto next_tuple;
994 : :
995 : : /* Set tuple context for evaluation of aggregate arguments */
6144 996 : 86816 : winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
997 : :
998 : : /* Accumulate row into the aggregates */
6147 999 [ + + ]: 183941 : for (i = 0; i < numaggs; i++)
1000 : : {
6144 1001 : 97131 : peraggstate = &winstate->peragg[i];
1002 : :
1003 : : /* Non-restarted aggs skip until aggregatedupto_nonrestarted */
4216 1004 [ + + ]: 97131 : if (!peraggstate->restart &&
1005 [ + + ]: 58878 : winstate->aggregatedupto < aggregatedupto_nonrestarted)
1006 : 9052 : continue;
1007 : :
6144 1008 : 88079 : wfuncno = peraggstate->wfuncno;
6147 1009 : 88079 : advance_windowaggregate(winstate,
1010 : 88079 : &winstate->perfunc[wfuncno],
1011 : : peraggstate);
1012 : : }
1013 : :
2819 1014 : 86810 : next_tuple:
1015 : : /* Reset per-input-tuple context after each tuple */
6147 1016 : 87758 : ResetExprContext(winstate->tmpcontext);
1017 : :
1018 : : /* And advance the aggregated-row state */
1019 : 87758 : winstate->aggregatedupto++;
6144 1020 : 87758 : ExecClearTuple(agg_row_slot);
1021 : : }
1022 : :
1023 : : /* The frame's end is not supposed to move backwards, ever */
4216 1024 [ - + ]: 4425 : Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
1025 : :
1026 : : /*
1027 : : * finalize aggregates and fill result/isnull fields.
1028 : : */
6147 1029 [ + + ]: 9735 : for (i = 0; i < numaggs; i++)
1030 : : {
1031 : : Datum *result;
1032 : : bool *isnull;
1033 : :
1034 : 5316 : peraggstate = &winstate->peragg[i];
1035 : 5316 : wfuncno = peraggstate->wfuncno;
1036 : 5316 : result = &econtext->ecxt_aggvalues[wfuncno];
1037 : 5316 : isnull = &econtext->ecxt_aggnulls[wfuncno];
1038 : 5316 : finalize_windowaggregate(winstate,
1039 : 5316 : &winstate->perfunc[wfuncno],
1040 : : peraggstate,
1041 : : result, isnull);
1042 : :
1043 : : /*
1044 : : * save the result in case next row shares the same frame.
1045 : : *
1046 : : * XXX in some framing modes, eg ROWS/END_CURRENT_ROW, we can know in
1047 : : * advance that the next row can't possibly share the same frame. Is
1048 : : * it worth detecting that and skipping this code?
1049 : : */
4216 1050 [ + + + + ]: 5310 : if (!peraggstate->resulttypeByVal && !*isnull)
1051 : : {
1052 : 1376 : oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
1053 : 1376 : peraggstate->resultValue =
1054 : 1376 : datumCopy(*result,
1055 : 1376 : peraggstate->resulttypeByVal,
1056 : 1376 : peraggstate->resulttypeLen);
1057 : 1376 : MemoryContextSwitchTo(oldContext);
1058 : : }
1059 : : else
1060 : : {
6147 1061 : 3934 : peraggstate->resultValue = *result;
1062 : : }
1063 : 5310 : peraggstate->resultValueIsNull = *isnull;
1064 : : }
1065 : : }
1066 : :
1067 : : /*
1068 : : * eval_windowfunction
1069 : : *
1070 : : * Arguments of window functions are not evaluated here, because a window
1071 : : * function can need random access to arbitrary rows in the partition.
1072 : : * The window function uses the special WinGetFuncArgInPartition and
1073 : : * WinGetFuncArgInFrame functions to evaluate the arguments for the rows
1074 : : * it wants.
1075 : : */
1076 : : static void
1077 : 435464 : eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
1078 : : Datum *result, bool *isnull)
1079 : : {
2466 andres@anarazel.de 1080 : 435464 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
1081 : : MemoryContext oldContext;
1082 : :
6147 tgl@sss.pgh.pa.us 1083 : 435464 : 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 : : */
2466 andres@anarazel.de 1091 : 435464 : 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 [ + + ]: 560117 : for (int argno = 0; argno < perfuncstate->numArguments; argno++)
1097 : 124653 : fcinfo->args[argno].isnull = true;
1098 : : /* Window functions don't have a current aggregate context, either */
4216 tgl@sss.pgh.pa.us 1099 : 435464 : winstate->curaggcontext = NULL;
1100 : :
2466 andres@anarazel.de 1101 : 435464 : *result = FunctionCallInvoke(fcinfo);
1102 : 435383 : *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 [ + + + + ]: 435383 : if (!perfuncstate->resulttypeByVal && !fcinfo->isnull &&
1117 tgl@sss.pgh.pa.us 1112 [ + + ]: 510 : winstate->numfuncs > 1)
6147 1113 : 54 : *result = datumCopy(*result,
1114 : 54 : perfuncstate->resulttypeByVal,
1115 : 54 : perfuncstate->resulttypeLen);
1116 : :
1117 : 435383 : MemoryContextSwitchTo(oldContext);
1118 : 435383 : }
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
417 drowley@postgresql.o 1129 : 1143 : prepare_tuplestore(WindowAggState *winstate)
1130 : : {
2819 tgl@sss.pgh.pa.us 1131 : 1143 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
2665 1132 : 1143 : int frameOptions = winstate->frameOptions;
5982 bruce@momjian.us 1133 : 1143 : int numfuncs = winstate->numfuncs;
1134 : :
1135 : : /* we shouldn't be called if this was done already */
417 drowley@postgresql.o 1136 [ - + ]: 1143 : Assert(winstate->buffer == NULL);
1137 : :
1138 : : /* Create new tuplestore */
6147 tgl@sss.pgh.pa.us 1139 : 1143 : 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 : 1143 : winstate->current_ptr = 0; /* read pointer 0 is pre-allocated */
1147 : :
1148 : : /* reset default REWIND capability bit for current ptr */
1149 : 1143 : tuplestore_set_eflags(winstate->buffer, 0);
1150 : :
1151 : : /* create read pointers for aggregates, if needed */
1152 [ + + ]: 1143 : if (winstate->numaggs > 0)
1153 : : {
5736 1154 : 562 : WindowObject agg_winobj = winstate->agg_winobj;
1155 : 562 : 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 : : */
2665 1161 [ + + ]: 562 : if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
1162 [ + + ]: 195 : (frameOptions & FRAMEOPTION_EXCLUSION))
1163 : : {
1164 : : /* ... so create a mark pointer to track the frame head */
5736 1165 : 376 : agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
1166 : : /* and the read pointer will need BACKWARD capability */
1167 : 376 : readptr_flags |= EXEC_FLAG_BACKWARD;
1168 : : }
1169 : :
1170 : 562 : agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1171 : : readptr_flags);
1172 : : }
1173 : :
1174 : : /* create mark and read pointers for each real window function */
417 drowley@postgresql.o 1175 [ + + ]: 2667 : for (int i = 0; i < numfuncs; i++)
1176 : : {
5982 bruce@momjian.us 1177 : 1524 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1178 : :
6147 tgl@sss.pgh.pa.us 1179 [ + + ]: 1524 : if (!perfuncstate->plain_agg)
1180 : : {
5982 bruce@momjian.us 1181 : 914 : WindowObject winobj = perfuncstate->winobj;
1182 : :
6147 tgl@sss.pgh.pa.us 1183 : 914 : winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer,
1184 : : 0);
1185 : 914 : winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
1186 : : EXEC_FLAG_BACKWARD);
1187 : : }
1188 : : }
1189 : :
1190 : : /*
1191 : : * If we are in RANGE or GROUPS mode, then determining frame boundaries
1192 : : * requires physical access to the frame endpoint rows, except in certain
1193 : : * degenerate cases. We create read pointers to point to those rows, to
1194 : : * simplify access and ensure that the tuplestore doesn't discard the
1195 : : * endpoint rows prematurely. (Must create pointers in exactly the same
1196 : : * cases that update_frameheadpos and update_frametailpos need them.)
1197 : : */
2819 1198 : 1143 : winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */
1199 : :
2665 1200 [ + + ]: 1143 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1201 : : {
1202 [ + + ]: 631 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
1203 [ - + ]: 34 : node->ordNumCols != 0) ||
1204 [ + + ]: 597 : (frameOptions & FRAMEOPTION_START_OFFSET))
2819 1205 : 361 : winstate->framehead_ptr =
1206 : 361 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
2665 1207 [ + + ]: 631 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
1208 [ + + ]: 246 : node->ordNumCols != 0) ||
1209 [ + + ]: 468 : (frameOptions & FRAMEOPTION_END_OFFSET))
2819 1210 : 520 : winstate->frametail_ptr =
1211 : 520 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1212 : : }
1213 : :
1214 : : /*
1215 : : * If we have an exclusion clause that requires knowing the boundaries of
1216 : : * the current row's peer group, we create a read pointer to track the
1217 : : * tail position of the peer group (i.e., first row of the next peer
1218 : : * group). The head position does not require its own pointer because we
1219 : : * maintain that as a side effect of advancing the current row.
1220 : : */
1221 : 1143 : winstate->grouptail_ptr = -1;
1222 : :
2665 1223 [ + + ]: 1143 : if ((frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
1224 : 90 : FRAMEOPTION_EXCLUDE_TIES)) &&
2819 1225 [ + + ]: 90 : node->ordNumCols != 0)
1226 : : {
1227 : 84 : winstate->grouptail_ptr =
1228 : 84 : tuplestore_alloc_read_pointer(winstate->buffer, 0);
1229 : : }
417 drowley@postgresql.o 1230 : 1143 : }
1231 : :
1232 : : /*
1233 : : * begin_partition
1234 : : * Start buffering rows of the next partition.
1235 : : */
1236 : : static void
1237 : 1804 : begin_partition(WindowAggState *winstate)
1238 : : {
1239 : 1804 : PlanState *outerPlan = outerPlanState(winstate);
1240 : 1804 : int numfuncs = winstate->numfuncs;
1241 : :
1242 : 1804 : winstate->partition_spooled = false;
1243 : 1804 : winstate->framehead_valid = false;
1244 : 1804 : winstate->frametail_valid = false;
1245 : 1804 : winstate->grouptail_valid = false;
1246 : 1804 : winstate->spooled_rows = 0;
1247 : 1804 : winstate->currentpos = 0;
1248 : 1804 : winstate->frameheadpos = 0;
1249 : 1804 : winstate->frametailpos = 0;
1250 : 1804 : winstate->currentgroup = 0;
1251 : 1804 : winstate->frameheadgroup = 0;
1252 : 1804 : winstate->frametailgroup = 0;
1253 : 1804 : winstate->groupheadpos = 0;
1254 : 1804 : winstate->grouptailpos = -1; /* see update_grouptailpos */
1255 : 1804 : ExecClearTuple(winstate->agg_row_slot);
1256 [ + + ]: 1804 : if (winstate->framehead_slot)
1257 : 512 : ExecClearTuple(winstate->framehead_slot);
1258 [ + + ]: 1804 : if (winstate->frametail_slot)
1259 : 857 : 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 [ + - + + ]: 1804 : if (TupIsNull(winstate->first_part_slot))
1266 : : {
1267 : 1182 : TupleTableSlot *outerslot = ExecProcNode(outerPlan);
1268 : :
1269 [ + + + + ]: 1182 : if (!TupIsNull(outerslot))
1270 : 1173 : ExecCopySlot(winstate->first_part_slot, outerslot);
1271 : : else
1272 : : {
1273 : : /* outer plan is empty, so we have nothing to do */
1274 : 9 : winstate->partition_spooled = true;
1275 : 9 : winstate->more_partitions = false;
1276 : 9 : return;
1277 : : }
1278 : : }
1279 : :
1280 : : /* Create new tuplestore if not done already. */
1281 [ + + ]: 1795 : if (unlikely(winstate->buffer == NULL))
1282 : 1143 : prepare_tuplestore(winstate);
1283 : :
1284 : 1795 : winstate->next_partition = false;
1285 : :
1286 [ + + ]: 1795 : if (winstate->numaggs > 0)
1287 : : {
1288 : 932 : WindowObject agg_winobj = winstate->agg_winobj;
1289 : :
1290 : : /* reset mark and see positions for aggregate functions */
1291 : 932 : agg_winobj->markpos = -1;
1292 : 932 : agg_winobj->seekpos = -1;
1293 : :
1294 : : /* Also reset the row counters for aggregates */
1295 : 932 : winstate->aggregatedbase = 0;
1296 : 932 : winstate->aggregatedupto = 0;
1297 : : }
1298 : :
1299 : : /* reset mark and seek positions for each real window function */
1300 [ + + ]: 4124 : for (int i = 0; i < numfuncs; i++)
1301 : : {
1302 : 2329 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1303 : :
1304 [ + + ]: 2329 : if (!perfuncstate->plain_agg)
1305 : : {
1306 : 1298 : WindowObject winobj = perfuncstate->winobj;
1307 : :
1308 : 1298 : winobj->markpos = -1;
1309 : 1298 : winobj->seekpos = -1;
1310 : :
1311 : : /* reset null map */
5 ishii@postgresql.org 1312 [ + + ]:GNC 1298 : if (winobj->ignore_nulls == IGNORE_NULLS ||
1313 [ + + ]: 1283 : winobj->ignore_nulls == PARSER_IGNORE_NULLS)
1314 : : {
1315 : 108 : int numargs = perfuncstate->numArguments;
1316 : :
1317 [ + + ]: 249 : for (int j = 0; j < numargs; j++)
1318 : : {
1319 : 141 : int n = winobj->num_notnull_info[j];
1320 : :
1321 [ + + ]: 141 : if (n > 0)
1322 : 15 : memset(winobj->notnull_info[j], 0,
1323 : 15 : NN_POS_TO_BYTES(n));
1324 : : }
1325 : : }
1326 : : }
1327 : : }
1328 : :
1329 : : /*
1330 : : * Store the first tuple into the tuplestore (it's always available now;
1331 : : * we either read it above, or saved it at the end of previous partition)
1332 : : */
6147 tgl@sss.pgh.pa.us 1333 :CBC 1795 : tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
1334 : 1795 : winstate->spooled_rows++;
1335 : : }
1336 : :
1337 : : /*
1338 : : * Read tuples from the outer node, up to and including position 'pos', and
1339 : : * store them into the tuplestore. If pos is -1, reads the whole partition.
1340 : : */
1341 : : static void
1342 : 929592 : spool_tuples(WindowAggState *winstate, int64 pos)
1343 : : {
5982 bruce@momjian.us 1344 : 929592 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1345 : : PlanState *outerPlan;
1346 : : TupleTableSlot *outerslot;
1347 : : MemoryContext oldcontext;
1348 : :
6147 tgl@sss.pgh.pa.us 1349 [ + + ]: 929592 : if (!winstate->buffer)
1350 : 3 : return; /* just a safety check */
1351 [ + + ]: 929589 : if (winstate->partition_spooled)
1352 : 64008 : 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 : : */
1298 drowley@postgresql.o 1360 [ + + ]: 865581 : if (winstate->status != WINDOWAGG_RUN)
1361 : : {
1362 [ + - - + ]: 15 : Assert(winstate->status == WINDOWAGG_PASSTHROUGH ||
1363 : : winstate->status == WINDOWAGG_PASSTHROUGH_STRICT);
1364 : :
1365 : 15 : pos = -1;
1366 : : }
1367 : :
1368 : : /*
1369 : : * If the tuplestore has spilled to disk, alternate reading and writing
1370 : : * becomes quite expensive due to frequent buffer flushes. It's cheaper
1371 : : * to force the entire partition to get spooled in one go.
1372 : : *
1373 : : * XXX this is a horrid kluge --- it'd be better to fix the performance
1374 : : * problem inside tuplestore. FIXME
1375 : : */
1376 [ + + ]: 865566 : else if (!tuplestore_in_memory(winstate->buffer))
6147 tgl@sss.pgh.pa.us 1377 : 6 : pos = -1;
1378 : :
1379 : 865581 : outerPlan = outerPlanState(winstate);
1380 : :
1381 : : /* Must be in query context to call outerplan */
1382 : 865581 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1383 : :
1384 [ + + + + ]: 2242106 : while (winstate->spooled_rows <= pos || pos == -1)
1385 : : {
1386 : 512640 : outerslot = ExecProcNode(outerPlan);
1387 [ + + + + ]: 512640 : if (TupIsNull(outerslot))
1388 : : {
1389 : : /* reached the end of the last partition */
1390 : 1074 : winstate->partition_spooled = true;
1391 : 1074 : winstate->more_partitions = false;
1392 : 1074 : break;
1393 : : }
1394 : :
1395 [ + + ]: 511566 : if (node->partNumCols > 0)
1396 : : {
2811 andres@anarazel.de 1397 : 69351 : ExprContext *econtext = winstate->tmpcontext;
1398 : :
1399 : 69351 : econtext->ecxt_innertuple = winstate->first_part_slot;
1400 : 69351 : econtext->ecxt_outertuple = outerslot;
1401 : :
1402 : : /* Check if this tuple still belongs to the current partition */
1403 [ + + ]: 69351 : if (!ExecQualAndReset(winstate->partEqfunction, econtext))
1404 : : {
1405 : : /*
1406 : : * end of partition; copy the tuple for the next cycle.
1407 : : */
6147 tgl@sss.pgh.pa.us 1408 : 622 : ExecCopySlot(winstate->first_part_slot, outerslot);
1409 : 622 : winstate->partition_spooled = true;
1410 : 622 : winstate->more_partitions = true;
1411 : 622 : break;
1412 : : }
1413 : : }
1414 : :
1415 : : /*
1416 : : * Remember the tuple unless we're the top-level window and we're in
1417 : : * pass-through mode.
1418 : : */
1298 drowley@postgresql.o 1419 [ + + ]: 510944 : if (winstate->status != WINDOWAGG_PASSTHROUGH_STRICT)
1420 : : {
1421 : : /* Still in partition, so save it into the tuplestore */
1422 : 510938 : tuplestore_puttupleslot(winstate->buffer, outerslot);
1423 : 510938 : winstate->spooled_rows++;
1424 : : }
1425 : : }
1426 : :
6147 tgl@sss.pgh.pa.us 1427 : 865581 : MemoryContextSwitchTo(oldcontext);
1428 : : }
1429 : :
1430 : : /*
1431 : : * release_partition
1432 : : * clear information kept within a partition, including
1433 : : * tuplestore and aggregate results.
1434 : : */
1435 : : static void
1436 : 2990 : release_partition(WindowAggState *winstate)
1437 : : {
1438 : : int i;
1439 : :
1440 [ + + ]: 6853 : for (i = 0; i < winstate->numfuncs; i++)
1441 : : {
5982 bruce@momjian.us 1442 : 3863 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
1443 : :
1444 : : /* Release any partition-local state of this window function */
6147 tgl@sss.pgh.pa.us 1445 [ + + ]: 3863 : if (perfuncstate->winobj)
1446 : 2063 : perfuncstate->winobj->localmem = NULL;
1447 : : }
1448 : :
1449 : : /*
1450 : : * Release all partition-local memory (in particular, any partition-local
1451 : : * state that we might have trashed our pointers to in the above loop, and
1452 : : * any aggregate temp data). We don't rely on retail pfree because some
1453 : : * aggregates might have allocated data we don't have direct pointers to.
1454 : : */
712 nathan@postgresql.or 1455 : 2990 : MemoryContextReset(winstate->partcontext);
1456 : 2990 : MemoryContextReset(winstate->aggcontext);
4216 tgl@sss.pgh.pa.us 1457 [ + + ]: 4790 : for (i = 0; i < winstate->numaggs; i++)
1458 : : {
1459 [ + + ]: 1800 : if (winstate->peragg[i].aggcontext != winstate->aggcontext)
712 nathan@postgresql.or 1460 : 966 : MemoryContextReset(winstate->peragg[i].aggcontext);
1461 : : }
1462 : :
6147 tgl@sss.pgh.pa.us 1463 [ + + ]: 2990 : if (winstate->buffer)
417 drowley@postgresql.o 1464 : 1711 : tuplestore_clear(winstate->buffer);
6147 tgl@sss.pgh.pa.us 1465 : 2990 : winstate->partition_spooled = false;
417 drowley@postgresql.o 1466 : 2990 : winstate->next_partition = true;
6147 tgl@sss.pgh.pa.us 1467 : 2990 : }
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
24 ishii@postgresql.org 1486 :GNC 95503 : row_is_in_frame(WindowObject winobj, int64 pos, TupleTableSlot *slot,
1487 : : bool fetch_tuple)
1488 : : {
1489 : 95503 : WindowAggState *winstate = winobj->winstate;
5736 tgl@sss.pgh.pa.us 1490 :CBC 95503 : int frameOptions = winstate->frameOptions;
1491 : :
6144 1492 [ - + ]: 95503 : 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 : : */
2819 1498 : 95503 : update_frameheadpos(winstate);
1499 [ + + ]: 95503 : if (pos < winstate->frameheadpos)
1500 : 72 : 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 : : */
5736 1507 [ + + ]: 95431 : if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1508 : : {
1509 [ + + ]: 79061 : if (frameOptions & FRAMEOPTION_ROWS)
1510 : : {
1511 : : /* rows after current row are out of frame */
1512 [ + + ]: 1104 : if (pos > winstate->currentpos)
2819 1513 : 486 : return -1;
1514 : : }
1515 [ + - ]: 77957 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1516 : : {
1517 : : /* following row that is not peer is out of frame */
24 ishii@postgresql.org 1518 [ + + ]:GNC 77957 : if (pos > winstate->currentpos)
1519 : : {
19 1520 [ - + ]: 76272 : if (fetch_tuple) /* need to fetch tuple? */
19 ishii@postgresql.org 1521 [ # # ]:UNC 0 : if (!window_gettupleslot(winobj, pos, slot))
1522 : 0 : return -1;
24 ishii@postgresql.org 1523 [ + + ]:GNC 76272 : if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
1524 : 632 : return -1;
1525 : : }
1526 : : }
1527 : : else
5736 tgl@sss.pgh.pa.us 1528 :UBC 0 : Assert(false);
1529 : : }
2819 tgl@sss.pgh.pa.us 1530 [ + + ]:CBC 16370 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1531 : : {
5736 1532 [ + + ]: 9957 : if (frameOptions & FRAMEOPTION_ROWS)
1533 : : {
5722 bruce@momjian.us 1534 : 2964 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1535 : :
1536 : : /* rows after current row + offset are out of frame */
2819 tgl@sss.pgh.pa.us 1537 [ + + ]: 2964 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
5736 1538 : 57 : offset = -offset;
1539 : :
1540 [ + + ]: 2964 : if (pos > winstate->currentpos + offset)
2819 1541 : 603 : return -1;
1542 : : }
1543 [ + - ]: 6993 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1544 : : {
1545 : : /* hard cases, so delegate to update_frametailpos */
1546 : 6993 : update_frametailpos(winstate);
1547 [ + + ]: 6972 : if (pos >= winstate->frametailpos)
1548 : 735 : return -1;
1549 : : }
1550 : : else
5736 tgl@sss.pgh.pa.us 1551 :UBC 0 : Assert(false);
1552 : : }
1553 : :
1554 : : /* Check exclusion clause */
2819 tgl@sss.pgh.pa.us 1555 [ + + ]:CBC 92954 : if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
1556 : : {
1557 [ + + ]: 1473 : if (pos == winstate->currentpos)
1558 : 249 : return 0;
1559 : : }
1560 [ + + ]: 91481 : else if ((frameOptions & FRAMEOPTION_EXCLUDE_GROUP) ||
1561 [ + + ]: 90050 : ((frameOptions & FRAMEOPTION_EXCLUDE_TIES) &&
1562 [ + + ]: 1485 : pos != winstate->currentpos))
1563 : : {
1564 : 2646 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1565 : :
1566 : : /* If no ORDER BY, all rows are peers with each other */
1567 [ + + ]: 2646 : if (node->ordNumCols == 0)
1568 : 234 : return 0;
1569 : : /* Otherwise, check the group boundaries */
1570 [ + + ]: 2412 : if (pos >= winstate->groupheadpos)
1571 : : {
1572 : 1296 : update_grouptailpos(winstate);
1573 [ + + ]: 1296 : if (pos < winstate->grouptailpos)
1574 : 504 : return 0;
1575 : : }
1576 : : }
1577 : :
1578 : : /* If we get here, it's in frame */
1579 : 91967 : return 1;
1580 : : }
1581 : :
1582 : : /*
1583 : : * update_frameheadpos
1584 : : * make frameheadpos valid for the current row
1585 : : *
1586 : : * Note that frameheadpos is computed without regard for any window exclusion
1587 : : * clause; the current row and/or its peers are considered part of the frame
1588 : : * for this purpose even if they must be excluded later.
1589 : : *
1590 : : * May clobber winstate->temp_slot_2.
1591 : : */
1592 : : static void
1593 : 181536 : update_frameheadpos(WindowAggState *winstate)
1594 : : {
6144 1595 : 181536 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
5736 1596 : 181536 : int frameOptions = winstate->frameOptions;
1597 : : MemoryContext oldcontext;
1598 : :
1599 [ + + ]: 181536 : if (winstate->framehead_valid)
6144 1600 : 98747 : return; /* already known for current row */
1601 : :
1602 : : /* We may be called in a short-lived context */
2819 1603 : 82789 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1604 : :
5736 1605 [ + + ]: 82789 : if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
1606 : : {
1607 : : /* In UNBOUNDED PRECEDING mode, frame head is always row 0 */
1608 : 77439 : winstate->frameheadpos = 0;
1609 : 77439 : winstate->framehead_valid = true;
1610 : : }
1611 [ + + ]: 5350 : else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
1612 : : {
1613 [ + + ]: 1402 : if (frameOptions & FRAMEOPTION_ROWS)
1614 : : {
1615 : : /* In ROWS mode, frame head is the same as current */
1616 : 1188 : winstate->frameheadpos = winstate->currentpos;
1617 : 1188 : winstate->framehead_valid = true;
1618 : : }
2819 1619 [ + - ]: 214 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1620 : : {
1621 : : /* If no ORDER BY, all rows are peers with each other */
5736 1622 [ - + ]: 214 : if (node->ordNumCols == 0)
1623 : : {
5736 tgl@sss.pgh.pa.us 1624 :UBC 0 : winstate->frameheadpos = 0;
1625 : 0 : winstate->framehead_valid = true;
2819 1626 : 0 : MemoryContextSwitchTo(oldcontext);
5736 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 : : */
2819 tgl@sss.pgh.pa.us 1637 :CBC 214 : tuplestore_select_read_pointer(winstate->buffer,
1638 : : winstate->framehead_ptr);
1639 [ + + ]: 214 : if (winstate->frameheadpos == 0 &&
1640 [ + - + + ]: 106 : TupIsNull(winstate->framehead_slot))
1641 : : {
1642 : : /* fetch first row into framehead_slot, if we didn't already */
1643 [ - + ]: 41 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1644 : : winstate->framehead_slot))
2819 tgl@sss.pgh.pa.us 1645 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1646 : : }
1647 : :
2819 tgl@sss.pgh.pa.us 1648 [ + - + - ]:CBC 372 : while (!TupIsNull(winstate->framehead_slot))
1649 : : {
1650 [ + + ]: 372 : if (are_peers(winstate, winstate->framehead_slot,
1651 : : winstate->ss.ss_ScanTupleSlot))
1652 : 214 : break; /* this row is the correct frame head */
1653 : : /* Note we advance frameheadpos even if the fetch fails */
1654 : 158 : winstate->frameheadpos++;
1655 : 158 : spool_tuples(winstate, winstate->frameheadpos);
1656 [ - + ]: 158 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1657 : : winstate->framehead_slot))
2819 tgl@sss.pgh.pa.us 1658 :UBC 0 : break; /* end of partition */
1659 : : }
5736 tgl@sss.pgh.pa.us 1660 :CBC 214 : winstate->framehead_valid = true;
1661 : : }
1662 : : else
5736 tgl@sss.pgh.pa.us 1663 :UBC 0 : Assert(false);
1664 : : }
2819 tgl@sss.pgh.pa.us 1665 [ + - ]:CBC 3948 : else if (frameOptions & FRAMEOPTION_START_OFFSET)
1666 : : {
5736 1667 [ + + ]: 3948 : if (frameOptions & FRAMEOPTION_ROWS)
1668 : : {
1669 : : /* In ROWS mode, bound is physically n before/after current */
5722 bruce@momjian.us 1670 : 996 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1671 : :
2819 tgl@sss.pgh.pa.us 1672 [ + + ]: 996 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
5736 1673 : 966 : offset = -offset;
1674 : :
1675 : 996 : winstate->frameheadpos = winstate->currentpos + offset;
1676 : : /* frame head can't go before first row */
1677 [ + + ]: 996 : if (winstate->frameheadpos < 0)
1678 : 168 : winstate->frameheadpos = 0;
2819 1679 [ - + ]: 828 : else if (winstate->frameheadpos > winstate->currentpos + 1)
1680 : : {
1681 : : /* make sure frameheadpos is not past end of partition */
5736 tgl@sss.pgh.pa.us 1682 :UBC 0 : spool_tuples(winstate, winstate->frameheadpos - 1);
1683 [ # # ]: 0 : if (winstate->frameheadpos > winstate->spooled_rows)
1684 : 0 : winstate->frameheadpos = winstate->spooled_rows;
1685 : : }
5736 tgl@sss.pgh.pa.us 1686 :CBC 996 : winstate->framehead_valid = true;
1687 : : }
1688 [ + + ]: 2952 : 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 : : */
2803 1698 : 2262 : int sortCol = node->ordColIdx[0];
1699 : : bool sub,
1700 : : less;
1701 : :
1702 : : /* We must have an ordering column */
2665 1703 [ - + ]: 2262 : Assert(node->ordNumCols == 1);
1704 : :
1705 : : /* Precompute flags for in_range checks */
2819 1706 [ + + ]: 2262 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1707 : 1851 : sub = true; /* subtract startOffset from current row */
1708 : : else
1709 : 411 : sub = false; /* add it */
1710 : 2262 : less = false; /* normally, we want frame head >= sum */
1711 : : /* If sort order is descending, flip both flags */
1712 [ + + ]: 2262 : if (!winstate->inRangeAsc)
1713 : : {
1714 : 327 : sub = !sub;
1715 : 327 : less = true;
1716 : : }
1717 : :
1718 : 2262 : tuplestore_select_read_pointer(winstate->buffer,
1719 : : winstate->framehead_ptr);
1720 [ + + ]: 2262 : if (winstate->frameheadpos == 0 &&
1721 [ + - + + ]: 1251 : TupIsNull(winstate->framehead_slot))
1722 : : {
1723 : : /* fetch first row into framehead_slot, if we didn't already */
1724 [ - + ]: 285 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1725 : : winstate->framehead_slot))
2819 tgl@sss.pgh.pa.us 1726 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1727 : : }
1728 : :
2819 tgl@sss.pgh.pa.us 1729 [ + - + + ]:CBC 3633 : while (!TupIsNull(winstate->framehead_slot))
1730 : : {
1731 : : Datum headval,
1732 : : currval;
1733 : : bool headisnull,
1734 : : currisnull;
1735 : :
2803 1736 : 3531 : headval = slot_getattr(winstate->framehead_slot, sortCol,
1737 : : &headisnull);
1738 : 3531 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1739 : : &currisnull);
2819 1740 [ + + + + ]: 3531 : if (headisnull || currisnull)
1741 : : {
1742 : : /* order of the rows depends only on nulls_first */
1743 [ + + ]: 54 : if (winstate->inRangeNullsFirst)
1744 : : {
1745 : : /* advance head if head is null and curr is not */
1746 [ + - + + ]: 24 : if (!headisnull || currisnull)
1747 : : break;
1748 : : }
1749 : : else
1750 : : {
1751 : : /* advance head if head is not null and curr is null */
1752 [ + + + - ]: 30 : if (headisnull || !currisnull)
1753 : : break;
1754 : : }
1755 : : }
1756 : : else
1757 : : {
1758 [ + + ]: 3477 : if (DatumGetBool(FunctionCall5Coll(&winstate->startInRangeFunc,
1759 : : winstate->inRangeColl,
1760 : : headval,
1761 : : currval,
1762 : : winstate->startOffsetValue,
1763 : : BoolGetDatum(sub),
1764 : : BoolGetDatum(less))))
1765 : 2085 : break; /* this row is the correct frame head */
1766 : : }
1767 : : /* Note we advance frameheadpos even if the fetch fails */
1768 : 1398 : winstate->frameheadpos++;
1769 : 1398 : spool_tuples(winstate, winstate->frameheadpos);
1770 [ + + ]: 1398 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1771 : : winstate->framehead_slot))
1772 : 27 : break; /* end of partition */
1773 : : }
1774 : 2238 : winstate->framehead_valid = true;
1775 : : }
1776 [ + - ]: 690 : 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 : 690 : int64 offset = DatumGetInt64(winstate->startOffsetValue);
1787 : : int64 minheadgroup;
1788 : :
1789 [ + + ]: 690 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
1790 : 564 : minheadgroup = winstate->currentgroup - offset;
1791 : : else
1792 : 126 : minheadgroup = winstate->currentgroup + offset;
1793 : :
1794 : 690 : tuplestore_select_read_pointer(winstate->buffer,
1795 : : winstate->framehead_ptr);
1796 [ + + ]: 690 : if (winstate->frameheadpos == 0 &&
1797 [ + - + + ]: 375 : TupIsNull(winstate->framehead_slot))
1798 : : {
1799 : : /* fetch first row into framehead_slot, if we didn't already */
1800 [ - + ]: 186 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1801 : : winstate->framehead_slot))
2819 tgl@sss.pgh.pa.us 1802 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1803 : : }
1804 : :
2819 tgl@sss.pgh.pa.us 1805 [ + - + + ]:CBC 1761 : while (!TupIsNull(winstate->framehead_slot))
1806 : : {
1807 [ + + ]: 1059 : if (winstate->frameheadgroup >= minheadgroup)
1808 : 660 : break; /* this row is the correct frame head */
1809 : 399 : ExecCopySlot(winstate->temp_slot_2, winstate->framehead_slot);
1810 : : /* Note we advance frameheadpos even if the fetch fails */
1811 : 399 : winstate->frameheadpos++;
1812 : 399 : spool_tuples(winstate, winstate->frameheadpos);
1813 [ + + ]: 399 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1814 : : winstate->framehead_slot))
1815 : 18 : break; /* end of partition */
1816 [ + + ]: 381 : if (!are_peers(winstate, winstate->temp_slot_2,
1817 : : winstate->framehead_slot))
1818 : 261 : winstate->frameheadgroup++;
1819 : : }
1820 : 690 : ExecClearTuple(winstate->temp_slot_2);
1821 : 690 : winstate->framehead_valid = true;
1822 : : }
1823 : : else
5736 tgl@sss.pgh.pa.us 1824 :UBC 0 : Assert(false);
1825 : : }
1826 : : else
1827 : 0 : Assert(false);
1828 : :
2819 tgl@sss.pgh.pa.us 1829 :CBC 82765 : MemoryContextSwitchTo(oldcontext);
1830 : : }
1831 : :
1832 : : /*
1833 : : * update_frametailpos
1834 : : * make frametailpos valid for the current row
1835 : : *
1836 : : * Note that frametailpos is computed without regard for any window exclusion
1837 : : * clause; the current row and/or its peers are considered part of the frame
1838 : : * for this purpose even if they must be excluded later.
1839 : : *
1840 : : * May clobber winstate->temp_slot_2.
1841 : : */
1842 : : static void
1843 : 101371 : update_frametailpos(WindowAggState *winstate)
1844 : : {
5736 1845 : 101371 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
1846 : 101371 : int frameOptions = winstate->frameOptions;
1847 : : MemoryContext oldcontext;
1848 : :
1849 [ + + ]: 101371 : if (winstate->frametail_valid)
1850 : 8970 : return; /* already known for current row */
1851 : :
1852 : : /* We may be called in a short-lived context */
2819 1853 : 92401 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
1854 : :
5736 1855 [ + + ]: 92401 : if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
1856 : : {
1857 : : /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
6144 1858 : 120 : spool_tuples(winstate, -1);
2819 1859 : 120 : winstate->frametailpos = winstate->spooled_rows;
6144 1860 : 120 : winstate->frametail_valid = true;
1861 : : }
5736 1862 [ + + ]: 92281 : else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
1863 : : {
1864 [ + + ]: 88969 : if (frameOptions & FRAMEOPTION_ROWS)
1865 : : {
1866 : : /* In ROWS mode, exactly the rows up to current are in frame */
2819 1867 : 60 : winstate->frametailpos = winstate->currentpos + 1;
5736 1868 : 60 : winstate->frametail_valid = true;
1869 : : }
2819 1870 [ + - ]: 88909 : else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
1871 : : {
1872 : : /* If no ORDER BY, all rows are peers with each other */
5736 1873 [ + + ]: 88909 : if (node->ordNumCols == 0)
1874 : : {
1875 : 30 : spool_tuples(winstate, -1);
2819 1876 : 30 : winstate->frametailpos = winstate->spooled_rows;
5736 1877 : 30 : winstate->frametail_valid = true;
2819 1878 : 30 : MemoryContextSwitchTo(oldcontext);
5736 1879 : 30 : 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 : : */
2819 1890 : 88879 : tuplestore_select_read_pointer(winstate->buffer,
1891 : : winstate->frametail_ptr);
1892 [ + + ]: 88879 : if (winstate->frametailpos == 0 &&
1893 [ + - + - ]: 347 : TupIsNull(winstate->frametail_slot))
1894 : : {
1895 : : /* fetch first row into frametail_slot, if we didn't already */
1896 [ - + ]: 347 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1897 : : winstate->frametail_slot))
2819 tgl@sss.pgh.pa.us 1898 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1899 : : }
1900 : :
2819 tgl@sss.pgh.pa.us 1901 [ + - + + ]:CBC 177417 : while (!TupIsNull(winstate->frametail_slot))
1902 : : {
1903 [ + + ]: 165357 : if (winstate->frametailpos > winstate->currentpos &&
1904 [ + + ]: 136658 : !are_peers(winstate, winstate->frametail_slot,
1905 : : winstate->ss.ss_ScanTupleSlot))
1906 : 76478 : break; /* this row is the frame tail */
1907 : : /* Note we advance frametailpos even if the fetch fails */
1908 : 88879 : winstate->frametailpos++;
1909 : 88879 : spool_tuples(winstate, winstate->frametailpos);
1910 [ + + ]: 88879 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1911 : : winstate->frametail_slot))
5722 bruce@momjian.us 1912 : 341 : break; /* end of partition */
1913 : : }
5736 tgl@sss.pgh.pa.us 1914 : 88879 : winstate->frametail_valid = true;
1915 : : }
1916 : : else
5736 tgl@sss.pgh.pa.us 1917 :UBC 0 : Assert(false);
1918 : : }
2819 tgl@sss.pgh.pa.us 1919 [ + - ]:CBC 3312 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
1920 : : {
5736 1921 [ + + ]: 3312 : if (frameOptions & FRAMEOPTION_ROWS)
1922 : : {
1923 : : /* In ROWS mode, bound is physically n before/after current */
5722 bruce@momjian.us 1924 : 210 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
1925 : :
2819 tgl@sss.pgh.pa.us 1926 [ - + ]: 210 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
5736 tgl@sss.pgh.pa.us 1927 :UBC 0 : offset = -offset;
1928 : :
2819 tgl@sss.pgh.pa.us 1929 :CBC 210 : winstate->frametailpos = winstate->currentpos + offset + 1;
1930 : : /* smallest allowable value of frametailpos is 0 */
5736 1931 [ - + ]: 210 : if (winstate->frametailpos < 0)
2819 tgl@sss.pgh.pa.us 1932 :UBC 0 : winstate->frametailpos = 0;
2819 tgl@sss.pgh.pa.us 1933 [ + - ]:CBC 210 : else if (winstate->frametailpos > winstate->currentpos + 1)
1934 : : {
1935 : : /* make sure frametailpos is not past end of partition */
1936 : 210 : spool_tuples(winstate, winstate->frametailpos - 1);
1937 [ + + ]: 210 : if (winstate->frametailpos > winstate->spooled_rows)
1938 : 48 : winstate->frametailpos = winstate->spooled_rows;
1939 : : }
5736 1940 : 210 : winstate->frametail_valid = true;
1941 : : }
1942 [ + + ]: 3102 : 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 : : */
2803 1952 : 2442 : int sortCol = node->ordColIdx[0];
1953 : : bool sub,
1954 : : less;
1955 : :
1956 : : /* We must have an ordering column */
2665 1957 [ - + ]: 2442 : Assert(node->ordNumCols == 1);
1958 : :
1959 : : /* Precompute flags for in_range checks */
2819 1960 [ + + ]: 2442 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
1961 : 456 : sub = true; /* subtract endOffset from current row */
1962 : : else
1963 : 1986 : sub = false; /* add it */
1964 : 2442 : less = true; /* normally, we want frame tail <= sum */
1965 : : /* If sort order is descending, flip both flags */
1966 [ + + ]: 2442 : if (!winstate->inRangeAsc)
1967 : : {
1968 : 345 : sub = !sub;
1969 : 345 : less = false;
1970 : : }
1971 : :
1972 : 2442 : tuplestore_select_read_pointer(winstate->buffer,
1973 : : winstate->frametail_ptr);
1974 [ + + ]: 2442 : if (winstate->frametailpos == 0 &&
1975 [ + - + + ]: 411 : TupIsNull(winstate->frametail_slot))
1976 : : {
1977 : : /* fetch first row into frametail_slot, if we didn't already */
1978 [ - + ]: 294 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
1979 : : winstate->frametail_slot))
2819 tgl@sss.pgh.pa.us 1980 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1981 : : }
1982 : :
2819 tgl@sss.pgh.pa.us 1983 [ + - + + ]:CBC 4503 : while (!TupIsNull(winstate->frametail_slot))
1984 : : {
1985 : : Datum tailval,
1986 : : currval;
1987 : : bool tailisnull,
1988 : : currisnull;
1989 : :
2803 1990 : 3720 : tailval = slot_getattr(winstate->frametail_slot, sortCol,
1991 : : &tailisnull);
1992 : 3720 : currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
1993 : : &currisnull);
2819 1994 [ + + + + ]: 3720 : if (tailisnull || currisnull)
1995 : : {
1996 : : /* order of the rows depends only on nulls_first */
1997 [ + + ]: 54 : if (winstate->inRangeNullsFirst)
1998 : : {
1999 : : /* advance tail if tail is null or curr is not */
2000 [ + + ]: 24 : if (!tailisnull)
2001 : 1635 : break;
2002 : : }
2003 : : else
2004 : : {
2005 : : /* advance tail if tail is not null or curr is null */
2006 [ + + ]: 30 : if (!currisnull)
2007 : 18 : break;
2008 : : }
2009 : : }
2010 : : else
2011 : : {
2012 [ + + ]: 3666 : if (!DatumGetBool(FunctionCall5Coll(&winstate->endInRangeFunc,
2013 : : winstate->inRangeColl,
2014 : : tailval,
2015 : : currval,
2016 : : winstate->endOffsetValue,
2017 : : BoolGetDatum(sub),
2018 : : BoolGetDatum(less))))
2019 : 1365 : break; /* this row is the correct frame tail */
2020 : : }
2021 : : /* Note we advance frametailpos even if the fetch fails */
2022 : 2301 : winstate->frametailpos++;
2023 : 2301 : spool_tuples(winstate, winstate->frametailpos);
2024 [ + + ]: 2301 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2025 : : winstate->frametail_slot))
2026 : 240 : break; /* end of partition */
2027 : : }
2028 : 2418 : winstate->frametail_valid = true;
2029 : : }
2030 [ + - ]: 660 : 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 : 660 : int64 offset = DatumGetInt64(winstate->endOffsetValue);
2041 : : int64 maxtailgroup;
2042 : :
2043 [ + + ]: 660 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
2044 : 36 : maxtailgroup = winstate->currentgroup - offset;
2045 : : else
2046 : 624 : maxtailgroup = winstate->currentgroup + offset;
2047 : :
2048 : 660 : tuplestore_select_read_pointer(winstate->buffer,
2049 : : winstate->frametail_ptr);
2050 [ + + ]: 660 : if (winstate->frametailpos == 0 &&
2051 [ + - + + ]: 192 : TupIsNull(winstate->frametail_slot))
2052 : : {
2053 : : /* fetch first row into frametail_slot, if we didn't already */
2054 [ - + ]: 183 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2055 : : winstate->frametail_slot))
2819 tgl@sss.pgh.pa.us 2056 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
2057 : : }
2058 : :
2819 tgl@sss.pgh.pa.us 2059 [ + - + + ]:CBC 1794 : while (!TupIsNull(winstate->frametail_slot))
2060 : : {
2061 [ + + ]: 1020 : if (winstate->frametailgroup > maxtailgroup)
2062 : 372 : break; /* this row is the correct frame tail */
2063 : 648 : ExecCopySlot(winstate->temp_slot_2, winstate->frametail_slot);
2064 : : /* Note we advance frametailpos even if the fetch fails */
2065 : 648 : winstate->frametailpos++;
2066 : 648 : spool_tuples(winstate, winstate->frametailpos);
2067 [ + + ]: 648 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2068 : : winstate->frametail_slot))
2069 : 174 : break; /* end of partition */
2070 [ + + ]: 474 : if (!are_peers(winstate, winstate->temp_slot_2,
2071 : : winstate->frametail_slot))
2072 : 300 : winstate->frametailgroup++;
2073 : : }
2074 : 660 : ExecClearTuple(winstate->temp_slot_2);
2075 : 660 : winstate->frametail_valid = true;
2076 : : }
2077 : : else
5736 tgl@sss.pgh.pa.us 2078 :UBC 0 : Assert(false);
2079 : : }
2080 : : else
2081 : 0 : Assert(false);
2082 : :
2819 tgl@sss.pgh.pa.us 2083 :CBC 92347 : MemoryContextSwitchTo(oldcontext);
2084 : : }
2085 : :
2086 : : /*
2087 : : * update_grouptailpos
2088 : : * make grouptailpos valid for the current row
2089 : : *
2090 : : * May clobber winstate->temp_slot_2.
2091 : : */
2092 : : static void
2093 : 2436 : update_grouptailpos(WindowAggState *winstate)
2094 : : {
2095 : 2436 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
2096 : : MemoryContext oldcontext;
2097 : :
2098 [ + + ]: 2436 : if (winstate->grouptail_valid)
2099 : 1977 : return; /* already known for current row */
2100 : :
2101 : : /* We may be called in a short-lived context */
2102 : 459 : 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 [ - + ]: 459 : if (node->ordNumCols == 0)
2106 : : {
2819 tgl@sss.pgh.pa.us 2107 :UBC 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 : : */
2819 tgl@sss.pgh.pa.us 2121 [ - + ]:CBC 459 : Assert(winstate->grouptailpos <= winstate->currentpos);
2122 : 459 : tuplestore_select_read_pointer(winstate->buffer,
2123 : : winstate->grouptail_ptr);
2124 : : for (;;)
2125 : : {
2126 : : /* Note we advance grouptailpos even if the fetch fails */
2127 : 879 : winstate->grouptailpos++;
2128 : 879 : spool_tuples(winstate, winstate->grouptailpos);
2129 [ + + ]: 879 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2130 : : winstate->temp_slot_2))
2131 : 129 : break; /* end of partition */
2132 [ + + ]: 750 : if (winstate->grouptailpos > winstate->currentpos &&
2133 [ + + ]: 621 : !are_peers(winstate, winstate->temp_slot_2,
2134 : : winstate->ss.ss_ScanTupleSlot))
2135 : 330 : break; /* this row is the group tail */
2136 : : }
2137 : 459 : ExecClearTuple(winstate->temp_slot_2);
2138 : 459 : winstate->grouptail_valid = true;
2139 : :
2140 : 459 : MemoryContextSwitchTo(oldcontext);
2141 : : }
2142 : :
2143 : : /*
2144 : : * calculate_frame_offsets
2145 : : * Determine the startOffsetValue and endOffsetValue values for the
2146 : : * WindowAgg's frame options.
2147 : : */
2148 : : static pg_noinline void
417 drowley@postgresql.o 2149 : 1182 : calculate_frame_offsets(PlanState *pstate)
2150 : : {
2151 : 1182 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2152 : : ExprContext *econtext;
2153 : 1182 : int frameOptions = winstate->frameOptions;
2154 : : Datum value;
2155 : : bool isnull;
2156 : : int16 len;
2157 : : bool byval;
2158 : :
2159 : : /* Ensure we've not been called before for this scan */
2160 [ - + ]: 1182 : Assert(winstate->all_first);
2161 : :
2162 : 1182 : econtext = winstate->ss.ps.ps_ExprContext;
2163 : :
2164 [ + + ]: 1182 : if (frameOptions & FRAMEOPTION_START_OFFSET)
2165 : : {
2166 [ - + ]: 432 : Assert(winstate->startOffset != NULL);
2167 : 432 : value = ExecEvalExprSwitchContext(winstate->startOffset,
2168 : : econtext,
2169 : : &isnull);
2170 [ - + ]: 432 : if (isnull)
417 drowley@postgresql.o 2171 [ # # ]:UBC 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 */
417 drowley@postgresql.o 2175 :CBC 432 : get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
2176 : : &len,
2177 : : &byval);
2178 : 432 : winstate->startOffsetValue = datumCopy(value, byval, len);
2179 [ + + ]: 432 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2180 : : {
2181 : : /* value is known to be int8 */
2182 : 174 : int64 offset = DatumGetInt64(value);
2183 : :
2184 [ - + ]: 174 : if (offset < 0)
417 drowley@postgresql.o 2185 [ # # ]:UBC 0 : ereport(ERROR,
2186 : : (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
2187 : : errmsg("frame starting offset must not be negative")));
2188 : : }
2189 : : }
2190 : :
417 drowley@postgresql.o 2191 [ + + ]:CBC 1182 : if (frameOptions & FRAMEOPTION_END_OFFSET)
2192 : : {
2193 [ - + ]: 480 : Assert(winstate->endOffset != NULL);
2194 : 480 : value = ExecEvalExprSwitchContext(winstate->endOffset,
2195 : : econtext,
2196 : : &isnull);
2197 [ - + ]: 480 : if (isnull)
417 drowley@postgresql.o 2198 [ # # ]:UBC 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 */
417 drowley@postgresql.o 2202 :CBC 480 : get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
2203 : : &len,
2204 : : &byval);
2205 : 480 : winstate->endOffsetValue = datumCopy(value, byval, len);
2206 [ + + ]: 480 : if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
2207 : : {
2208 : : /* value is known to be int8 */
2209 : 189 : int64 offset = DatumGetInt64(value);
2210 : :
2211 [ - + ]: 189 : if (offset < 0)
417 drowley@postgresql.o 2212 [ # # ]:UBC 0 : ereport(ERROR,
2213 : : (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
2214 : : errmsg("frame ending offset must not be negative")));
2215 : : }
2216 : : }
417 drowley@postgresql.o 2217 :CBC 1182 : winstate->all_first = false;
2218 : 1182 : }
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 *
3024 andres@anarazel.de 2230 : 453567 : ExecWindowAgg(PlanState *pstate)
2231 : : {
2232 : 453567 : WindowAggState *winstate = castNode(WindowAggState, pstate);
2233 : : TupleTableSlot *slot;
2234 : : ExprContext *econtext;
2235 : : int i;
2236 : : int numfuncs;
2237 : :
3016 2238 [ - + ]: 453567 : CHECK_FOR_INTERRUPTS();
2239 : :
1298 drowley@postgresql.o 2240 [ - + ]: 453567 : if (winstate->status == WINDOWAGG_DONE)
6147 tgl@sss.pgh.pa.us 2241 :UBC 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 : : */
417 drowley@postgresql.o 2248 [ + + ]:CBC 453567 : if (unlikely(winstate->all_first))
2249 : 1182 : calculate_frame_offsets(pstate);
2250 : :
2251 : : /* We need to loop as the runCondition or qual may filter out tuples */
2252 : : for (;;)
2253 : : {
2254 [ + + ]: 453633 : if (winstate->next_partition)
2255 : : {
2256 : : /* Initialize for first partition and set current row = 0 */
1298 2257 : 1182 : begin_partition(winstate);
2258 : : /* If there are no input rows, we'll detect that and exit below */
2259 : : }
2260 : : else
2261 : : {
2262 : : /* Advance current row within partition */
2263 : 452451 : winstate->currentpos++;
2264 : : /* This might mean that the frame moves, too */
2265 : 452451 : winstate->framehead_valid = false;
2266 : 452451 : 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 : 453633 : spool_tuples(winstate, winstate->currentpos);
2275 : :
2276 : : /* Move to the next partition if we reached the end of this partition */
2277 [ + + ]: 453633 : if (winstate->partition_spooled &&
2278 [ + + ]: 31507 : winstate->currentpos >= winstate->spooled_rows)
2279 : : {
2280 : 1678 : release_partition(winstate);
2281 : :
2282 [ + + ]: 1678 : if (winstate->more_partitions)
2283 : : {
2284 : 622 : begin_partition(winstate);
2285 [ - + ]: 622 : Assert(winstate->spooled_rows > 0);
2286 : :
2287 : : /* Come out of pass-through mode when changing partition */
2288 : 622 : winstate->status = WINDOWAGG_RUN;
2289 : : }
2290 : : else
2291 : : {
2292 : : /* No further partitions? We're done */
2293 : 1056 : winstate->status = WINDOWAGG_DONE;
2294 : 1056 : return NULL;
2295 : : }
2296 : : }
2297 : :
2298 : : /* final output execution is in ps_ExprContext */
2299 : 452577 : econtext = winstate->ss.ps.ps_ExprContext;
2300 : :
2301 : : /* Clear the per-output-tuple context for current row */
2302 : 452577 : 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 : 452577 : tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr);
2320 [ + + ]: 452577 : if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |
2321 : : FRAMEOPTION_EXCLUDE_GROUP |
2322 : 1449 : FRAMEOPTION_EXCLUDE_TIES)) &&
2323 [ + + ]: 1449 : winstate->currentpos > 0)
2324 : : {
2325 : 1179 : ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot);
2326 [ - + ]: 1179 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2327 : : winstate->ss.ss_ScanTupleSlot))
1298 drowley@postgresql.o 2328 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
1298 drowley@postgresql.o 2329 [ + + ]:CBC 1179 : if (!are_peers(winstate, winstate->temp_slot_2,
2330 : : winstate->ss.ss_ScanTupleSlot))
2331 : : {
2332 : 621 : winstate->currentgroup++;
2333 : 621 : winstate->groupheadpos = winstate->currentpos;
2334 : 621 : winstate->grouptail_valid = false;
2335 : : }
2336 : 1179 : ExecClearTuple(winstate->temp_slot_2);
2337 : : }
2338 : : else
2339 : : {
2340 [ - + ]: 451398 : if (!tuplestore_gettupleslot(winstate->buffer, true, true,
2341 : : winstate->ss.ss_ScanTupleSlot))
1298 drowley@postgresql.o 2342 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
2343 : : }
2344 : :
2345 : : /* don't evaluate the window functions when we're in pass-through mode */
1298 drowley@postgresql.o 2346 [ + + ]:CBC 452577 : if (winstate->status == WINDOWAGG_RUN)
2347 : : {
2348 : : /*
2349 : : * Evaluate true window functions
2350 : : */
2351 : 452544 : numfuncs = winstate->numfuncs;
2352 [ + + ]: 968946 : for (i = 0; i < numfuncs; i++)
2353 : : {
2354 : 516483 : WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
2355 : :
2356 [ + + ]: 516483 : if (perfuncstate->plain_agg)
2357 : 81019 : continue;
2358 : 435464 : eval_windowfunction(winstate, perfuncstate,
2359 : 435464 : &(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),
2360 : 435464 : &(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));
2361 : : }
2362 : :
2363 : : /*
2364 : : * Evaluate aggregates
2365 : : */
2366 [ + + ]: 452463 : if (winstate->numaggs > 0)
2367 : 80122 : eval_windowaggregates(winstate);
2368 : : }
2369 : :
2370 : : /*
2371 : : * If we have created auxiliary read pointers for the frame or group
2372 : : * boundaries, force them to be kept up-to-date, because we don't know
2373 : : * whether the window function(s) will do anything that requires that.
2374 : : * Failing to advance the pointers would result in being unable to
2375 : : * trim data from the tuplestore, which is bad. (If we could know in
2376 : : * advance whether the window functions will use frame boundary info,
2377 : : * we could skip creating these pointers in the first place ... but
2378 : : * unfortunately the window function API doesn't require that.)
2379 : : */
2380 [ + + ]: 452475 : if (winstate->framehead_ptr >= 0)
2381 : 3118 : update_frameheadpos(winstate);
2382 [ + + ]: 452475 : if (winstate->frametail_ptr >= 0)
2383 : 91957 : update_frametailpos(winstate);
2384 [ + + ]: 452475 : if (winstate->grouptail_ptr >= 0)
2385 : 750 : update_grouptailpos(winstate);
2386 : :
2387 : : /*
2388 : : * Truncate any no-longer-needed rows from the tuplestore.
2389 : : */
2390 : 452475 : 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 : 452475 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
2398 : :
2399 : 452475 : slot = ExecProject(winstate->ss.ps.ps_ProjInfo);
2400 : :
2401 [ + + ]: 452475 : if (winstate->status == WINDOWAGG_RUN)
2402 : : {
2403 : 452442 : 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 [ + + ]: 452442 : 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 [ + + ]: 66 : 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 : : */
322 2433 : 45 : numfuncs = winstate->numfuncs;
2434 [ + + ]: 132 : for (i = 0; i < numfuncs; i++)
2435 : : {
2436 : 87 : econtext->ecxt_aggvalues[i] = (Datum) 0;
2437 : 87 : econtext->ecxt_aggnulls[i] = true;
2438 : : }
2439 : :
2440 : : /*
2441 : : * STRICT pass-through mode is required for the top window
2442 : : * when there is a PARTITION BY clause. Otherwise we must
2443 : : * ensure we store tuples that don't match the
2444 : : * runcondition so they're available to WindowAggs above.
2445 : : */
1298 2446 [ + + ]: 45 : if (winstate->top_window)
2447 : : {
2448 : 36 : winstate->status = WINDOWAGG_PASSTHROUGH_STRICT;
2449 : 36 : continue;
2450 : : }
2451 : : else
2452 : : {
2453 : 9 : winstate->status = WINDOWAGG_PASSTHROUGH;
2454 : : }
2455 : : }
2456 : : else
2457 : : {
2458 : : /*
2459 : : * Pass-through not required. We can just return NULL.
2460 : : * Nothing else will match the runcondition.
2461 : : */
2462 : 21 : winstate->status = WINDOWAGG_DONE;
2463 : 21 : return NULL;
2464 : : }
2465 : : }
2466 : :
2467 : : /*
2468 : : * Filter out any tuples we don't need in the top-level WindowAgg.
2469 : : */
2470 [ + + ]: 452385 : if (!ExecQual(winstate->ss.ps.qual, econtext))
2471 : : {
2472 [ - + ]: 9 : InstrCountFiltered1(winstate, 1);
2473 : 9 : continue;
2474 : : }
2475 : :
2476 : 452376 : 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 [ + + ]: 33 : else if (!winstate->top_window)
2484 : 12 : break;
2485 : : }
2486 : :
2487 : 452388 : return slot;
2488 : : }
2489 : :
2490 : : /* -----------------
2491 : : * ExecInitWindowAgg
2492 : : *
2493 : : * Creates the run-time information for the WindowAgg node produced by the
2494 : : * planner and initializes its outer subtree
2495 : : * -----------------
2496 : : */
2497 : : WindowAggState *
6147 tgl@sss.pgh.pa.us 2498 : 1375 : ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
2499 : : {
2500 : : WindowAggState *winstate;
2501 : : Plan *outerPlan;
2502 : : ExprContext *econtext;
2503 : : ExprContext *tmpcontext;
2504 : : WindowStatePerFunc perfunc;
2505 : : WindowStatePerAgg peragg;
2819 2506 : 1375 : int frameOptions = node->frameOptions;
2507 : : int numfuncs,
2508 : : wfuncno,
2509 : : numaggs,
2510 : : aggno;
2511 : : TupleDesc scanDesc;
2512 : : ListCell *l;
2513 : :
2514 : : /* check for unsupported flags */
6147 2515 [ - + ]: 1375 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
2516 : :
2517 : : /*
2518 : : * create state structure
2519 : : */
2520 : 1375 : winstate = makeNode(WindowAggState);
2521 : 1375 : winstate->ss.ps.plan = (Plan *) node;
2522 : 1375 : winstate->ss.ps.state = estate;
3024 andres@anarazel.de 2523 : 1375 : winstate->ss.ps.ExecProcNode = ExecWindowAgg;
2524 : :
2525 : : /* copy frame options to state node for easy access */
579 tgl@sss.pgh.pa.us 2526 : 1375 : 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 : : */
6147 2533 : 1375 : ExecAssignExprContext(estate, &winstate->ss.ps);
2534 : 1375 : tmpcontext = winstate->ss.ps.ps_ExprContext;
2535 : 1375 : winstate->tmpcontext = tmpcontext;
2536 : 1375 : ExecAssignExprContext(estate, &winstate->ss.ps);
2537 : :
2538 : : /* Create long-lived context for storage of partition-local memory etc */
5736 2539 : 1375 : winstate->partcontext =
6147 2540 : 1375 : 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 : : */
5736 2550 : 1375 : winstate->aggcontext =
2551 : 1375 : AllocSetContextCreate(CurrentMemoryContext,
2552 : : "WindowAgg Aggregates",
2553 : : ALLOCSET_DEFAULT_SIZES);
2554 : :
2555 : : /* Only the top-level WindowAgg may have a qual */
1298 drowley@postgresql.o 2556 [ + + - + ]: 1375 : Assert(node->plan.qual == NIL || node->topWindow);
2557 : :
2558 : : /* Initialize the qual */
2559 : 1375 : winstate->ss.ps.qual = ExecInitQual(node->plan.qual,
2560 : : (PlanState *) winstate);
2561 : :
2562 : : /*
2563 : : * Setup the run condition, if we received one from the query planner.
2564 : : * When set, this may allow us to move into pass-through mode so that we
2565 : : * don't have to perform any further evaluation of WindowFuncs in the
2566 : : * current partition or possibly stop returning tuples altogether when all
2567 : : * tuples are in the same partition.
2568 : : */
2569 : 1375 : winstate->runcondition = ExecInitQual(node->runCondition,
2570 : : (PlanState *) winstate);
2571 : :
2572 : : /*
2573 : : * When we're not the top-level WindowAgg node or we are but have a
2574 : : * PARTITION BY clause we must move into one of the WINDOWAGG_PASSTHROUGH*
2575 : : * modes when the runCondition becomes false.
2576 : : */
2577 [ + + + + ]: 1375 : 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 : 1375 : winstate->top_window = node->topWindow;
2581 : :
2582 : : /*
2583 : : * initialize child nodes
2584 : : */
6147 tgl@sss.pgh.pa.us 2585 : 1375 : outerPlan = outerPlan(node);
2586 : 1375 : 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 : : */
2538 andres@anarazel.de 2592 : 1375 : ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple);
2811 2593 : 1375 : scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
2594 : :
2595 : : /* the outer tuple isn't the child's tuple, but always a minimal tuple */
2538 2596 : 1375 : winstate->ss.ps.outeropsset = true;
2597 : 1375 : winstate->ss.ps.outerops = &TTSOpsMinimalTuple;
2598 : 1375 : winstate->ss.ps.outeropsfixed = true;
2599 : :
2600 : : /*
2601 : : * tuple table initialization
2602 : : */
2603 : 1375 : winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2604 : : &TTSOpsMinimalTuple);
2605 : 1375 : winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2606 : : &TTSOpsMinimalTuple);
2607 : 1375 : winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,
2608 : : &TTSOpsMinimalTuple);
2609 : 1375 : 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 : : */
2810 2617 : 1375 : winstate->framehead_slot = winstate->frametail_slot = NULL;
2618 : :
2619 [ + + ]: 1375 : if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
2620 : : {
2665 tgl@sss.pgh.pa.us 2621 [ + + ]: 775 : if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
2622 [ - + ]: 38 : node->ordNumCols != 0) ||
2623 [ + + ]: 737 : (frameOptions & FRAMEOPTION_START_OFFSET))
2538 andres@anarazel.de 2624 : 371 : winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2625 : : &TTSOpsMinimalTuple);
2665 tgl@sss.pgh.pa.us 2626 [ + + ]: 775 : if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
2627 [ + + ]: 383 : node->ordNumCols != 0) ||
2628 [ + + ]: 533 : (frameOptions & FRAMEOPTION_END_OFFSET))
2538 andres@anarazel.de 2629 : 605 : winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,
2630 : : &TTSOpsMinimalTuple);
2631 : : }
2632 : :
2633 : : /*
2634 : : * Initialize result slot, type and projection.
2635 : : */
2636 : 1375 : ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual);
6147 tgl@sss.pgh.pa.us 2637 : 1375 : ExecAssignProjectionInfo(&winstate->ss.ps, NULL);
2638 : :
2639 : : /* Set up data for comparing tuples */
2640 [ + + ]: 1375 : if (node->partNumCols > 0)
2811 andres@anarazel.de 2641 : 341 : winstate->partEqfunction =
2642 : 341 : execTuplesMatchPrepare(scanDesc,
2643 : : node->partNumCols,
2644 : 341 : node->partColIdx,
2645 : 341 : node->partOperators,
2411 peter@eisentraut.org 2646 : 341 : node->partCollations,
2647 : : &winstate->ss.ps);
2648 : :
6147 tgl@sss.pgh.pa.us 2649 [ + + ]: 1375 : if (node->ordNumCols > 0)
2811 andres@anarazel.de 2650 : 1097 : winstate->ordEqfunction =
2651 : 1097 : execTuplesMatchPrepare(scanDesc,
2652 : : node->ordNumCols,
2653 : 1097 : node->ordColIdx,
2654 : 1097 : node->ordOperators,
2411 peter@eisentraut.org 2655 : 1097 : node->ordCollations,
2656 : : &winstate->ss.ps);
2657 : :
2658 : : /*
2659 : : * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes.
2660 : : */
6147 tgl@sss.pgh.pa.us 2661 : 1375 : numfuncs = winstate->numfuncs;
2662 : 1375 : numaggs = winstate->numaggs;
2663 : 1375 : econtext = winstate->ss.ps.ps_ExprContext;
2664 : 1375 : econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numfuncs);
2665 : 1375 : econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numfuncs);
2666 : :
2667 : : /*
2668 : : * allocate per-wfunc/per-agg state information.
2669 : : */
2670 : 1375 : perfunc = (WindowStatePerFunc) palloc0(sizeof(WindowStatePerFuncData) * numfuncs);
2671 : 1375 : peragg = (WindowStatePerAgg) palloc0(sizeof(WindowStatePerAggData) * numaggs);
2672 : 1375 : winstate->perfunc = perfunc;
2673 : 1375 : winstate->peragg = peragg;
2674 : :
2675 : 1375 : wfuncno = -1;
2676 : 1375 : aggno = -1;
2677 [ + - + + : 3170 : foreach(l, winstate->funcs)
+ + ]
2678 : : {
5982 bruce@momjian.us 2679 : 1795 : WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
3149 andres@anarazel.de 2680 : 1795 : WindowFunc *wfunc = wfuncstate->wfunc;
2681 : : WindowStatePerFunc perfuncstate;
2682 : : AclResult aclresult;
2683 : : int i;
2684 : :
3050 tgl@sss.pgh.pa.us 2685 [ - + ]: 1795 : if (wfunc->winref != node->winref) /* planner screwed up? */
6144 tgl@sss.pgh.pa.us 2686 [ # # ]:UBC 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 : : */
6147 tgl@sss.pgh.pa.us 2693 [ + + ]:CBC 2347 : for (i = 0; i <= wfuncno; i++)
2694 : : {
2695 [ + + ]: 555 : if (equal(wfunc, perfunc[i].wfunc) &&
2696 [ + - ]: 3 : !contain_volatile_functions((Node *) wfunc))
2697 : 3 : break;
2698 : : }
24 ishii@postgresql.org 2699 [ + + + - ]:GNC 1795 : if (i <= wfuncno && wfunc->ignore_nulls == perfunc[i].ignore_nulls)
2700 : : {
2701 : : /* Found a match to an existing entry, so just mark it */
6147 tgl@sss.pgh.pa.us 2702 :CBC 3 : wfuncstate->wfuncno = i;
2703 : 3 : continue;
2704 : : }
2705 : :
2706 : : /* Nope, so assign a new PerAgg record */
2707 : 1792 : perfuncstate = &perfunc[++wfuncno];
2708 : :
2709 : : /* Mark WindowFunc state node with assigned index in the result array */
2710 : 1792 : wfuncstate->wfuncno = wfuncno;
2711 : :
2712 : : /* Check permission to call window function */
1079 peter@eisentraut.org 2713 : 1792 : aclresult = object_aclcheck(ProcedureRelationId, wfunc->winfnoid, GetUserId(),
2714 : : ACL_EXECUTE);
6147 tgl@sss.pgh.pa.us 2715 [ - + ]: 1792 : if (aclresult != ACLCHECK_OK)
2886 peter_e@gmx.net 2716 :UBC 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
6147 tgl@sss.pgh.pa.us 2717 : 0 : get_func_name(wfunc->winfnoid));
4581 rhaas@postgresql.org 2718 [ - + ]:CBC 1792 : InvokeFunctionExecuteHook(wfunc->winfnoid);
2719 : :
2720 : : /* Fill in the perfuncstate data */
6147 tgl@sss.pgh.pa.us 2721 : 1792 : perfuncstate->wfuncstate = wfuncstate;
2722 : 1792 : perfuncstate->wfunc = wfunc;
2723 : 1792 : perfuncstate->numArguments = list_length(wfuncstate->args);
5312 2724 : 1792 : perfuncstate->winCollation = wfunc->inputcollid;
2725 : :
6147 2726 : 1792 : get_typlenbyval(wfunc->wintype,
2727 : : &perfuncstate->resulttypeLen,
2728 : : &perfuncstate->resulttypeByVal);
2729 : :
2730 : : /*
2731 : : * If it's really just a plain aggregate function, we'll emulate the
2732 : : * Agg environment for it.
2733 : : */
2734 : 1792 : perfuncstate->plain_agg = wfunc->winagg;
2735 [ + + ]: 1792 : if (wfunc->winagg)
2736 : : {
2737 : : WindowStatePerAgg peraggstate;
2738 : :
2739 : 772 : perfuncstate->aggno = ++aggno;
2740 : 772 : peraggstate = &winstate->peragg[aggno];
2741 : 772 : initialize_peragg(winstate, wfunc, peraggstate);
2742 : 772 : peraggstate->wfuncno = wfuncno;
2743 : : }
2744 : : else
2745 : : {
2746 : 1020 : WindowObject winobj = makeNode(WindowObjectData);
2747 : :
2748 : 1020 : winobj->winstate = winstate;
2749 : 1020 : winobj->argstates = wfuncstate->args;
2750 : 1020 : winobj->localmem = NULL;
2751 : 1020 : perfuncstate->winobj = winobj;
24 ishii@postgresql.org 2752 :GNC 1020 : winobj->ignore_nulls = wfunc->ignore_nulls;
5 2753 : 1020 : init_notnull_info(winobj, perfuncstate);
2754 : :
2755 : : /* It's a real window function, so set up to call it. */
1818 tgl@sss.pgh.pa.us 2756 :CBC 1020 : fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
2757 : : econtext->ecxt_per_query_memory);
2758 : 1020 : fmgr_info_set_expr((Node *) wfunc, &perfuncstate->flinfo);
2759 : : }
2760 : : }
2761 : :
2762 : : /* Update numfuncs, numaggs to match number of unique functions found */
6147 2763 : 1375 : winstate->numfuncs = wfuncno + 1;
2764 : 1375 : winstate->numaggs = aggno + 1;
2765 : :
2766 : : /* Set up WindowObject for aggregates, if needed */
5736 2767 [ + + ]: 1375 : if (winstate->numaggs > 0)
2768 : : {
2769 : 715 : WindowObject agg_winobj = makeNode(WindowObjectData);
2770 : :
2771 : 715 : agg_winobj->winstate = winstate;
2772 : 715 : agg_winobj->argstates = NIL;
2773 : 715 : agg_winobj->localmem = NULL;
2774 : : /* make sure markptr = -1 to invalidate. It may not get used */
2775 : 715 : agg_winobj->markptr = -1;
2776 : 715 : agg_winobj->readptr = -1;
2777 : 715 : winstate->agg_winobj = agg_winobj;
2778 : : }
2779 : :
2780 : : /* Set the status to running */
1298 drowley@postgresql.o 2781 : 1375 : winstate->status = WINDOWAGG_RUN;
2782 : :
2783 : : /* initialize frame bound offset expressions */
5736 tgl@sss.pgh.pa.us 2784 : 1375 : winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
2785 : : (PlanState *) winstate);
2786 : 1375 : winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
2787 : : (PlanState *) winstate);
2788 : :
2789 : : /* Lookup in_range support functions if needed */
2819 2790 [ + + ]: 1375 : if (OidIsValid(node->startInRangeFunc))
2791 : 261 : fmgr_info(node->startInRangeFunc, &winstate->startInRangeFunc);
2792 [ + + ]: 1375 : if (OidIsValid(node->endInRangeFunc))
2793 : 294 : fmgr_info(node->endInRangeFunc, &winstate->endInRangeFunc);
2794 : 1375 : winstate->inRangeColl = node->inRangeColl;
2795 : 1375 : winstate->inRangeAsc = node->inRangeAsc;
2796 : 1375 : winstate->inRangeNullsFirst = node->inRangeNullsFirst;
2797 : :
5736 2798 : 1375 : winstate->all_first = true;
6147 2799 : 1375 : winstate->partition_spooled = false;
2800 : 1375 : winstate->more_partitions = false;
417 drowley@postgresql.o 2801 : 1375 : winstate->next_partition = true;
2802 : :
6147 tgl@sss.pgh.pa.us 2803 : 1375 : return winstate;
2804 : : }
2805 : :
2806 : : /* -----------------
2807 : : * ExecEndWindowAgg
2808 : : * -----------------
2809 : : */
2810 : : void
2811 : 1273 : ExecEndWindowAgg(WindowAggState *node)
2812 : : {
2813 : : PlanState *outerPlan;
2814 : : int i;
2815 : :
417 drowley@postgresql.o 2816 [ + + ]: 1273 : if (node->buffer != NULL)
2817 : : {
2818 : 1041 : tuplestore_end(node->buffer);
2819 : :
2820 : : /* nullify so that release_partition skips the tuplestore_clear() */
2821 : 1041 : node->buffer = NULL;
2822 : : }
2823 : :
6147 tgl@sss.pgh.pa.us 2824 : 1273 : release_partition(node);
2825 : :
4216 2826 [ + + ]: 2024 : for (i = 0; i < node->numaggs; i++)
2827 : : {
2828 [ + + ]: 751 : if (node->peragg[i].aggcontext != node->aggcontext)
2829 : 393 : MemoryContextDelete(node->peragg[i].aggcontext);
2830 : : }
5736 2831 : 1273 : MemoryContextDelete(node->partcontext);
2832 : 1273 : MemoryContextDelete(node->aggcontext);
2833 : :
4216 2834 : 1273 : pfree(node->perfunc);
2835 : 1273 : pfree(node->peragg);
2836 : :
6147 2837 : 1273 : outerPlan = outerPlanState(node);
2838 : 1273 : ExecEndNode(outerPlan);
2839 : 1273 : }
2840 : :
2841 : : /* -----------------
2842 : : * ExecReScanWindowAgg
2843 : : * -----------------
2844 : : */
2845 : : void
5586 2846 : 39 : ExecReScanWindowAgg(WindowAggState *node)
2847 : : {
3810 bruce@momjian.us 2848 : 39 : PlanState *outerPlan = outerPlanState(node);
5982 2849 : 39 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
2850 : :
1298 drowley@postgresql.o 2851 : 39 : node->status = WINDOWAGG_RUN;
5736 tgl@sss.pgh.pa.us 2852 : 39 : node->all_first = true;
2853 : :
2854 : : /* release tuplestore et al */
6147 2855 : 39 : release_partition(node);
2856 : :
2857 : : /* release all temp tuples, but especially first_part_slot */
2858 : 39 : ExecClearTuple(node->ss.ss_ScanTupleSlot);
2859 : 39 : ExecClearTuple(node->first_part_slot);
6144 2860 : 39 : ExecClearTuple(node->agg_row_slot);
6147 2861 : 39 : ExecClearTuple(node->temp_slot_1);
2862 : 39 : ExecClearTuple(node->temp_slot_2);
2819 2863 [ - + ]: 39 : if (node->framehead_slot)
2819 tgl@sss.pgh.pa.us 2864 :UBC 0 : ExecClearTuple(node->framehead_slot);
2819 tgl@sss.pgh.pa.us 2865 [ + + ]:CBC 39 : if (node->frametail_slot)
2866 : 3 : ExecClearTuple(node->frametail_slot);
2867 : :
2868 : : /* Forget current wfunc values */
6147 2869 [ + - + - : 78 : MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numfuncs);
+ - + - +
+ ]
2870 [ + - - + : 39 : 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 : : */
3829 rhaas@postgresql.org 2876 [ + + ]: 39 : if (outerPlan->chgParam == NULL)
2877 : 3 : ExecReScan(outerPlan);
6147 tgl@sss.pgh.pa.us 2878 : 39 : }
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 : 772 : initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
2887 : : WindowStatePerAgg peraggstate)
2888 : : {
2889 : : Oid inputTypes[FUNC_MAX_ARGS];
2890 : : int numArguments;
2891 : : HeapTuple aggTuple;
2892 : : Form_pg_aggregate aggform;
2893 : : Oid aggtranstype;
2894 : : AttrNumber initvalAttNo;
2895 : : AclResult aclresult;
2896 : : bool use_ma_code;
2897 : : Oid transfn_oid,
2898 : : invtransfn_oid,
2899 : : finalfn_oid;
2900 : : bool finalextra;
2901 : : char finalmodify;
2902 : : Expr *transfnexpr,
2903 : : *invtransfnexpr,
2904 : : *finalfnexpr;
2905 : : Datum textInitVal;
2906 : : int i;
2907 : : ListCell *lc;
2908 : :
2909 : 772 : numArguments = list_length(wfunc->args);
2910 : :
2911 : 772 : i = 0;
2912 [ + + + + : 1481 : foreach(lc, wfunc->args)
+ + ]
2913 : : {
2914 : 709 : inputTypes[i++] = exprType((Node *) lfirst(lc));
2915 : : }
2916 : :
5734 rhaas@postgresql.org 2917 : 772 : aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(wfunc->winfnoid));
6147 tgl@sss.pgh.pa.us 2918 [ - + ]: 772 : if (!HeapTupleIsValid(aggTuple))
6147 tgl@sss.pgh.pa.us 2919 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for aggregate %u",
2920 : : wfunc->winfnoid);
6147 tgl@sss.pgh.pa.us 2921 :CBC 772 : 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 : : */
2935 2941 [ + + ]: 772 : if (!OidIsValid(aggform->aggminvtransfn))
2942 : 98 : use_ma_code = false; /* sine qua non */
2943 [ + - ]: 674 : else if (aggform->aggmfinalmodify == AGGMODIFY_READ_ONLY &&
892 2944 [ - + ]: 674 : aggform->aggfinalmodify != AGGMODIFY_READ_ONLY)
2935 tgl@sss.pgh.pa.us 2945 :UBC 0 : use_ma_code = true; /* decision forced by safety */
2935 tgl@sss.pgh.pa.us 2946 [ + + ]:CBC 674 : else if (winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
2947 : 269 : use_ma_code = false; /* non-moving frame head */
2948 [ + + ]: 405 : else if (contain_volatile_functions((Node *) wfunc))
2949 : 6 : use_ma_code = false; /* avoid possible behavioral change */
987 drowley@postgresql.o 2950 [ - + ]: 399 : else if (contain_subplans((Node *) wfunc))
987 drowley@postgresql.o 2951 :UBC 0 : use_ma_code = false; /* subplans might contain volatile functions */
2952 : : else
2935 tgl@sss.pgh.pa.us 2953 :CBC 399 : use_ma_code = true; /* yes, let's use it */
2954 [ + + ]: 772 : if (use_ma_code)
2955 : : {
4216 2956 : 399 : peraggstate->transfn_oid = transfn_oid = aggform->aggmtransfn;
2957 : 399 : peraggstate->invtransfn_oid = invtransfn_oid = aggform->aggminvtransfn;
2958 : 399 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggmfinalfn;
4205 2959 : 399 : finalextra = aggform->aggmfinalextra;
2935 2960 : 399 : finalmodify = aggform->aggmfinalmodify;
4216 2961 : 399 : aggtranstype = aggform->aggmtranstype;
2962 : 399 : initvalAttNo = Anum_pg_aggregate_aggminitval;
2963 : : }
2964 : : else
2965 : : {
2966 : 373 : peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
2967 : 373 : peraggstate->invtransfn_oid = invtransfn_oid = InvalidOid;
2968 : 373 : peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
4205 2969 : 373 : finalextra = aggform->aggfinalextra;
2935 2970 : 373 : finalmodify = aggform->aggfinalmodify;
4216 2971 : 373 : aggtranstype = aggform->aggtranstype;
2972 : 373 : initvalAttNo = Anum_pg_aggregate_agginitval;
2973 : : }
2974 : :
2975 : : /*
2976 : : * ExecInitWindowAgg already checked permission to call aggregate function
2977 : : * ... but we still need to check the component functions
2978 : : */
2979 : :
2980 : : /* Check that aggregate owner has permission to call component fns */
2981 : : {
2982 : : HeapTuple procTuple;
2983 : : Oid aggOwner;
2984 : :
5734 rhaas@postgresql.org 2985 : 772 : procTuple = SearchSysCache1(PROCOID,
2986 : : ObjectIdGetDatum(wfunc->winfnoid));
6147 tgl@sss.pgh.pa.us 2987 [ - + ]: 772 : if (!HeapTupleIsValid(procTuple))
6147 tgl@sss.pgh.pa.us 2988 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for function %u",
2989 : : wfunc->winfnoid);
6147 tgl@sss.pgh.pa.us 2990 :CBC 772 : aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner;
2991 : 772 : ReleaseSysCache(procTuple);
2992 : :
1079 peter@eisentraut.org 2993 : 772 : aclresult = object_aclcheck(ProcedureRelationId, transfn_oid, aggOwner,
2994 : : ACL_EXECUTE);
6147 tgl@sss.pgh.pa.us 2995 [ - + ]: 772 : if (aclresult != ACLCHECK_OK)
2886 peter_e@gmx.net 2996 :UBC 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
6147 tgl@sss.pgh.pa.us 2997 : 0 : get_func_name(transfn_oid));
4581 rhaas@postgresql.org 2998 [ - + ]:CBC 772 : InvokeFunctionExecuteHook(transfn_oid);
2999 : :
4216 tgl@sss.pgh.pa.us 3000 [ + + ]: 772 : if (OidIsValid(invtransfn_oid))
3001 : : {
1079 peter@eisentraut.org 3002 : 399 : aclresult = object_aclcheck(ProcedureRelationId, invtransfn_oid, aggOwner,
3003 : : ACL_EXECUTE);
4216 tgl@sss.pgh.pa.us 3004 [ - + ]: 399 : if (aclresult != ACLCHECK_OK)
2886 peter_e@gmx.net 3005 :UBC 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
4216 tgl@sss.pgh.pa.us 3006 : 0 : get_func_name(invtransfn_oid));
4216 tgl@sss.pgh.pa.us 3007 [ - + ]:CBC 399 : InvokeFunctionExecuteHook(invtransfn_oid);
3008 : : }
3009 : :
6147 3010 [ + + ]: 772 : if (OidIsValid(finalfn_oid))
3011 : : {
1079 peter@eisentraut.org 3012 : 421 : aclresult = object_aclcheck(ProcedureRelationId, finalfn_oid, aggOwner,
3013 : : ACL_EXECUTE);
6147 tgl@sss.pgh.pa.us 3014 [ - + ]: 421 : if (aclresult != ACLCHECK_OK)
2886 peter_e@gmx.net 3015 :UBC 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
6147 tgl@sss.pgh.pa.us 3016 : 0 : get_func_name(finalfn_oid));
4581 rhaas@postgresql.org 3017 [ - + ]:CBC 421 : InvokeFunctionExecuteHook(finalfn_oid);
3018 : : }
3019 : : }
3020 : :
3021 : : /*
3022 : : * If the selected finalfn isn't read-only, we can't run this aggregate as
3023 : : * a window function. This is a user-facing error, so we take a bit more
3024 : : * care with the error message than elsewhere in this function.
3025 : : */
2935 tgl@sss.pgh.pa.us 3026 [ - + ]: 772 : if (finalmodify != AGGMODIFY_READ_ONLY)
2935 tgl@sss.pgh.pa.us 3027 [ # # ]:UBC 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 */
4205 tgl@sss.pgh.pa.us 3033 [ + + ]:CBC 772 : if (finalextra)
3034 : 13 : peraggstate->numFinalArgs = numArguments + 1;
3035 : : else
3036 : 759 : peraggstate->numFinalArgs = 1;
3037 : :
3038 : : /* resolve actual type of transition state, if polymorphic */
4326 3039 : 772 : aggtranstype = resolve_aggregate_transtype(wfunc->winfnoid,
3040 : : aggtranstype,
3041 : : inputTypes,
3042 : : numArguments);
3043 : :
3044 : : /* build expression trees using actual argument & result types */
3737 heikki.linnakangas@i 3045 : 772 : build_aggregate_transfn_expr(inputTypes,
3046 : : numArguments,
3047 : : 0, /* no ordered-set window functions yet */
3048 : : false, /* no variadic window functions yet */
3049 : : aggtranstype,
3050 : : wfunc->inputcollid,
3051 : : transfn_oid,
3052 : : invtransfn_oid,
3053 : : &transfnexpr,
3054 : : &invtransfnexpr);
3055 : :
3056 : : /* set up infrastructure for calling the transfn(s) and finalfn */
6147 tgl@sss.pgh.pa.us 3057 : 772 : fmgr_info(transfn_oid, &peraggstate->transfn);
5336 3058 : 772 : fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
3059 : :
4216 3060 [ + + ]: 772 : if (OidIsValid(invtransfn_oid))
3061 : : {
3062 : 399 : fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
3063 : 399 : fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
3064 : : }
3065 : :
6147 3066 [ + + ]: 772 : if (OidIsValid(finalfn_oid))
3067 : : {
3737 heikki.linnakangas@i 3068 : 421 : build_aggregate_finalfn_expr(inputTypes,
3069 : : peraggstate->numFinalArgs,
3070 : : aggtranstype,
3071 : : wfunc->wintype,
3072 : : wfunc->inputcollid,
3073 : : finalfn_oid,
3074 : : &finalfnexpr);
6147 tgl@sss.pgh.pa.us 3075 : 421 : fmgr_info(finalfn_oid, &peraggstate->finalfn);
5336 3076 : 421 : fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
3077 : : }
3078 : :
3079 : : /* get info about relevant datatypes */
6147 3080 : 772 : get_typlenbyval(wfunc->wintype,
3081 : : &peraggstate->resulttypeLen,
3082 : : &peraggstate->resulttypeByVal);
3083 : 772 : get_typlenbyval(aggtranstype,
3084 : : &peraggstate->transtypeLen,
3085 : : &peraggstate->transtypeByVal);
3086 : :
3087 : : /*
3088 : : * initval is potentially null, so don't try to access it as a struct
3089 : : * field. Must do it the hard way with SysCacheGetAttr.
3090 : : */
4216 3091 : 772 : textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple, initvalAttNo,
3092 : : &peraggstate->initValueIsNull);
3093 : :
6147 3094 [ + + ]: 772 : if (peraggstate->initValueIsNull)
3095 : 419 : peraggstate->initValue = (Datum) 0;
3096 : : else
3097 : 353 : peraggstate->initValue = GetAggInitVal(textInitVal,
3098 : : aggtranstype);
3099 : :
3100 : : /*
3101 : : * If the transfn is strict and the initval is NULL, make sure input type
3102 : : * and transtype are the same (or at least binary-compatible), so that
3103 : : * it's OK to use the first input value as the initial transValue. This
3104 : : * should have been checked at agg definition time, but we must check
3105 : : * again in case the transfn's strictness property has been changed.
3106 : : */
3107 [ + + + + ]: 772 : if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
3108 : : {
3109 [ + - ]: 82 : if (numArguments < 1 ||
3110 [ - + ]: 82 : !IsBinaryCoercible(inputTypes[0], aggtranstype))
6147 tgl@sss.pgh.pa.us 3111 [ # # ]:UBC 0 : ereport(ERROR,
3112 : : (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
3113 : : errmsg("aggregate %u needs to have compatible input type and transition type",
3114 : : wfunc->winfnoid)));
3115 : : }
3116 : :
3117 : : /*
3118 : : * Insist that forward and inverse transition functions have the same
3119 : : * strictness setting. Allowing them to differ would require handling
3120 : : * more special cases in advance_windowaggregate and
3121 : : * advance_windowaggregate_base, for no discernible benefit. This should
3122 : : * have been checked at agg definition time, but we must check again in
3123 : : * case either function's strictness property has been changed.
3124 : : */
4216 tgl@sss.pgh.pa.us 3125 [ + + ]:CBC 772 : if (OidIsValid(invtransfn_oid) &&
3126 [ - + ]: 399 : peraggstate->transfn.fn_strict != peraggstate->invtransfn.fn_strict)
4216 tgl@sss.pgh.pa.us 3127 [ # # ]:UBC 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 : : */
4216 tgl@sss.pgh.pa.us 3145 [ + + ]:CBC 772 : if (OidIsValid(invtransfn_oid))
3146 : 399 : peraggstate->aggcontext =
3147 : 399 : AllocSetContextCreate(CurrentMemoryContext,
3148 : : "WindowAgg Per Aggregate",
3149 : : ALLOCSET_DEFAULT_SIZES);
3150 : : else
3151 : 373 : peraggstate->aggcontext = winstate->aggcontext;
3152 : :
6147 3153 : 772 : ReleaseSysCache(aggTuple);
3154 : :
3155 : 772 : return peraggstate;
3156 : : }
3157 : :
3158 : : static Datum
3159 : 353 : GetAggInitVal(Datum textInitVal, Oid transtype)
3160 : : {
3161 : : Oid typinput,
3162 : : typioparam;
3163 : : char *strInitVal;
3164 : : Datum initVal;
3165 : :
3166 : 353 : getTypeInputInfo(transtype, &typinput, &typioparam);
3167 : 353 : strInitVal = TextDatumGetCString(textInitVal);
3168 : 353 : initVal = OidInputFunctionCall(typinput, strInitVal,
3169 : : typioparam, -1);
3170 : 353 : pfree(strInitVal);
3171 : 353 : return initVal;
3172 : : }
3173 : :
3174 : : /*
3175 : : * are_peers
3176 : : * compare two rows to see if they are equal according to the ORDER BY clause
3177 : : *
3178 : : * NB: this does not consider the window frame mode.
3179 : : */
3180 : : static bool
3181 : 298604 : are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
3182 : : TupleTableSlot *slot2)
3183 : : {
3184 : 298604 : WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
2811 andres@anarazel.de 3185 : 298604 : ExprContext *econtext = winstate->tmpcontext;
3186 : :
3187 : : /* If no ORDER BY, all rows are peers with each other */
6147 tgl@sss.pgh.pa.us 3188 [ + + ]: 298604 : if (node->ordNumCols == 0)
3189 : 15526 : return true;
3190 : :
2811 andres@anarazel.de 3191 : 283078 : econtext->ecxt_outertuple = slot1;
3192 : 283078 : econtext->ecxt_innertuple = slot2;
3193 : 283078 : return ExecQualAndReset(winstate->ordEqfunction, econtext);
3194 : : }
3195 : :
3196 : : /*
3197 : : * window_gettupleslot
3198 : : * Fetch the pos'th tuple of the current partition into the slot,
3199 : : * using the winobj's read pointer
3200 : : *
3201 : : * Returns true if successful, false if no such row
3202 : : */
3203 : : static bool
6147 tgl@sss.pgh.pa.us 3204 : 380985 : window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
3205 : : {
3206 : 380985 : WindowAggState *winstate = winobj->winstate;
3207 : : MemoryContext oldcontext;
3208 : :
3209 : : /* often called repeatedly in a row */
3016 andres@anarazel.de 3210 [ - + ]: 380985 : CHECK_FOR_INTERRUPTS();
3211 : :
3212 : : /* Don't allow passing -1 to spool_tuples here */
6147 tgl@sss.pgh.pa.us 3213 [ + + ]: 380985 : if (pos < 0)
3214 : 204 : return false;
3215 : :
3216 : : /* If necessary, fetch the tuple into the spool */
3217 : 380781 : spool_tuples(winstate, pos);
3218 : :
3219 [ + + ]: 380781 : if (pos >= winstate->spooled_rows)
3220 : 2374 : return false;
3221 : :
3222 [ - + ]: 378407 : if (pos < winobj->markpos)
6147 tgl@sss.pgh.pa.us 3223 [ # # ]:UBC 0 : elog(ERROR, "cannot fetch row before WindowObject's mark position");
3224 : :
6147 tgl@sss.pgh.pa.us 3225 :CBC 378407 : oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3226 : :
3227 : 378407 : 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 : : */
4215 3232 [ + + ]: 378407 : if (winobj->seekpos < pos - 1)
3233 : : {
3234 [ - + ]: 1245 : if (!tuplestore_skiptuples(winstate->buffer,
3235 : 1245 : pos - 1 - winobj->seekpos,
3236 : : true))
4215 tgl@sss.pgh.pa.us 3237 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
4215 tgl@sss.pgh.pa.us 3238 :CBC 1245 : winobj->seekpos = pos - 1;
3239 : : }
3240 [ + + ]: 377162 : else if (winobj->seekpos > pos + 1)
3241 : : {
3242 [ - + ]: 1372 : if (!tuplestore_skiptuples(winstate->buffer,
3243 : 1372 : winobj->seekpos - (pos + 1),
3244 : : false))
4215 tgl@sss.pgh.pa.us 3245 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
4215 tgl@sss.pgh.pa.us 3246 :CBC 1372 : winobj->seekpos = pos + 1;
3247 : : }
3248 [ + + ]: 375790 : 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 : : */
6147 3257 : 86561 : tuplestore_advance(winstate->buffer, true);
3258 : 86561 : winobj->seekpos++;
3259 : : }
3260 : :
3261 : : /*
3262 : : * Now we should be on the tuple immediately before or after the one we
3263 : : * want, so just fetch forwards or backwards as appropriate.
3264 : : *
3265 : : * Notice that we tell tuplestore_gettupleslot to make a physical copy of
3266 : : * the fetched tuple. This ensures that the slot's contents remain valid
3267 : : * through manipulations of the tuplestore, which some callers depend on.
3268 : : */
4215 3269 [ + + ]: 378407 : if (winobj->seekpos > pos)
3270 : : {
6058 3271 [ - + ]: 88072 : if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot))
6147 tgl@sss.pgh.pa.us 3272 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
6147 tgl@sss.pgh.pa.us 3273 :CBC 88072 : winobj->seekpos--;
3274 : : }
3275 : : else
3276 : : {
6058 3277 [ - + ]: 290335 : if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot))
6147 tgl@sss.pgh.pa.us 3278 [ # # ]:UBC 0 : elog(ERROR, "unexpected end of tuplestore");
6147 tgl@sss.pgh.pa.us 3279 :CBC 290335 : winobj->seekpos++;
3280 : : }
3281 : :
4215 3282 [ - + ]: 378407 : Assert(winobj->seekpos == pos);
3283 : :
6147 3284 : 378407 : MemoryContextSwitchTo(oldcontext);
3285 : :
3286 : 378407 : return true;
3287 : : }
3288 : :
3289 : : /* gettuple_eval_partition
3290 : : * get tuple in a patition and evaluate the window function's argument
3291 : : * expression on it.
3292 : : */
3293 : : static Datum
24 ishii@postgresql.org 3294 :GNC 118770 : gettuple_eval_partition(WindowObject winobj, int argno,
3295 : : int64 abs_pos, bool *isnull, bool *isout)
3296 : : {
3297 : : WindowAggState *winstate;
3298 : : ExprContext *econtext;
3299 : : TupleTableSlot *slot;
3300 : :
3301 : 118770 : winstate = winobj->winstate;
3302 : 118770 : slot = winstate->temp_slot_1;
3303 [ + + ]: 118770 : if (!window_gettupleslot(winobj, abs_pos, slot))
3304 : : {
3305 : : /* out of partition */
3306 [ + - ]: 291 : if (isout)
3307 : 291 : *isout = true;
3308 : 291 : *isnull = true;
3309 : 291 : return (Datum) 0;
3310 : : }
3311 : :
3312 [ + - ]: 118479 : if (isout)
3313 : 118479 : *isout = false;
3314 : 118479 : econtext = winstate->ss.ps.ps_ExprContext;
3315 : 118479 : econtext->ecxt_outertuple = slot;
3316 : 118479 : return ExecEvalExpr((ExprState *) list_nth
3317 : 118479 : (winobj->argstates, argno),
3318 : : econtext, isnull);
3319 : : }
3320 : :
3321 : : /*
3322 : : * ignorenulls_getfuncarginframe
3323 : : * For IGNORE NULLS, get the next nonnull value in the frame, moving forward
3324 : : * or backward until we find a value or reach the frame's end.
3325 : : */
3326 : : static Datum
3327 : 480 : ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
3328 : : int relpos, int seektype, bool set_mark,
3329 : : bool *isnull, bool *isout)
3330 : : {
3331 : : WindowAggState *winstate;
3332 : : ExprContext *econtext;
3333 : : TupleTableSlot *slot;
3334 : : Datum datum;
3335 : : int64 abs_pos;
3336 : : int64 mark_pos;
3337 : : int notnull_offset;
3338 : : int notnull_relpos;
3339 : : int forward;
3340 : :
3341 [ + - - + ]: 480 : Assert(WindowObjectIsValid(winobj));
3342 : 480 : winstate = winobj->winstate;
3343 : 480 : econtext = winstate->ss.ps.ps_ExprContext;
3344 : 480 : slot = winstate->temp_slot_1;
3345 : 480 : datum = (Datum) 0;
3346 : 480 : notnull_offset = 0;
3347 : 480 : notnull_relpos = abs(relpos);
3348 : :
3349 [ - + + - ]: 480 : switch (seektype)
3350 : : {
24 ishii@postgresql.org 3351 :UNC 0 : case WINDOW_SEEK_CURRENT:
3352 [ # # ]: 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3353 : : abs_pos = mark_pos = 0; /* keep compiler quiet */
3354 : : break;
24 ishii@postgresql.org 3355 :GNC 330 : case WINDOW_SEEK_HEAD:
3356 : : /* rejecting relpos < 0 is easy and simplifies code below */
3357 [ - + ]: 330 : if (relpos < 0)
24 ishii@postgresql.org 3358 :UNC 0 : goto out_of_frame;
24 ishii@postgresql.org 3359 :GNC 330 : update_frameheadpos(winstate);
3360 : 330 : abs_pos = winstate->frameheadpos;
3361 : 330 : mark_pos = winstate->frameheadpos;
3362 : 330 : forward = 1;
3363 : 330 : break;
3364 : 150 : case WINDOW_SEEK_TAIL:
3365 : : /* rejecting relpos > 0 is easy and simplifies code below */
3366 [ - + ]: 150 : if (relpos > 0)
24 ishii@postgresql.org 3367 :UNC 0 : goto out_of_frame;
24 ishii@postgresql.org 3368 :GNC 150 : update_frametailpos(winstate);
3369 : 150 : abs_pos = winstate->frametailpos - 1;
3370 : 150 : mark_pos = 0; /* keep compiler quiet */
3371 : 150 : forward = -1;
3372 : 150 : break;
24 ishii@postgresql.org 3373 :UNC 0 : default:
3374 [ # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3375 : : abs_pos = mark_pos = 0; /* keep compiler quiet */
3376 : : break;
3377 : : }
3378 : :
3379 : : /*
3380 : : * Get the next nonnull value in the frame, moving forward or backward
3381 : : * until we find a value or reach the frame's end.
3382 : : */
3383 : : do
3384 : : {
3385 : : int inframe;
3386 : : int v;
3387 : :
3388 : : /*
3389 : : * Check apparent out of frame case. We need to do this because we
3390 : : * may not call window_gettupleslot before row_is_in_frame, which
3391 : : * supposes abs_pos is never negative.
3392 : : */
24 ishii@postgresql.org 3393 [ + + ]:GNC 1146 : if (abs_pos < 0)
3394 : 6 : goto out_of_frame;
3395 : :
3396 : : /* check whether row is in frame */
3397 : 1140 : inframe = row_is_in_frame(winobj, abs_pos, slot, true);
3398 [ + + ]: 1140 : if (inframe == -1)
3399 : 27 : goto out_of_frame;
3400 [ + + ]: 1113 : else if (inframe == 0)
3401 : 39 : goto advance;
3402 : :
3403 [ - + ]: 1074 : if (isout)
24 ishii@postgresql.org 3404 :UNC 0 : *isout = false;
3405 : :
5 ishii@postgresql.org 3406 :GNC 1074 : v = get_notnull_info(winobj, abs_pos, argno);
24 3407 [ + + ]: 1074 : if (v == NN_NULL) /* this row is known to be NULL */
3408 : 300 : goto advance;
3409 : :
3410 [ + + ]: 774 : else if (v == NN_UNKNOWN) /* need to check NULL or not */
3411 : : {
3412 [ + + ]: 393 : if (!window_gettupleslot(winobj, abs_pos, slot))
3413 : 15 : goto out_of_frame;
3414 : :
3415 : 378 : econtext->ecxt_outertuple = slot;
3416 : 378 : datum = ExecEvalExpr(
3417 : 378 : (ExprState *) list_nth(winobj->argstates,
3418 : : argno), econtext,
3419 : : isnull);
3420 [ + + ]: 378 : if (!*isnull)
3421 : 225 : notnull_offset++;
3422 : :
3423 : : /* record the row status */
5 3424 : 378 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3425 : : }
3426 : : else /* this row is known to be NOT NULL */
3427 : : {
24 3428 : 381 : notnull_offset++;
3429 [ + + ]: 381 : if (notnull_offset > notnull_relpos)
3430 : : {
3431 : : /* to prepare exiting this loop, datum needs to be set */
3432 [ - + ]: 240 : if (!window_gettupleslot(winobj, abs_pos, slot))
24 ishii@postgresql.org 3433 :UNC 0 : goto out_of_frame;
3434 : :
24 ishii@postgresql.org 3435 :GNC 240 : econtext->ecxt_outertuple = slot;
3436 : 240 : datum = ExecEvalExpr(
3437 : 240 : (ExprState *) list_nth
3438 : 240 : (winobj->argstates, argno),
3439 : : econtext, isnull);
3440 : : }
3441 : : }
3442 : 141 : advance:
3443 : 1098 : abs_pos += forward;
3444 [ + + ]: 1098 : } while (notnull_offset <= notnull_relpos);
3445 : :
3446 [ + - ]: 432 : if (set_mark)
3447 : 432 : WinSetMarkPosition(winobj, mark_pos);
3448 : :
3449 : 432 : return datum;
3450 : :
3451 : 48 : out_of_frame:
3452 [ - + ]: 48 : if (isout)
24 ishii@postgresql.org 3453 :UNC 0 : *isout = true;
24 ishii@postgresql.org 3454 :GNC 48 : *isnull = true;
3455 : 48 : return (Datum) 0;
3456 : : }
3457 : :
3458 : :
3459 : : /*
3460 : : * init_notnull_info
3461 : : * Initialize non null map.
3462 : : */
3463 : : static void
5 3464 : 1020 : init_notnull_info(WindowObject winobj, WindowStatePerFunc perfuncstate)
3465 : : {
3466 : 1020 : int numargs = perfuncstate->numArguments;
3467 : :
24 3468 [ + + ]: 1020 : if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3469 : : {
5 3470 : 93 : winobj->notnull_info = palloc0(sizeof(uint8 *) * numargs);
3471 : 93 : winobj->num_notnull_info = palloc0(sizeof(int64) * numargs);
3472 : : }
24 3473 : 1020 : }
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
5 3482 : 1974 : 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 [ + + ]: 1974 : if (pos >= winobj->num_notnull_info[argno])
3488 : : {
3489 : : /* We may be called in a short-lived context */
3490 : 75 : MemoryContext oldcontext = MemoryContextSwitchTo
3491 : 75 : (winobj->winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
3492 : :
3493 : : for (;;)
24 ishii@postgresql.org 3494 :UNC 0 : {
5 ishii@postgresql.org 3495 :GNC 75 : Size oldsize = NN_POS_TO_BYTES
3496 : : (winobj->num_notnull_info[argno]);
3497 : : Size newsize;
3498 : :
3499 [ + - ]: 75 : if (oldsize == 0) /* memory has not been allocated yet for this
3500 : : * arg */
3501 : : {
3502 : 75 : newsize = NN_POS_TO_BYTES(INIT_NOT_NULL_INFO_NUM);
3503 : 75 : winobj->notnull_info[argno] = palloc0(newsize);
3504 : : }
3505 : : else
3506 : : {
5 ishii@postgresql.org 3507 :UNC 0 : newsize = oldsize * 2;
3508 : 0 : winobj->notnull_info[argno] =
3509 : 0 : repalloc0(winobj->notnull_info[argno], oldsize, newsize);
3510 : : }
5 ishii@postgresql.org 3511 :GNC 75 : winobj->num_notnull_info[argno] = NN_BYTES_TO_POS(newsize);
3512 [ + - ]: 75 : if (winobj->num_notnull_info[argno] > pos)
24 3513 : 75 : break;
3514 : : }
5 3515 : 75 : MemoryContextSwitchTo(oldcontext);
3516 : : }
24 3517 : 1974 : }
3518 : :
3519 : : /*
3520 : : * get_notnull_info
3521 : : * retrieve a map
3522 : : * pos: map position
3523 : : * argno: argument number
3524 : : */
3525 : : static uint8
5 3526 : 1386 : get_notnull_info(WindowObject winobj, int64 pos, int argno)
3527 : : {
3528 : : uint8 *mbp;
3529 : : uint8 mb;
3530 : : int64 bpos;
3531 : :
3532 : 1386 : grow_notnull_info(winobj, pos, argno);
24 3533 : 1386 : bpos = NN_POS_TO_BYTES(pos);
5 3534 : 1386 : mbp = winobj->notnull_info[argno];
3535 : 1386 : mb = mbp[bpos];
24 3536 : 1386 : return (mb >> (NN_SHIFT(pos))) & NN_MASK;
3537 : : }
3538 : :
3539 : : /*
3540 : : * put_notnull_info
3541 : : * update map
3542 : : * pos: map position
3543 : : * argno: argument number
3544 : : * isnull: indicate NULL or NOT
3545 : : */
3546 : : static void
5 3547 : 588 : put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull)
3548 : : {
3549 : : uint8 *mbp;
3550 : : uint8 mb;
3551 : : int64 bpos;
24 3552 [ + + ]: 588 : uint8 val = isnull ? NN_NULL : NN_NOTNULL;
3553 : : int shift;
3554 : :
5 3555 : 588 : grow_notnull_info(winobj, pos, argno);
24 3556 : 588 : bpos = NN_POS_TO_BYTES(pos);
5 3557 : 588 : mbp = winobj->notnull_info[argno];
3558 : 588 : mb = mbp[bpos];
24 3559 : 588 : shift = NN_SHIFT(pos);
3560 : 588 : mb &= ~(NN_MASK << shift); /* clear map */
3561 : 588 : mb |= (val << shift); /* update map */
5 3562 : 588 : mbp[bpos] = mb;
24 3563 : 588 : }
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 : 435464 : WinCheckAndInitializeNullTreatment(WindowObject winobj,
3579 : : bool allowNullTreatment,
3580 : : FunctionCallInfo fcinfo)
3581 : : {
13 3582 [ + - - + ]: 435464 : Assert(WindowObjectIsValid(winobj));
24 3583 [ + + + + ]: 435464 : if (winobj->ignore_nulls != NO_NULLTREATMENT && !allowNullTreatment)
3584 : : {
13 3585 : 36 : const char *funcname = get_func_name(fcinfo->flinfo->fn_oid);
3586 : :
3587 [ - + ]: 36 : if (!funcname)
13 ishii@postgresql.org 3588 [ # # ]:UNC 0 : elog(ERROR, "could not get function name");
13 ishii@postgresql.org 3589 [ + - ]:GNC 36 : ereport(ERROR,
3590 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3591 : : errmsg("function %s does not allow RESPECT/IGNORE NULLS",
3592 : : funcname)));
3593 : : }
24 3594 [ + + ]: 435428 : else if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
3595 : 75 : winobj->ignore_nulls = IGNORE_NULLS;
3596 : 435428 : }
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 *
6147 tgl@sss.pgh.pa.us 3611 :CBC 165965 : WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
3612 : : {
3613 [ + - - + ]: 165965 : Assert(WindowObjectIsValid(winobj));
3614 [ + + ]: 165965 : if (winobj->localmem == NULL)
5736 3615 : 223 : winobj->localmem =
3616 : 223 : MemoryContextAllocZero(winobj->winstate->partcontext, sz);
6147 3617 : 165965 : 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 : 378959 : WinGetCurrentPosition(WindowObject winobj)
3627 : : {
3628 [ + - - + ]: 378959 : Assert(WindowObjectIsValid(winobj));
3629 : 378959 : 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 : 156 : WinGetPartitionRowCount(WindowObject winobj)
3642 : : {
3643 [ + - - + ]: 156 : Assert(WindowObjectIsValid(winobj));
3644 : 156 : spool_tuples(winobj->winstate, -1);
3645 : 156 : 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 : 437625 : WinSetMarkPosition(WindowObject winobj, int64 markpos)
3660 : : {
3661 : : WindowAggState *winstate;
3662 : :
3663 [ + - - + ]: 437625 : Assert(WindowObjectIsValid(winobj));
3664 : 437625 : winstate = winobj->winstate;
3665 : :
3666 [ - + ]: 437625 : if (markpos < winobj->markpos)
6147 tgl@sss.pgh.pa.us 3667 [ # # ]:UBC 0 : elog(ERROR, "cannot move WindowObject's mark position backward");
6147 tgl@sss.pgh.pa.us 3668 :CBC 437625 : tuplestore_select_read_pointer(winstate->buffer, winobj->markptr);
4215 3669 [ + + ]: 437625 : if (markpos > winobj->markpos)
3670 : : {
3671 : 434466 : tuplestore_skiptuples(winstate->buffer,
3672 : 434466 : markpos - winobj->markpos,
3673 : : true);
3674 : 434466 : winobj->markpos = markpos;
3675 : : }
6147 3676 : 437625 : tuplestore_select_read_pointer(winstate->buffer, winobj->readptr);
4215 3677 [ + + ]: 437625 : if (markpos > winobj->seekpos)
3678 : : {
3679 : 231232 : tuplestore_skiptuples(winstate->buffer,
3680 : 231232 : markpos - winobj->seekpos,
3681 : : true);
3682 : 231232 : winobj->seekpos = markpos;
3683 : : }
6147 3684 : 437625 : }
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 : 82782 : WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
3695 : : {
3696 : : WindowAggState *winstate;
3697 : : WindowAgg *node;
3698 : : TupleTableSlot *slot1;
3699 : : TupleTableSlot *slot2;
3700 : : bool res;
3701 : :
3702 [ + - - + ]: 82782 : Assert(WindowObjectIsValid(winobj));
3703 : 82782 : winstate = winobj->winstate;
3704 : 82782 : node = (WindowAgg *) winstate->ss.ps.plan;
3705 : :
3706 : : /* If no ORDER BY, all rows are peers; don't bother to fetch them */
3707 [ + + ]: 82782 : if (node->ordNumCols == 0)
6147 tgl@sss.pgh.pa.us 3708 :GBC 135 : 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 : : */
6147 tgl@sss.pgh.pa.us 3714 :CBC 82647 : slot1 = winstate->temp_slot_1;
3715 : 82647 : slot2 = winstate->temp_slot_2;
3716 : :
3717 [ - + ]: 82647 : if (!window_gettupleslot(winobj, pos1, slot1))
6147 tgl@sss.pgh.pa.us 3718 [ # # ]:UBC 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3719 : : pos1);
6147 tgl@sss.pgh.pa.us 3720 [ - + ]:CBC 82647 : if (!window_gettupleslot(winobj, pos2, slot2))
6147 tgl@sss.pgh.pa.us 3721 [ # # ]:UBC 0 : elog(ERROR, "specified position is out of window: " INT64_FORMAT,
3722 : : pos2);
3723 : :
6147 tgl@sss.pgh.pa.us 3724 :CBC 82647 : res = are_peers(winstate, slot1, slot2);
3725 : :
3726 : 82647 : ExecClearTuple(slot1);
3727 : 82647 : ExecClearTuple(slot2);
3728 : :
3729 : 82647 : return res;
3730 : : }
3731 : :
3732 : : /*
3733 : : * WinGetFuncArgInPartition
3734 : : * Evaluate a window function's argument expression on a specified
3735 : : * row of the partition. The row is identified in lseek(2) style,
3736 : : * i.e. relative to the current, first, or last row.
3737 : : *
3738 : : * argno: argument number to evaluate (counted from 0)
3739 : : * relpos: signed rowcount offset from the seek position
3740 : : * seektype: WINDOW_SEEK_CURRENT, WINDOW_SEEK_HEAD, or WINDOW_SEEK_TAIL
3741 : : * set_mark: If the row is found and set_mark is true, the mark is moved to
3742 : : * the row as a side-effect.
3743 : : * isnull: output argument, receives isnull status of result
3744 : : * isout: output argument, set to indicate whether target row position
3745 : : * is out of partition (can pass NULL if caller doesn't care about this)
3746 : : *
3747 : : * Specifying a nonexistent row is not an error, it just causes a null result
3748 : : * (plus setting *isout true, if isout isn't NULL).
3749 : : */
3750 : : Datum
3751 : 118530 : WinGetFuncArgInPartition(WindowObject winobj, int argno,
3752 : : int relpos, int seektype, bool set_mark,
3753 : : bool *isnull, bool *isout)
3754 : : {
3755 : : WindowAggState *winstate;
3756 : : int64 abs_pos;
3757 : : int64 mark_pos;
3758 : : Datum datum;
3759 : : bool null_treatment;
3760 : : int notnull_offset;
3761 : : int notnull_relpos;
3762 : : int forward;
3763 : : bool myisout;
3764 : :
3765 [ + - - + ]: 118530 : Assert(WindowObjectIsValid(winobj));
6144 3766 : 118530 : winstate = winobj->winstate;
3767 : :
18 tgl@sss.pgh.pa.us 3768 [ + + + - ]:GNC 118530 : null_treatment = (winobj->ignore_nulls == IGNORE_NULLS && relpos != 0);
3769 : :
6147 tgl@sss.pgh.pa.us 3770 [ + - - - ]:CBC 118530 : switch (seektype)
3771 : : {
3772 : 118530 : case WINDOW_SEEK_CURRENT:
24 ishii@postgresql.org 3773 [ + + ]:GNC 118530 : if (null_treatment)
3774 : 240 : abs_pos = winstate->currentpos;
3775 : : else
3776 : 118290 : abs_pos = winstate->currentpos + relpos;
6147 tgl@sss.pgh.pa.us 3777 :CBC 118530 : break;
6147 tgl@sss.pgh.pa.us 3778 :UBC 0 : case WINDOW_SEEK_HEAD:
24 ishii@postgresql.org 3779 [ # # ]:UNC 0 : if (null_treatment)
3780 : 0 : abs_pos = 0;
3781 : : else
3782 : 0 : abs_pos = relpos;
6147 tgl@sss.pgh.pa.us 3783 :UBC 0 : break;
3784 : 0 : case WINDOW_SEEK_TAIL:
6144 3785 : 0 : spool_tuples(winstate, -1);
3786 : 0 : abs_pos = winstate->spooled_rows - 1 + relpos;
6147 3787 : 0 : break;
3788 : 0 : default:
3789 [ # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
3790 : : abs_pos = 0; /* keep compiler quiet */
3791 : : break;
3792 : : }
3793 : :
3794 : : /* Easy case if IGNORE NULLS is not specified */
18 tgl@sss.pgh.pa.us 3795 [ + + ]:GNC 118530 : if (!null_treatment)
3796 : : {
3797 : : /* get tuple and evaluate in partition */
24 ishii@postgresql.org 3798 : 118290 : datum = gettuple_eval_partition(winobj, argno,
3799 : : abs_pos, isnull, &myisout);
19 3800 [ + + + + ]: 118290 : if (!myisout && set_mark)
2819 tgl@sss.pgh.pa.us 3801 : 118020 : WinSetMarkPosition(winobj, abs_pos);
19 ishii@postgresql.org 3802 [ + - ]:CBC 118290 : if (isout)
19 ishii@postgresql.org 3803 :GNC 118290 : *isout = myisout;
24 3804 : 118290 : return datum;
3805 : : }
3806 : :
3807 : : /* Prepare for loop */
18 tgl@sss.pgh.pa.us 3808 : 240 : notnull_offset = 0;
3809 : 240 : notnull_relpos = abs(relpos);
3810 [ + + ]: 240 : forward = relpos > 0 ? 1 : -1;
19 ishii@postgresql.org 3811 : 240 : myisout = false;
3812 : 240 : 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 : : */
5 3822 [ + - + + ]: 240 : if (seektype == WINDOW_SEEK_CURRENT && relpos > 0)
3823 : 120 : 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 : 120 : mark_pos = 0;
3832 : : }
3833 : :
3834 : : /*
3835 : : * Get the next nonnull value in the partition, moving forward or backward
3836 : : * until we find a value or reach the partition's end. We cache the
3837 : : * nullness status because we may repeat this process many times.
3838 : : */
3839 : : do
3840 : : {
3841 : : int nn_info; /* NOT NULL status */
3842 : :
24 3843 : 351 : abs_pos += forward;
18 tgl@sss.pgh.pa.us 3844 [ + + ]: 351 : if (abs_pos < 0) /* clearly out of partition */
24 ishii@postgresql.org 3845 : 39 : break;
3846 : :
3847 : : /* check NOT NULL cached info */
5 3848 : 312 : nn_info = get_notnull_info(winobj, abs_pos, argno);
19 3849 [ + + ]: 312 : if (nn_info == NN_NOTNULL) /* this row is known to be NOT NULL */
3850 : 45 : notnull_offset++;
3851 [ + + ]: 267 : else if (nn_info == NN_NULL) /* this row is known to be NULL */
3852 : 27 : 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 : : */
8 3862 : 240 : (void) gettuple_eval_partition(winobj, argno,
3863 : : abs_pos, isnull, &myisout);
19 3864 [ + + ]: 240 : if (myisout) /* out of partition? */
24 3865 : 30 : break;
19 3866 [ + + ]: 210 : if (!*isnull)
3867 : 126 : notnull_offset++;
3868 : : /* record the row status */
5 3869 : 210 : put_notnull_info(winobj, abs_pos, argno, *isnull);
3870 : : }
24 3871 [ + + ]: 282 : } while (notnull_offset < notnull_relpos);
3872 : :
3873 : : /* get tuple and evaluate func arg in partition */
19 3874 : 240 : datum = gettuple_eval_partition(winobj, argno,
3875 : : abs_pos, isnull, &myisout);
3876 [ + + + - ]: 240 : if (!myisout && set_mark)
5 3877 : 171 : WinSetMarkPosition(winobj, mark_pos);
19 3878 [ + - ]: 240 : if (isout)
3879 : 240 : *isout = myisout;
3880 : :
24 3881 : 240 : return datum;
3882 : : }
3883 : :
3884 : : /*
3885 : : * WinGetFuncArgInFrame
3886 : : * Evaluate a window function's argument expression on a specified
3887 : : * row of the window frame. The row is identified in lseek(2) style,
3888 : : * i.e. relative to the first or last row of the frame. (We do not
3889 : : * support WINDOW_SEEK_CURRENT here, because it's not very clear what
3890 : : * that should mean if the current row isn't part of the frame.)
3891 : : *
3892 : : * argno: argument number to evaluate (counted from 0)
3893 : : * relpos: signed rowcount offset from the seek position
3894 : : * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
3895 : : * set_mark: If the row is found/in frame and set_mark is true, the mark is
3896 : : * moved to the row as a side-effect.
3897 : : * isnull: output argument, receives isnull status of result
3898 : : * isout: output argument, set to indicate whether target row position
3899 : : * is out of frame (can pass NULL if caller doesn't care about this)
3900 : : *
3901 : : * Specifying a nonexistent or not-in-frame row is not an error, it just
3902 : : * causes a null result (plus setting *isout true, if isout isn't NULL).
3903 : : *
3904 : : * Note that some exclusion-clause options lead to situations where the
3905 : : * rows that are in-frame are not consecutive in the partition. But we
3906 : : * count only in-frame rows when measuring relpos.
3907 : : *
3908 : : * The set_mark flag is interpreted as meaning that the caller will specify
3909 : : * a constant (or, perhaps, monotonically increasing) relpos in successive
3910 : : * calls, so that *if there is no exclusion clause* there will be no need
3911 : : * to fetch a row before the previously fetched row. But we do not expect
3912 : : * the caller to know how to account for exclusion clauses. Therefore,
3913 : : * if there is an exclusion clause we take responsibility for adjusting the
3914 : : * mark request to something that will be safe given the above assumption
3915 : : * about relpos.
3916 : : */
3917 : : Datum
6147 tgl@sss.pgh.pa.us 3918 :CBC 4974 : WinGetFuncArgInFrame(WindowObject winobj, int argno,
3919 : : int relpos, int seektype, bool set_mark,
3920 : : bool *isnull, bool *isout)
3921 : : {
3922 : : WindowAggState *winstate;
3923 : : ExprContext *econtext;
3924 : : TupleTableSlot *slot;
3925 : : int64 abs_pos;
3926 : : int64 mark_pos;
3927 : :
3928 [ + - - + ]: 4974 : Assert(WindowObjectIsValid(winobj));
6144 3929 : 4974 : winstate = winobj->winstate;
3930 : 4974 : econtext = winstate->ss.ps.ps_ExprContext;
3931 : 4974 : slot = winstate->temp_slot_1;
3932 : :
24 ishii@postgresql.org 3933 [ + + ]:GNC 4974 : if (winobj->ignore_nulls == IGNORE_NULLS)
3934 : 480 : return ignorenulls_getfuncarginframe(winobj, argno, relpos, seektype,
3935 : : set_mark, isnull, isout);
3936 : :
6147 tgl@sss.pgh.pa.us 3937 [ - + + - ]:CBC 4494 : switch (seektype)
3938 : : {
6147 tgl@sss.pgh.pa.us 3939 :UBC 0 : case WINDOW_SEEK_CURRENT:
2819 3940 [ # # ]: 0 : elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
3941 : : abs_pos = mark_pos = 0; /* keep compiler quiet */
3942 : : break;
6147 tgl@sss.pgh.pa.us 3943 :CBC 2223 : case WINDOW_SEEK_HEAD:
3944 : : /* rejecting relpos < 0 is easy and simplifies code below */
2819 3945 [ - + ]: 2223 : if (relpos < 0)
2819 tgl@sss.pgh.pa.us 3946 :UBC 0 : goto out_of_frame;
2819 tgl@sss.pgh.pa.us 3947 :CBC 2223 : update_frameheadpos(winstate);
5736 3948 : 2202 : abs_pos = winstate->frameheadpos + relpos;
2819 3949 : 2202 : 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 [ + + + + : 2202 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
- ]
3964 : : {
3965 : 1872 : case 0:
3966 : : /* no adjustment needed */
3967 : 1872 : break;
3968 : 120 : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
3969 [ + + ]: 120 : if (abs_pos >= winstate->currentpos &&
3970 [ + + ]: 93 : winstate->currentpos >= winstate->frameheadpos)
3971 : 33 : abs_pos++;
3972 : 120 : break;
3973 : 60 : case FRAMEOPTION_EXCLUDE_GROUP:
3974 : 60 : update_grouptailpos(winstate);
3975 [ + + ]: 60 : if (abs_pos >= winstate->groupheadpos &&
3976 [ + - ]: 36 : winstate->grouptailpos > winstate->frameheadpos)
3977 : : {
3978 : 36 : int64 overlapstart = Max(winstate->groupheadpos,
3979 : : winstate->frameheadpos);
3980 : :
3981 : 36 : abs_pos += winstate->grouptailpos - overlapstart;
3982 : : }
3983 : 60 : break;
3984 : 150 : case FRAMEOPTION_EXCLUDE_TIES:
3985 : 150 : update_grouptailpos(winstate);
3986 [ + + ]: 150 : if (abs_pos >= winstate->groupheadpos &&
3987 [ + + ]: 102 : winstate->grouptailpos > winstate->frameheadpos)
3988 : : {
3989 : 42 : int64 overlapstart = Max(winstate->groupheadpos,
3990 : : winstate->frameheadpos);
3991 : :
3992 [ + - ]: 42 : if (abs_pos == overlapstart)
3993 : 42 : abs_pos = winstate->currentpos;
3994 : : else
2819 tgl@sss.pgh.pa.us 3995 :UBC 0 : abs_pos += winstate->grouptailpos - overlapstart - 1;
3996 : : }
2819 tgl@sss.pgh.pa.us 3997 :CBC 150 : break;
2819 tgl@sss.pgh.pa.us 3998 :UBC 0 : default:
3999 [ # # ]: 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4000 : : winstate->frameOptions);
4001 : : break;
4002 : : }
6147 tgl@sss.pgh.pa.us 4003 :CBC 2202 : break;
4004 : 2271 : case WINDOW_SEEK_TAIL:
4005 : : /* rejecting relpos > 0 is easy and simplifies code below */
2819 4006 [ - + ]: 2271 : if (relpos > 0)
2819 tgl@sss.pgh.pa.us 4007 :UBC 0 : goto out_of_frame;
2819 tgl@sss.pgh.pa.us 4008 :CBC 2271 : update_frametailpos(winstate);
4009 : 2268 : 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 [ + + + + : 2268 : switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)
- ]
4021 : : {
4022 : 2028 : case 0:
4023 : : /* no adjustment needed */
4024 : 2028 : mark_pos = abs_pos;
4025 : 2028 : break;
4026 : 60 : case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
4027 [ + + ]: 60 : if (abs_pos <= winstate->currentpos &&
4028 [ + - ]: 6 : winstate->currentpos < winstate->frametailpos)
4029 : 6 : abs_pos--;
4030 : 60 : update_frameheadpos(winstate);
4031 [ + + ]: 60 : if (abs_pos < winstate->frameheadpos)
4032 : 3 : goto out_of_frame;
4033 : 57 : mark_pos = winstate->frameheadpos;
4034 : 57 : break;
4035 : 120 : case FRAMEOPTION_EXCLUDE_GROUP:
4036 : 120 : update_grouptailpos(winstate);
4037 [ + + ]: 120 : if (abs_pos < winstate->grouptailpos &&
4038 [ + - ]: 27 : winstate->groupheadpos < winstate->frametailpos)
4039 : : {
4040 : 27 : int64 overlapend = Min(winstate->grouptailpos,
4041 : : winstate->frametailpos);
4042 : :
4043 : 27 : abs_pos -= overlapend - winstate->groupheadpos;
4044 : : }
4045 : 120 : update_frameheadpos(winstate);
4046 [ + + ]: 120 : if (abs_pos < winstate->frameheadpos)
4047 : 27 : goto out_of_frame;
4048 : 93 : mark_pos = winstate->frameheadpos;
4049 : 93 : break;
4050 : 60 : case FRAMEOPTION_EXCLUDE_TIES:
4051 : 60 : update_grouptailpos(winstate);
4052 [ + + ]: 60 : if (abs_pos < winstate->grouptailpos &&
4053 [ + - ]: 18 : winstate->groupheadpos < winstate->frametailpos)
4054 : : {
4055 : 18 : int64 overlapend = Min(winstate->grouptailpos,
4056 : : winstate->frametailpos);
4057 : :
4058 [ + - ]: 18 : if (abs_pos == overlapend - 1)
4059 : 18 : abs_pos = winstate->currentpos;
4060 : : else
2819 tgl@sss.pgh.pa.us 4061 :UBC 0 : abs_pos -= overlapend - 1 - winstate->groupheadpos;
4062 : : }
2819 tgl@sss.pgh.pa.us 4063 :CBC 60 : update_frameheadpos(winstate);
4064 [ - + ]: 60 : if (abs_pos < winstate->frameheadpos)
2819 tgl@sss.pgh.pa.us 4065 :UBC 0 : goto out_of_frame;
2819 tgl@sss.pgh.pa.us 4066 :CBC 60 : mark_pos = winstate->frameheadpos;
4067 : 60 : break;
2819 tgl@sss.pgh.pa.us 4068 :UBC 0 : default:
4069 [ # # ]: 0 : elog(ERROR, "unrecognized frame option state: 0x%x",
4070 : : winstate->frameOptions);
4071 : : mark_pos = 0; /* keep compiler quiet */
4072 : : break;
4073 : : }
6147 tgl@sss.pgh.pa.us 4074 :CBC 2238 : break;
6147 tgl@sss.pgh.pa.us 4075 :UBC 0 : default:
4076 [ # # ]: 0 : elog(ERROR, "unrecognized window seek type: %d", seektype);
4077 : : abs_pos = mark_pos = 0; /* keep compiler quiet */
4078 : : break;
4079 : : }
4080 : :
2819 tgl@sss.pgh.pa.us 4081 [ + + ]:CBC 4440 : if (!window_gettupleslot(winobj, abs_pos, slot))
4082 : 198 : goto out_of_frame;
4083 : :
4084 : : /* The code above does not detect all out-of-frame cases, so check */
24 ishii@postgresql.org 4085 [ + + ]:GNC 4242 : if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0)
2819 tgl@sss.pgh.pa.us 4086 :CBC 150 : goto out_of_frame;
4087 : :
4088 [ - + ]: 4077 : if (isout)
2819 tgl@sss.pgh.pa.us 4089 :UBC 0 : *isout = false;
2819 tgl@sss.pgh.pa.us 4090 [ + + ]:CBC 4077 : if (set_mark)
4091 : 4056 : WinSetMarkPosition(winobj, mark_pos);
4092 : 4077 : econtext->ecxt_outertuple = slot;
4093 : 4077 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4094 : : econtext, isnull);
4095 : :
4096 : 378 : out_of_frame:
4097 [ - + ]: 378 : if (isout)
2819 tgl@sss.pgh.pa.us 4098 :UBC 0 : *isout = true;
2819 tgl@sss.pgh.pa.us 4099 :CBC 378 : *isnull = true;
4100 : 378 : return (Datum) 0;
4101 : : }
4102 : :
4103 : : /*
4104 : : * WinGetFuncArgCurrent
4105 : : * Evaluate a window function's argument expression on the current row.
4106 : : *
4107 : : * argno: argument number to evaluate (counted from 0)
4108 : : * isnull: output argument, receives isnull status of result
4109 : : *
4110 : : * Note: this isn't quite equivalent to WinGetFuncArgInPartition or
4111 : : * WinGetFuncArgInFrame targeting the current row, because it will succeed
4112 : : * even if the WindowObject's mark has been set beyond the current row.
4113 : : * This should generally be used for "ordinary" arguments of a window
4114 : : * function, such as the offset argument of lead() or lag().
4115 : : */
4116 : : Datum
6147 4117 : 1005 : WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
4118 : : {
4119 : : WindowAggState *winstate;
4120 : : ExprContext *econtext;
4121 : :
4122 [ + - - + ]: 1005 : Assert(WindowObjectIsValid(winobj));
4123 : 1005 : winstate = winobj->winstate;
4124 : :
4125 : 1005 : econtext = winstate->ss.ps.ps_ExprContext;
4126 : :
4127 : 1005 : econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
4128 : 1005 : return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
4129 : : econtext, isnull);
4130 : : }
|