mirror of
https://github.com/postgres/postgres.git
synced 2025-11-19 13:42:17 +03:00
Improve EXPLAIN's display of window functions.
Up to now we just punted on showing the window definitions used in a plan, with window function calls represented as "OVER (?)". To improve that, show the window definition implemented by each WindowAgg plan node, and reference their window names in OVER. For nameless window clauses generated by "OVER (...)", assign unique names w1, w2, etc. In passing, re-order the properties shown for a WindowAgg node so that the Run Condition (if any) appears after the Window property and before the Filter (if any). This seems more sensible since the Run Condition is associated with the Window and acts before the Filter. Thanks to David G. Johnston and Álvaro Herrera for design suggestions. Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: David Rowley <dgrowleyml@gmail.com> Discussion: https://postgr.es/m/144530.1741469955@sss.pgh.pa.us
This commit is contained in:
@@ -107,6 +107,11 @@ static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
|
||||
List *ancestors, ExplainState *es);
|
||||
static void show_sortorder_options(StringInfo buf, Node *sortexpr,
|
||||
Oid sortOperator, Oid collation, bool nullsFirst);
|
||||
static void show_window_def(WindowAggState *planstate,
|
||||
List *ancestors, ExplainState *es);
|
||||
static void show_window_keys(StringInfo buf, PlanState *planstate,
|
||||
int nkeys, AttrNumber *keycols,
|
||||
List *ancestors, ExplainState *es);
|
||||
static void show_storage_info(char *maxStorageType, int64 maxSpaceUsed,
|
||||
ExplainState *es);
|
||||
static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
|
||||
@@ -2333,12 +2338,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
|
||||
planstate, es);
|
||||
break;
|
||||
case T_WindowAgg:
|
||||
show_window_def(castNode(WindowAggState, planstate), ancestors, es);
|
||||
show_upper_qual(((WindowAgg *) plan)->runConditionOrig,
|
||||
"Run Condition", planstate, ancestors, es);
|
||||
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
||||
if (plan->qual)
|
||||
show_instrumentation_count("Rows Removed by Filter", 1,
|
||||
planstate, es);
|
||||
show_upper_qual(((WindowAgg *) plan)->runConditionOrig,
|
||||
"Run Condition", planstate, ancestors, es);
|
||||
show_windowagg_info(castNode(WindowAggState, planstate), es);
|
||||
break;
|
||||
case T_Group:
|
||||
@@ -3007,6 +3013,113 @@ show_sortorder_options(StringInfo buf, Node *sortexpr,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the window definition for a WindowAgg node.
|
||||
*/
|
||||
static void
|
||||
show_window_def(WindowAggState *planstate, List *ancestors, ExplainState *es)
|
||||
{
|
||||
WindowAgg *wagg = (WindowAgg *) planstate->ss.ps.plan;
|
||||
StringInfoData wbuf;
|
||||
bool needspace = false;
|
||||
|
||||
initStringInfo(&wbuf);
|
||||
appendStringInfo(&wbuf, "%s AS (", quote_identifier(wagg->winname));
|
||||
|
||||
/* The key columns refer to the tlist of the child plan */
|
||||
ancestors = lcons(wagg, ancestors);
|
||||
if (wagg->partNumCols > 0)
|
||||
{
|
||||
appendStringInfoString(&wbuf, "PARTITION BY ");
|
||||
show_window_keys(&wbuf, outerPlanState(planstate),
|
||||
wagg->partNumCols, wagg->partColIdx,
|
||||
ancestors, es);
|
||||
needspace = true;
|
||||
}
|
||||
if (wagg->ordNumCols > 0)
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(&wbuf, ' ');
|
||||
appendStringInfoString(&wbuf, "ORDER BY ");
|
||||
show_window_keys(&wbuf, outerPlanState(planstate),
|
||||
wagg->ordNumCols, wagg->ordColIdx,
|
||||
ancestors, es);
|
||||
needspace = true;
|
||||
}
|
||||
ancestors = list_delete_first(ancestors);
|
||||
if (wagg->frameOptions & FRAMEOPTION_NONDEFAULT)
|
||||
{
|
||||
List *context;
|
||||
bool useprefix;
|
||||
char *framestr;
|
||||
|
||||
/* Set up deparsing context for possible frame expressions */
|
||||
context = set_deparse_context_plan(es->deparse_cxt,
|
||||
(Plan *) wagg,
|
||||
ancestors);
|
||||
useprefix = (es->rtable_size > 1 || es->verbose);
|
||||
framestr = get_window_frame_options_for_explain(wagg->frameOptions,
|
||||
wagg->startOffset,
|
||||
wagg->endOffset,
|
||||
context,
|
||||
useprefix);
|
||||
if (needspace)
|
||||
appendStringInfoChar(&wbuf, ' ');
|
||||
appendStringInfoString(&wbuf, framestr);
|
||||
pfree(framestr);
|
||||
}
|
||||
appendStringInfoChar(&wbuf, ')');
|
||||
ExplainPropertyText("Window", wbuf.data, es);
|
||||
pfree(wbuf.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Append the keys of a window's PARTITION BY or ORDER BY clause to buf.
|
||||
* We can't use show_sort_group_keys for this because that's too opinionated
|
||||
* about how the result will be displayed.
|
||||
* Note that the "planstate" node should be the WindowAgg's child.
|
||||
*/
|
||||
static void
|
||||
show_window_keys(StringInfo buf, PlanState *planstate,
|
||||
int nkeys, AttrNumber *keycols,
|
||||
List *ancestors, ExplainState *es)
|
||||
{
|
||||
Plan *plan = planstate->plan;
|
||||
List *context;
|
||||
bool useprefix;
|
||||
|
||||
/* Set up deparsing context */
|
||||
context = set_deparse_context_plan(es->deparse_cxt,
|
||||
plan,
|
||||
ancestors);
|
||||
useprefix = (es->rtable_size > 1 || es->verbose);
|
||||
|
||||
for (int keyno = 0; keyno < nkeys; keyno++)
|
||||
{
|
||||
/* find key expression in tlist */
|
||||
AttrNumber keyresno = keycols[keyno];
|
||||
TargetEntry *target = get_tle_by_resno(plan->targetlist,
|
||||
keyresno);
|
||||
char *exprstr;
|
||||
|
||||
if (!target)
|
||||
elog(ERROR, "no tlist entry for key %d", keyresno);
|
||||
/* Deparse the expression, showing any top-level cast */
|
||||
exprstr = deparse_expression((Node *) target->expr, context,
|
||||
useprefix, true);
|
||||
if (keyno > 0)
|
||||
appendStringInfoString(buf, ", ");
|
||||
appendStringInfoString(buf, exprstr);
|
||||
pfree(exprstr);
|
||||
|
||||
/*
|
||||
* We don't attempt to provide sort order information because
|
||||
* WindowAgg carries equality operators not comparison operators;
|
||||
* compare show_agg_keys.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Show information on storage method and maximum memory/disk space used.
|
||||
*/
|
||||
|
||||
@@ -285,12 +285,9 @@ static Memoize *make_memoize(Plan *lefttree, Oid *hashoperators,
|
||||
Oid *collations, List *param_exprs,
|
||||
bool singlerow, bool binary_mode,
|
||||
uint32 est_entries, Bitmapset *keyparamids);
|
||||
static WindowAgg *make_windowagg(List *tlist, Index winref,
|
||||
static WindowAgg *make_windowagg(List *tlist, WindowClause *wc,
|
||||
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
|
||||
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
|
||||
int frameOptions, Node *startOffset, Node *endOffset,
|
||||
Oid startInRangeFunc, Oid endInRangeFunc,
|
||||
Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
|
||||
List *runCondition, List *qual, bool topWindow,
|
||||
Plan *lefttree);
|
||||
static Group *make_group(List *tlist, List *qual, int numGroupCols,
|
||||
@@ -2683,7 +2680,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
|
||||
|
||||
/* And finally we can make the WindowAgg node */
|
||||
plan = make_windowagg(tlist,
|
||||
wc->winref,
|
||||
wc,
|
||||
partNumCols,
|
||||
partColIdx,
|
||||
partOperators,
|
||||
@@ -2692,14 +2689,6 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
|
||||
ordColIdx,
|
||||
ordOperators,
|
||||
ordCollations,
|
||||
wc->frameOptions,
|
||||
wc->startOffset,
|
||||
wc->endOffset,
|
||||
wc->startInRangeFunc,
|
||||
wc->endInRangeFunc,
|
||||
wc->inRangeColl,
|
||||
wc->inRangeAsc,
|
||||
wc->inRangeNullsFirst,
|
||||
best_path->runCondition,
|
||||
best_path->qual,
|
||||
best_path->topwindow,
|
||||
@@ -6704,18 +6693,16 @@ make_agg(List *tlist, List *qual,
|
||||
}
|
||||
|
||||
static WindowAgg *
|
||||
make_windowagg(List *tlist, Index winref,
|
||||
make_windowagg(List *tlist, WindowClause *wc,
|
||||
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
|
||||
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
|
||||
int frameOptions, Node *startOffset, Node *endOffset,
|
||||
Oid startInRangeFunc, Oid endInRangeFunc,
|
||||
Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
|
||||
List *runCondition, List *qual, bool topWindow, Plan *lefttree)
|
||||
{
|
||||
WindowAgg *node = makeNode(WindowAgg);
|
||||
Plan *plan = &node->plan;
|
||||
|
||||
node->winref = winref;
|
||||
node->winname = wc->name;
|
||||
node->winref = wc->winref;
|
||||
node->partNumCols = partNumCols;
|
||||
node->partColIdx = partColIdx;
|
||||
node->partOperators = partOperators;
|
||||
@@ -6724,17 +6711,17 @@ make_windowagg(List *tlist, Index winref,
|
||||
node->ordColIdx = ordColIdx;
|
||||
node->ordOperators = ordOperators;
|
||||
node->ordCollations = ordCollations;
|
||||
node->frameOptions = frameOptions;
|
||||
node->startOffset = startOffset;
|
||||
node->endOffset = endOffset;
|
||||
node->frameOptions = wc->frameOptions;
|
||||
node->startOffset = wc->startOffset;
|
||||
node->endOffset = wc->endOffset;
|
||||
node->runCondition = runCondition;
|
||||
/* a duplicate of the above for EXPLAIN */
|
||||
node->runConditionOrig = runCondition;
|
||||
node->startInRangeFunc = startInRangeFunc;
|
||||
node->endInRangeFunc = endInRangeFunc;
|
||||
node->inRangeColl = inRangeColl;
|
||||
node->inRangeAsc = inRangeAsc;
|
||||
node->inRangeNullsFirst = inRangeNullsFirst;
|
||||
node->startInRangeFunc = wc->startInRangeFunc;
|
||||
node->endInRangeFunc = wc->endInRangeFunc;
|
||||
node->inRangeColl = wc->inRangeColl;
|
||||
node->inRangeAsc = wc->inRangeAsc;
|
||||
node->inRangeNullsFirst = wc->inRangeNullsFirst;
|
||||
node->topWindow = topWindow;
|
||||
|
||||
plan->targetlist = tlist;
|
||||
|
||||
@@ -214,6 +214,7 @@ static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
|
||||
static void optimize_window_clauses(PlannerInfo *root,
|
||||
WindowFuncLists *wflists);
|
||||
static List *select_active_windows(PlannerInfo *root, WindowFuncLists *wflists);
|
||||
static void name_active_windows(List *activeWindows);
|
||||
static PathTarget *make_window_input_target(PlannerInfo *root,
|
||||
PathTarget *final_target,
|
||||
List *activeWindows);
|
||||
@@ -1539,7 +1540,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction,
|
||||
*/
|
||||
optimize_window_clauses(root, wflists);
|
||||
|
||||
/* Extract the list of windows actually in use. */
|
||||
activeWindows = select_active_windows(root, wflists);
|
||||
|
||||
/* Make sure they all have names, for EXPLAIN's use. */
|
||||
name_active_windows(activeWindows);
|
||||
}
|
||||
else
|
||||
parse->hasWindowFuncs = false;
|
||||
@@ -5914,6 +5919,52 @@ select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* name_active_windows
|
||||
* Ensure all active windows have unique names.
|
||||
*
|
||||
* The parser will have checked that user-assigned window names are unique
|
||||
* within the Query. Here we assign made-up names to any unnamed
|
||||
* WindowClauses for the benefit of EXPLAIN. (We don't want to do this
|
||||
* at parse time, because it'd mess up decompilation of views.)
|
||||
*
|
||||
* activeWindows: result of select_active_windows
|
||||
*/
|
||||
static void
|
||||
name_active_windows(List *activeWindows)
|
||||
{
|
||||
int next_n = 1;
|
||||
char newname[16];
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, activeWindows)
|
||||
{
|
||||
WindowClause *wc = lfirst_node(WindowClause, lc);
|
||||
|
||||
/* Nothing to do if it has a name already. */
|
||||
if (wc->name)
|
||||
continue;
|
||||
|
||||
/* Select a name not currently present in the list. */
|
||||
for (;;)
|
||||
{
|
||||
ListCell *lc2;
|
||||
|
||||
snprintf(newname, sizeof(newname), "w%d", next_n++);
|
||||
foreach(lc2, activeWindows)
|
||||
{
|
||||
WindowClause *wc2 = lfirst_node(WindowClause, lc2);
|
||||
|
||||
if (wc2->name && strcmp(wc2->name, newname) == 0)
|
||||
break; /* matched */
|
||||
}
|
||||
if (lc2 == NULL)
|
||||
break; /* reached the end with no match */
|
||||
}
|
||||
wc->name = pstrdup(newname);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* common_prefix_cmp
|
||||
* QSort comparison function for WindowClauseSortData
|
||||
|
||||
@@ -441,6 +441,9 @@ static void get_rule_orderby(List *orderList, List *targetList,
|
||||
static void get_rule_windowclause(Query *query, deparse_context *context);
|
||||
static void get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
deparse_context *context);
|
||||
static void get_window_frame_options(int frameOptions,
|
||||
Node *startOffset, Node *endOffset,
|
||||
deparse_context *context);
|
||||
static char *get_variable(Var *var, int levelsup, bool istoplevel,
|
||||
deparse_context *context);
|
||||
static void get_special_variable(Node *node, deparse_context *context,
|
||||
@@ -6811,45 +6814,64 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(buf, ' ');
|
||||
if (wc->frameOptions & FRAMEOPTION_RANGE)
|
||||
get_window_frame_options(wc->frameOptions,
|
||||
wc->startOffset, wc->endOffset,
|
||||
context);
|
||||
}
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/*
|
||||
* Append the description of a window's framing options to context->buf
|
||||
*/
|
||||
static void
|
||||
get_window_frame_options(int frameOptions,
|
||||
Node *startOffset, Node *endOffset,
|
||||
deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
|
||||
if (frameOptions & FRAMEOPTION_NONDEFAULT)
|
||||
{
|
||||
if (frameOptions & FRAMEOPTION_RANGE)
|
||||
appendStringInfoString(buf, "RANGE ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_ROWS)
|
||||
else if (frameOptions & FRAMEOPTION_ROWS)
|
||||
appendStringInfoString(buf, "ROWS ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_GROUPS)
|
||||
else if (frameOptions & FRAMEOPTION_GROUPS)
|
||||
appendStringInfoString(buf, "GROUPS ");
|
||||
else
|
||||
Assert(false);
|
||||
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
|
||||
if (frameOptions & FRAMEOPTION_BETWEEN)
|
||||
appendStringInfoString(buf, "BETWEEN ");
|
||||
if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
|
||||
if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
|
||||
appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
|
||||
else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
|
||||
appendStringInfoString(buf, "CURRENT ROW ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_START_OFFSET)
|
||||
else if (frameOptions & FRAMEOPTION_START_OFFSET)
|
||||
{
|
||||
get_rule_expr(wc->startOffset, context, false);
|
||||
if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
|
||||
get_rule_expr(startOffset, context, false);
|
||||
if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
|
||||
appendStringInfoString(buf, " PRECEDING ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
|
||||
else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
|
||||
appendStringInfoString(buf, " FOLLOWING ");
|
||||
else
|
||||
Assert(false);
|
||||
}
|
||||
else
|
||||
Assert(false);
|
||||
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
|
||||
if (frameOptions & FRAMEOPTION_BETWEEN)
|
||||
{
|
||||
appendStringInfoString(buf, "AND ");
|
||||
if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
|
||||
if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
|
||||
appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
|
||||
else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
|
||||
appendStringInfoString(buf, "CURRENT ROW ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_END_OFFSET)
|
||||
else if (frameOptions & FRAMEOPTION_END_OFFSET)
|
||||
{
|
||||
get_rule_expr(wc->endOffset, context, false);
|
||||
if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
|
||||
get_rule_expr(endOffset, context, false);
|
||||
if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
|
||||
appendStringInfoString(buf, " PRECEDING ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
|
||||
else if (frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
|
||||
appendStringInfoString(buf, " FOLLOWING ");
|
||||
else
|
||||
Assert(false);
|
||||
@@ -6857,16 +6879,46 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
else
|
||||
Assert(false);
|
||||
}
|
||||
if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
|
||||
if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
|
||||
appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
|
||||
else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
|
||||
appendStringInfoString(buf, "EXCLUDE GROUP ");
|
||||
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES)
|
||||
else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
|
||||
appendStringInfoString(buf, "EXCLUDE TIES ");
|
||||
/* we will now have a trailing space; remove it */
|
||||
buf->len--;
|
||||
buf->data[--(buf->len)] = '\0';
|
||||
}
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the description of a window's framing options as a palloc'd string
|
||||
*/
|
||||
char *
|
||||
get_window_frame_options_for_explain(int frameOptions,
|
||||
Node *startOffset, Node *endOffset,
|
||||
List *dpcontext, bool forceprefix)
|
||||
{
|
||||
StringInfoData buf;
|
||||
deparse_context context;
|
||||
|
||||
initStringInfo(&buf);
|
||||
context.buf = &buf;
|
||||
context.namespaces = dpcontext;
|
||||
context.resultDesc = NULL;
|
||||
context.targetList = NIL;
|
||||
context.windowClause = NIL;
|
||||
context.varprefix = forceprefix;
|
||||
context.prettyFlags = 0;
|
||||
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
||||
context.indentLevel = 0;
|
||||
context.colNamesVisible = true;
|
||||
context.inGroupBy = false;
|
||||
context.varInOrderBy = false;
|
||||
context.appendparents = NULL;
|
||||
|
||||
get_window_frame_options(frameOptions, startOffset, endOffset, &context);
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
/* ----------
|
||||
@@ -11030,30 +11082,50 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
|
||||
|
||||
appendStringInfoString(buf, ") OVER ");
|
||||
|
||||
foreach(l, context->windowClause)
|
||||
if (context->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->winref == wfunc->winref)
|
||||
/* Query-decompilation case: search the windowClause list */
|
||||
foreach(l, context->windowClause)
|
||||
{
|
||||
if (wc->name)
|
||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||
else
|
||||
get_rule_windowspec(wc, context->targetList, context);
|
||||
break;
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->winref == wfunc->winref)
|
||||
{
|
||||
if (wc->name)
|
||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||
else
|
||||
get_rule_windowspec(wc, context->targetList, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (l == NULL)
|
||||
{
|
||||
if (context->windowClause)
|
||||
if (l == NULL)
|
||||
elog(ERROR, "could not find window clause for winref %u",
|
||||
wfunc->winref);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* In EXPLAIN, we don't have window context information available, so
|
||||
* we have to settle for this:
|
||||
* In EXPLAIN, search the namespace stack for a matching WindowAgg
|
||||
* node (probably it's always the first entry), and print winname.
|
||||
*/
|
||||
appendStringInfoString(buf, "(?)");
|
||||
foreach(l, context->namespaces)
|
||||
{
|
||||
deparse_namespace *dpns = (deparse_namespace *) lfirst(l);
|
||||
|
||||
if (dpns->plan && IsA(dpns->plan, WindowAgg))
|
||||
{
|
||||
WindowAgg *wagg = (WindowAgg *) dpns->plan;
|
||||
|
||||
if (wagg->winref == wfunc->winref)
|
||||
{
|
||||
appendStringInfoString(buf, quote_identifier(wagg->winname));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (l == NULL)
|
||||
elog(ERROR, "could not find window clause for winref %u",
|
||||
wfunc->winref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user