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:
parent
b76356ac22
commit
f6ce81f55a
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
--
|
--
|
||||||
|
|
|
@ -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
|
||||||
--
|
--
|
||||||
|
|
Loading…
Reference in New Issue