mirror of
https://github.com/postgres/postgres.git
synced 2025-11-18 02:02:55 +03:00
If there are subqueries in the grouping expressions, each of these subqueries in the targetlist and HAVING clause is expanded into distinct SubPlan nodes. As a result, only one of these SubPlan nodes would be converted to reference to the grouping key column output by the Agg node; others would have to get evaluated afresh. This is not efficient, and with grouping sets this can cause wrong results issues in cases where they should go to NULL because they are from the wrong grouping set. Furthermore, during re-evaluation, these SubPlan nodes might use nulled column values from grouping sets, which is not correct. This issue is not limited to subqueries. For other types of expressions that are part of grouping items, if they are transformed into another form during preprocessing, they may fail to match lower target items. This can also lead to wrong results with grouping sets. To fix this issue, we introduce a new kind of RTE representing the output of the grouping step, with columns that are the Vars or expressions being grouped on. In the parser, we replace the grouping expressions in the targetlist and HAVING clause with Vars referencing this new RTE, so that the output of the parser directly expresses the semantic requirement that the grouping expressions be gotten from the grouping output rather than computed some other way. In the planner, we first preprocess all the columns of this new RTE and then replace any Vars in the targetlist and HAVING clause that reference this new RTE with the underlying grouping expressions, so that we will have only one instance of a SubPlan node for each subquery contained in the grouping expressions. Bump catversion because this changes the querytree produced by the parser. Thanks to Tom Lane for the idea to invent a new kind of RTE. Per reports from Geoff Winkless, Tobias Wendorff, Richard Guo from various threads. Author: Richard Guo Reviewed-by: Ashutosh Bapat, Sutou Kouhei Discussion: https://postgr.es/m/CAMbWs4_dp7e7oTwaiZeBX8+P1rXw4ThkZxh1QG81rhu9Z47VsQ@mail.gmail.com
511 lines
9.8 KiB
C
511 lines
9.8 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* print.c
|
|
* various print routines (used mostly for debugging)
|
|
*
|
|
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/nodes/print.c
|
|
*
|
|
* HISTORY
|
|
* AUTHOR DATE MAJOR EVENT
|
|
* Andrew Yu Oct 26, 1994 file creation
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/printtup.h"
|
|
#include "lib/stringinfo.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/pathnodes.h"
|
|
#include "nodes/print.h"
|
|
#include "parser/parsetree.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
|
|
/*
|
|
* print
|
|
* print contents of Node to stdout
|
|
*/
|
|
void
|
|
print(const void *obj)
|
|
{
|
|
char *s;
|
|
char *f;
|
|
|
|
s = nodeToStringWithLocations(obj);
|
|
f = format_node_dump(s);
|
|
pfree(s);
|
|
printf("%s\n", f);
|
|
fflush(stdout);
|
|
pfree(f);
|
|
}
|
|
|
|
/*
|
|
* pprint
|
|
* pretty-print contents of Node to stdout
|
|
*/
|
|
void
|
|
pprint(const void *obj)
|
|
{
|
|
char *s;
|
|
char *f;
|
|
|
|
s = nodeToStringWithLocations(obj);
|
|
f = pretty_format_node_dump(s);
|
|
pfree(s);
|
|
printf("%s\n", f);
|
|
fflush(stdout);
|
|
pfree(f);
|
|
}
|
|
|
|
/*
|
|
* elog_node_display
|
|
* send pretty-printed contents of Node to postmaster log
|
|
*/
|
|
void
|
|
elog_node_display(int lev, const char *title, const void *obj, bool pretty)
|
|
{
|
|
char *s;
|
|
char *f;
|
|
|
|
s = nodeToStringWithLocations(obj);
|
|
if (pretty)
|
|
f = pretty_format_node_dump(s);
|
|
else
|
|
f = format_node_dump(s);
|
|
pfree(s);
|
|
ereport(lev,
|
|
(errmsg_internal("%s:", title),
|
|
errdetail_internal("%s", f)));
|
|
pfree(f);
|
|
}
|
|
|
|
/*
|
|
* Format a nodeToString output for display on a terminal.
|
|
*
|
|
* The result is a palloc'd string.
|
|
*
|
|
* This version just tries to break at whitespace.
|
|
*/
|
|
char *
|
|
format_node_dump(const char *dump)
|
|
{
|
|
#define LINELEN 78
|
|
char line[LINELEN + 1];
|
|
StringInfoData str;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
|
|
initStringInfo(&str);
|
|
i = 0;
|
|
for (;;)
|
|
{
|
|
for (j = 0; j < LINELEN && dump[i] != '\0'; i++, j++)
|
|
line[j] = dump[i];
|
|
if (dump[i] == '\0')
|
|
break;
|
|
if (dump[i] == ' ')
|
|
{
|
|
/* ok to break at adjacent space */
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
for (k = j - 1; k > 0; k--)
|
|
if (line[k] == ' ')
|
|
break;
|
|
if (k > 0)
|
|
{
|
|
/* back up; will reprint all after space */
|
|
i -= (j - k - 1);
|
|
j = k;
|
|
}
|
|
}
|
|
line[j] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
if (j > 0)
|
|
{
|
|
line[j] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
return str.data;
|
|
#undef LINELEN
|
|
}
|
|
|
|
/*
|
|
* Format a nodeToString output for display on a terminal.
|
|
*
|
|
* The result is a palloc'd string.
|
|
*
|
|
* This version tries to indent intelligently.
|
|
*/
|
|
char *
|
|
pretty_format_node_dump(const char *dump)
|
|
{
|
|
#define INDENTSTOP 3
|
|
#define MAXINDENT 60
|
|
#define LINELEN 78
|
|
char line[LINELEN + 1];
|
|
StringInfoData str;
|
|
int indentLev;
|
|
int indentDist;
|
|
int i;
|
|
int j;
|
|
|
|
initStringInfo(&str);
|
|
indentLev = 0; /* logical indent level */
|
|
indentDist = 0; /* physical indent distance */
|
|
i = 0;
|
|
for (;;)
|
|
{
|
|
for (j = 0; j < indentDist; j++)
|
|
line[j] = ' ';
|
|
for (; j < LINELEN && dump[i] != '\0'; i++, j++)
|
|
{
|
|
line[j] = dump[i];
|
|
switch (line[j])
|
|
{
|
|
case '}':
|
|
if (j != indentDist)
|
|
{
|
|
/* print data before the } */
|
|
line[j] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
/* print the } at indentDist */
|
|
line[indentDist] = '}';
|
|
line[indentDist + 1] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
/* outdent */
|
|
if (indentLev > 0)
|
|
{
|
|
indentLev--;
|
|
indentDist = Min(indentLev * INDENTSTOP, MAXINDENT);
|
|
}
|
|
j = indentDist - 1;
|
|
/* j will equal indentDist on next loop iteration */
|
|
/* suppress whitespace just after } */
|
|
while (dump[i + 1] == ' ')
|
|
i++;
|
|
break;
|
|
case ')':
|
|
/* force line break after ), unless another ) follows */
|
|
if (dump[i + 1] != ')')
|
|
{
|
|
line[j + 1] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
j = indentDist - 1;
|
|
while (dump[i + 1] == ' ')
|
|
i++;
|
|
}
|
|
break;
|
|
case '{':
|
|
/* force line break before { */
|
|
if (j != indentDist)
|
|
{
|
|
line[j] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
/* indent */
|
|
indentLev++;
|
|
indentDist = Min(indentLev * INDENTSTOP, MAXINDENT);
|
|
for (j = 0; j < indentDist; j++)
|
|
line[j] = ' ';
|
|
line[j] = dump[i];
|
|
break;
|
|
case ':':
|
|
/* force line break before : */
|
|
if (j != indentDist)
|
|
{
|
|
line[j] = '\0';
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
j = indentDist;
|
|
line[j] = dump[i];
|
|
break;
|
|
}
|
|
}
|
|
line[j] = '\0';
|
|
if (dump[i] == '\0')
|
|
break;
|
|
appendStringInfo(&str, "%s\n", line);
|
|
}
|
|
if (j > 0)
|
|
appendStringInfo(&str, "%s\n", line);
|
|
return str.data;
|
|
#undef INDENTSTOP
|
|
#undef MAXINDENT
|
|
#undef LINELEN
|
|
}
|
|
|
|
/*
|
|
* print_rt
|
|
* print contents of range table
|
|
*/
|
|
void
|
|
print_rt(const List *rtable)
|
|
{
|
|
const ListCell *l;
|
|
int i = 1;
|
|
|
|
printf("resno\trefname \trelid\tinFromCl\n");
|
|
printf("-----\t---------\t-----\t--------\n");
|
|
foreach(l, rtable)
|
|
{
|
|
RangeTblEntry *rte = lfirst(l);
|
|
|
|
switch (rte->rtekind)
|
|
{
|
|
case RTE_RELATION:
|
|
printf("%d\t%s\t%u\t%c",
|
|
i, rte->eref->aliasname, rte->relid, rte->relkind);
|
|
break;
|
|
case RTE_SUBQUERY:
|
|
printf("%d\t%s\t[subquery]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_JOIN:
|
|
printf("%d\t%s\t[join]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_FUNCTION:
|
|
printf("%d\t%s\t[rangefunction]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_TABLEFUNC:
|
|
printf("%d\t%s\t[table function]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_VALUES:
|
|
printf("%d\t%s\t[values list]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_CTE:
|
|
printf("%d\t%s\t[cte]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_NAMEDTUPLESTORE:
|
|
printf("%d\t%s\t[tuplestore]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_RESULT:
|
|
printf("%d\t%s\t[result]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
case RTE_GROUP:
|
|
printf("%d\t%s\t[group]",
|
|
i, rte->eref->aliasname);
|
|
break;
|
|
default:
|
|
printf("%d\t%s\t[unknown rtekind]",
|
|
i, rte->eref->aliasname);
|
|
}
|
|
|
|
printf("\t%s\t%s\n",
|
|
(rte->inh ? "inh" : ""),
|
|
(rte->inFromCl ? "inFromCl" : ""));
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* print_expr
|
|
* print an expression
|
|
*/
|
|
void
|
|
print_expr(const Node *expr, const List *rtable)
|
|
{
|
|
if (expr == NULL)
|
|
{
|
|
printf("<>");
|
|
return;
|
|
}
|
|
|
|
if (IsA(expr, Var))
|
|
{
|
|
const Var *var = (const Var *) expr;
|
|
char *relname,
|
|
*attname;
|
|
|
|
switch (var->varno)
|
|
{
|
|
case INNER_VAR:
|
|
relname = "INNER";
|
|
attname = "?";
|
|
break;
|
|
case OUTER_VAR:
|
|
relname = "OUTER";
|
|
attname = "?";
|
|
break;
|
|
case INDEX_VAR:
|
|
relname = "INDEX";
|
|
attname = "?";
|
|
break;
|
|
default:
|
|
{
|
|
RangeTblEntry *rte;
|
|
|
|
Assert(var->varno > 0 &&
|
|
(int) var->varno <= list_length(rtable));
|
|
rte = rt_fetch(var->varno, rtable);
|
|
relname = rte->eref->aliasname;
|
|
attname = get_rte_attribute_name(rte, var->varattno);
|
|
}
|
|
break;
|
|
}
|
|
printf("%s.%s", relname, attname);
|
|
}
|
|
else if (IsA(expr, Const))
|
|
{
|
|
const Const *c = (const Const *) expr;
|
|
Oid typoutput;
|
|
bool typIsVarlena;
|
|
char *outputstr;
|
|
|
|
if (c->constisnull)
|
|
{
|
|
printf("NULL");
|
|
return;
|
|
}
|
|
|
|
getTypeOutputInfo(c->consttype,
|
|
&typoutput, &typIsVarlena);
|
|
|
|
outputstr = OidOutputFunctionCall(typoutput, c->constvalue);
|
|
printf("%s", outputstr);
|
|
pfree(outputstr);
|
|
}
|
|
else if (IsA(expr, OpExpr))
|
|
{
|
|
const OpExpr *e = (const OpExpr *) expr;
|
|
char *opname;
|
|
|
|
opname = get_opname(e->opno);
|
|
if (list_length(e->args) > 1)
|
|
{
|
|
print_expr(get_leftop((const Expr *) e), rtable);
|
|
printf(" %s ", ((opname != NULL) ? opname : "(invalid operator)"));
|
|
print_expr(get_rightop((const Expr *) e), rtable);
|
|
}
|
|
else
|
|
{
|
|
printf("%s ", ((opname != NULL) ? opname : "(invalid operator)"));
|
|
print_expr(get_leftop((const Expr *) e), rtable);
|
|
}
|
|
}
|
|
else if (IsA(expr, FuncExpr))
|
|
{
|
|
const FuncExpr *e = (const FuncExpr *) expr;
|
|
char *funcname;
|
|
ListCell *l;
|
|
|
|
funcname = get_func_name(e->funcid);
|
|
printf("%s(", ((funcname != NULL) ? funcname : "(invalid function)"));
|
|
foreach(l, e->args)
|
|
{
|
|
print_expr(lfirst(l), rtable);
|
|
if (lnext(e->args, l))
|
|
printf(",");
|
|
}
|
|
printf(")");
|
|
}
|
|
else
|
|
printf("unknown expr");
|
|
}
|
|
|
|
/*
|
|
* print_pathkeys -
|
|
* pathkeys list of PathKeys
|
|
*/
|
|
void
|
|
print_pathkeys(const List *pathkeys, const List *rtable)
|
|
{
|
|
const ListCell *i;
|
|
|
|
printf("(");
|
|
foreach(i, pathkeys)
|
|
{
|
|
PathKey *pathkey = (PathKey *) lfirst(i);
|
|
EquivalenceClass *eclass;
|
|
ListCell *k;
|
|
bool first = true;
|
|
|
|
eclass = pathkey->pk_eclass;
|
|
/* chase up, in case pathkey is non-canonical */
|
|
while (eclass->ec_merged)
|
|
eclass = eclass->ec_merged;
|
|
|
|
printf("(");
|
|
foreach(k, eclass->ec_members)
|
|
{
|
|
EquivalenceMember *mem = (EquivalenceMember *) lfirst(k);
|
|
|
|
if (first)
|
|
first = false;
|
|
else
|
|
printf(", ");
|
|
print_expr((Node *) mem->em_expr, rtable);
|
|
}
|
|
printf(")");
|
|
if (lnext(pathkeys, i))
|
|
printf(", ");
|
|
}
|
|
printf(")\n");
|
|
}
|
|
|
|
/*
|
|
* print_tl
|
|
* print targetlist in a more legible way.
|
|
*/
|
|
void
|
|
print_tl(const List *tlist, const List *rtable)
|
|
{
|
|
const ListCell *tl;
|
|
|
|
printf("(\n");
|
|
foreach(tl, tlist)
|
|
{
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tl);
|
|
|
|
printf("\t%d %s\t", tle->resno,
|
|
tle->resname ? tle->resname : "<null>");
|
|
if (tle->ressortgroupref != 0)
|
|
printf("(%u):\t", tle->ressortgroupref);
|
|
else
|
|
printf(" :\t");
|
|
print_expr((Node *) tle->expr, rtable);
|
|
printf("\n");
|
|
}
|
|
printf(")\n");
|
|
}
|
|
|
|
/*
|
|
* print_slot
|
|
* print out the tuple with the given TupleTableSlot
|
|
*/
|
|
void
|
|
print_slot(TupleTableSlot *slot)
|
|
{
|
|
if (TupIsNull(slot))
|
|
{
|
|
printf("tuple is null.\n");
|
|
return;
|
|
}
|
|
if (!slot->tts_tupleDescriptor)
|
|
{
|
|
printf("no tuple descriptor.\n");
|
|
return;
|
|
}
|
|
|
|
debugtup(slot, NULL);
|
|
}
|