Fix WITH attached to a nested set operation (UNION/INTERSECT/EXCEPT).

Parse analysis neglected to cover the case of a WITH clause attached to an
intermediate-level set operation; it only handled WITH at the top level
or WITH attached to a leaf-level SELECT.  Per report from Adam Mackler.

In HEAD, I rearranged the order of SelectStmt's fields to put withClause
with the other fields that can appear on non-leaf SelectStmts.  In back
branches, leave it alone to avoid a possible ABI break for third-party
code.

Back-patch to 8.4 where WITH support was added.
This commit is contained in:
Tom Lane 2012-07-31 17:56:21 -04:00
parent b76356ac22
commit f6ce81f55a
10 changed files with 123 additions and 21 deletions

View File

@ -2494,12 +2494,12 @@ _copySelectStmt(const SelectStmt *from)
COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(havingClause); COPY_NODE_FIELD(havingClause);
COPY_NODE_FIELD(windowClause); COPY_NODE_FIELD(windowClause);
COPY_NODE_FIELD(withClause);
COPY_NODE_FIELD(valuesLists); COPY_NODE_FIELD(valuesLists);
COPY_NODE_FIELD(sortClause); COPY_NODE_FIELD(sortClause);
COPY_NODE_FIELD(limitOffset); COPY_NODE_FIELD(limitOffset);
COPY_NODE_FIELD(limitCount); COPY_NODE_FIELD(limitCount);
COPY_NODE_FIELD(lockingClause); COPY_NODE_FIELD(lockingClause);
COPY_NODE_FIELD(withClause);
COPY_SCALAR_FIELD(op); COPY_SCALAR_FIELD(op);
COPY_SCALAR_FIELD(all); COPY_SCALAR_FIELD(all);
COPY_NODE_FIELD(larg); COPY_NODE_FIELD(larg);

View File

@ -976,12 +976,12 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(havingClause); COMPARE_NODE_FIELD(havingClause);
COMPARE_NODE_FIELD(windowClause); COMPARE_NODE_FIELD(windowClause);
COMPARE_NODE_FIELD(withClause);
COMPARE_NODE_FIELD(valuesLists); COMPARE_NODE_FIELD(valuesLists);
COMPARE_NODE_FIELD(sortClause); COMPARE_NODE_FIELD(sortClause);
COMPARE_NODE_FIELD(limitOffset); COMPARE_NODE_FIELD(limitOffset);
COMPARE_NODE_FIELD(limitCount); COMPARE_NODE_FIELD(limitCount);
COMPARE_NODE_FIELD(lockingClause); COMPARE_NODE_FIELD(lockingClause);
COMPARE_NODE_FIELD(withClause);
COMPARE_SCALAR_FIELD(op); COMPARE_SCALAR_FIELD(op);
COMPARE_SCALAR_FIELD(all); COMPARE_SCALAR_FIELD(all);
COMPARE_NODE_FIELD(larg); COMPARE_NODE_FIELD(larg);

View File

