1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-28 05:21:27 +03:00
postgres/src/backend/parser/parse_cte.c
Tom Lane 1e7c4bb004 Change unknown-type literals to type text in SELECT and RETURNING lists.
Previously, we left such literals alone if the query or subquery had
no properties forcing a type decision to be made (such as an ORDER BY or
DISTINCT clause using that output column).  This meant that "unknown" could
be an exposed output column type, which has never been a great idea because
it could result in strange failures later on.  For example, an outer query
that tried to do any operations on an unknown-type subquery output would
generally fail with some weird error like "failed to find conversion
function from unknown to text" or "could not determine which collation to
use for string comparison".  Also, if the case occurred in a CREATE VIEW's
query then the view would have an unknown-type column, causing similar
failures in queries trying to use the view.

To fix, at the tail end of parse analysis of a query, forcibly convert any
remaining "unknown" literals in its SELECT or RETURNING list to type text.
However, provide a switch to suppress that, and use it in the cases of
SELECT inside a set operation or INSERT command.  In those cases we already
had type resolution rules that make use of context information from outside
the subquery proper, and we don't want to change that behavior.

Also, change creation of an unknown-type column in a relation from a
warning to a hard error.  The error should be unreachable now in CREATE
VIEW or CREATE MATVIEW, but it's still possible to explicitly say "unknown"
in CREATE TABLE or CREATE (composite) TYPE.  We want to forbid that because
it's nothing but a foot-gun.

This change creates a pg_upgrade failure case: a matview that contains an
unknown-type column can't be pg_upgraded, because reparsing the matview's
defining query will now decide that the column is of type text, which
doesn't match the cstring-like storage that the old materialized column
would actually have.  Add a checking pass to detect that.  While at it,
we can detect tables or composite types that would fail, essentially
for free.  Those would fail safely anyway later on, but we might as
well fail earlier.

This patch is by me, but it owes something to previous investigations
by Rahila Syed.  Also thanks to Ashutosh Bapat and Michael Paquier for
review.

Discussion: https://postgr.es/m/CAH2L28uwwbL9HUM-WR=hromW1Cvamkn7O-g8fPY2m=_7muJ0oA@mail.gmail.com
2017-01-25 09:17:24 -05:00

974 lines
29 KiB
C

/*-------------------------------------------------------------------------
*
* parse_cte.c
* handle CTEs (common table expressions) in parser
*
* Portions Copyright (c) 1996-2017, 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);
}
}
}