*** a/doc/src/sgml/ref/delete.sgml --- b/doc/src/sgml/ref/delete.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query ] DELETE FROM [ ONLY ] table [ [ AS ] alias ] [ USING using_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] *************** *** 84,89 **** DELETE FROM [ ONLY ] table [ [ AS ] --- 85,102 ---- + with_query + + + The WITH clause allows you to specify one or more + subqueries that can be referenced by name in the primary query. + See and + for details. + + + + + ONLY *** a/doc/src/sgml/ref/insert.sgml --- b/doc/src/sgml/ref/insert.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query ] INSERT INTO table [ ( column [, ...] ) ] { DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query } [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] *************** *** 85,90 **** INSERT INTO table [ ( + with_query + + + The WITH clause allows you to specify one or more + subqueries that can be referenced by name in the primary query. + See and + for details. + + + It is possible that SELECT query also has + WITH. In this case the two + with_query can be referred from + the SELECT query. + + + + + table *** a/doc/src/sgml/ref/update.sgml --- b/doc/src/sgml/ref/update.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query ] UPDATE [ ONLY ] table [ [ AS ] alias ] SET { column = { expression | DEFAULT } | ( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...] *************** *** 80,85 **** UPDATE [ ONLY ] table [ [ AS ] + with_query + + + The WITH clause allows you to specify one or more + subqueries that can be referenced by name in the primary query. + See and + for details. + + + + + table *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 2287,2292 **** _copyInsertStmt(InsertStmt *from) --- 2287,2293 ---- COPY_NODE_FIELD(cols); COPY_NODE_FIELD(selectStmt); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *************** *** 2300,2305 **** _copyDeleteStmt(DeleteStmt *from) --- 2301,2307 ---- COPY_NODE_FIELD(usingClause); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *************** *** 2314,2319 **** _copyUpdateStmt(UpdateStmt *from) --- 2316,2322 ---- COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 889,894 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b) --- 889,895 ---- COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(selectStmt); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *************** *** 900,905 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b) --- 901,907 ---- COMPARE_NODE_FIELD(usingClause); COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *************** *** 912,917 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b) --- 914,920 ---- COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(fromClause); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 282,287 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) --- 282,294 ---- qry->commandType = CMD_DELETE; + /* process the WITH clause independently of all else */ + if (stmt->withClause) + { + qry->hasRecursive = stmt->withClause->recursive; + qry->cteList = transformWithClause(pstate, stmt->withClause); + } + /* set up range table with just the result rel */ qry->resultRelation = setTargetTable(pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), *************** *** 343,348 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt) --- 350,374 ---- pstate->p_is_insert = true; /* + * The WITH of INSERT is interpreted as WITH of SELECT (or VALUES) and + * the original WITH of SELECT is appended to that of INSERT. + */ + if (stmt->withClause) + { + if (selectStmt && selectStmt->withClause) + { + WithClause *with = selectStmt->withClause; + with->recursive |= stmt->withClause->recursive; + with->ctes = list_concat(copyObject(stmt->withClause->ctes), with->ctes); + } + else if (selectStmt) + { + selectStmt->withClause = stmt->withClause; + } + stmt->withClause = NULL; + } + + /* * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL), * VALUES list, or general SELECT input. We special-case VALUES, both for * efficiency and so we can handle DEFAULT specifications. *************** *** 1726,1731 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) --- 1752,1764 ---- qry->commandType = CMD_UPDATE; pstate->p_is_update = true; + /* process the WITH clause independently of all else */ + if (stmt->withClause) + { + qry->hasRecursive = stmt->withClause->recursive; + qry->cteList = transformWithClause(pstate, stmt->withClause); + } + qry->resultRelation = setTargetTable(pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), true, *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 430,436 **** static TypeName *TableFuncTypeName(List *columns); %type xml_whitespace_option %type common_table_expr ! %type with_clause %type cte_list %type window_clause window_definition_list opt_partition_clause --- 430,436 ---- %type xml_whitespace_option %type common_table_expr ! %type with_clause opt_with_clause %type cte_list %type window_clause window_definition_list opt_partition_clause *************** *** 7129,7139 **** DeallocateStmt: DEALLOCATE name *****************************************************************************/ InsertStmt: ! INSERT INTO qualified_name insert_rest returning_clause { ! $4->relation = $3; ! $4->returningList = $5; ! $$ = (Node *) $4; } ; --- 7129,7140 ---- *****************************************************************************/ InsertStmt: ! opt_with_clause INSERT INTO qualified_name insert_rest returning_clause { ! $5->relation = $4; ! $5->returningList = $6; ! $5->withClause = $1; ! $$ = (Node *) $5; } ; *************** *** 7189,7202 **** returning_clause: * *****************************************************************************/ ! DeleteStmt: DELETE_P FROM relation_expr_opt_alias using_clause where_or_current_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); ! n->relation = $3; ! n->usingClause = $4; ! n->whereClause = $5; ! n->returningList = $6; $$ = (Node *)n; } ; --- 7190,7204 ---- * *****************************************************************************/ ! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias using_clause where_or_current_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); ! n->relation = $4; ! n->usingClause = $5; ! n->whereClause = $6; ! n->returningList = $7; ! n->withClause = $1; $$ = (Node *)n; } ; *************** *** 7251,7268 **** opt_nowait: NOWAIT { $$ = TRUE; } * *****************************************************************************/ ! UpdateStmt: UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause returning_clause { UpdateStmt *n = makeNode(UpdateStmt); ! n->relation = $2; ! n->targetList = $4; ! n->fromClause = $5; ! n->whereClause = $6; ! n->returningList = $7; $$ = (Node *)n; } ; --- 7253,7271 ---- * *****************************************************************************/ ! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause returning_clause { UpdateStmt *n = makeNode(UpdateStmt); ! n->relation = $3; ! n->targetList = $5; ! n->fromClause = $6; ! n->whereClause = $7; ! n->returningList = $8; ! n->withClause = $1; $$ = (Node *)n; } ; *************** *** 7604,7609 **** common_table_expr: name opt_name_list AS select_with_parens --- 7607,7618 ---- } ; + opt_with_clause: + with_clause { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + + into_clause: INTO OptTempTableName { *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** *** 1865,1870 **** transformRuleStmt(RuleStmt *stmt, const char *queryString, --- 1865,1878 ---- } /* + * OLD/NEW is not allowed in CTE queries. + */ + if (checkCTEHasOldNew(sub_qry)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot refer to OLD/NEW in CTE query"))); + + /* * For efficiency's sake, add OLD to the rule action's jointree * only if it was actually referenced in the statement or qual. * *** a/src/backend/rewrite/rewriteManip.c --- b/src/backend/rewrite/rewriteManip.c *************** *** 291,296 **** checkExprHasSubLink_walker(Node *node, void *context) --- 291,320 ---- return expression_tree_walker(node, checkExprHasSubLink_walker, context); } + /* + * checkCTEHasOldNew - check if OLD/NEW is referred in CTE queries. + */ + bool + checkCTEHasOldNew(Query *node) + { + ListCell *l; + + foreach (l, node->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); + int new_varno = PRS2_NEW_VARNO; + int old_varno = PRS2_OLD_VARNO; + + /* 1 == the top CTE */ + if (rangeTableEntry_used(cte->ctequery, new_varno, 1)) + return true; + + if (rangeTableEntry_used(cte->ctequery, old_varno, 1)) + return true; + } + + return false; + } /* * OffsetVarNodes - adjust Vars when appending one query's RT to another *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 3345,3350 **** get_insert_query_def(Query *query, deparse_context *context) --- 3345,3353 ---- ListCell *l; List *strippedexprs; + /* Insert the WITH clause if given */ + get_with_clause(query, context); + /* * If it's an INSERT ... SELECT or VALUES (...), (...), ... there will be * a single RTE for the SELECT or VALUES. *************** *** 3482,3487 **** get_update_query_def(Query *query, deparse_context *context) --- 3485,3493 ---- RangeTblEntry *rte; ListCell *l; + /* Insert the WITH clause if given */ + get_with_clause(query, context); + /* * Start the query with UPDATE relname SET */ *************** *** 3563,3568 **** get_delete_query_def(Query *query, deparse_context *context) --- 3569,3577 ---- StringInfo buf = context->buf; RangeTblEntry *rte; + /* Insert the WITH clause if given */ + get_with_clause(query, context); + /* * Start the query with DELETE FROM relname */ *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 896,901 **** typedef struct InsertStmt --- 896,902 ---- List *cols; /* optional: names of the target columns */ Node *selectStmt; /* the source SELECT/VALUES, or NULL */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } InsertStmt; /* ---------------------- *************** *** 909,914 **** typedef struct DeleteStmt --- 910,916 ---- List *usingClause; /* optional using clause for more tables */ Node *whereClause; /* qualifications */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } DeleteStmt; /* ---------------------- *************** *** 923,928 **** typedef struct UpdateStmt --- 925,931 ---- Node *whereClause; /* qualifications */ List *fromClause; /* optional from clause for more tables */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } UpdateStmt; /* ---------------------- *** a/src/include/rewrite/rewriteManip.h --- b/src/include/rewrite/rewriteManip.h *************** *** 56,61 **** extern int locate_windowfunc(Node *node); --- 56,62 ---- extern bool checkExprHasAggs(Node *node); extern bool checkExprHasWindowFuncs(Node *node); extern bool checkExprHasSubLink(Node *node); + extern bool checkCTEHasOldNew(Query *node); extern Node *replace_rte_variables(Node *node, int target_varno, int sublevels_up, *** a/src/test/regress/expected/with.out --- b/src/test/regress/expected/with.out *************** *** 738,743 **** WITH RECURSIVE --- 738,820 ---- (54 rows) -- + -- WITH on top of a DML statement + -- + CREATE TEMPORARY TABLE y (a INTEGER); + INSERT INTO y SELECT generate_series(1, 10); + WITH t AS ( + SELECT a FROM y + ) + INSERT INTO y + SELECT a+20 FROM t RETURNING *; + a + ---- + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + (10 rows) + + WITH t AS ( + SELECT a FROM y + ) + UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a; + a + ---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + (10 rows) + + WITH RECURSIVE t(a) AS ( + SELECT 11 + UNION ALL + SELECT a+1 FROM t WHERE a < 50 + ) + DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a; + a + ---- + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + (10 rows) + + SELECT * FROM y; + a + ---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + (10 rows) + + -- -- error cases -- -- INTERSECT *************** *** 774,781 **** WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) ERROR: recursive reference to query "x" must not appear within its non-recursive term LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) ^ - CREATE TEMPORARY TABLE y (a INTEGER); - INSERT INTO y SELECT generate_series(1, 10); -- LEFT JOIN WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 UNION ALL --- 851,856 ---- *************** *** 912,917 **** ERROR: recursive query "foo" column 1 has type numeric(3,0) in non-recursive te --- 987,997 ---- LINE 2: (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i) ^ HINT: Cast the output of the non-recursive term to the correct type. + -- disallow OLD/NEW reference in CTE + CREATE TEMPORARY TABLE x (n integer); + CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD + WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t; + ERROR: cannot refer to OLD/NEW in CTE query -- -- test for bug #4902 -- *** a/src/test/regress/sql/with.sql --- b/src/test/regress/sql/with.sql *************** *** 339,344 **** WITH RECURSIVE --- 339,371 ---- SELECT * FROM z; -- + -- WITH on top of a DML statement + -- + + CREATE TEMPORARY TABLE y (a INTEGER); + INSERT INTO y SELECT generate_series(1, 10); + + WITH t AS ( + SELECT a FROM y + ) + INSERT INTO y + SELECT a+20 FROM t RETURNING *; + + WITH t AS ( + SELECT a FROM y + ) + UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a; + + WITH RECURSIVE t(a) AS ( + SELECT 11 + UNION ALL + SELECT a+1 FROM t WHERE a < 50 + ) + DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a; + + SELECT * FROM y; + + -- -- error cases -- *************** *** 364,372 **** WITH RECURSIVE x(n) AS (SELECT n FROM x) WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1) SELECT * FROM x; - CREATE TEMPORARY TABLE y (a INTEGER); - INSERT INTO y SELECT generate_series(1, 10); - -- LEFT JOIN WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1 --- 391,396 ---- *************** *** 470,475 **** WITH RECURSIVE foo(i) AS --- 494,504 ---- SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10) SELECT * FROM foo; + -- disallow OLD/NEW reference in CTE + CREATE TEMPORARY TABLE x (n integer); + CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD + WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t; + -- -- test for bug #4902 --