@ -2909,8 +2909,6 @@ raw_expression_tree_walker(Node *node,
return true; return true;
if (walker(stmt->windowClause, context)) if (walker(stmt->windowClause, context))
return true; return true;
if (walker(stmt->withClause, context))
return true;
if (walker(stmt->valuesLists, context)) if (walker(stmt->valuesLists, context))
return true; return true;
if (walker(stmt->sortClause, context)) if (walker(stmt->sortClause, context))
@ -2921,6 +2919,8 @@ raw_expression_tree_walker(Node *node,
return true; return true;
if (walker(stmt->lockingClause, context)) if (walker(stmt->lockingClause, context))
return true; return true;
if (walker(stmt->withClause, context))
return true;
if (walker(stmt->larg, context)) if (walker(stmt->larg, context))
return true; return true;
if (walker(stmt->rarg, context)) if (walker(stmt->rarg, context))

View File

@ -2037,12 +2037,12 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(havingClause); WRITE_NODE_FIELD(havingClause);
WRITE_NODE_FIELD(windowClause); WRITE_NODE_FIELD(windowClause);
WRITE_NODE_FIELD(withClause);
WRITE_NODE_FIELD(valuesLists); WRITE_NODE_FIELD(valuesLists);
WRITE_NODE_FIELD(sortClause); WRITE_NODE_FIELD(sortClause);
WRITE_NODE_FIELD(limitOffset); WRITE_NODE_FIELD(limitOffset);
WRITE_NODE_FIELD(limitCount); WRITE_NODE_FIELD(limitCount);
WRITE_NODE_FIELD(lockingClause); WRITE_NODE_FIELD(lockingClause);
WRITE_NODE_FIELD(withClause);
WRITE_ENUM_FIELD(op, SetOperation); WRITE_ENUM_FIELD(op, SetOperation);
WRITE_BOOL_FIELD(all); WRITE_BOOL_FIELD(all);
WRITE_NODE_FIELD(larg); WRITE_NODE_FIELD(larg);

View File

@ -1322,6 +1322,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
Node *limitOffset; Node *limitOffset;
Node *limitCount; Node *limitCount;
List *lockingClause; List *lockingClause;
WithClause *withClause;
Node *node; Node *node;
ListCell *left_tlist, ListCell *left_tlist,
*lct, *lct,
@ -1338,14 +1339,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->commandType = CMD_SELECT; qry->commandType = CMD_SELECT;
/* process the WITH clause independently of all else */
if (stmt->withClause)
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/* /*
* Find leftmost leaf SelectStmt. We currently only need to do this in * Find leftmost leaf SelectStmt. We currently only need to do this in
* order to deliver a suitable error message if there's an INTO clause * order to deliver a suitable error message if there's an INTO clause
@ -1375,11 +1368,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
limitOffset = stmt->limitOffset; limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount; limitCount = stmt->limitCount;
lockingClause = stmt->lockingClause; lockingClause = stmt->lockingClause;
withClause = stmt->withClause;
stmt->sortClause = NIL; stmt->sortClause = NIL;
stmt->limitOffset = NULL; stmt->limitOffset = NULL;
stmt->limitCount = NULL; stmt->limitCount = NULL;
stmt->lockingClause = NIL; stmt->lockingClause = NIL;
stmt->withClause = NULL;
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */ /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockingClause) if (lockingClause)
@ -1387,6 +1382,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT"))); errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
/* Process the WITH clause independently of all else */
if (withClause)
{
qry->hasRecursive = withClause->recursive;
qry->cteList = transformWithClause(pstate, withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/* /*
* Recursively transform the components of the tree. * Recursively transform the components of the tree.
*/ */
@ -1572,10 +1575,10 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT"))); errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
/* /*
* If an internal node of a set-op tree has ORDER BY, LIMIT, or FOR UPDATE * If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE,
* clauses attached, we need to treat it like a leaf node to generate an * or WITH clauses attached, we need to treat it like a leaf node to
* independent sub-Query tree. Otherwise, it can be represented by a * generate an independent sub-Query tree. Otherwise, it can be
* SetOperationStmt node underneath the parent Query. * represented by a SetOperationStmt node underneath the parent Query.
*/ */
if (stmt->op == SETOP_NONE) if (stmt->op == SETOP_NONE)
{ {
@ -1586,7 +1589,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
{ {
Assert(stmt->larg != NULL && stmt->rarg != NULL); Assert(stmt->larg != NULL && stmt->rarg != NULL);
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount || if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
stmt->lockingClause) stmt->lockingClause || stmt->withClause)
isLeaf = true; isLeaf = true;
else else
isLeaf = false; isLeaf = false;

View File

@ -678,6 +678,18 @@ checkWellFormedRecursion(CteState *cstate)
if (cstate->selfrefcount != 1) /* shouldn't happen */ if (cstate->selfrefcount != 1) /* shouldn't happen */
elog(ERROR, "missing recursive reference"); 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 * Disallow ORDER BY and similar decoration atop the UNION. These
* don't make sense because it's impossible to figure out what they * don't make sense because it's impossible to figure out what they
@ -933,7 +945,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate); cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause, checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate); cstate);
break; /* stmt->withClause is intentionally ignored here */
break; break;
case SETOP_EXCEPT: case SETOP_EXCEPT:
if (stmt->all) if (stmt->all)
@ -952,6 +964,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate); cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause, checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate); cstate);
/* stmt->withClause is intentionally ignored here */
break; break;
default: default:
elog(ERROR, "unrecognized set op: %d", elog(ERROR, "unrecognized set op: %d",

View File

@ -705,12 +705,12 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
stmt->groupClause != NIL || stmt->groupClause != NIL ||
stmt->havingClause != NULL || stmt->havingClause != NULL ||
stmt->windowClause != NIL || stmt->windowClause != NIL ||
stmt->withClause != NULL ||
stmt->valuesLists != NIL || stmt->valuesLists != NIL ||
stmt->sortClause != NIL || stmt->sortClause != NIL ||
stmt->limitOffset != NULL || stmt->limitOffset != NULL ||
stmt->limitCount != NULL || stmt->limitCount != NULL ||
stmt->lockingClause != NIL || stmt->lockingClause != NIL ||
stmt->withClause != NULL ||
stmt->op != SETOP_NONE) stmt->op != SETOP_NONE)
goto fail; goto fail;
if (list_length(stmt->targetList) != 1) if (list_length(stmt->targetList) != 1)

