1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Allow WITH clauses to be attached to INSERT, UPDATE, DELETE statements.

This is not the hoped-for facility of using INSERT/UPDATE/DELETE inside
a WITH, but rather the other way around.  It seems useful in its own
right anyway.

Note: catversion bumped because, although the contents of stored rules
might look compatible, there's actually a subtle semantic change.
A single Query containing a WITH and INSERT...VALUES now represents
writing the WITH before the INSERT, not before the VALUES.  While it's
not clear that that matters to anyone, it seems like a good idea to
have it cited in the git history for catversion.h.

Original patch by Marko Tiikkaja, with updating and cleanup by
Hitoshi Harada.
This commit is contained in:
Tom Lane
2010-10-15 19:53:59 -04:00
parent 6ab42ae367
commit 07f1264dda
14 changed files with 333 additions and 44 deletions

View File

@ -2313,6 +2313,7 @@ _copyInsertStmt(InsertStmt *from)
COPY_NODE_FIELD(cols);
COPY_NODE_FIELD(selectStmt);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(withClause);
return newnode;
}
@ -2326,6 +2327,7 @@ _copyDeleteStmt(DeleteStmt *from)
COPY_NODE_FIELD(usingClause);
COPY_NODE_FIELD(whereClause);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(withClause);
return newnode;
}
@ -2340,6 +2342,7 @@ _copyUpdateStmt(UpdateStmt *from)
COPY_NODE_FIELD(whereClause);
COPY_NODE_FIELD(fromClause);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(withClause);
return newnode;
}

View File

@ -890,6 +890,7 @@ _equalInsertStmt(InsertStmt *a, InsertStmt *b)
COMPARE_NODE_FIELD(cols);
COMPARE_NODE_FIELD(selectStmt);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(withClause);
return true;
}
@ -901,6 +902,7 @@ _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
COMPARE_NODE_FIELD(usingClause);
COMPARE_NODE_FIELD(whereClause);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(withClause);
return true;
}
@ -913,6 +915,7 @@ _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
COMPARE_NODE_FIELD(whereClause);
COMPARE_NODE_FIELD(fromClause);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(withClause);
return true;
}

View File

@ -283,6 +283,13 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
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),
@ -340,9 +347,19 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
ListCell *attnos;
ListCell *lc;
/* There can't be any outer WITH to worry about */
Assert(pstate->p_ctenamespace == NIL);
qry->commandType = CMD_INSERT;
pstate->p_is_insert = true;
/* process the WITH clause independently of all else */
if (stmt->withClause)
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
}
/*
* We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
* VALUES list, or general SELECT input. We special-case VALUES, both for
@ -376,8 +393,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
pstate->p_relnamespace = NIL;
sub_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = NIL;
/* There can't be any outer WITH to worry about */
Assert(pstate->p_ctenamespace == NIL);
}
else
{
@ -518,13 +533,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *exprsLists = NIL;
int sublist_length = -1;
/* process the WITH clause */
if (selectStmt->withClause)
{
qry->hasRecursive = selectStmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, selectStmt->withClause);
}
foreach(lc, selectStmt->valuesLists)
{
List *sublist = (List *) lfirst(lc);
@ -618,13 +626,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
Assert(list_length(valuesLists) == 1);
/* process the WITH clause */
if (selectStmt->withClause)
{
qry->hasRecursive = selectStmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, selectStmt->withClause);
}
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate,
(List *) linitial(valuesLists));
@ -1794,6 +1795,13 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
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,

View File

