diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9d9de7c425..799930ad61 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2494,12 +2494,12 @@ _copySelectStmt(const SelectStmt *from) COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(havingClause); COPY_NODE_FIELD(windowClause); - COPY_NODE_FIELD(withClause); COPY_NODE_FIELD(valuesLists); COPY_NODE_FIELD(sortClause); COPY_NODE_FIELD(limitOffset); COPY_NODE_FIELD(limitCount); COPY_NODE_FIELD(lockingClause); + COPY_NODE_FIELD(withClause); COPY_SCALAR_FIELD(op); COPY_SCALAR_FIELD(all); COPY_NODE_FIELD(larg); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6d4030a322..802b063671 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -976,12 +976,12 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b) COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(havingClause); COMPARE_NODE_FIELD(windowClause); - COMPARE_NODE_FIELD(withClause); COMPARE_NODE_FIELD(valuesLists); COMPARE_NODE_FIELD(sortClause); COMPARE_NODE_FIELD(limitOffset); COMPARE_NODE_FIELD(limitCount); COMPARE_NODE_FIELD(lockingClause); + COMPARE_NODE_FIELD(withClause); COMPARE_SCALAR_FIELD(op); COMPARE_SCALAR_FIELD(all); COMPARE_NODE_FIELD(larg); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 813d1da1a2..b130902dcf 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2909,8 +2909,6 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->windowClause, context)) return true; - if (walker(stmt->withClause, context)) - return true; if (walker(stmt->valuesLists, context)) return true; if (walker(stmt->sortClause, context)) @@ -2921,6 +2919,8 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->lockingClause, context)) return true; + if (walker(stmt->withClause, context)) + return true; if (walker(stmt->larg, context)) return true; if (walker(stmt->rarg, context)) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 91b54265af..b83bd1c9fd 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2037,12 +2037,12 @@ _outSelectStmt(StringInfo str, const SelectStmt *node) WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(havingClause); WRITE_NODE_FIELD(windowClause); - WRITE_NODE_FIELD(withClause); WRITE_NODE_FIELD(valuesLists); WRITE_NODE_FIELD(sortClause); WRITE_NODE_FIELD(limitOffset); WRITE_NODE_FIELD(limitCount); WRITE_NODE_FIELD(lockingClause); + WRITE_NODE_FIELD(withClause); WRITE_ENUM_FIELD(op, SetOperation); WRITE_BOOL_FIELD(all); WRITE_NODE_FIELD(larg); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index bfd3ab941a..263edb5a7a 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1322,6 +1322,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) Node *limitOffset; Node *limitCount; List *lockingClause; + WithClause *withClause; Node *node; ListCell *left_tlist, *lct, @@ -1338,14 +1339,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) 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 * 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; limitCount = stmt->limitCount; lockingClause = stmt->lockingClause; + withClause = stmt->withClause; stmt->sortClause = NIL; stmt->limitOffset = NULL; stmt->limitCount = NULL; stmt->lockingClause = NIL; + stmt->withClause = NULL; /* We don't support FOR UPDATE/SHARE with set ops at the moment. */ if (lockingClause) @@ -1387,6 +1382,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 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. */ @@ -1572,10 +1575,10 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, 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 - * clauses attached, we need to treat it like a leaf node to generate an - * independent sub-Query tree. Otherwise, it can be represented by a - * SetOperationStmt node underneath the parent Query. + * If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE, + * or WITH clauses attached, we need to treat it like a leaf node to + * generate an independent sub-Query tree. Otherwise, it can be + * represented by a SetOperationStmt node underneath the parent Query. */ if (stmt->op == SETOP_NONE) { @@ -1586,7 +1589,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, { Assert(stmt->larg != NULL && stmt->rarg != NULL); if (stmt->sortClause || stmt->limitOffset || stmt->limitCount || - stmt->lockingClause) + stmt->lockingClause || stmt->withClause) isLeaf = true; else isLeaf = false; diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 2a7d4cd072..d04fb6dec1 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -678,6 +678,18 @@ checkWellFormedRecursion(CteState *cstate) 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 @@ -933,7 +945,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate) cstate); checkWellFormedRecursionWalker((Node *) stmt->lockingClause, cstate); - break; + /* stmt->withClause is intentionally ignored here */ break; case SETOP_EXCEPT: if (stmt->all) @@ -952,6 +964,7 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate) cstate); checkWellFormedRecursionWalker((Node *) stmt->lockingClause, cstate); + /* stmt->withClause is intentionally ignored here */ break; default: elog(ERROR, "unrecognized set op: %d", diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 871a7d1ce3..cf2ff0cf70 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -705,12 +705,12 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p) stmt->groupClause != NIL || stmt->havingClause != NULL || stmt->windowClause != NIL || - stmt->withClause != NULL || stmt->valuesLists != NIL || stmt->sortClause != NIL || stmt->limitOffset != NULL || stmt->limitCount != NULL || stmt->lockingClause != NIL || + stmt->withClause != NULL || stmt->op != SETOP_NONE) goto fail; if (list_length(stmt->targetList) != 1) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1f89cd5159..119e1ed2f6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1016,7 +1016,6 @@ typedef struct SelectStmt List *groupClause; /* GROUP BY clauses */ Node *havingClause; /* HAVING conditional-expression */ List *windowClause; /* WINDOW window_name AS (...), ... */ - WithClause *withClause; /* WITH clause */ /* * 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 *limitCount; /* # of result tuples to return */ List *lockingClause; /* FOR UPDATE (list of LockingClause's) */ + WithClause *withClause; /* WITH clause */ /* * These fields are used only in upper-level SelectStmts. diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 2a41736cc8..0d59ea3fdf 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -1158,6 +1158,57 @@ SELECT * FROM t; 10 (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 -- diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index 9c6732b961..22fdddc4ee 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -539,6 +539,41 @@ WITH RECURSIVE t(j) AS ( ) 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 --