View File

@ -1016,7 +1016,6 @@ typedef struct SelectStmt
List *groupClause; /* GROUP BY clauses */ List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */ Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */ List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
/* /*
* In a "leaf" node representing a VALUES list, the above fields are all * In a "leaf" node representing a VALUES list, the above fields are all
@ -1036,6 +1035,7 @@ typedef struct SelectStmt
Node *limitOffset; /* # of result tuples to skip */ Node *limitOffset; /* # of result tuples to skip */
Node *limitCount; /* # of result tuples to return */ Node *limitCount; /* # of result tuples to return */
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */ List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
WithClause *withClause; /* WITH clause */
/* /*
* These fields are used only in upper-level SelectStmts. * These fields are used only in upper-level SelectStmts.

View File

@ -1158,6 +1158,57 @@ SELECT * FROM t;
10 10
(55 rows) (55 rows)
--
-- test WITH attached to intermediate-level set operation
--
WITH outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM innermost
UNION SELECT 3)
)
SELECT * FROM outermost;
x
---
1
2
3
(3 rows)
WITH outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM outermost -- fail
UNION SELECT * FROM innermost)
)
SELECT * FROM outermost;
ERROR: relation "outermost" does not exist
LINE 4: SELECT * FROM outermost
^
DETAIL: There is a WITH item named "outermost", but it cannot be referenced from this part of the query.
HINT: Use WITH RECURSIVE, or re-order the WITH items to remove forward references.
WITH RECURSIVE outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM outermost
UNION SELECT * FROM innermost)
)
SELECT * FROM outermost;
x
---
1
2
(2 rows)
WITH RECURSIVE outermost(x) AS (
WITH innermost as (SELECT 2 FROM outermost) -- fail
SELECT * FROM innermost
UNION SELECT * from outermost
)
SELECT * FROM outermost;
ERROR: recursive reference to query "outermost" must not appear within a subquery
LINE 2: WITH innermost as (SELECT 2 FROM outermost)
^
-- --
-- Data-modifying statements in WITH -- Data-modifying statements in WITH
-- --

View File

@ -539,6 +539,41 @@ WITH RECURSIVE t(j) AS (
) )
SELECT * FROM t; SELECT * FROM t;
--
-- test WITH attached to intermediate-level set operation
--
WITH outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM innermost
UNION SELECT 3)
)
SELECT * FROM outermost;
WITH outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM outermost -- fail
UNION SELECT * FROM innermost)
)
SELECT * FROM outermost;
WITH RECURSIVE outermost(x) AS (
SELECT 1
UNION (WITH innermost as (SELECT 2)
SELECT * FROM outermost
UNION SELECT * FROM innermost)
)
SELECT * FROM outermost;
WITH RECURSIVE outermost(x) AS (
WITH innermost as (SELECT 2 FROM outermost) -- fail
SELECT * FROM innermost
UNION SELECT * from outermost
)
SELECT * FROM outermost;
-- --
-- Data-modifying statements in WITH -- Data-modifying statements in WITH
-- --