/* *AllthewindowfunctionAPIsarecalledwiththisobject,whichispassed *towindowfunctionsasfcinfo->context.
*/ typedefstruct WindowObjectData
{
NodeTag type;
WindowAggState *winstate; /* parent WindowAggState */
List *argstates; /* ExprState trees for fn's arguments */ void *localmem; /* WinGetPartitionLocalMemory's chunk */ int markptr; /* tuplestore mark pointer for this fn */ int readptr; /* tuplestore read pointer for this fn */
int64 markpos; /* row that markptr is positioned on */
int64 seekpos; /* row that readptr is positioned on */
} WindowObjectData;
/* *WehaveoneWindowStatePerFuncstructforeachwindowfunctionand *windowaggregatehandledbythisnode.
*/ typedefstruct WindowStatePerFuncData
{ /* Links to WindowFunc expr and state nodes this working state is for */
WindowFuncExprState *wfuncstate;
WindowFunc *wfunc;
int numArguments; /* number of arguments */
FmgrInfo flinfo; /* fmgr lookup data for window function */
Oid winCollation; /* collation derived for window function */
bool plain_agg; /* is it just a plain aggregate function? */ int aggno; /* if so, index of its WindowStatePerAggData */
WindowObject winobj; /* object used in window function API */
} WindowStatePerFuncData;
/* *Forplainaggregatewindowfunctions,wealsohaveoneofthese.
*/ typedefstruct WindowStatePerAggData
{ /* Oids of transition functions */
Oid transfn_oid;
Oid invtransfn_oid; /* may be InvalidOid */
Oid finalfn_oid; /* may be InvalidOid */
/* Skip anything FILTERed out */ if (filter)
{ bool isnull;
Datum res = ExecEvalExpr(filter, econtext, &isnull);
if (isnull || !DatumGetBool(res))
{
MemoryContextSwitchTo(oldContext); return;
}
}
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
{
ExprState *argstate = (ExprState *) lfirst(arg);
if (peraggstate->transfn.fn_strict)
{ /* *Forastricttransfn,nothinghappenswhenthere'saNULLinput;we *justkeepthepriortransValue.NotetransValueCountdoesn't *changeeither.
*/ for (i = 1; i <= numArguments; i++)
{ if (fcinfo->args[i].isnull)
{
MemoryContextSwitchTo(oldContext); return;
}
}
/* *Moving-aggregatetransitionfunctionsmustnotreturnnull,see *advance_windowaggregate_base().
*/ if (fcinfo->isnull && OidIsValid(peraggstate->invtransfn_oid))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("moving-aggregate transition function must not return null")));
/* Skip anything FILTERed out */ if (filter)
{ bool isnull;
Datum res = ExecEvalExpr(filter, econtext, &isnull);
if (isnull || !DatumGetBool(res))
{
MemoryContextSwitchTo(oldContext); returntrue;
}
}
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
{
ExprState *argstate = (ExprState *) lfirst(arg);
if (peraggstate->invtransfn.fn_strict)
{ /* *Forastrict(inv)transfn,nothinghappenswhenthere'saNULL *input;wejustkeepthepriortransValue.NotetransValueCount *doesn'tchangeeither.
*/ for (i = 1; i <= numArguments; i++)
{ if (fcinfo->args[i].isnull)
{
MemoryContextSwitchTo(oldContext); returntrue;
}
}
}
/* There should still be an added but not yet removed value */
Assert(peraggstate->transValueCount > 0);
/* *Inmoving-aggregatemode,thestatemustneverbeNULL,exceptpossibly *beforeanyrowshavebeenaggregated(whichissurelynotthecaseat *thispoint).ThisrestrictionallowsustointerpretaNULLresult *fromtheinversefunctionasmeaning"sorry,can'tdoaninverse *transitioninthiscase".Wealreadycheckedthisin *advance_windowaggregate,butjustforsafety,checkagain.
*/ if (peraggstate->transValueIsNull)
elog(ERROR, "aggregate transition value is NULL before inverse transition");
/* Fill any remaining argument positions with nulls */ for (i = 1; i < numFinalArgs; i++)
{
fcinfo->args[i].value = (Datum) 0;
fcinfo->args[i].isnull = true;
anynull = true;
}
if (fcinfo->flinfo->fn_strict && anynull)
{ /* don't call a strict function with NULL inputs */
*result = (Datum) 0;
*isnull = true;
} else
{
Datum res;
/* Set tuple context for evaluation of aggregate arguments */
winstate->tmpcontext->ecxt_outertuple = temp_slot;
/* *Performtheinversetransitionforeachaggregatefunctioninthe *window,unlessithasalreadybeenmarkedasneedingarestart.
*/ for (i = 0; i < numaggs; i++)
{ bool ok;
peraggstate = &winstate->peragg[i]; if (peraggstate->restart) continue;
wfuncno = peraggstate->wfuncno;
ok = advance_windowaggregate_base(winstate,
&winstate->perfunc[wfuncno],
peraggstate); if (!ok)
{ /* Inverse transition function has failed, must restart */
peraggstate->restart = true;
numaggs_restart++;
}
}
/* Reset per-input-tuple context after each tuple */
ResetExprContext(winstate->tmpcontext);
/* And advance the aggregated-row state */
winstate->aggregatedbase++;
ExecClearTuple(temp_slot);
}
/* *Ifwecreatedamarkpointerforaggregates,keepitpusheduptoframe *head,sothattuplestorecandiscardunnecessaryrows.
*/ if (agg_winobj->markptr >= 0)
WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
/* *Nowrestarttheaggregatesthatrequireit. * *Weassumethataggregatesusingthesharedcontextalwaysrestartif **any*aggregaterestarts,andwemaythuscleanuptheshared *aggcontextifthatisthecase.Privateaggcontextsareresetby *initialize_windowaggregate()iftheirowningaggregaterestarts.Ifwe *aren'trestartinganaggregate,weneedtofreeanypreviouslysaved *resultforit,elsewe'llleakmemory.
*/ if (numaggs_restart > 0)
MemoryContextReset(winstate->aggcontext); for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
/* Aggregates using the shared ctx must restart if *any* agg does */
Assert(peraggstate->aggcontext != winstate->aggcontext ||
numaggs_restart == 0 ||
peraggstate->restart);
/* *Advanceuntilwereacharownotinframe(orendofpartition). * *Notetheloopinvariant:agg_row_slotiseitheremptyorholdstherow *atpositionaggregatedupto.Weadvanceaggregateduptoafterprocessing *arow.
*/ for (;;)
{ int ret;
/* Fetch next row if we didn't already */ if (TupIsNull(agg_row_slot))
{ if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
agg_row_slot)) break; /* must be end of partition */
}
/* *Exitloopifnomorerowscanbeinframe.Skipaggregationif *currentrowisnotinframebuttheremightbemoreintheframe.
*/
ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot); if (ret < 0) break; if (ret == 0) goto next_tuple;
/* Set tuple context for evaluation of aggregate arguments */
winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
/* Accumulate row into the aggregates */ for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
/* Non-restarted aggs skip until aggregatedupto_nonrestarted */ if (!peraggstate->restart &&
winstate->aggregatedupto < aggregatedupto_nonrestarted) continue;
/* *Wedon'tpassanynormalargumentstoawindowfunction,butwedopass *itthenumberofarguments,inordertopermitwindowfunction *implementationstosupportvaryingnumbersofarguments.Therealinfo *goesthroughtheWindowObject,whichispassedviafcinfo->context.
*/
InitFunctionCallInfoData(*fcinfo, &(perfuncstate->flinfo),
perfuncstate->numArguments,
perfuncstate->winCollation,
(Node *) perfuncstate->winobj, NULL); /* Just in case, make all the regular argument slots be null */ for (int argno = 0; argno < perfuncstate->numArguments; argno++)
fcinfo->args[argno].isnull = true; /* Window functions don't have a current aggregate context, either */
winstate->curaggcontext = NULL;
/* reset default REWIND capability bit for current ptr */
tuplestore_set_eflags(winstate->buffer, 0);
/* create read pointers for aggregates, if needed */ if (winstate->numaggs > 0)
{
WindowObject agg_winobj = winstate->agg_winobj; int readptr_flags = 0;
/* *Iftheframeheadispotentiallymovable,orwehaveanEXCLUSION *clause,wemightneedtorestartaggregation...
*/ if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
(frameOptions & FRAMEOPTION_EXCLUSION))
{ /* ... so create a mark pointer to track the frame head */
agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0); /* and the read pointer will need BACKWARD capability */
readptr_flags |= EXEC_FLAG_BACKWARD;
}
/* create mark and read pointers for each real window function */ for (int i = 0; i < numfuncs; i++)
{
WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
if (!perfuncstate->plain_agg)
{
WindowObject winobj = perfuncstate->winobj;
if (!TupIsNull(outerslot))
ExecCopySlot(winstate->first_part_slot, outerslot); else
{ /* outer plan is empty, so we have nothing to do */
winstate->partition_spooled = true;
winstate->more_partitions = false; return;
}
}
/* Create new tuplestore if not done already. */ if (unlikely(winstate->buffer == NULL))
prepare_tuplestore(winstate);
winstate->next_partition = false;
if (winstate->numaggs > 0)
{
WindowObject agg_winobj = winstate->agg_winobj;
/* reset mark and see positions for aggregate functions */
agg_winobj->markpos = -1;
agg_winobj->seekpos = -1;
/* Also reset the row counters for aggregates */
winstate->aggregatedbase = 0;
winstate->aggregatedupto = 0;
}
/* reset mark and seek positions for each real window function */ for (int i = 0; i < numfuncs; i++)
{
WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
if (!perfuncstate->plain_agg)
{
WindowObject winobj = perfuncstate->winobj;
/* Must be in query context to call outerplan */
oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
while (winstate->spooled_rows <= pos || pos == -1)
{
outerslot = ExecProcNode(outerPlan); if (TupIsNull(outerslot))
{ /* reached the end of the last partition */
winstate->partition_spooled = true;
winstate->more_partitions = false; break;
}
if (node->partNumCols > 0)
{
ExprContext *econtext = winstate->tmpcontext;
/* Check if this tuple still belongs to the current partition */ if (!ExecQualAndReset(winstate->partEqfunction, econtext))
{ /* *endofpartition;copythetupleforthenextcycle.
*/
ExecCopySlot(winstate->first_part_slot, outerslot);
winstate->partition_spooled = true;
winstate->more_partitions = true; break;
}
}
/* *Rememberthetupleunlesswe'rethetop-levelwindowandwe'rein *pass-throughmode.
*/ if (winstate->status != WINDOWAGG_PASSTHROUGH_STRICT)
{ /* Still in partition, so save it into the tuplestore */
tuplestore_puttupleslot(winstate->buffer, outerslot);
winstate->spooled_rows++;
}
}
/* *First,checkframestartingconditions.Wemightaswelldelegatethis *toupdate_frameheadposalways;itdoesn'taddanynotablecost.
*/
update_frameheadpos(winstate); if (pos < winstate->frameheadpos) return0;
/* *Okaysofar,nowcheckframeendingconditions.Here,weavoidcalling *update_frametailposinsimplecases,soasnottospooltuplesfurther *aheadthannecessary.
*/ if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
{ if (frameOptions & FRAMEOPTION_ROWS)
{ /* rows after current row are out of frame */ if (pos > winstate->currentpos) return -1;
} elseif (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{ /* following row that is not peer is out of frame */ if (pos > winstate->currentpos &&
!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot)) return -1;
} else
Assert(false);
} elseif (frameOptions & FRAMEOPTION_END_OFFSET)
{ if (frameOptions & FRAMEOPTION_ROWS)
{
int64 offset = DatumGetInt64(winstate->endOffsetValue);
int64 frameendpos = 0;
/* rows after current row + offset are out of frame */ if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
offset = -offset;
/* *Ifwehaveanoverflow,itmeanstheframeendisbeyondthe *rangeofint64.Sincecurrentpos>=0,thiscanonlybea *positiveoverflow.Wetreatthisasmeaningthattheframe *extendstoendofpartition.
*/ if (!pg_add_s64_overflow(winstate->currentpos, offset,
&frameendpos) &&
pos > frameendpos) return -1;
} elseif (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{ /* hard cases, so delegate to update_frametailpos */
update_frametailpos(winstate); if (pos >= winstate->frametailpos) return -1;
} else
Assert(false);
}
/* If no ORDER BY, all rows are peers with each other */ if (node->ordNumCols == 0) return0; /* Otherwise, check the group boundaries */ if (pos >= winstate->groupheadpos)
{
update_grouptailpos(winstate); if (pos < winstate->grouptailpos) return0;
}
}
if (winstate->framehead_valid) return; /* already known for current row */
/* We may be called in a short-lived context */
oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
{ /* In UNBOUNDED PRECEDING mode, frame head is always row 0 */
winstate->frameheadpos = 0;
winstate->framehead_valid = true;
} elseif (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
{ if (frameOptions & FRAMEOPTION_ROWS)
{ /* In ROWS mode, frame head is the same as current */
winstate->frameheadpos = winstate->currentpos;
winstate->framehead_valid = true;
} elseif (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{ /* If no ORDER BY, all rows are peers with each other */ if (node->ordNumCols == 0)
{
winstate->frameheadpos = 0;
winstate->framehead_valid = true;
MemoryContextSwitchTo(oldcontext); return;
}
/* *InRANGEorGROUPSSTART_CURRENT_ROWmode,frameheadisthe *firstrowthatisapeerofcurrentrow.Wekeepacopyofthe *last-knownframeheadrowinframehead_slot,andadvanceas *necessary.Notethatifwereachendofpartition,wewill *leaveframeheadpos=end+1andframehead_slotempty.
*/
tuplestore_select_read_pointer(winstate->buffer,
winstate->framehead_ptr); if (winstate->frameheadpos == 0 &&
TupIsNull(winstate->framehead_slot))
{ /* fetch first row into framehead_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->framehead_slot))
{ if (are_peers(winstate, winstate->framehead_slot,
winstate->ss.ss_ScanTupleSlot)) break; /* this row is the correct frame head */ /* Note we advance frameheadpos even if the fetch fails */
winstate->frameheadpos++;
spool_tuples(winstate, winstate->frameheadpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot)) break; /* end of partition */
}
winstate->framehead_valid = true;
} else
Assert(false);
} elseif (frameOptions & FRAMEOPTION_START_OFFSET)
{ if (frameOptions & FRAMEOPTION_ROWS)
{ /* In ROWS mode, bound is physically n before/after current */
int64 offset = DatumGetInt64(winstate->startOffsetValue);
if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
offset = -offset;
/* frame head can't go before first row */ if (winstate->frameheadpos < 0)
winstate->frameheadpos = 0; elseif (winstate->frameheadpos > winstate->currentpos + 1)
{ /* make sure frameheadpos is not past end of partition */
spool_tuples(winstate, winstate->frameheadpos - 1); if (winstate->frameheadpos > winstate->spooled_rows)
winstate->frameheadpos = winstate->spooled_rows;
}
winstate->framehead_valid = true;
} elseif (frameOptions & FRAMEOPTION_RANGE)
{ /* *InRANGESTART_OFFSETmode,frameheadisthefirstrowthat *satisfiesthein_rangeconstraintrelativetothecurrentrow. *Wekeepacopyofthelast-knownframeheadrowin *framehead_slot,andadvanceasnecessary.Notethatifwe *reachendofpartition,wewillleaveframeheadpos=end+1and *framehead_slotempty.
*/ int sortCol = node->ordColIdx[0]; bool sub,
less;
/* We must have an ordering column */
Assert(node->ordNumCols == 1);
/* Precompute flags for in_range checks */ if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
sub = true; /* subtract startOffset from current row */ else
sub = false; /* add it */
less = false; /* normally, we want frame head >= sum */ /* If sort order is descending, flip both flags */ if (!winstate->inRangeAsc)
{
sub = !sub;
less = true;
}
tuplestore_select_read_pointer(winstate->buffer,
winstate->framehead_ptr); if (winstate->frameheadpos == 0 &&
TupIsNull(winstate->framehead_slot))
{ /* fetch first row into framehead_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->framehead_slot))
{
Datum headval,
currval; bool headisnull,
currisnull;
headval = slot_getattr(winstate->framehead_slot, sortCol,
&headisnull);
currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
&currisnull); if (headisnull || currisnull)
{ /* order of the rows depends only on nulls_first */ if (winstate->inRangeNullsFirst)
{ /* advance head if head is null and curr is not */ if (!headisnull || currisnull) break;
} else
{ /* advance head if head is not null and curr is null */ if (headisnull || !currisnull) break;
}
} else
{ if (DatumGetBool(FunctionCall5Coll(&winstate->startInRangeFunc,
winstate->inRangeColl,
headval,
currval,
winstate->startOffsetValue,
BoolGetDatum(sub),
BoolGetDatum(less)))) break; /* this row is the correct frame head */
} /* Note we advance frameheadpos even if the fetch fails */
winstate->frameheadpos++;
spool_tuples(winstate, winstate->frameheadpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot)) break; /* end of partition */
}
winstate->framehead_valid = true;
} elseif (frameOptions & FRAMEOPTION_GROUPS)
{ /* *InGROUPSSTART_OFFSETmode,frameheadisthefirstrowofthe *firstpeergroupwhosenumbersatisfiestheoffsetconstraint. *Wekeepacopyofthelast-knownframeheadrowin *framehead_slot,andadvanceasnecessary.Notethatifwe *reachendofpartition,wewillleaveframeheadpos=end+1and *framehead_slotempty.
*/
int64 offset = DatumGetInt64(winstate->startOffsetValue);
int64 minheadgroup = 0;
tuplestore_select_read_pointer(winstate->buffer,
winstate->framehead_ptr); if (winstate->frameheadpos == 0 &&
TupIsNull(winstate->framehead_slot))
{ /* fetch first row into framehead_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->framehead_slot))
{ if (winstate->frameheadgroup >= minheadgroup) break; /* this row is the correct frame head */
ExecCopySlot(winstate->temp_slot_2, winstate->framehead_slot); /* Note we advance frameheadpos even if the fetch fails */
winstate->frameheadpos++;
spool_tuples(winstate, winstate->frameheadpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->framehead_slot)) break; /* end of partition */ if (!are_peers(winstate, winstate->temp_slot_2,
winstate->framehead_slot))
winstate->frameheadgroup++;
}
ExecClearTuple(winstate->temp_slot_2);
winstate->framehead_valid = true;
} else
Assert(false);
} else
Assert(false);
if (winstate->frametail_valid) return; /* already known for current row */
/* We may be called in a short-lived context */
oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
{ /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
spool_tuples(winstate, -1);
winstate->frametailpos = winstate->spooled_rows;
winstate->frametail_valid = true;
} elseif (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
{ if (frameOptions & FRAMEOPTION_ROWS)
{ /* In ROWS mode, exactly the rows up to current are in frame */
winstate->frametailpos = winstate->currentpos + 1;
winstate->frametail_valid = true;
} elseif (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{ /* If no ORDER BY, all rows are peers with each other */ if (node->ordNumCols == 0)
{
spool_tuples(winstate, -1);
winstate->frametailpos = winstate->spooled_rows;
winstate->frametail_valid = true;
MemoryContextSwitchTo(oldcontext); return;
}
/* *InRANGEorGROUPSEND_CURRENT_ROWmode,frameendisthelast *rowthatisapeerofcurrentrow,frametailistherowafter *that(ifany).Wekeepacopyofthelast-knownframetailrow *inframetail_slot,andadvanceasnecessary.Notethatifwe *reachendofpartition,wewillleaveframetailpos=end+1and *frametail_slotempty.
*/
tuplestore_select_read_pointer(winstate->buffer,
winstate->frametail_ptr); if (winstate->frametailpos == 0 &&
TupIsNull(winstate->frametail_slot))
{ /* fetch first row into frametail_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->frametail_slot))
{ if (winstate->frametailpos > winstate->currentpos &&
!are_peers(winstate, winstate->frametail_slot,
winstate->ss.ss_ScanTupleSlot)) break; /* this row is the frame tail */ /* Note we advance frametailpos even if the fetch fails */
winstate->frametailpos++;
spool_tuples(winstate, winstate->frametailpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot)) break; /* end of partition */
}
winstate->frametail_valid = true;
} else
Assert(false);
} elseif (frameOptions & FRAMEOPTION_END_OFFSET)
{ if (frameOptions & FRAMEOPTION_ROWS)
{ /* In ROWS mode, bound is physically n before/after current */
int64 offset = DatumGetInt64(winstate->endOffsetValue);
if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
offset = -offset;
/* smallest allowable value of frametailpos is 0 */ if (winstate->frametailpos < 0)
winstate->frametailpos = 0; elseif (winstate->frametailpos > winstate->currentpos + 1)
{ /* make sure frametailpos is not past end of partition */
spool_tuples(winstate, winstate->frametailpos - 1); if (winstate->frametailpos > winstate->spooled_rows)
winstate->frametailpos = winstate->spooled_rows;
}
winstate->frametail_valid = true;
} elseif (frameOptions & FRAMEOPTION_RANGE)
{ /* *InRANGEEND_OFFSETmode,frameendisthelastrowthat *satisfiesthein_rangeconstraintrelativetothecurrentrow, *frametailistherowafterthat(ifany).Wekeepacopyof *thelast-knownframetailrowinframetail_slot,andadvanceas *necessary.Notethatifwereachendofpartition,wewill *leaveframetailpos=end+1andframetail_slotempty.
*/ int sortCol = node->ordColIdx[0]; bool sub,
less;
/* We must have an ordering column */
Assert(node->ordNumCols == 1);
/* Precompute flags for in_range checks */ if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
sub = true; /* subtract endOffset from current row */ else
sub = false; /* add it */
less = true; /* normally, we want frame tail <= sum */ /* If sort order is descending, flip both flags */ if (!winstate->inRangeAsc)
{
sub = !sub;
less = false;
}
tuplestore_select_read_pointer(winstate->buffer,
winstate->frametail_ptr); if (winstate->frametailpos == 0 &&
TupIsNull(winstate->frametail_slot))
{ /* fetch first row into frametail_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->frametail_slot))
{
Datum tailval,
currval; bool tailisnull,
currisnull;
tailval = slot_getattr(winstate->frametail_slot, sortCol,
&tailisnull);
currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, sortCol,
&currisnull); if (tailisnull || currisnull)
{ /* order of the rows depends only on nulls_first */ if (winstate->inRangeNullsFirst)
{ /* advance tail if tail is null or curr is not */ if (!tailisnull) break;
} else
{ /* advance tail if tail is not null or curr is null */ if (!currisnull) break;
}
} else
{ if (!DatumGetBool(FunctionCall5Coll(&winstate->endInRangeFunc,
winstate->inRangeColl,
tailval,
currval,
winstate->endOffsetValue,
BoolGetDatum(sub),
BoolGetDatum(less)))) break; /* this row is the correct frame tail */
} /* Note we advance frametailpos even if the fetch fails */
winstate->frametailpos++;
spool_tuples(winstate, winstate->frametailpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot)) break; /* end of partition */
}
winstate->frametail_valid = true;
} elseif (frameOptions & FRAMEOPTION_GROUPS)
{ /* *InGROUPSEND_OFFSETmode,frameendisthelastrowofthe *lastpeergroupwhosenumbersatisfiestheoffsetconstraint, *andframetailistherowafterthat(ifany).Wekeepacopy *ofthelast-knownframetailrowinframetail_slot,andadvance *asnecessary.Notethatifwereachendofpartition,wewill *leaveframetailpos=end+1andframetail_slotempty.
*/
int64 offset = DatumGetInt64(winstate->endOffsetValue);
int64 maxtailgroup = 0;
tuplestore_select_read_pointer(winstate->buffer,
winstate->frametail_ptr); if (winstate->frametailpos == 0 &&
TupIsNull(winstate->frametail_slot))
{ /* fetch first row into frametail_slot, if we didn't already */ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot))
elog(ERROR, "unexpected end of tuplestore");
}
while (!TupIsNull(winstate->frametail_slot))
{ if (winstate->frametailgroup > maxtailgroup) break; /* this row is the correct frame tail */
ExecCopySlot(winstate->temp_slot_2, winstate->frametail_slot); /* Note we advance frametailpos even if the fetch fails */
winstate->frametailpos++;
spool_tuples(winstate, winstate->frametailpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->frametail_slot)) break; /* end of partition */ if (!are_peers(winstate, winstate->temp_slot_2,
winstate->frametail_slot))
winstate->frametailgroup++;
}
ExecClearTuple(winstate->temp_slot_2);
winstate->frametail_valid = true;
} else
Assert(false);
} else
Assert(false);
if (winstate->grouptail_valid) return; /* already known for current row */
/* We may be called in a short-lived context */
oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
/* If no ORDER BY, all rows are peers with each other */ if (node->ordNumCols == 0)
{
spool_tuples(winstate, -1);
winstate->grouptailpos = winstate->spooled_rows;
winstate->grouptail_valid = true;
MemoryContextSwitchTo(oldcontext); return;
}
/* *Becausegrouptail_validisresetonlywhencurrentrowadvancesintoa *newpeergroup,wealwaysreachhereknowingthatgrouptailposneedsto *beadvancedbyatleastonerow.Hence,unliketheotherwisesimilar *caseforframetailtracking,wedonotneedpersistentstorageofthe *grouptailrow.
*/
Assert(winstate->grouptailpos <= winstate->currentpos);
tuplestore_select_read_pointer(winstate->buffer,
winstate->grouptail_ptr); for (;;)
{ /* Note we advance grouptailpos even if the fetch fails */
winstate->grouptailpos++;
spool_tuples(winstate, winstate->grouptailpos); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->temp_slot_2)) break; /* end of partition */ if (winstate->grouptailpos > winstate->currentpos &&
!are_peers(winstate, winstate->temp_slot_2,
winstate->ss.ss_ScanTupleSlot)) break; /* this row is the group tail */
}
ExecClearTuple(winstate->temp_slot_2);
winstate->grouptail_valid = true;
/* Ensure we've not been called before for this scan */
Assert(winstate->all_first);
econtext = winstate->ss.ps.ps_ExprContext;
if (frameOptions & FRAMEOPTION_START_OFFSET)
{
Assert(winstate->startOffset != NULL);
value = ExecEvalExprSwitchContext(winstate->startOffset,
econtext,
&isnull); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("frame starting offset must not be null"))); /* copy value into query-lifespan context */
get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
&len,
&byval);
winstate->startOffsetValue = datumCopy(value, byval, len); if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
{ /* value is known to be int8 */
int64 offset = DatumGetInt64(value);
if (offset < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("frame starting offset must not be negative")));
}
}
if (frameOptions & FRAMEOPTION_END_OFFSET)
{
Assert(winstate->endOffset != NULL);
value = ExecEvalExprSwitchContext(winstate->endOffset,
econtext,
&isnull); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("frame ending offset must not be null"))); /* copy value into query-lifespan context */
get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
&len,
&byval);
winstate->endOffsetValue = datumCopy(value, byval, len); if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
{ /* value is known to be int8 */
int64 offset = DatumGetInt64(value);
if (offset < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("frame ending offset must not be negative")));
}
}
winstate->all_first = false;
}
if (winstate->status == WINDOWAGG_DONE) return NULL;
/* *Computeframeoffsetvalues,ifany,duringfirstcall(oraftera *rescan).Theseareassumedtoholdconstantthroughoutthescan;if *usergivesusavolatileexpression,we'llonlyuseitsinitialvalue.
*/ if (unlikely(winstate->all_first))
calculate_frame_offsets(pstate);
/* We need to loop as the runCondition or qual may filter out tuples */ for (;;)
{ if (winstate->next_partition)
{ /* Initialize for first partition and set current row = 0 */
begin_partition(winstate); /* If there are no input rows, we'll detect that and exit below */
} else
{ /* Advance current row within partition */
winstate->currentpos++; /* This might mean that the frame moves, too */
winstate->framehead_valid = false;
winstate->frametail_valid = false; /* we don't need to invalidate grouptail here; see below */
}
/* Move to the next partition if we reached the end of this partition */ if (winstate->partition_spooled &&
winstate->currentpos >= winstate->spooled_rows)
{
release_partition(winstate);
if (winstate->more_partitions)
{
begin_partition(winstate);
Assert(winstate->spooled_rows > 0);
/* Come out of pass-through mode when changing partition */
winstate->status = WINDOWAGG_RUN;
} else
{ /* No further partitions? We're done */
winstate->status = WINDOWAGG_DONE; return NULL;
}
}
/* final output execution is in ps_ExprContext */
econtext = winstate->ss.ps.ps_ExprContext;
/* Clear the per-output-tuple context for current row */
ResetExprContext(econtext);
/* *Readthecurrentrowfromthetuplestore,andsavein *ScanTupleSlot.(Wecan'trelyontheouterplan'soutputslot *becausewemayhavetoreadbeyondthecurrentrow.Also,wehave *toactuallycopytherowoutofthetuplestore,sincewindow *functionevaluationmightcausethetuplestoretodumpitsstateto *disk.) * *InGROUPSmode,orwhentrackingagroup-orientedexclusionclause, *wemustalsodetectenteringanewpeergroupandupdateassociated *statewhenthathappens.Weusetemp_slot_2totemporarilyhold *thepreviousrowforthispurpose. * *Currentrowmustbeinthetuplestore,sincewespooleditabove.
*/
tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr); if ((winstate->frameOptions & (FRAMEOPTION_GROUPS |
FRAMEOPTION_EXCLUDE_GROUP |
FRAMEOPTION_EXCLUDE_TIES)) &&
winstate->currentpos > 0)
{
ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot); if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->ss.ss_ScanTupleSlot))
elog(ERROR, "unexpected end of tuplestore"); if (!are_peers(winstate, winstate->temp_slot_2,
winstate->ss.ss_ScanTupleSlot))
{
winstate->currentgroup++;
winstate->groupheadpos = winstate->currentpos;
winstate->grouptail_valid = false;
}
ExecClearTuple(winstate->temp_slot_2);
} else
{ if (!tuplestore_gettupleslot(winstate->buffer, true, true,
winstate->ss.ss_ScanTupleSlot))
elog(ERROR, "unexpected end of tuplestore");
}
/* don't evaluate the window functions when we're in pass-through mode */ if (winstate->status == WINDOWAGG_RUN)
{ /* *Evaluatetruewindowfunctions
*/
numfuncs = winstate->numfuncs; for (i = 0; i < numfuncs; i++)
{
WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]);
if (perfuncstate->plain_agg) continue;
eval_windowfunction(winstate, perfuncstate,
&(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]),
&(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno]));
}
/* *Evaluateaggregates
*/ if (winstate->numaggs > 0)
eval_windowaggregates(winstate);
}
/* *Ifwehavecreatedauxiliaryreadpointersfortheframeorgroup *boundaries,forcethemtobekeptup-to-date,becausewedon'tknow *whetherthewindowfunction(s)willdoanythingthatrequiresthat. *Failingtoadvancethepointerswouldresultinbeingunableto *trimdatafromthetuplestore,whichisbad.(Ifwecouldknowin *advancewhetherthewindowfunctionswilluseframeboundaryinfo, *wecouldskipcreatingthesepointersinthefirstplace...but *unfortunatelythewindowfunctionAPIdoesn'trequirethat.)
*/ if (winstate->framehead_ptr >= 0)
update_frameheadpos(winstate); if (winstate->frametail_ptr >= 0)
update_frametailpos(winstate); if (winstate->grouptail_ptr >= 0)
update_grouptailpos(winstate);
/* Set up data for comparing tuples */ if (node->partNumCols > 0)
winstate->partEqfunction =
execTuplesMatchPrepare(scanDesc,
node->partNumCols,
node->partColIdx,
node->partOperators,
node->partCollations,
&winstate->ss.ps);
if (wfunc->winref != node->winref) /* planner screwed up? */
elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u",
wfunc->winref, node->winref);
/* Look for a previous duplicate window function */ for (i = 0; i <= wfuncno; i++)
{ if (equal(wfunc, perfunc[i].wfunc) &&
!contain_volatile_functions((Node *) wfunc)) break;
} if (i <= wfuncno)
{ /* Found a match to an existing entry, so just mark it */
wfuncstate->wfuncno = i; continue;
}
/* Nope, so assign a new PerAgg record */
perfuncstate = &perfunc[++wfuncno];
/* Mark WindowFunc state node with assigned index in the result array */
wfuncstate->wfuncno = wfuncno;
/* Check permission to call window function */
aclresult = object_aclcheck(ProcedureRelationId, wfunc->winfnoid, GetUserId(),
ACL_EXECUTE); if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(wfunc->winfnoid));
InvokeFunctionExecuteHook(wfunc->winfnoid);
/* Fill in the perfuncstate data */
perfuncstate->wfuncstate = wfuncstate;
perfuncstate->wfunc = wfunc;
perfuncstate->numArguments = list_length(wfuncstate->args);
perfuncstate->winCollation = wfunc->inputcollid;
/* It's a real window function, so set up to call it. */
fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
econtext->ecxt_per_query_memory);
fmgr_info_set_expr((Node *) wfunc, &perfuncstate->flinfo);
}
}
/* Update numfuncs, numaggs to match number of unique functions found */
winstate->numfuncs = wfuncno + 1;
winstate->numaggs = aggno + 1;
/* Set up WindowObject for aggregates, if needed */ if (winstate->numaggs > 0)
{
WindowObject agg_winobj = makeNode(WindowObjectData);
agg_winobj->winstate = winstate;
agg_winobj->argstates = NIL;
agg_winobj->localmem = NULL; /* make sure markptr = -1 to invalidate. It may not get used */
agg_winobj->markptr = -1;
agg_winobj->readptr = -1;
winstate->agg_winobj = agg_winobj;
}
/* Set the status to running */
winstate->status = WINDOWAGG_RUN;
if (node->buffer != NULL)
{
tuplestore_end(node->buffer);
/* nullify so that release_partition skips the tuplestore_clear() */
node->buffer = NULL;
}
release_partition(node);
for (i = 0; i < node->numaggs; i++)
{ if (node->peragg[i].aggcontext != node->aggcontext)
MemoryContextDelete(node->peragg[i].aggcontext);
}
MemoryContextDelete(node->partcontext);
MemoryContextDelete(node->aggcontext);
/* release tuplestore et al */
release_partition(node);
/* release all temp tuples, but especially first_part_slot */
ExecClearTuple(node->ss.ss_ScanTupleSlot);
ExecClearTuple(node->first_part_slot);
ExecClearTuple(node->agg_row_slot);
ExecClearTuple(node->temp_slot_1);
ExecClearTuple(node->temp_slot_2); if (node->framehead_slot)
ExecClearTuple(node->framehead_slot); if (node->frametail_slot)
ExecClearTuple(node->frametail_slot);
if (OidIsValid(invtransfn_oid))
{
aclresult = object_aclcheck(ProcedureRelationId, invtransfn_oid, aggOwner,
ACL_EXECUTE); if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(invtransfn_oid));
InvokeFunctionExecuteHook(invtransfn_oid);
}
if (OidIsValid(finalfn_oid))
{
aclresult = object_aclcheck(ProcedureRelationId, finalfn_oid, aggOwner,
ACL_EXECUTE); if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(finalfn_oid));
InvokeFunctionExecuteHook(finalfn_oid);
}
}
/* *Iftheselectedfinalfnisn'tread-only,wecan'trunthisaggregateas *awindowfunction.Thisisauser-facingerror,sowetakeabitmore *carewiththeerrormessagethanelsewhereinthisfunction.
*/ if (finalmodify != AGGMODIFY_READ_ONLY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("aggregate function %s does not support use as a window function",
format_procedure(wfunc->winfnoid))));
/* Detect how many arguments to pass to the finalfn */ if (finalextra)
peraggstate->numFinalArgs = numArguments + 1; else
peraggstate->numFinalArgs = 1;
/* resolve actual type of transition state, if polymorphic */
aggtranstype = resolve_aggregate_transtype(wfunc->winfnoid,
aggtranstype,
inputTypes,
numArguments);
/* build expression trees using actual argument & result types */
build_aggregate_transfn_expr(inputTypes,
numArguments, 0, /* no ordered-set window functions yet */ false, /* no variadic window functions yet */
aggtranstype,
wfunc->inputcollid,
transfn_oid,
invtransfn_oid,
&transfnexpr,
&invtransfnexpr);
/* set up infrastructure for calling the transfn(s) and finalfn */
fmgr_info(transfn_oid, &peraggstate->transfn);
fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
if (OidIsValid(invtransfn_oid))
{
fmgr_info(invtransfn_oid, &peraggstate->invtransfn);
fmgr_info_set_expr((Node *) invtransfnexpr, &peraggstate->invtransfn);
}
/* get info about relevant datatypes */
get_typlenbyval(wfunc->wintype,
&peraggstate->resulttypeLen,
&peraggstate->resulttypeByVal);
get_typlenbyval(aggtranstype,
&peraggstate->transtypeLen,
&peraggstate->transtypeByVal);
if (!window_gettupleslot(winobj, pos1, slot1))
elog(ERROR, "specified position is out of window: " INT64_FORMAT,
pos1); if (!window_gettupleslot(winobj, pos2, slot2))
elog(ERROR, "specified position is out of window: " INT64_FORMAT,
pos2);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.152Bemerkung:
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.