mirror of
https://github.com/postgres/postgres.git
synced 2025-05-03 22:24:49 +03:00
974 lines
29 KiB
C
974 lines
29 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* parse_cte.c
|
|
* handle CTEs (common table expressions) in parser
|
|
*
|
|
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/parser/parse_cte.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_collation.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "parser/analyze.h"
|
|
#include "parser/parse_cte.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
|
|
/* Enumeration of contexts in which a self-reference is disallowed */
|
|
typedef enum
|
|
{
|
|
RECURSION_OK,
|
|
RECURSION_NONRECURSIVETERM, /* inside the left-hand term */
|
|
RECURSION_SUBLINK, /* inside a sublink */
|
|
RECURSION_OUTERJOIN, /* inside nullable side of an outer join */
|
|
RECURSION_INTERSECT, /* underneath INTERSECT (ALL) */
|
|
RECURSION_EXCEPT /* underneath EXCEPT (ALL) */
|
|
} RecursionContext;
|
|
|
|
/* Associated error messages --- each must have one %s for CTE name */
|
|
static const char *const recursion_errormsgs[] = {
|
|
/* RECURSION_OK */
|
|
NULL,
|
|
/* RECURSION_NONRECURSIVETERM */
|
|
gettext_noop("recursive reference to query \"%s\" must not appear within its non-recursive term"),
|
|
/* RECURSION_SUBLINK */
|
|
gettext_noop("recursive reference to query \"%s\" must not appear within a subquery"),
|
|
/* RECURSION_OUTERJOIN */
|
|
gettext_noop("recursive reference to query \"%s\" must not appear within an outer join"),
|
|
/* RECURSION_INTERSECT */
|
|
gettext_noop("recursive reference to query \"%s\" must not appear within INTERSECT"),
|
|
/* RECURSION_EXCEPT */
|
|
gettext_noop("recursive reference to query \"%s\" must not appear within EXCEPT")
|
|
};
|
|
|
|
/*
|
|
* For WITH RECURSIVE, we have to find an ordering of the clause members
|
|
* with no forward references, and determine which members are recursive
|
|
* (i.e., self-referential). It is convenient to do this with an array
|
|
* of CteItems instead of a list of CommonTableExprs.
|
|
*/
|
|
typedef struct CteItem
|
|
{
|
|
CommonTableExpr *cte; /* One CTE to examine */
|
|
int id; /* Its ID number for dependencies */
|
|
Bitmapset *depends_on; /* CTEs depended on (not including self) */
|
|
} CteItem;
|
|
|
|
/* CteState is what we need to pass around in the tree walkers */
|
|
typedef struct CteState
|
|
{
|
|
/* global state: */
|
|
ParseState *pstate; /* global parse state */
|
|
CteItem *items; /* array of CTEs and extra data */
|
|
int numitems; /* number of CTEs */
|
|
/* working state during a tree walk: */
|
|
int curitem; /* index of item currently being examined */
|
|
List *innerwiths; /* list of lists of CommonTableExpr */
|
|
/* working state for checkWellFormedRecursion walk only: */
|
|
int selfrefcount; /* number of self-references detected */
|
|
RecursionContext context; /* context to allow or disallow self-ref */
|
|
} CteState;
|
|
|
|
|
|
static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte);
|
|
|
|
/* Dependency processing functions */
|
|
static void makeDependencyGraph(CteState *cstate);
|
|
static bool makeDependencyGraphWalker(Node *node, CteState *cstate);
|
|
static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems);
|
|
|
|
/* Recursion validity checker functions */
|
|
static void checkWellFormedRecursion(CteState *cstate);
|
|
static bool checkWellFormedRecursionWalker(Node *node, CteState *cstate);
|
|
static void checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate);
|
|
|
|
|
|
/*
|
|
* transformWithClause -
|
|
* Transform the list of WITH clause "common table expressions" into
|
|
* Query nodes.
|
|
*
|
|
* The result is the list of transformed CTEs to be put into the output
|
|
* Query. (This is in fact the same as the ending value of p_ctenamespace,
|
|
* but it seems cleaner to not expose that in the function's API.)
|
|
*/
|
|
List *
|
|
transformWithClause(ParseState *pstate, WithClause *withClause)
|
|
{
|
|
ListCell *lc;
|
|
|
|
/* Only one WITH clause per query level */
|
|
Assert(pstate->p_ctenamespace == NIL);
|
|
Assert(pstate->p_future_ctes == NIL);
|
|
|
|
/*
|
|
* For either type of WITH, there must not be duplicate CTE names in the
|
|
* list. Check this right away so we needn't worry later.
|
|
*
|
|
* Also, tentatively mark each CTE as non-recursive, and initialize its
|
|
* reference count to zero, and set pstate->p_hasModifyingCTE if needed.
|
|
*/
|
|
foreach(lc, withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
ListCell *rest;
|
|
|
|
for_each_cell(rest, lnext(lc))
|
|
{
|
|
CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
|
|
|
|
if (strcmp(cte->ctename, cte2->ctename) == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_ALIAS),
|
|
errmsg("WITH query name \"%s\" specified more than once",
|
|
cte2->ctename),
|
|
parser_errposition(pstate, cte2->location)));
|
|
}
|
|
|
|
cte->cterecursive = false;
|
|
cte->cterefcount = 0;
|
|
|
|
if (!IsA(cte->ctequery, SelectStmt))
|
|
{
|
|
/* must be a data-modifying statement */
|
|
Assert(IsA(cte->ctequery, InsertStmt) ||
|
|
IsA(cte->ctequery, UpdateStmt) ||
|
|
IsA(cte->ctequery, DeleteStmt));
|
|
|
|
pstate->p_hasModifyingCTE = true;
|
|
}
|
|
}
|
|
|
|
if (withClause->recursive)
|
|
{
|
|
/*
|
|
* For WITH RECURSIVE, we rearrange the list elements if needed to
|
|
* eliminate forward references. First, build a work array and set up
|
|
* the data structure needed by the tree walkers.
|
|
*/
|
|
CteState cstate;
|
|
int i;
|
|
|
|
cstate.pstate = pstate;
|
|
cstate.numitems = list_length(withClause->ctes);
|
|
cstate.items = (CteItem *) palloc0(cstate.numitems * sizeof(CteItem));
|
|
i = 0;
|
|
foreach(lc, withClause->ctes)
|
|
{
|
|
cstate.items[i].cte = (CommonTableExpr *) lfirst(lc);
|
|
cstate.items[i].id = i;
|
|
i++;
|
|
}
|
|
|
|
/*
|
|
* Find all the dependencies and sort the CteItems into a safe
|
|
* processing order. Also, mark CTEs that contain self-references.
|
|
*/
|
|
makeDependencyGraph(&cstate);
|
|
|
|
/*
|
|
* Check that recursive queries are well-formed.
|
|
*/
|
|
checkWellFormedRecursion(&cstate);
|
|
|
|
/*
|
|
* Set up the ctenamespace for parse analysis. Per spec, all the WITH
|
|
* items are visible to all others, so stuff them all in before parse
|
|
* analysis. We build the list in safe processing order so that the
|
|
* planner can process the queries in sequence.
|
|
*/
|
|
for (i = 0; i < cstate.numitems; i++)
|
|
{
|
|
CommonTableExpr *cte = cstate.items[i].cte;
|
|
|
|
pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
|
|
}
|
|
|
|
/*
|
|
* Do parse analysis in the order determined by the topological sort.
|
|
*/
|
|
for (i = 0; i < cstate.numitems; i++)
|
|
{
|
|
CommonTableExpr *cte = cstate.items[i].cte;
|
|
|
|
analyzeCTE(pstate, cte);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* For non-recursive WITH, just analyze each CTE in sequence and then
|
|
* add it to the ctenamespace. This corresponds to the spec's
|
|
* definition of the scope of each WITH name. However, to allow error
|
|
* reports to be aware of the possibility of an erroneous reference,
|
|
* we maintain a list in p_future_ctes of the not-yet-visible CTEs.
|
|
*/
|
|
pstate->p_future_ctes = list_copy(withClause->ctes);
|
|
|
|
foreach(lc, withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
analyzeCTE(pstate, cte);
|
|
pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
|
|
pstate->p_future_ctes = list_delete_first(pstate->p_future_ctes);
|
|
}
|
|
}
|
|
|
|
return pstate->p_ctenamespace;
|
|
}
|
|
|
|
|
|
/*
|
|
* Perform the actual parse analysis transformation of one CTE. All
|
|
* CTEs it depends on have already been loaded into pstate->p_ctenamespace,
|
|
* and have been marked with the correct output column names/types.
|
|
*/
|
|
static void
|
|
analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
|
|
{
|
|
Query *query;
|
|
|
|
/* Analysis not done already */
|
|
Assert(!IsA(cte->ctequery, Query));
|
|
|
|
query = parse_sub_analyze(cte->ctequery, pstate, cte, false, true);
|
|
cte->ctequery = (Node *) query;
|
|
|
|
/*
|
|
* Check that we got something reasonable. These first two cases should
|
|
* be prevented by the grammar.
|
|
*/
|
|
if (!IsA(query, Query))
|
|
elog(ERROR, "unexpected non-Query statement in WITH");
|
|
if (query->utilityStmt != NULL)
|
|
elog(ERROR, "unexpected utility statement in WITH");
|
|
|
|
/*
|
|
* We disallow data-modifying WITH except at the top level of a query,
|
|
* because it's not clear when such a modification should be executed.
|
|
*/
|
|
if (query->commandType != CMD_SELECT &&
|
|
pstate->parentParseState != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("WITH clause containing a data-modifying statement must be at the top level"),
|
|
parser_errposition(pstate, cte->location)));
|
|
|
|
/*
|
|
* CTE queries are always marked not canSetTag. (Currently this only
|
|
* matters for data-modifying statements, for which the flag will be
|
|
* propagated to the ModifyTable plan node.)
|
|
*/
|
|
query->canSetTag = false;
|
|
|
|
if (!cte->cterecursive)
|
|
{
|
|
/* Compute the output column names/types if not done yet */
|
|
analyzeCTETargetList(pstate, cte, GetCTETargetList(cte));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Verify that the previously determined output column types and
|
|
* collations match what the query really produced. We have to check
|
|
* this because the recursive term could have overridden the
|
|
* non-recursive term, and we don't have any easy way to fix that.
|
|
*/
|
|
ListCell *lctlist,
|
|
*lctyp,
|
|
*lctypmod,
|
|
*lccoll;
|
|
int varattno;
|
|
|
|
lctyp = list_head(cte->ctecoltypes);
|
|
lctypmod = list_head(cte->ctecoltypmods);
|
|
lccoll = list_head(cte->ctecolcollations);
|
|
varattno = 0;
|
|
foreach(lctlist, GetCTETargetList(cte))
|
|
{
|
|
TargetEntry *te = (TargetEntry *) lfirst(lctlist);
|
|
Node *texpr;
|
|
|
|
if (te->resjunk)
|
|
continue;
|
|
varattno++;
|
|
Assert(varattno == te->resno);
|
|
if (lctyp == NULL || lctypmod == NULL || lccoll == NULL) /* shouldn't happen */
|
|
elog(ERROR, "wrong number of output columns in WITH");
|
|
texpr = (Node *) te->expr;
|
|
if (exprType(texpr) != lfirst_oid(lctyp) ||
|
|
exprTypmod(texpr) != lfirst_int(lctypmod))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("recursive query \"%s\" column %d has type %s in non-recursive term but type %s overall",
|
|
cte->ctename, varattno,
|
|
format_type_with_typemod(lfirst_oid(lctyp),
|
|
lfirst_int(lctypmod)),
|
|
format_type_with_typemod(exprType(texpr),
|
|
exprTypmod(texpr))),
|
|
errhint("Cast the output of the non-recursive term to the correct type."),
|
|
parser_errposition(pstate, exprLocation(texpr))));
|
|
if (exprCollation(texpr) != lfirst_oid(lccoll))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_COLLATION_MISMATCH),
|
|
errmsg("recursive query \"%s\" column %d has collation \"%s\" in non-recursive term but collation \"%s\" overall",
|
|
cte->ctename, varattno,
|
|
get_collation_name(lfirst_oid(lccoll)),
|
|
get_collation_name(exprCollation(texpr))),
|
|
errhint("Use the COLLATE clause to set the collation of the non-recursive term."),
|
|
parser_errposition(pstate, exprLocation(texpr))));
|
|
lctyp = lnext(lctyp);
|
|
lctypmod = lnext(lctypmod);
|
|
lccoll = lnext(lccoll);
|
|
}
|
|
if (lctyp != NULL || lctypmod != NULL || lccoll != NULL) /* shouldn't happen */
|
|
elog(ERROR, "wrong number of output columns in WITH");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute derived fields of a CTE, given the transformed output targetlist
|
|
*
|
|
* For a nonrecursive CTE, this is called after transforming the CTE's query.
|
|
* For a recursive CTE, we call it after transforming the non-recursive term,
|
|
* and pass the targetlist emitted by the non-recursive term only.
|
|
*
|
|
* Note: in the recursive case, the passed pstate is actually the one being
|
|
* used to analyze the CTE's query, so it is one level lower down than in
|
|
* the nonrecursive case. This doesn't matter since we only use it for
|
|
* error message context anyway.
|
|
*/
|
|
void
|
|
analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist)
|
|
{
|
|
int numaliases;
|
|
int varattno;
|
|
ListCell *tlistitem;
|
|
|
|
/* Not done already ... */
|
|
Assert(cte->ctecolnames == NIL);
|
|
|
|
/*
|
|
* We need to determine column names, types, and collations. The alias
|
|
* column names override anything coming from the query itself. (Note:
|
|
* the SQL spec says that the alias list must be empty or exactly as long
|
|
* as the output column set; but we allow it to be shorter for consistency
|
|
* with Alias handling.)
|
|
*/
|
|
cte->ctecolnames = copyObject(cte->aliascolnames);
|
|
cte->ctecoltypes = cte->ctecoltypmods = cte->ctecolcollations = NIL;
|
|
numaliases = list_length(cte->aliascolnames);
|
|
varattno = 0;
|
|
foreach(tlistitem, tlist)
|
|
{
|
|
TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
|
|
Oid coltype;
|
|
int32 coltypmod;
|
|
Oid colcoll;
|
|
|
|
if (te->resjunk)
|
|
continue;
|
|
varattno++;
|
|
Assert(varattno == te->resno);
|
|
if (varattno > numaliases)
|
|
{
|
|
char *attrname;
|
|
|
|
attrname = pstrdup(te->resname);
|
|
cte->ctecolnames = lappend(cte->ctecolnames, makeString(attrname));
|
|
}
|
|
coltype = exprType((Node *) te->expr);
|
|
coltypmod = exprTypmod((Node *) te->expr);
|
|
colcoll = exprCollation((Node *) te->expr);
|
|
|
|
/*
|
|
* If the CTE is recursive, force the exposed column type of any
|
|
* "unknown" column to "text". We must deal with this here because
|
|
* we're called on the non-recursive term before there's been any
|
|
* attempt to force unknown output columns to some other type. We
|
|
* have to resolve unknowns before looking at the recursive term.
|
|
*
|
|
* The column might contain 'foo' COLLATE "bar", so don't override
|
|
* collation if it's already set.
|
|
*/
|
|
if (cte->cterecursive && coltype == UNKNOWNOID)
|
|
{
|
|
coltype = TEXTOID;
|
|
coltypmod = -1; /* should be -1 already, but be sure */
|
|
if (!OidIsValid(colcoll))
|
|
colcoll = DEFAULT_COLLATION_OID;
|
|
}
|
|
cte->ctecoltypes = lappend_oid(cte->ctecoltypes, coltype);
|
|
cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, coltypmod);
|
|
cte->ctecolcollations = lappend_oid(cte->ctecolcollations, colcoll);
|
|
}
|
|
if (varattno < numaliases)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
|
errmsg("WITH query \"%s\" has %d columns available but %d columns specified",
|
|
cte->ctename, varattno, numaliases),
|
|
parser_errposition(pstate, cte->location)));
|
|
}
|
|
|
|
|
|
/*
|
|
* Identify the cross-references of a list of WITH RECURSIVE items,
|
|
* and sort into an order that has no forward references.
|
|
*/
|
|
static void
|
|
makeDependencyGraph(CteState *cstate)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cstate->numitems; i++)
|
|
{
|
|
CommonTableExpr *cte = cstate->items[i].cte;
|
|
|
|
cstate->curitem = i;
|
|
cstate->innerwiths = NIL;
|
|
makeDependencyGraphWalker((Node *) cte->ctequery, cstate);
|
|
Assert(cstate->innerwiths == NIL);
|
|
}
|
|
|
|
TopologicalSort(cstate->pstate, cstate->items, cstate->numitems);
|
|
}
|
|
|
|
/*
|
|
* Tree walker function to detect cross-references and self-references of the
|
|
* CTEs in a WITH RECURSIVE list.
|
|
*/
|
|
static bool
|
|
makeDependencyGraphWalker(Node *node, CteState *cstate)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, RangeVar))
|
|
{
|
|
RangeVar *rv = (RangeVar *) node;
|
|
|
|
/* If unqualified name, might be a CTE reference */
|
|
if (!rv->schemaname)
|
|
{
|
|
ListCell *lc;
|
|
int i;
|
|
|
|
/* ... but first see if it's captured by an inner WITH */
|
|
foreach(lc, cstate->innerwiths)
|
|
{
|
|
List *withlist = (List *) lfirst(lc);
|
|
ListCell *lc2;
|
|
|
|
foreach(lc2, withlist)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);
|
|
|
|
if (strcmp(rv->relname, cte->ctename) == 0)
|
|
return false; /* yes, so bail out */
|
|
}
|
|
}
|
|
|
|
/* No, could be a reference to the query level we are working on */
|
|
for (i = 0; i < cstate->numitems; i++)
|
|
{
|
|
CommonTableExpr *cte = cstate->items[i].cte;
|
|
|
|
if (strcmp(rv->relname, cte->ctename) == 0)
|
|
{
|
|
int myindex = cstate->curitem;
|
|
|
|
if (i != myindex)
|
|
{
|
|
/* Add cross-item dependency */
|
|
cstate->items[myindex].depends_on =
|
|
bms_add_member(cstate->items[myindex].depends_on,
|
|
cstate->items[i].id);
|
|
}
|
|
else
|
|
{
|
|
/* Found out this one is self-referential */
|
|
cte->cterecursive = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, SelectStmt))
|
|
{
|
|
SelectStmt *stmt = (SelectStmt *) node;
|
|
ListCell *lc;
|
|
|
|
if (stmt->withClause)
|
|
{
|
|
if (stmt->withClause->recursive)
|
|
{
|
|
/*
|
|
* In the RECURSIVE case, all query names of the WITH are
|
|
* visible to all WITH items as well as the main query. So
|
|
* push them all on, process, pop them all off.
|
|
*/
|
|
cstate->innerwiths = lcons(stmt->withClause->ctes,
|
|
cstate->innerwiths);
|
|
foreach(lc, stmt->withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
|
|
}
|
|
(void) raw_expression_tree_walker(node,
|
|
makeDependencyGraphWalker,
|
|
(void *) cstate);
|
|
cstate->innerwiths = list_delete_first(cstate->innerwiths);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* In the non-RECURSIVE case, query names are visible to the
|
|
* WITH items after them and to the main query.
|
|
*/
|
|
ListCell *cell1;
|
|
|
|
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
|
|
cell1 = list_head(cstate->innerwiths);
|
|
foreach(lc, stmt->withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
|
|
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
|
|
}
|
|
(void) raw_expression_tree_walker(node,
|
|
makeDependencyGraphWalker,
|
|
(void *) cstate);
|
|
cstate->innerwiths = list_delete_first(cstate->innerwiths);
|
|
}
|
|
/* We're done examining the SelectStmt */
|
|
return false;
|
|
}
|
|
/* if no WITH clause, just fall through for normal processing */
|
|
}
|
|
if (IsA(node, WithClause))
|
|
{
|
|
/*
|
|
* Prevent raw_expression_tree_walker from recursing directly into a
|
|
* WITH clause. We need that to happen only under the control of the
|
|
* code above.
|
|
*/
|
|
return false;
|
|
}
|
|
return raw_expression_tree_walker(node,
|
|
makeDependencyGraphWalker,
|
|
(void *) cstate);
|
|
}
|
|
|
|
/*
|
|
* Sort by dependencies, using a standard topological sort operation
|
|
*/
|
|
static void
|
|
TopologicalSort(ParseState *pstate, CteItem *items, int numitems)
|
|
{
|
|
int i,
|
|
j;
|
|
|
|
/* for each position in sequence ... */
|
|
for (i = 0; i < numitems; i++)
|
|
{
|
|
/* ... scan the remaining items to find one that has no dependencies */
|
|
for (j = i; j < numitems; j++)
|
|
{
|
|
if (bms_is_empty(items[j].depends_on))
|
|
break;
|
|
}
|
|
|
|
/* if we didn't find one, the dependency graph has a cycle */
|
|
if (j >= numitems)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("mutual recursion between WITH items is not implemented"),
|
|
parser_errposition(pstate, items[i].cte->location)));
|
|
|
|
/*
|
|
* Found one. Move it to front and remove it from every other item's
|
|
* dependencies.
|
|
*/
|
|
if (i != j)
|
|
{
|
|
CteItem tmp;
|
|
|
|
tmp = items[i];
|
|
items[i] = items[j];
|
|
items[j] = tmp;
|
|
}
|
|
|
|
/*
|
|
* Items up through i are known to have no dependencies left, so we
|
|
* can skip them in this loop.
|
|
*/
|
|
for (j = i + 1; j < numitems; j++)
|
|
{
|
|
items[j].depends_on = bms_del_member(items[j].depends_on,
|
|
items[i].id);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Check that recursive queries are well-formed.
|
|
*/
|
|
static void
|
|
checkWellFormedRecursion(CteState *cstate)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cstate->numitems; i++)
|
|
{
|
|
CommonTableExpr *cte = cstate->items[i].cte;
|
|
SelectStmt *stmt = (SelectStmt *) cte->ctequery;
|
|
|
|
Assert(!IsA(stmt, Query)); /* not analyzed yet */
|
|
|
|
/* Ignore items that weren't found to be recursive */
|
|
if (!cte->cterecursive)
|
|
continue;
|
|
|
|
/* Must be a SELECT statement */
|
|
if (!IsA(stmt, SelectStmt))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_RECURSION),
|
|
errmsg("recursive query \"%s\" must not contain data-modifying statements",
|
|
cte->ctename),
|
|
parser_errposition(cstate->pstate, cte->location)));
|
|
|
|
/* Must have top-level UNION */
|
|
if (stmt->op != SETOP_UNION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_RECURSION),
|
|
errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term",
|
|
cte->ctename),
|
|
parser_errposition(cstate->pstate, cte->location)));
|
|
|
|
/* The left-hand operand mustn't contain self-reference at all */
|
|
cstate->curitem = i;
|
|
cstate->innerwiths = NIL;
|
|
cstate->selfrefcount = 0;
|
|
cstate->context = RECURSION_NONRECURSIVETERM;
|
|
checkWellFormedRecursionWalker((Node *) stmt->larg, cstate);
|
|
Assert(cstate->innerwiths == NIL);
|
|
|
|
/* Right-hand operand should contain one reference in a valid place */
|
|
cstate->curitem = i;
|
|
cstate->innerwiths = NIL;
|
|
cstate->selfrefcount = 0;
|
|
cstate->context = RECURSION_OK;
|
|
checkWellFormedRecursionWalker((Node *) stmt->rarg, cstate);
|
|
Assert(cstate->innerwiths == NIL);
|
|
if (cstate->selfrefcount != 1) /* shouldn't happen */
|
|
elog(ERROR, "missing recursive reference");
|
|
|
|
/* WITH mustn't contain self-reference, either */
|
|
if (stmt->withClause)
|
|
{
|
|
cstate->curitem = i;
|
|
cstate->innerwiths = NIL;
|
|
cstate->selfrefcount = 0;
|
|
cstate->context = RECURSION_SUBLINK;
|
|
checkWellFormedRecursionWalker((Node *) stmt->withClause->ctes,
|
|
cstate);
|
|
Assert(cstate->innerwiths == NIL);
|
|
}
|
|
|
|
/*
|
|
* Disallow ORDER BY and similar decoration atop the UNION. These
|
|
* don't make sense because it's impossible to figure out what they
|
|
* mean when we have only part of the recursive query's results. (If
|
|
* we did allow them, we'd have to check for recursive references
|
|
* inside these subtrees.)
|
|
*/
|
|
if (stmt->sortClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("ORDER BY in a recursive query is not implemented"),
|
|
parser_errposition(cstate->pstate,
|
|
exprLocation((Node *) stmt->sortClause))));
|
|
if (stmt->limitOffset)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("OFFSET in a recursive query is not implemented"),
|
|
parser_errposition(cstate->pstate,
|
|
exprLocation(stmt->limitOffset))));
|
|
if (stmt->limitCount)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("LIMIT in a recursive query is not implemented"),
|
|
parser_errposition(cstate->pstate,
|
|
exprLocation(stmt->limitCount))));
|
|
if (stmt->lockingClause)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("FOR UPDATE/SHARE in a recursive query is not implemented"),
|
|
parser_errposition(cstate->pstate,
|
|
exprLocation((Node *) stmt->lockingClause))));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tree walker function to detect invalid self-references in a recursive query.
|
|
*/
|
|
static bool
|
|
checkWellFormedRecursionWalker(Node *node, CteState *cstate)
|
|
{
|
|
RecursionContext save_context = cstate->context;
|
|
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, RangeVar))
|
|
{
|
|
RangeVar *rv = (RangeVar *) node;
|
|
|
|
/* If unqualified name, might be a CTE reference */
|
|
if (!rv->schemaname)
|
|
{
|
|
ListCell *lc;
|
|
CommonTableExpr *mycte;
|
|
|
|
/* ... but first see if it's captured by an inner WITH */
|
|
foreach(lc, cstate->innerwiths)
|
|
{
|
|
List *withlist = (List *) lfirst(lc);
|
|
ListCell *lc2;
|
|
|
|
foreach(lc2, withlist)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);
|
|
|
|
if (strcmp(rv->relname, cte->ctename) == 0)
|
|
return false; /* yes, so bail out */
|
|
}
|
|
}
|
|
|
|
/* No, could be a reference to the query level we are working on */
|
|
mycte = cstate->items[cstate->curitem].cte;
|
|
if (strcmp(rv->relname, mycte->ctename) == 0)
|
|
{
|
|
/* Found a recursive reference to the active query */
|
|
if (cstate->context != RECURSION_OK)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_RECURSION),
|
|
errmsg(recursion_errormsgs[cstate->context],
|
|
mycte->ctename),
|
|
parser_errposition(cstate->pstate,
|
|
rv->location)));
|
|
/* Count references */
|
|
if (++(cstate->selfrefcount) > 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_RECURSION),
|
|
errmsg("recursive reference to query \"%s\" must not appear more than once",
|
|
mycte->ctename),
|
|
parser_errposition(cstate->pstate,
|
|
rv->location)));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, SelectStmt))
|
|
{
|
|
SelectStmt *stmt = (SelectStmt *) node;
|
|
ListCell *lc;
|
|
|
|
if (stmt->withClause)
|
|
{
|
|
if (stmt->withClause->recursive)
|
|
{
|
|
/*
|
|
* In the RECURSIVE case, all query names of the WITH are
|
|
* visible to all WITH items as well as the main query. So
|
|
* push them all on, process, pop them all off.
|
|
*/
|
|
cstate->innerwiths = lcons(stmt->withClause->ctes,
|
|
cstate->innerwiths);
|
|
foreach(lc, stmt->withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
(void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
|
|
}
|
|
checkWellFormedSelectStmt(stmt, cstate);
|
|
cstate->innerwiths = list_delete_first(cstate->innerwiths);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* In the non-RECURSIVE case, query names are visible to the
|
|
* WITH items after them and to the main query.
|
|
*/
|
|
ListCell *cell1;
|
|
|
|
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
|
|
cell1 = list_head(cstate->innerwiths);
|
|
foreach(lc, stmt->withClause->ctes)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
|
|
|
|
(void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
|
|
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
|
|
}
|
|
checkWellFormedSelectStmt(stmt, cstate);
|
|
cstate->innerwiths = list_delete_first(cstate->innerwiths);
|
|
}
|
|
}
|
|
else
|
|
checkWellFormedSelectStmt(stmt, cstate);
|
|
/* We're done examining the SelectStmt */
|
|
return false;
|
|
}
|
|
if (IsA(node, WithClause))
|
|
{
|
|
/*
|
|
* Prevent raw_expression_tree_walker from recursing directly into a
|
|
* WITH clause. We need that to happen only under the control of the
|
|
* code above.
|
|
*/
|
|
return false;
|
|
}
|
|
if (IsA(node, JoinExpr))
|
|
{
|
|
JoinExpr *j = (JoinExpr *) node;
|
|
|
|
switch (j->jointype)
|
|
{
|
|
case JOIN_INNER:
|
|
checkWellFormedRecursionWalker(j->larg, cstate);
|
|
checkWellFormedRecursionWalker(j->rarg, cstate);
|
|
checkWellFormedRecursionWalker(j->quals, cstate);
|
|
break;
|
|
case JOIN_LEFT:
|
|
checkWellFormedRecursionWalker(j->larg, cstate);
|
|
if (save_context == RECURSION_OK)
|
|
cstate->context = RECURSION_OUTERJOIN;
|
|
checkWellFormedRecursionWalker(j->rarg, cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker(j->quals, cstate);
|
|
break;
|
|
case JOIN_FULL:
|
|
if (save_context == RECURSION_OK)
|
|
cstate->context = RECURSION_OUTERJOIN;
|
|
checkWellFormedRecursionWalker(j->larg, cstate);
|
|
checkWellFormedRecursionWalker(j->rarg, cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker(j->quals, cstate);
|
|
break;
|
|
case JOIN_RIGHT:
|
|
if (save_context == RECURSION_OK)
|
|
cstate->context = RECURSION_OUTERJOIN;
|
|
checkWellFormedRecursionWalker(j->larg, cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker(j->rarg, cstate);
|
|
checkWellFormedRecursionWalker(j->quals, cstate);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized join type: %d",
|
|
(int) j->jointype);
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, SubLink))
|
|
{
|
|
SubLink *sl = (SubLink *) node;
|
|
|
|
/*
|
|
* we intentionally override outer context, since subquery is
|
|
* independent
|
|
*/
|
|
cstate->context = RECURSION_SUBLINK;
|
|
checkWellFormedRecursionWalker(sl->subselect, cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker(sl->testexpr, cstate);
|
|
return false;
|
|
}
|
|
return raw_expression_tree_walker(node,
|
|
checkWellFormedRecursionWalker,
|
|
(void *) cstate);
|
|
}
|
|
|
|
/*
|
|
* subroutine for checkWellFormedRecursionWalker: process a SelectStmt
|
|
* without worrying about its WITH clause
|
|
*/
|
|
static void
|
|
checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
|
|
{
|
|
RecursionContext save_context = cstate->context;
|
|
|
|
if (save_context != RECURSION_OK)
|
|
{
|
|
/* just recurse without changing state */
|
|
raw_expression_tree_walker((Node *) stmt,
|
|
checkWellFormedRecursionWalker,
|
|
(void *) cstate);
|
|
}
|
|
else
|
|
{
|
|
switch (stmt->op)
|
|
{
|
|
case SETOP_NONE:
|
|
case SETOP_UNION:
|
|
raw_expression_tree_walker((Node *) stmt,
|
|
checkWellFormedRecursionWalker,
|
|
(void *) cstate);
|
|
break;
|
|
case SETOP_INTERSECT:
|
|
if (stmt->all)
|
|
cstate->context = RECURSION_INTERSECT;
|
|
checkWellFormedRecursionWalker((Node *) stmt->larg,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->rarg,
|
|
cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker((Node *) stmt->sortClause,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->limitCount,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
|
|
cstate);
|
|
/* stmt->withClause is intentionally ignored here */
|
|
break;
|
|
case SETOP_EXCEPT:
|
|
if (stmt->all)
|
|
cstate->context = RECURSION_EXCEPT;
|
|
checkWellFormedRecursionWalker((Node *) stmt->larg,
|
|
cstate);
|
|
cstate->context = RECURSION_EXCEPT;
|
|
checkWellFormedRecursionWalker((Node *) stmt->rarg,
|
|
cstate);
|
|
cstate->context = save_context;
|
|
checkWellFormedRecursionWalker((Node *) stmt->sortClause,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->limitCount,
|
|
cstate);
|
|
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
|
|
cstate);
|
|
/* stmt->withClause is intentionally ignored here */
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized set op: %d",
|
|
(int) stmt->op);
|
|
}
|
|
}
|
|
}
|