@ -433,7 +433,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <boolean> xml_whitespace_option
%type <node> common_table_expr
%type <with> with_clause
%type <with> with_clause opt_with_clause
%type <list> cte_list
%type <list> window_clause window_definition_list opt_partition_clause
@ -7269,11 +7269,12 @@ DeallocateStmt: DEALLOCATE name
*****************************************************************************/
InsertStmt:
INSERT INTO qualified_name insert_rest returning_clause
opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
{
$4->relation = $3;
$4->returningList = $5;
$$ = (Node *) $4;
$5->relation = $4;
$5->returningList = $6;
$5->withClause = $1;
$$ = (Node *) $5;
}
;
@ -7329,14 +7330,15 @@ returning_clause:
*
*****************************************************************************/
DeleteStmt: DELETE_P FROM relation_expr_opt_alias
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 = $3;
n->usingClause = $4;
n->whereClause = $5;
n->returningList = $6;
n->relation = $4;
n->usingClause = $5;
n->whereClause = $6;
n->returningList = $7;
n->withClause = $1;
$$ = (Node *)n;
}
;
@ -7391,18 +7393,19 @@ opt_nowait: NOWAIT { $$ = TRUE; }
*
*****************************************************************************/
UpdateStmt: UPDATE relation_expr_opt_alias
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 = $2;
n->targetList = $4;
n->fromClause = $5;
n->whereClause = $6;
n->returningList = $7;
n->relation = $3;
n->targetList = $5;
n->fromClause = $6;
n->whereClause = $7;
n->returningList = $8;
n->withClause = $1;
$$ = (Node *)n;
}
;
@ -7744,6 +7747,11 @@ common_table_expr: name opt_name_list AS select_with_parens
}
;
opt_with_clause:
with_clause { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
into_clause:
INTO OptTempTableName
{

View File

@ -1867,6 +1867,35 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
break;
}
/*
* OLD/NEW are not allowed in WITH queries, because they would
* amount to outer references for the WITH, which we disallow.
* However, they were already in the outer rangetable when we
* analyzed the query, so we have to check.
*
* Note that in the INSERT...SELECT case, we need to examine
* the CTE lists of both top_subqry and sub_qry.
*
* Note that we aren't digging into the body of the query
* looking for WITHs in nested sub-SELECTs. A WITH down there
* can legitimately refer to OLD/NEW, because it'd be an
* indirect-correlated outer reference.
*/
if (rangeTableEntry_used((Node *) top_subqry->cteList,
PRS2_OLD_VARNO, 0) ||
rangeTableEntry_used((Node *) sub_qry->cteList,
PRS2_OLD_VARNO, 0))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot refer to OLD within WITH query")));
if (rangeTableEntry_used((Node *) top_subqry->cteList,
PRS2_NEW_VARNO, 0) ||
rangeTableEntry_used((Node *) sub_qry->cteList,
PRS2_NEW_VARNO, 0))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot refer to NEW within WITH query")));
/*
* For efficiency's sake, add OLD to the rule action's jointree
* only if it was actually referenced in the statement or qual.

View File

@ -3352,6 +3352,9 @@ get_insert_query_def(Query *query, deparse_context *context)
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.
@ -3451,15 +3454,11 @@ get_insert_query_def(Query *query, deparse_context *context)
}
else if (values_rte)
{
/* A WITH clause is possible here */
get_with_clause(query, context);
/* Add the multi-VALUES expression lists */
get_values_def(values_rte->values_lists, context);
}
else
{
/* A WITH clause is possible here */
get_with_clause(query, context);
/* Add the single-VALUES expression list */
appendContextKeyword(context, "VALUES (",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
@ -3489,6 +3488,9 @@ get_update_query_def(Query *query, deparse_context *context)
RangeTblEntry *rte;
ListCell *l;
/* Insert the WITH clause if given */
get_with_clause(query, context);
/*
* Start the query with UPDATE relname SET
*/
@ -3570,6 +3572,9 @@ get_delete_query_def(Query *query, deparse_context *context)
StringInfo buf = context->buf;
RangeTblEntry *rte;
/* Insert the WITH clause if given */
get_with_clause(query, context);
/*
* Start the query with DELETE FROM relname
*/

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201010101
#define CATALOG_VERSION_NO 201010151
#endif

View File

@ -896,6 +896,7 @@ typedef struct InsertStmt
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,6 +910,7 @@ typedef struct DeleteStmt
List *usingClause; /* optional using clause for more tables */
Node *whereClause; /* qualifications */
List *returningList; /* list of expressions to return */
WithClause *withClause; /* WITH clause */
} DeleteStmt;
/* ----------------------
@ -923,6 +925,7 @@ typedef struct UpdateStmt
Node *whereClause; /* qualifications */
List *fromClause; /* optional from clause for more tables */
List *returningList; /* list of expressions to return */
WithClause *withClause; /* WITH clause */
} UpdateStmt;
/* ----------------------

View File

@ -737,6 +737,134 @@ WITH RECURSIVE
10
(54 rows)
--
-- Test WITH attached to 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)
SELECT * FROM y;
a
----
1
2
3
4
5
6
7
8
9
10
21
22
23
24
25
26
27
28
29
30
(20 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)
SELECT * FROM y;
a
----
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(20 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)
DROP TABLE y;
--
-- error cases
--
@ -912,6 +1040,11 @@ ERROR: recursive query "foo" column 1 has type numeric(3,0) in non-recursive te
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 within WITH query
--
-- test for bug #4902
--

View File

@ -338,6 +338,39 @@ WITH RECURSIVE
(SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
SELECT * FROM z;
--
-- Test WITH attached to 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 *;
SELECT * FROM y;
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;
SELECT * FROM y;
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;
DROP TABLE y;
--
-- error cases
--
@ -470,6 +503,11 @@ WITH RECURSIVE foo(i) AS
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
--