On 2015-11-16 08:24, Michael Paquier wrote:
On Sun, Nov 1, 2015 at 2:49 AM, Marko Tiikkaja <ma...@joh.to> wrote:
Attached is a patch for being able to do $SUBJECT without a CTE. The
reasons this is better than a CTE version are:
1) It's not obvious why a CTE version works but a plain one doesn't
2) This one has less overhead (I measured a ~12% improvement on a
not-too-unreasonable test case)
Would you mind sharing this test case as well as numbers?
Attached the test case I used; removing a batch of old rows from a table
through an index. Best out of three runs, I get 13.1 seconds versus
15.0 seconds, which should amount to an improvement of around 12.7%.
Also attached a v3 of the patch which adds an Assert on top of the test
case changes suggested by you.
.m
drop table if exists tbl;
create table tbl(a serial, b int not null, c text not null);
insert into tbl (b,c) select i, random()::text from generate_series(1,
100000000) i;
alter table tbl add primary key (a);
analyze tbl;
checkpoint;
\timing
-- new code
--\copy (delete from tbl where a <= 5000000 returning *) to foo.dat
-- old code
--SET work_mem TO '1GB';
--\copy (with t as (delete from tbl where a <= 5000000 returning *) select *
from t) to foo.dat
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 112,121 **** COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
<term><replaceable class="parameter">query</replaceable></term>
<listitem>
<para>
! A <xref linkend="sql-select"> or
! <xref linkend="sql-values"> command
! whose results are to be copied.
! Note that parentheses are required around the query.
</para>
</listitem>
</varlistentry>
--- 112,128 ----
<term><replaceable class="parameter">query</replaceable></term>
<listitem>
<para>
! A <xref linkend="sql-select">, <xref linkend="sql-values">,
! <xref linkend="sql-insert">, <xref linkend="sql-update"> or
! <xref linkend="sql-delete"> command whose results are to be
! copied. Note that parentheses are required around the query.
! </para>
! <para>
! For <command>INSERT</>, <command>UPDATE</> and
! <command>DELETE</> queries a RETURNING clause must be provided,
! and the target relation must not have a conditional rule, nor
! an <literal>ALSO</> rule, nor an <literal>INSTEAD</> rule
! that expands to multiple statements.
</para>
</listitem>
</varlistentry>
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 201,207 **** typedef struct CopyStateData
int raw_buf_len; /* total # of bytes stored */
} CopyStateData;
! /* DestReceiver for COPY (SELECT) TO */
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
--- 201,207 ----
int raw_buf_len; /* total # of bytes stored */
} CopyStateData;
! /* DestReceiver for COPY (query) TO */
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
***************
*** 772,778 **** CopyLoadRawBuf(CopyState cstate)
*
* Either unload or reload contents of table <relation>, depending on <from>.
* (<from> = TRUE means we are inserting into the table.) In the "TO" case
! * we also support copying the output of an arbitrary SELECT query.
*
* If <pipe> is false, transfer is between the table and the file named
* <filename>. Otherwise, transfer is between the table and our regular
--- 772,779 ----
*
* Either unload or reload contents of table <relation>, depending on <from>.
* (<from> = TRUE means we are inserting into the table.) In the "TO" case
! * we also support copying the output of an arbitrary SELECT, INSERT, UPDATE
! * or DELETE query.
*
* If <pipe> is false, transfer is between the table and the file named
* <filename>. Otherwise, transfer is between the table and our regular
***************
*** 1374,1384 **** BeginCopy(bool is_from,
Assert(!is_from);
cstate->rel = NULL;
! /* Don't allow COPY w/ OIDs from a select */
if (cstate->oids)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("COPY (SELECT) WITH OIDS is not supported")));
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
--- 1375,1385 ----
Assert(!is_from);
cstate->rel = NULL;
! /* Don't allow COPY w/ OIDs from a query */
if (cstate->oids)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("COPY (query) WITH OIDS is not supported")));
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
***************
*** 1393,1401 **** BeginCopy(bool is_from,
rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
queryString, NULL, 0);
! /* We don't expect more or less than one result query */
! if (list_length(rewritten) != 1)
! elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten);
--- 1394,1429 ----
rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
queryString, NULL, 0);
! /* check that we got back something we can work with */
! if (rewritten == NIL)
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("DO INSTEAD NOTHING rules are not supported for COPY")));
! }
! else if (list_length(rewritten) > 1)
! {
! ListCell *lc;
!
! /* examine queries to determine which error message to issue */
! foreach(lc, rewritten)
! {
! Query *q = (Query *) lfirst(lc);
!
! if (q->querySource == QSRC_QUAL_INSTEAD_RULE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("conditional DO INSTEAD rules are not supported for COPY")));
! if (q->querySource == QSRC_NON_INSTEAD_RULE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("DO ALSO rules are not supported for the COPY")));
! }
!
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("multi-statement DO INSTEAD rules are not supported for COPY")));
! }
query = (Query *) linitial(rewritten);
***************
*** 1406,1414 **** BeginCopy(bool is_from,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
- Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
/* plan the query */
plan = pg_plan_query(query, 0, NULL);
--- 1434,1457 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
Assert(query->utilityStmt == NULL);
+ /*
+ * Similarly the grammar doesn't enforce he presence of a RETURNING
+ * clause, but we require it.
+ */
+ if (query->commandType != CMD_SELECT)
+ {
+ Assert(query->commandType == CMD_INSERT ||
+ query->commandType == CMD_UPDATE ||
+ query->commandType == CMD_DELETE);
+
+ if (query->returningList == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("COPY query must have a RETURNING clause")));
+ }
+
/* plan the query */
plan = pg_plan_query(query, 0, NULL);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 2561,2569 **** ClosePortalStmt:
*
* QUERY :
* COPY relname [(columnList)] FROM/TO file [WITH] [(options)]
! * COPY ( SELECT ... ) TO file [WITH] [(options)]
*
! * where 'file' can be one of:
* { PROGRAM 'command' | STDIN | STDOUT | 'filename' }
*
* In the preferred syntax the options are comma-separated
--- 2561,2572 ----
*
* QUERY :
* COPY relname [(columnList)] FROM/TO file [WITH] [(options)]
! * COPY ( query ) TO file [WITH] [(options)]
*
! * where 'query' is one of:
! * { select | update | insert | delete }
! *
! * and 'file' can be one of:
* { PROGRAM 'command' | STDIN | STDOUT | 'filename' }
*
* In the preferred syntax the options are comma-separated
***************
*** 2574,2580 **** ClosePortalStmt:
* COPY [ BINARY ] table [ WITH OIDS ] FROM/TO file
* [ [ USING ] DELIMITERS 'delimiter' ] ]
* [ WITH NULL AS 'null string' ]
! * This option placement is not supported with COPY (SELECT...).
*
*****************************************************************************/
--- 2577,2583 ----
* COPY [ BINARY ] table [ WITH OIDS ] FROM/TO file
* [ [ USING ] DELIMITERS 'delimiter' ] ]
* [ WITH NULL AS 'null string' ]
! * This option placement is not supported with COPY (query).
*
*****************************************************************************/
***************
*** 2607,2622 **** CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
n->options = list_concat(n->options, $11);
$$ = (Node *)n;
}
! | COPY select_with_parens TO opt_program copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
! n->query = $2;
n->attlist = NIL;
n->is_from = false;
! n->is_program = $4;
! n->filename = $5;
! n->options = $7;
if (n->is_program && n->filename == NULL)
ereport(ERROR,
--- 2610,2625 ----
n->options = list_concat(n->options, $11);
$$ = (Node *)n;
}
! | COPY '(' PreparableStmt ')' TO opt_program copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
! n->query = $3;
n->attlist = NIL;
n->is_from = false;
! n->is_program = $6;
! n->filename = $7;
! n->options = $9;
if (n->is_program && n->filename == NULL)
ereport(ERROR,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1680,1686 **** typedef struct CopyStmt
{
NodeTag type;
RangeVar *relation; /* the relation to copy */
! Node *query; /* the SELECT query to copy */
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
--- 1680,1686 ----
{
NodeTag type;
RangeVar *relation; /* the relation to copy */
! Node *query; /* the query (SELECT or I/U/D with RETURNING) to copy */
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
*** /dev/null
--- b/src/test/regress/expected/copydml.out
***************
*** 0 ****
--- 1,112 ----
+ --
+ -- Test cases for COPY (INSERT/UPDATE/DELETE) TO
+ --
+ create table copydml_test (id serial, t text);
+ insert into copydml_test (t) values ('a');
+ insert into copydml_test (t) values ('b');
+ insert into copydml_test (t) values ('c');
+ insert into copydml_test (t) values ('d');
+ insert into copydml_test (t) values ('e');
+ --
+ -- Test COPY (insert/update/delete ...)
+ --
+ copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ 6
+ copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ 6
+ copy (delete from copydml_test where t = 'g' returning id) to stdout;
+ 6
+ --
+ -- Test \copy (insert/update/delete ...)
+ --
+ \copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ 7
+ \copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ 7
+ \copy (delete from copydml_test where t = 'g' returning id) to stdout;
+ 7
+ -- Error cases
+ copy (insert into copydml_test default values) to stdout;
+ ERROR: COPY query must have a RETURNING clause
+ copy (update copydml_test set t = 'g') to stdout;
+ ERROR: COPY query must have a RETURNING clause
+ copy (delete from copydml_test) to stdout;
+ ERROR: COPY query must have a RETURNING clause
+ create rule qqq as on insert to copydml_test do instead nothing;
+ copy (insert into copydml_test default values) to stdout;
+ ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test do also delete from copydml_test;
+ copy (insert into copydml_test default values) to stdout;
+ ERROR: DO ALSO rules are not supported for the COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+ copy (insert into copydml_test default values) to stdout;
+ ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+ copy (insert into copydml_test default values) to stdout;
+ ERROR: conditional DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test do instead nothing;
+ copy (update copydml_test set t = 'f') to stdout;
+ ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test do also delete from copydml_test;
+ copy (update copydml_test set t = 'f') to stdout;
+ ERROR: DO ALSO rules are not supported for the COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+ copy (update copydml_test set t = 'f') to stdout;
+ ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+ copy (update copydml_test set t = 'f') to stdout;
+ ERROR: conditional DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test do instead nothing;
+ copy (delete from copydml_test) to stdout;
+ ERROR: DO INSTEAD NOTHING rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test do also insert into copydml_test default values;
+ copy (delete from copydml_test) to stdout;
+ ERROR: DO ALSO rules are not supported for the COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test do instead (insert into copydml_test default values; insert into copydml_test default values);
+ copy (delete from copydml_test) to stdout;
+ ERROR: multi-statement DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test where old.t <> 'f' do instead insert into copydml_test default values;
+ copy (delete from copydml_test) to stdout;
+ ERROR: conditional DO INSTEAD rules are not supported for COPY
+ drop rule qqq on copydml_test;
+ -- triggers
+ create function qqq_trig() returns trigger as $$
+ begin
+ if tg_op in ('INSERT', 'UPDATE') then
+ raise notice '% %', tg_op, new.id;
+ return new;
+ else
+ raise notice '% %', tg_op, old.id;
+ return old;
+ end if;
+ end
+ $$ language plpgsql;
+ create trigger qqqbef before insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+ create trigger qqqaf after insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+ copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ NOTICE: INSERT 8
+ 8
+ NOTICE: INSERT 8
+ copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ NOTICE: UPDATE 8
+ 8
+ NOTICE: UPDATE 8
+ copy (delete from copydml_test where t = 'g' returning id) to stdout;
+ NOTICE: DELETE 8
+ 8
+ NOTICE: DELETE 8
+ drop table copydml_test;
+ drop function qqq_trig();
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 48,54 **** test: create_function_2
# execute two copy tests parallel, to check that copy itself
# is concurrent safe.
# ----------
! test: copy copyselect
# ----------
# More groups of parallel tests
--- 48,54 ----
# execute two copy tests parallel, to check that copy itself
# is concurrent safe.
# ----------
! test: copy copyselect copydml
# ----------
# More groups of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 57,62 **** test: create_table
--- 57,63 ----
test: create_function_2
test: copy
test: copyselect
+ test: copydml
test: create_misc
test: create_operator
test: create_index
*** /dev/null
--- b/src/test/regress/sql/copydml.sql
***************
*** 0 ****
--- 1,91 ----
+ --
+ -- Test cases for COPY (INSERT/UPDATE/DELETE) TO
+ --
+ create table copydml_test (id serial, t text);
+ insert into copydml_test (t) values ('a');
+ insert into copydml_test (t) values ('b');
+ insert into copydml_test (t) values ('c');
+ insert into copydml_test (t) values ('d');
+ insert into copydml_test (t) values ('e');
+
+ --
+ -- Test COPY (insert/update/delete ...)
+ --
+ copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+ --
+ -- Test \copy (insert/update/delete ...)
+ --
+ \copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ \copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ \copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+ -- Error cases
+ copy (insert into copydml_test default values) to stdout;
+ copy (update copydml_test set t = 'g') to stdout;
+ copy (delete from copydml_test) to stdout;
+
+ create rule qqq as on insert to copydml_test do instead nothing;
+ copy (insert into copydml_test default values) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test do also delete from copydml_test;
+ copy (insert into copydml_test default values) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+ copy (insert into copydml_test default values) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on insert to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+ copy (insert into copydml_test default values) to stdout;
+ drop rule qqq on copydml_test;
+
+ create rule qqq as on update to copydml_test do instead nothing;
+ copy (update copydml_test set t = 'f') to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test do also delete from copydml_test;
+ copy (update copydml_test set t = 'f') to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test do instead (delete from copydml_test; delete from copydml_test);
+ copy (update copydml_test set t = 'f') to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on update to copydml_test where new.t <> 'f' do instead delete from copydml_test;
+ copy (update copydml_test set t = 'f') to stdout;
+ drop rule qqq on copydml_test;
+
+ create rule qqq as on delete to copydml_test do instead nothing;
+ copy (delete from copydml_test) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test do also insert into copydml_test default values;
+ copy (delete from copydml_test) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test do instead (insert into copydml_test default values; insert into copydml_test default values);
+ copy (delete from copydml_test) to stdout;
+ drop rule qqq on copydml_test;
+ create rule qqq as on delete to copydml_test where old.t <> 'f' do instead insert into copydml_test default values;
+ copy (delete from copydml_test) to stdout;
+ drop rule qqq on copydml_test;
+
+ -- triggers
+ create function qqq_trig() returns trigger as $$
+ begin
+ if tg_op in ('INSERT', 'UPDATE') then
+ raise notice '% %', tg_op, new.id;
+ return new;
+ else
+ raise notice '% %', tg_op, old.id;
+ return old;
+ end if;
+ end
+ $$ language plpgsql;
+ create trigger qqqbef before insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+ create trigger qqqaf after insert or update or delete on copydml_test
+ for each row execute procedure qqq_trig();
+
+ copy (insert into copydml_test (t) values ('f') returning id) to stdout;
+ copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
+ copy (delete from copydml_test where t = 'g' returning id) to stdout;
+
+ drop table copydml_test;
+ drop function qqq_trig();
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers