toplevel-dml-cte.20100917.patch
application/octet-stream
Filename: toplevel-dml-cte.20100917.patch
Type: application/octet-stream
Part: 0
Message:
Re: top-level DML under CTEs
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/delete.sgml | 13 | 0 |
| doc/src/sgml/ref/insert.sgml | 19 | 0 |
| doc/src/sgml/ref/update.sgml | 13 | 0 |
| src/backend/nodes/copyfuncs.c | 3 | 0 |
| src/backend/nodes/equalfuncs.c | 3 | 0 |
| src/backend/parser/analyze.c | 47 | 0 |
| src/backend/parser/gram.y | 25 | 0 |
| src/backend/parser/parse_utilcmd.c | 8 | 0 |
| src/backend/rewrite/rewriteManip.c | 24 | 0 |
| src/backend/utils/adt/ruleutils.c | 9 | 0 |
| src/include/nodes/parsenodes.h | 3 | 0 |
| src/include/rewrite/rewriteManip.h | 1 | 0 |
| src/test/regress/expected/with.out | 82 | 0 |
| src/test/regress/sql/with.sql | 32 | 0 |
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
+ [ WITH [ RECURSIVE ] with_query ]
DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
[ USING <replaceable class="PARAMETER">using_list</replaceable> ]
[ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
***************
*** 84,89 **** DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ]
--- 85,102 ----
<variablelist>
<varlistentry>
+ <term><replaceable class="PARAMETER">with_query</replaceable></term>
+ <listitem>
+ <para>
+ The <literal>WITH</literal> clause allows you to specify one or more
+ subqueries that can be referenced by name in the primary query.
+ See <xref linkend="queries-with"> and <xref linkend="sql-select">
+ for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>ONLY</></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
+ [ WITH [ RECURSIVE ] with_query ]
INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) ]
{ DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> }
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
***************
*** 85,90 **** INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable
--- 86,109 ----
<variablelist>
<varlistentry>
+ <term><replaceable class="PARAMETER">with_query</replaceable></term>
+ <listitem>
+ <para>
+ The <literal>WITH</literal> clause allows you to specify one or more
+ subqueries that can be referenced by name in the primary query.
+ See <xref linkend="queries-with"> and <xref linkend="sql-select">
+ for details.
+ </para>
+ <para>
+ It is possible that <literal>SELECT</literal> query also has
+ <literal>WITH</literal>. In this case the two
+ <replaceable>with_query</replaceable> can be referred from
+ the <literal>SELECT</literal> query.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">table</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 21,26 **** PostgreSQL documentation
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
+ [ WITH [ RECURSIVE ] with_query ]
UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
***************
*** 80,85 **** UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
--- 81,98 ----
<variablelist>
<varlistentry>
+ <term><replaceable class="PARAMETER">with_query</replaceable></term>
+ <listitem>
+ <para>
+ The <literal>WITH</literal> clause allows you to specify one or more
+ subqueries that can be referenced by name in the primary query.
+ See <xref linkend="queries-with"> and <xref linkend="sql-select">
+ for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">table</replaceable></term>
<listitem>
<para>
*** 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),
***************
*** 342,347 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 349,361 ----
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
***************
*** 366,373 **** 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
{
--- 380,385 ----
***************
*** 511,518 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* process the WITH clause */
if (selectStmt->withClause)
{
! qry->hasRecursive = selectStmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, selectStmt->withClause);
}
foreach(lc, selectStmt->valuesLists)
--- 523,542 ----
/* process the WITH clause */
if (selectStmt->withClause)
{
! List *cteList = qry->cteList;
!
! /* pretend CTE transform is brand-new */
! pstate->p_ctenamespace = NIL;
! pstate->p_future_ctes = NIL;
! qry->hasRecursive |= selectStmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+ /*
+ * To match the behavior to generalSelect case,
+ * SELECT's CTE shoud come first. It is also to
+ * save both(top-level and SELECT-level CTEs
+ * in the pstate->p_ctenamespace.
+ */
+ qry->cteList = list_concat(qry->cteList, cteList);
}
foreach(lc, selectStmt->valuesLists)
***************
*** 611,618 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* 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) */
--- 635,654 ----
/* process the WITH clause */
if (selectStmt->withClause)
{
! List *cteList = qry->cteList;
!
! /* pretend CTE transform is brand-new */
! pstate->p_ctenamespace = NIL;
! pstate->p_future_ctes = NIL;
! qry->hasRecursive |= selectStmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, selectStmt->withClause);
+ /*
+ * To match the behavior to generalSelect case,
+ * SELECT's CTE shoud come first. It is also to
+ * save both(top-level and SELECT-level CTEs
+ * in the pstate->p_ctenamespace.
+ */
+ qry->cteList = list_concat(qry->cteList, cteList);
}
/* Do basic expression transformation (same as a ROW() expr) */
***************
*** 1726,1731 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1762,1774 ----
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 <boolean> xml_whitespace_option
%type <node> common_table_expr
! %type <with> with_clause
%type <list> cte_list
%type <list> window_clause window_definition_list opt_partition_clause
--- 430,436 ----
%type <boolean> xml_whitespace_option
%type <node> common_table_expr
! %type <with> with_clause opt_with_clause
%type <list> cte_list
%type <list> 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
--