[PATCH] Implement INSERT SET syntax
Hello,
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values may also be sourced from a CTE using a FROM clause:
WITH x AS (
SELECT 'foo' AS c1, 'bar' AS c2, 'baz' AS c3
)
INSERT INTO t SET c1 = x.c1, c2 = x.c2, c3 = x.c3 FROM x;
The advantage of using the SET clause style is that the column and value
are kept together, which can make changing or removing a column or value from
a large list easier.
Internally the grammar parser converts INSERT SET without a FROM clause into
the equivalent INSERT with a VALUES clause. When using a FROM clause it becomes
the equivalent of INSERT with a SELECT statement.
There was a brief discussion regarding INSERT SET on pgsql-hackers in late
August 2009 [1]/messages/by-id/2c5ef4e30908251010s46d9d566m1da21357891bab3d@mail.gmail.com.
INSERT SET is not part of any SQL standard (that I am aware of), however this
syntax is also implemented by MySQL [2]https://dev.mysql.com/doc/refman/8.0/en/insert.html. Their implementation does not support
specifying a FROM clause.
Patch also contains regression tests and documentation.
Regards,
Gareth
[1]: /messages/by-id/2c5ef4e30908251010s46d9d566m1da21357891bab3d@mail.gmail.com
[2]: https://dev.mysql.com/doc/refman/8.0/en/insert.html
Attachments:
insert-set-v1.patchapplication/octet-stream; name=insert-set-v1.patch; x-unix-mode=0644Download
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index f995a7637f..341616d8b5 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -24,7 +24,12 @@ PostgreSQL documentation
[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
[ OVERRIDING { SYSTEM | USER} VALUE ]
- { DEFAULT VALUES | VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="parameter">query</replaceable> }
+ { DEFAULT VALUES | VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) [, ...] |
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( <replaceable class="parameter">sub-SELECT</replaceable> )
+ } [, ...] [ FROM <replaceable class="parameter">from_list</replaceable> ] } |
+ <replaceable class="parameter">query</replaceable> }
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
@@ -254,6 +259,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_list</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -675,6 +690,13 @@ WITH upd AS (
RETURNING *
)
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
+</programlisting>
+ </para>
+ <para>
+ Insert a single row into table <literal>distributors</literal> using a
+<literal>SET</literal> clause to specify the columns and values:
+<programlisting>
+INSERT INTO distributors SET did = 4, dname = 'Hammers Unlimited, LLC';
</programlisting>
</para>
<para>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..3015a2925a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -463,7 +463,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -10892,6 +10892,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -10923,6 +10932,35 @@ insert_column_item:
}
;
+insert_set_clause:
+ SET set_clause_list from_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ if ($3 != NULL)
+ {
+ n->targetList = $2;
+ n->fromClause = $3;
+ }
+ else
+ {
+ List *values = NIL;
+ ListCell *col_cell;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ }
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 75e25cdf48..c15260ea76 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -80,6 +80,22 @@ select col1, col2, char_length(col3) from inserttest;
30 | 50 | 10000
(8 rows)
+drop table inserttest;
+--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+select * from inserttest;
+ col1 | col2
+------+------
+ 1 | foo
+ 2 | bar
+ 3 | baz
+(3 rows)
+
drop table inserttest;
--
-- check indirection (field/array assignment), cf bug #14265
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 2a2085556b..b92f9f8a13 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,27 @@ SELECT * FROM y;
10
(10 rows)
+DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..00747e0939 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -37,6 +37,18 @@ select col1, col2, char_length(col3) from inserttest;
drop table inserttest;
+--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+
+select * from inserttest;
+
+drop table inserttest;
+
--
-- check indirection (field/array assignment), cf bug #14265
--
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..e6d2e13714 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -406,6 +406,16 @@ SELECT * FROM y;
DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
+DROP TABLE y;
+
--
-- error cases
--
On Wed, Jul 17, 2019 at 7:30 AM Gareth Palmer <gareth@internetnz.net.nz>
wrote:
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.
Cool! Thanks for working on this, I'd love to see the syntax in PG.
There was a brief discussion regarding INSERT SET on pgsql-hackers in late
August 2009 [1].
There was also at least one slightly more recent adventure:
/messages/by-id/709e06c0-59c9-ccec-d216-21e38cb5ed61@joh.to
You might want to check that thread too, in case any of the criticism there
applies to this patch as well.
.m
Hi Marko,
On 17/07/2019, at 5:52 PM, Marko Tiikkaja <marko@joh.to> wrote:
On Wed, Jul 17, 2019 at 7:30 AM Gareth Palmer <gareth@internetnz.net.nz> wrote:
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.Cool! Thanks for working on this, I'd love to see the syntax in PG.
There was a brief discussion regarding INSERT SET on pgsql-hackers in late
August 2009 [1].There was also at least one slightly more recent adventure: /messages/by-id/709e06c0-59c9-ccec-d216-21e38cb5ed61@joh.to
You might want to check that thread too, in case any of the criticism there applies to this patch as well.
Thank-you for the pointer to that thread.
I think my version avoids issue raised there by doing the conversion of the SET clause as part of the INSERT grammar rules.
Gareth
Hello.
At Thu, 18 Jul 2019 11:30:04 +1200, Gareth Palmer <gareth@internetnz.net.nz> wrote in <D50A93EB-11F3-4ED2-8192-0328DF901BBA@internetnz.net.nz>
Hi Marko,
On 17/07/2019, at 5:52 PM, Marko Tiikkaja <marko@joh.to> wrote:
On Wed, Jul 17, 2019 at 7:30 AM Gareth Palmer <gareth@internetnz.net.nz> wrote:
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.Cool! Thanks for working on this, I'd love to see the syntax in PG.
There was a brief discussion regarding INSERT SET on pgsql-hackers in late
August 2009 [1].There was also at least one slightly more recent adventure: /messages/by-id/709e06c0-59c9-ccec-d216-21e38cb5ed61@joh.to
You might want to check that thread too, in case any of the criticism there applies to this patch as well.
Thank-you for the pointer to that thread.
I think my version avoids issue raised there by doing the conversion of the SET clause as part of the INSERT grammar rules.
If I'm not missing something, "SELECT <targetlist>" without
having FROM clause doesn't need to be tweaked. Thus
insert_set_clause is useless and all we need here would be
something like the following. (and the same for OVERRIDING.)
+ | SET set_clause_list from_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->targetList = $2;
+ n->fromClause = $3;
+ $$ = makeNode(InsertStmt);
+ $$->selectStmt = (Node *)n;
+ $$->cols = $2;
+ }
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
Hi Kyotaro,
Thank-you for looking at the patch.
On 18/07/2019, at 6:54 PM, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
Hello.
If I'm not missing something, "SELECT <targetlist>" without
having FROM clause doesn't need to be tweaked. Thus
insert_set_clause is useless and all we need here would be
something like the following. (and the same for OVERRIDING.)+ | SET set_clause_list from_clause + { + SelectStmt *n = makeNode(SelectStmt); + n->targetList = $2; + n->fromClause = $3; + $$ = makeNode(InsertStmt); + $$->selectStmt = (Node *)n; + $$->cols = $2; + }
While that would mostly work, it would prevent setting the column to its
default value using the DEFAULT keyword.
Only expressions specified in valuesLists allow DEFAULT to be used. Those
in targetList do not because transformInsertStmt() treats that as a general
SELECT statement and the grammar does not allow the use of DEFAULT there.
So this would generate a "DEFAULT is not allowed in this context" error
if only targetList was used:
INSERT INTO t set c1 = DEFAULT;
Regards,
Gareth
Show quoted text
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
Patch conflict with this assertion
Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
src/backend/parser/parse_expr.c line 1570
The new status of this patch is: Waiting on Author
Hi Ibrar,
On 16/08/2019, at 7:14 AM, Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:
Patch conflict with this assertion
Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);src/backend/parser/parse_expr.c line 1570
The new status of this patch is: Waiting on Author
Thank-you for reviewing the patch.
Attached is version 2 of the patch that fixes the above by allowing
p_expr_kind to be EXPR_KIND_VALUES_SINGLE as well.
Gareth
Attachments:
insert-set-v2.patchapplication/octet-stream; name=insert-set-v2.patch; x-unix-mode=0644Download
From 25fad5800d1f0054effa49e86c081306d9bee7fa Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Fri, 16 Aug 2019 11:56:42 +1200
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values may also be sourced from a CTE using a FROM clause:
WITH x AS (
SELECT 'foo' AS c1, 'bar' AS c2, 'baz' AS c3
)
INSERT INTO t SET c1 = x.c1, c2 = x.c2, c3 = x.c3 FROM x;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 24 ++++++++++-
src/backend/parser/gram.y | 40 ++++++++++++++++++-
src/backend/parser/parse_expr.c | 8 +++-
src/test/regress/expected/insert.out | 16 ++++++++
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 21 ++++++++++
src/test/regress/sql/insert.sql | 12 ++++++
src/test/regress/sql/insert_conflict.sql | 3 ++
src/test/regress/sql/with.sql | 10 +++++
9 files changed, 132 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index f995a7637f..341616d8b5 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -24,7 +24,12 @@ PostgreSQL documentation
[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
[ OVERRIDING { SYSTEM | USER} VALUE ]
- { DEFAULT VALUES | VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="parameter">query</replaceable> }
+ { DEFAULT VALUES | VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) [, ...] |
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( <replaceable class="parameter">sub-SELECT</replaceable> )
+ } [, ...] [ FROM <replaceable class="parameter">from_list</replaceable> ] } |
+ <replaceable class="parameter">query</replaceable> }
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
@@ -254,6 +259,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_list</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -675,6 +690,13 @@ WITH upd AS (
RETURNING *
)
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
+</programlisting>
+ </para>
+ <para>
+ Insert a single row into table <literal>distributors</literal> using a
+<literal>SET</literal> clause to specify the columns and values:
+<programlisting>
+INSERT INTO distributors SET did = 4, dname = 'Hammers Unlimited, LLC';
</programlisting>
</para>
<para>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..3015a2925a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -463,7 +463,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -10892,6 +10892,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -10923,6 +10932,35 @@ insert_column_item:
}
;
+insert_set_clause:
+ SET set_clause_list from_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ if ($3 != NULL)
+ {
+ n->targetList = $2;
+ n->fromClause = $3;
+ }
+ else
+ {
+ List *values = NIL;
+ ListCell *col_cell;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ }
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 76f3dd7076..63ef879447 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1566,8 +1566,12 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Query *qtree;
TargetEntry *tle;
- /* We should only see this in first-stage processing of UPDATE tlists */
- Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ /*
+ * We should only see this in first-stage processing of UPDATE tlists
+ * or of an INSERT SET tlist.
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE ||
+ pstate->p_expr_kind == EXPR_KIND_VALUES_SINGLE);
/* We only need to transform the source if this is the first column */
if (maref->colno == 1)
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 75e25cdf48..c15260ea76 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -80,6 +80,22 @@ select col1, col2, char_length(col3) from inserttest;
30 | 50 | 10000
(8 rows)
+drop table inserttest;
+--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+select * from inserttest;
+ col1 | col2
+------+------
+ 1 | foo
+ 2 | bar
+ 3 | baz
+(3 rows)
+
drop table inserttest;
--
-- check indirection (field/array assignment), cf bug #14265
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 2a2085556b..b92f9f8a13 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,27 @@ SELECT * FROM y;
10
(10 rows)
+DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..00747e0939 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -37,6 +37,18 @@ select col1, col2, char_length(col3) from inserttest;
drop table inserttest;
+--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+
+select * from inserttest;
+
+drop table inserttest;
+
--
-- check indirection (field/array assignment), cf bug #14265
--
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..e6d2e13714 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -406,6 +406,16 @@ SELECT * FROM y;
DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
+DROP TABLE y;
+
--
-- error cases
--
--
2.17.1
On Wed, Jul 17, 2019 at 10:00 AM Gareth Palmer <gareth@internetnz.net.nz> wrote:
Hello,
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values may also be sourced from a CTE using a FROM clause:
WITH x AS (
SELECT 'foo' AS c1, 'bar' AS c2, 'baz' AS c3
)
INSERT INTO t SET c1 = x.c1, c2 = x.c2, c3 = x.c3 FROM x;The advantage of using the SET clause style is that the column and value
are kept together, which can make changing or removing a column or value from
a large list easier.Internally the grammar parser converts INSERT SET without a FROM clause into
the equivalent INSERT with a VALUES clause. When using a FROM clause it becomes
the equivalent of INSERT with a SELECT statement.There was a brief discussion regarding INSERT SET on pgsql-hackers in late
August 2009 [1].INSERT SET is not part of any SQL standard (that I am aware of), however this
syntax is also implemented by MySQL [2]. Their implementation does not support
specifying a FROM clause.
I think this can be a handy feature in some cases as pointed by you,
but do we really want it for PostgreSQL? In the last round of
discussions as pointed by you, there doesn't seem to be a consensus
that we want this feature. I guess before spending too much time into
reviewing this feature, we should first build a consensus on whether
we need this.
Along with users, I request some senior hackers/committers to also
weigh in about the desirability of this feature.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Aug 16, 2019 at 8:19 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Jul 17, 2019 at 10:00 AM Gareth Palmer <gareth@internetnz.net.nz>
wrote:Hello,
Attached is a patch that adds the option of using SET clause to specify
the columns and values in an INSERT statement in the same manner as that
of an UPDATE statement.A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values may also be sourced from a CTE using a FROM clause:
WITH x AS (
SELECT 'foo' AS c1, 'bar' AS c2, 'baz' AS c3
)
INSERT INTO t SET c1 = x.c1, c2 = x.c2, c3 = x.c3 FROM x;The advantage of using the SET clause style is that the column and value
are kept together, which can make changing or removing a column or valuefrom
a large list easier.
Internally the grammar parser converts INSERT SET without a FROM clause
into
the equivalent INSERT with a VALUES clause. When using a FROM clause it
becomes
the equivalent of INSERT with a SELECT statement.
There was a brief discussion regarding INSERT SET on pgsql-hackers in
late
August 2009 [1].
INSERT SET is not part of any SQL standard (that I am aware of), however
this
syntax is also implemented by MySQL [2]. Their implementation does not
support
specifying a FROM clause.
I think this can be a handy feature in some cases as pointed by you,
but do we really want it for PostgreSQL? In the last round of
discussions as pointed by you, there doesn't seem to be a consensus
that we want this feature. I guess before spending too much time into
reviewing this feature, we should first build a consensus on whether
we need this.
I agree with you Amit, that we need a consensus on that. Do we really need
that
feature or not. In the previous discussion, there was no resistance to have
that
in PostgreSQL, but some problem with the patch. Current patch is very simple
and not invasive, but still, we need a consensus on that.
Along with users, I request some senior hackers/committers to also
weigh in about the desirability of this feature.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Ibrar Ahmed
On 2019-08-16 05:19, Amit Kapila wrote:
I think this can be a handy feature in some cases as pointed by you,
but do we really want it for PostgreSQL? In the last round of
discussions as pointed by you, there doesn't seem to be a consensus
that we want this feature. I guess before spending too much time into
reviewing this feature, we should first build a consensus on whether
we need this.
I think the problem this is attempting to solve is valid.
What I don't like about the syntax is that it kind of breaks the
notional processing model of INSERT in a fundamental way. The model is
INSERT INTO $target $table_source
where $table_source could be VALUES, SELECT, possibly others in theory.
The proposed syntax changes this to only allow a single row to be
specified via the SET syntax, and the SET syntax does not function as a
row or table source in other contexts.
Let's think about how we can achieve this using existing concepts in
SQL. What we really need here at a fundamental level is an option to
match $target to $table_source by column *name* rather than column
*position*. There is existing syntax in SQL for that, namely
a UNION b
vs
a UNION CORRESPONDING b
I think this could be used for INSERT as well.
And then you need a syntax to assign column names inside the VALUES
rows. I think you could do either of the following:
VALUES (a => 1, b => 2)
or
VALUES (1 AS a, 2 AS b)
Another nice effect of this would be that you could so something like
INSERT INTO tbl2 CORRESPONDING SELECT * FROM tbl1;
which copies the contents of tbl1 to tbl2 if they have the same column
names but allowing for a different column order.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 18/08/2019 11:03, Peter Eisentraut wrote:
a UNION b
vs
a UNION CORRESPONDING b
I have a WIP patch for CORRESPONDING [BY]. Is there any interest in me
continuing it? If so, I'll start another thread for it.
--
Vik Fearing
Vik Fearing <vik.fearing@2ndquadrant.com> writes:
On 18/08/2019 11:03, Peter Eisentraut wrote:
a UNION b
vs
a UNION CORRESPONDING b
I have a WIP patch for CORRESPONDING [BY]. Is there any interest in me
continuing it? If so, I'll start another thread for it.
CORRESPONDING is in the SQL standard, so in theory we ought to provide
it. I think the hard question is how big/complicated the patch would be
--- if the answer is "complicated", maybe it's not worth it. People
have submitted patches for it before that didn't go anywhere, suggesting
that the tradeoffs are not very good ... but maybe you'll think of a
better way.
regards, tom lane
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
What I don't like about the syntax is that it kind of breaks the
notional processing model of INSERT in a fundamental way.
Agreed. I really don't like that this only works for a VALUES-like case
(and only the one-row form at that). It's hard to see it as anything
but a wart pasted onto the syntax.
Let's think about how we can achieve this using existing concepts in
SQL. What we really need here at a fundamental level is an option to
match $target to $table_source by column *name* rather than column
*position*. There is existing syntax in SQL for that, namely
a UNION b
vs
a UNION CORRESPONDING b
A potential issue here --- and something that applies to Vik's question
as well, now that I think about it --- is that CORRESPONDING breaks down
in the face of ALTER TABLE RENAME COLUMN. Something that had been a
legal query before the rename might be invalid, or mean something quite
different, afterwards. This is really nasty for stored views/rules,
because we have neither a mechanism for forbidding input-table renames
nor a mechanism for revalidating views/rules afterwards. Maybe we could
make it go by resolving CORRESPONDING in the rewriter or planner, rather
than in parse analysis; but that seems quite unpleasant as well.
Changing our conclusions about the data types coming out of a UNION
really shouldn't happen later than parse analysis.
The SET-style syntax doesn't have that problem, since it's explicit
about which values go into which columns.
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).
Of course, this is not functionally distinct from
INSERT INTO target(c1,c2,...) SELECT x, y+z, ... FROM tables-providing-x-y-z
and it's fair to question whether it's worth supporting a nonstandard
syntax just to allow the target column names to be written closer to
the expressions-to-be-assigned.
regards, tom lane
Hi Tom,
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
What I don't like about the syntax is that it kind of breaks the
notional processing model of INSERT in a fundamental way.Agreed. I really don't like that this only works for a VALUES-like case
(and only the one-row form at that). It's hard to see it as anything
but a wart pasted onto the syntax.Let's think about how we can achieve this using existing concepts in
SQL. What we really need here at a fundamental level is an option to
match $target to $table_source by column *name* rather than column
*position*. There is existing syntax in SQL for that, namely
a UNION b
vs
a UNION CORRESPONDING bA potential issue here --- and something that applies to Vik's question
as well, now that I think about it --- is that CORRESPONDING breaks down
in the face of ALTER TABLE RENAME COLUMN. Something that had been a
legal query before the rename might be invalid, or mean something quite
different, afterwards. This is really nasty for stored views/rules,
because we have neither a mechanism for forbidding input-table renames
nor a mechanism for revalidating views/rules afterwards. Maybe we could
make it go by resolving CORRESPONDING in the rewriter or planner, rather
than in parse analysis; but that seems quite unpleasant as well.
Changing our conclusions about the data types coming out of a UNION
really shouldn't happen later than parse analysis.The SET-style syntax doesn't have that problem, since it's explicit
about which values go into which columns.Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Of course, this is not functionally distinct from
INSERT INTO target(c1,c2,...) SELECT x, y+z, ... FROM tables-providing-x-y-z
and it's fair to question whether it's worth supporting a nonstandard
syntax just to allow the target column names to be written closer to
the expressions-to-be-assigned.
Thanks for the feedback. Attached is version 3 of the patch that makes
the syntax work more like an UPDATE statement when a FROM clause is used.
So, an updated summary of the new syntax is:
1. Equivalent to VALUES(...):
INSERT INTO t SET c1 = x, c2 = y, c3 = z;
2. Equivalent to INSERT INTO ... SELECT ...:
INSERT INTO t SET c1 = sum(x.c1) FROM x WHERE x.c1 < y AND x.c2 != z
GROUP BY x.c3 ORDER BY x.c4 ASC LIMIT a OFFSET b;
Gareth
Show quoted text
regards, tom lane
Attachments:
insert-set-v3.patchapplication/octet-stream; name=insert-set-v3.patch; x-unix-mode=0644Download
From 44ff9500dfa14994047d2d64279259c39360b6b9 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Mon, 26 Aug 2019 14:39:16 +1200
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 52 +++++++++++++++++++
src/backend/parser/gram.y | 50 +++++++++++++++++-
src/backend/parser/parse_expr.c | 9 +++-
src/test/regress/expected/insert.out | 38 ++++++++++++++
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 21 ++++++++
src/test/regress/sql/insert.sql | 30 +++++++++++
src/test/regress/sql/insert_conflict.sql | 3 ++
src/test/regress/sql/with.sql | 10 ++++
9 files changed, 212 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index f995a7637f..71fab5126d 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,25 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( <replaceable class="parameter">sub-SELECT</replaceable> )
+ } [, ...]
+ [ FROM <replaceable class="parameter">from_list</replaceable>
+ [ WHERE <replaceable class="parameter">condition</replaceable> ]
+ [ GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...] ]
+ [ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
+ [ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
+ [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
+ [ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
+ [ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ] ]
+ [ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } ONLY ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -254,6 +273,32 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_list</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">condition</replaceable></term>
+ <term><replaceable class="parameter">grouping_element</replaceable></term>
+ <term><replaceable class="parameter">window_name</replaceable></term>
+ <term><replaceable class="parameter">count</replaceable></term>
+ <term><replaceable class="parameter">start</replaceable></term>
+ <listitem>
+ <para>
+ These have the same meaning as when used in a query
+ (<command>SELECT</command> statement). Refer to the
+ <xref linkend="sql-select"/> statement for a description of the
+ syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -675,6 +720,13 @@ WITH upd AS (
RETURNING *
)
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
+</programlisting>
+ </para>
+ <para>
+ Insert a single row into table <literal>distributors</literal> using a
+<literal>SET</literal> clause to specify the columns and values:
+<programlisting>
+INSERT INTO distributors SET did = 4, dname = 'Hammers Unlimited, LLC';
</programlisting>
</para>
<para>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..9c907dfd1b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -463,7 +463,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -10892,6 +10892,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -10923,6 +10932,45 @@ insert_column_item:
}
;
+insert_set_clause:
+ SET set_clause_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ List *values = NIL;
+ ListCell *col_cell;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET set_clause_list FROM from_list where_clause
+ group_clause having_clause window_clause opt_sort_clause
+ opt_select_limit
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = $6;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, NULL,
+ list_nth($10, 0), list_nth($10, 1),
+ NULL,
+ yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 76f3dd7076..91bf1530b2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1566,8 +1566,13 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Query *qtree;
TargetEntry *tle;
- /* We should only see this in first-stage processing of UPDATE tlists */
- Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ /*
+ * We should only see this in first-stage processing of UPDATE tlists
+ * or of an INSERT SET tlist.
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_VALUES_SINGLE ||
+ pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET ||
+ pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
/* We only need to transform the source if this is the first column */
if (maref->colno == 1)
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 75e25cdf48..6a934d1873 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -82,6 +82,44 @@ select col1, col2, char_length(col3) from inserttest;
drop table inserttest;
--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+select * from inserttest;
+ col1 | col2
+------+------
+ 1 | foo
+ 2 | bar
+ 3 | baz
+(3 rows)
+
+truncate inserttest;
+create table inserttest2 (col1 int4, col2 text default 'f');
+insert into inserttest2 values (10, 't'), (20, 'f'), (50, 't');
+insert into inserttest set col1 = inserttest2.col1 from inserttest2
+ where inserttest2.col1 > 10 order by inserttest2.col1 desc limit 1;
+select * from inserttest;
+ col1 | col2
+------+------
+ 50 | bar
+(1 row)
+
+truncate inserttest;
+insert into inserttest set col1 = sum(inserttest2.col1) from inserttest2
+ group by inserttest2.col2;
+select * from inserttest;
+ col1 | col2
+------+------
+ 60 | bar
+ 20 | bar
+(2 rows)
+
+drop table inserttest;
+drop table inserttest2;
+--
-- check indirection (field/array assignment), cf bug #14265
--
-- these tests are aware that transformInsertStmt has 3 separate code paths
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 2a2085556b..b92f9f8a13 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,27 @@ SELECT * FROM y;
10
(10 rows)
+DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..c00a23322b 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -37,6 +37,36 @@ select col1, col2, char_length(col3) from inserttest;
drop table inserttest;
+--
+-- SET test
+--
+create table inserttest (col1 int4, col2 text default 'bar');
+insert into inserttest set col1 = 1, col2 = 'foo';
+insert into inserttest set col1 = 2, col2 = DEFAULT;
+insert into inserttest set (col1, col2) = (3, 'baz');
+
+select * from inserttest;
+
+truncate inserttest;
+
+create table inserttest2 (col1 int4, col2 text default 'f');
+insert into inserttest2 values (10, 't'), (20, 'f'), (50, 't');
+
+insert into inserttest set col1 = inserttest2.col1 from inserttest2
+ where inserttest2.col1 > 10 order by inserttest2.col1 desc limit 1;
+
+select * from inserttest;
+
+truncate inserttest;
+
+insert into inserttest set col1 = sum(inserttest2.col1) from inserttest2
+ group by inserttest2.col2;
+
+select * from inserttest;
+
+drop table inserttest;
+drop table inserttest2;
+
--
-- check indirection (field/array assignment), cf bug #14265
--
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..e6d2e13714 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -406,6 +406,16 @@ SELECT * FROM y;
DROP TABLE y;
+CREATE TEMPORARY TABLE y (a INTEGER);
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
+DROP TABLE y;
+
--
-- error cases
--
--
2.17.1
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: not tested
Patch looks to me and works on my machine 73025140885c889410b9bfc4a30a3866396fc5db - HEAD I have not reviewed the documentaion changes.
The new status of this patch is: Ready for Committer
On Sun, Aug 18, 2019 at 11:00 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Of course, this is not functionally distinct from
INSERT INTO target(c1,c2,...) SELECT x, y+z, ... FROM tables-providing-x-y-z
and it's fair to question whether it's worth supporting a nonstandard
syntax just to allow the target column names to be written closer to
the expressions-to-be-assigned.
For what it's worth, I think this would be useful enough to justify
its existence. Back in days of yore when dragons roamed the earth and
I wrote database-driven applications instead of hacking on the
database itself, I often wondered why I had to write two
completely-different looking SQL statements, one to insert the data
which a user had entered into a webform into the database, and another
to update previously-entered data. This feature would allow those
queries to be written in the same way, which would have pleased me,
back in the day.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, Nov 1, 2019 at 6:31 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Sun, Aug 18, 2019 at 11:00 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Of course, this is not functionally distinct from
INSERT INTO target(c1,c2,...) SELECT x, y+z, ... FROM
tables-providing-x-y-z
and it's fair to question whether it's worth supporting a nonstandard
syntax just to allow the target column names to be written closer to
the expressions-to-be-assigned.For what it's worth, I think this would be useful enough to justify
its existence. Back in days of yore when dragons roamed the earth and
I wrote database-driven applications instead of hacking on the
database itself, I often wondered why I had to write two
completely-different looking SQL statements, one to insert the data
which a user had entered into a webform into the database, and another
to update previously-entered data. This feature would allow those
queries to be written in the same way, which would have pleased me,
back in the day.
I still do, and this would be a big help. I don't care if it's
non-standard.
.m
Gareth Palmer <gareth@internetnz.net.nz> writes:
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).
Thanks for the feedback. Attached is version 3 of the patch that makes
the syntax work more like an UPDATE statement when a FROM clause is used.
Since nobody has objected to this, I'm supposing that there's general
consensus that that design sketch is OK, and we can move on to critiquing
implementation details. I took a look, and didn't like much of what I saw.
* In the grammar, there's no real need to have separate productions
for the cases with FROM and without. The way you have it is awkward,
and it arbitrarily rejects combinations that work fine in plain
SELECT, such as WHERE without FROM. You should just do
insert_set_clause:
SET set_clause_list from_clause where_clause
group_clause having_clause window_clause opt_sort_clause
opt_select_limit
relying on the ability of all those symbols (except set_clause_list) to
reduce to empty.
* This is randomly inconsistent with select_no_parens, and not in a
good way, because you've omitted the option that's actually most likely
to be useful, namely for_locking_clause. I wonder whether it's practical
to refactor select_no_parens so that the stuff involving optional trailing
clauses can be separated out into a production that insert_set_clause
could also use. Might not be worth the trouble, but I'm concerned
about select_no_parens growing additional clauses that we then forget
to also add to insert_set_clause.
* I'm not sure if it's worth also refactoring simple_select so that
the "into_clause ... window_clause" business could be shared. But
it'd likely be a good idea to at least have a comment there noting
that any changes in that production might need to be applied to
insert_set_clause as well.
* In kind of the same vein, it feels like the syntax documentation
is awkwardly failing to share commonality that it ought to be
able to share with the SELECT man page.
* I dislike the random hacking you did in transformMultiAssignRef.
That weakens a useful check for error cases, and it's far from clear
why the new assertion is OK. It also raises the question of whether
this is really the only place you need to touch in parse analysis.
Perhaps it'd be better to consider inventing new EXPR_KIND_ values
for this situation; you'd then have to run around and look at all the
existing EXPR_KIND uses, but that seems like a useful cross-check
activity anyway. Or maybe we need to take two steps back and
understand why that change is needed at all. I'd imagined that this
patch would be only syntactic sugar for something you can do already,
so it's not quite clear to me why we need additional changes.
(If it's *not* just syntactic sugar, then the scope of potential
problems becomes far greater, eg does ruleutils.c need to know
how to reconstruct a valid SQL command from a querytree like this.
If we're not touching ruleutils.c, we need to be sure that every
command that can be written this way can be written old-style.)
* Other documentation gripes: the lone example seems insufficient,
and there needs to be an entry under COMPATIBILITY pointing out
that this is not per SQL spec.
* Some of the test cases seem to be expensively repeating
construction/destruction of tables that they could have shared with
existing test cases. I do not consider it a virtue for new tests
added to an existing test script to be resolutely independent of
what's already in that script.
I'm setting this back to Waiting on Author.
regards, tom lane
On Thu, Nov 14, 2019 at 9:20 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Gareth Palmer <gareth@internetnz.net.nz> writes:
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROMtables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Thanks for the feedback. Attached is version 3 of the patch that makes
the syntax work more like an UPDATE statement when a FROM clause is used.Since nobody has objected to this, I'm supposing that there's general
consensus that that design sketch is OK, and we can move on to critiquing
implementation details. I took a look, and didn't like much of what I saw....
I'm setting this back to Waiting on Author.
regards, tom lane
Regarding syntax and considering that it makes INSERT look like UPDATE:
there is another difference between INSERT and UPDATE. INSERT allows SELECT
with ORDER BY and OFFSET/LIMIT (or FETCH FIRST), e.g.:
INSERT INTO t (a,b)
SELECT a+10. b+10
FROM t
ORDER BY a
LIMIT 3;
But UPDATE doesn't. I suppose the proposed behaviour of INSERT .. SET will
be the same as standard INSERT. So we'll need a note for the differences
between INSERT/SET and UPDATE/SET syntax.
On a related not, column aliases can be used in ORDER BY, e.g:
insert into t (a, b)
select
a + 20,
b - 2 * a as f
from t
order by f desc
limit 3 ;
Would that be expressed as follows?:
insert into t
set
a = a + 20,
b = b - 2 * a as f
from t
order by f desc
limit 3 ;
Best regards,
Pantelis Theodosiou
Pantelis Theodosiou <ypercube@gmail.com> writes:
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROM
tables-providing-x-y-z
Regarding syntax and considering that it makes INSERT look like UPDATE:
there is another difference between INSERT and UPDATE. INSERT allows SELECT
with ORDER BY and OFFSET/LIMIT (or FETCH FIRST), e.g.: ...
But UPDATE doesn't. I suppose the proposed behaviour of INSERT .. SET will
be the same as standard INSERT. So we'll need a note for the differences
between INSERT/SET and UPDATE/SET syntax.
I was supposing that this syntax should be just another way to spell
INSERT INTO target (columnlist) SELECT ...
So everything past FROM would work exactly like it does in SELECT.
On a related not, column aliases can be used in ORDER BY, e.g:
As proposed, there's no option equivalent to writing output-column aliases
in the INSERT ... SELECT form, so the question doesn't come up.
regards, tom lane
On 15/11/2019, at 10:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Gareth Palmer <gareth@internetnz.net.nz> writes:
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Thanks for the feedback. Attached is version 3 of the patch that makes
the syntax work more like an UPDATE statement when a FROM clause is used.Since nobody has objected to this, I'm supposing that there's general
consensus that that design sketch is OK, and we can move on to critiquing
implementation details. I took a look, and didn't like much of what I saw.* In the grammar, there's no real need to have separate productions
for the cases with FROM and without. The way you have it is awkward,
and it arbitrarily rejects combinations that work fine in plain
SELECT, such as WHERE without FROM. You should just doinsert_set_clause:
SET set_clause_list from_clause where_clause
group_clause having_clause window_clause opt_sort_clause
opt_select_limitrelying on the ability of all those symbols (except set_clause_list) to
reduce to empty.
There are two separate productions to match the two different types
of inserts: INSERT with VALUES and INSERT with SELECT.
The former has to store the the values in valuesLists so that DEFAULT
can still be used.
Allowing a WHERE without a FROM also mean that while this would
work:
INSERT INTO t SET c = DEFAULT;
But this would fail with 'DEFAULT is not allowed in this context':
INSERT INTO t SET c = DEFAULT WHERE true;
I should have put a comment explaining why there are two rules.
It could be combined into one production but there would have to be
a check that $4 .. $9 are NULL to determine what type of INSERT to
use.
transformInsertStmt() also has an optimisation for the case of a
single valueLists entry.
* This is randomly inconsistent with select_no_parens, and not in a
good way, because you've omitted the option that's actually most likely
to be useful, namely for_locking_clause. I wonder whether it's practical
to refactor select_no_parens so that the stuff involving optional trailing
clauses can be separated out into a production that insert_set_clause
could also use. Might not be worth the trouble, but I'm concerned
about select_no_parens growing additional clauses that we then forget
to also add to insert_set_clause.* I'm not sure if it's worth also refactoring simple_select so that
the "into_clause ... window_clause" business could be shared. But
it'd likely be a good idea to at least have a comment there noting
that any changes in that production might need to be applied to
insert_set_clause as well.
I can add opt_for_locking_clause and a comment to simple_select to
start with while the format of insert_set_clause is still being
worked out.
* In kind of the same vein, it feels like the syntax documentation
is awkwardly failing to share commonality that it ought to be
able to share with the SELECT man page.
I could collapse the from clause to just '[ FROM from_clause ]'
and have it refer to the from clause and everything after it in
SELECT.
* I dislike the random hacking you did in transformMultiAssignRef.
That weakens a useful check for error cases, and it's far from clear
why the new assertion is OK. It also raises the question of whether
this is really the only place you need to touch in parse analysis.
Perhaps it'd be better to consider inventing new EXPR_KIND_ values
for this situation; you'd then have to run around and look at all the
existing EXPR_KIND uses, but that seems like a useful cross-check
activity anyway. Or maybe we need to take two steps back and
understand why that change is needed at all. I'd imagined that this
patch would be only syntactic sugar for something you can do already,
so it's not quite clear to me why we need additional changes.(If it's *not* just syntactic sugar, then the scope of potential
problems becomes far greater, eg does ruleutils.c need to know
how to reconstruct a valid SQL command from a querytree like this.
If we're not touching ruleutils.c, we need to be sure that every
command that can be written this way can be written old-style.)
It was intended to just be syntatic sugar, however because
set_clause_list is being re-used the ability to do multi-assignment
in an INSERT's targetList 'came along for the ride' which has
no equivalent in the current INSERT syntax.
That would be why those EXPR_KIND's are now appearing in
transformMultiAssignRef().
There are 3 things that could be done here:
1. Update ruletutils.c to emit INSERT SET in get_insert_query_def()
if query->hasSubLinks is true.
2. Add a new production similar to set_clause_list which doesn't
allow multi-assignment.
3. Re-use set_clause_list but reject targetLists that contain
multi-assignment.
Keeping that feature is probably desirable at least for consistency
with other SET clauses.
I will work on getting get_insert_query_def() to correctly
reconstruct the new syntax.
* Other documentation gripes: the lone example seems insufficient,
and there needs to be an entry under COMPATIBILITY pointing out
that this is not per SQL spec.
I will add something to in the compatibility section.
* Some of the test cases seem to be expensively repeating
construction/destruction of tables that they could have shared with
existing test cases. I do not consider it a virtue for new tests
added to an existing test script to be resolutely independent of
what's already in that script.
Those test cases will be changed to share the those tables.
Show quoted text
I'm setting this back to Waiting on Author.
regards, tom lane
On 15/11/2019, at 10:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Gareth Palmer <gareth@internetnz.net.nz> writes:
On 19/08/2019, at 3:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Perhaps the way to resolve Peter's objection is to make the syntax
more fully like UPDATE:
INSERT INTO target SET c1 = x, c2 = y+z, ... FROM tables-providing-x-y-z
(with the patch as-submitted corresponding to the case with an empty
FROM clause, hence no variables in the expressions-to-be-assigned).Thanks for the feedback. Attached is version 3 of the patch that makes
the syntax work more like an UPDATE statement when a FROM clause is used.Since nobody has objected to this, I'm supposing that there's general
consensus that that design sketch is OK, and we can move on to critiquing
implementation details. I took a look, and didn't like much of what I saw.* In the grammar, there's no real need to have separate productions
for the cases with FROM and without. The way you have it is awkward,
and it arbitrarily rejects combinations that work fine in plain
SELECT, such as WHERE without FROM. You should just doinsert_set_clause:
SET set_clause_list from_clause where_clause
group_clause having_clause window_clause opt_sort_clause
opt_select_limitrelying on the ability of all those symbols (except set_clause_list) to
reduce to empty.* This is randomly inconsistent with select_no_parens, and not in a
good way, because you've omitted the option that's actually most likely
to be useful, namely for_locking_clause. I wonder whether it's practical
to refactor select_no_parens so that the stuff involving optional trailing
clauses can be separated out into a production that insert_set_clause
could also use. Might not be worth the trouble, but I'm concerned
about select_no_parens growing additional clauses that we then forget
to also add to insert_set_clause.* I'm not sure if it's worth also refactoring simple_select so that
the "into_clause ... window_clause" business could be shared. But
it'd likely be a good idea to at least have a comment there noting
that any changes in that production might need to be applied to
insert_set_clause as well.* In kind of the same vein, it feels like the syntax documentation
is awkwardly failing to share commonality that it ought to be
able to share with the SELECT man page.* I dislike the random hacking you did in transformMultiAssignRef.
That weakens a useful check for error cases, and it's far from clear
why the new assertion is OK. It also raises the question of whether
this is really the only place you need to touch in parse analysis.
Perhaps it'd be better to consider inventing new EXPR_KIND_ values
for this situation; you'd then have to run around and look at all the
existing EXPR_KIND uses, but that seems like a useful cross-check
activity anyway. Or maybe we need to take two steps back and
understand why that change is needed at all. I'd imagined that this
patch would be only syntactic sugar for something you can do already,
so it's not quite clear to me why we need additional changes.(If it's *not* just syntactic sugar, then the scope of potential
problems becomes far greater, eg does ruleutils.c need to know
how to reconstruct a valid SQL command from a querytree like this.
If we're not touching ruleutils.c, we need to be sure that every
command that can be written this way can be written old-style.)
So it appears as though it may not require any changes to ruleutils.c
as the parser is converting the multi-assignments into separate
columns, eg:
CREATE RULE r1 AS ON INSERT TO tab1
DO INSTEAD
INSERT INTO tab2 SET (col2, col1) = (new.col2, 0), col3 = tab3.col3
FROM tab3
The rule generated is:
r1 AS ON INSERT TO tab1 DO INSTEAD
INSERT INTO tab2 (col2, col1, col3)
SELECT new.col2, 0 AS col1, tab3.col3 FROM tab3
It will trigger that Assert() though, as EXPR_KIND_SELECT_TARGET is
now also being passed to transformMultiassignRef().
Show quoted text
* Other documentation gripes: the lone example seems insufficient,
and there needs to be an entry under COMPATIBILITY pointing out
that this is not per SQL spec.* Some of the test cases seem to be expensively repeating
construction/destruction of tables that they could have shared with
existing test cases. I do not consider it a virtue for new tests
added to an existing test script to be resolutely independent of
what's already in that script.I'm setting this back to Waiting on Author.
regards, tom lane
On 19/11/2019, at 5:05 PM, Gareth Palmer <gareth@internetnz.net.nz> wrote:
Since nobody has objected to this, I'm supposing that there's general
consensus that that design sketch is OK, and we can move on to critiquing
implementation details. I took a look, and didn't like much of what I saw.
Attached is an updated patch with for_locking_clause added, test-cases
re-use existing tables and the comments and documentation have been
expanded.
Show quoted text
I'm setting this back to Waiting on Author.
Attachments:
insert-set-v4.patchapplication/octet-stream; name=insert-set-v4.patch; x-unix-mode=0644Download
From 16595f1ac8cfa183061586711c98ef2c9a622e71 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Thu, 21 Nov 2019 16:40:39 +1300
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 59 ++++++++++++++++++-
src/backend/parser/gram.y | 58 +++++++++++++++++-
src/backend/parser/parse_expr.c | 10 +++-
src/test/regress/expected/insert.out | 26 +++++---
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++++
src/test/regress/sql/insert.sql | 2 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
9 files changed, 177 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index f995a7637f..728edb9bda 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,19 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( <replaceable class="parameter">sub-SELECT</replaceable> )
+ } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -254,6 +267,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -631,6 +656,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -677,6 +711,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -733,6 +777,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</title> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -743,7 +799,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2f7bd662e8..57633260e9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -465,7 +465,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -10962,6 +10962,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -10993,6 +11002,50 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET set_clause_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ List *values = NIL;
+ ListCell *col_cell;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET set_clause_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = $6;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11,
+ list_nth($10, 0), list_nth($10, 1),
+ NULL,
+ yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11376,6 +11429,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index eb91da2d87..dc96ce76e4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1565,8 +1565,14 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Query *qtree;
TargetEntry *tle;
- /* We should only see this in first-stage processing of UPDATE tlists */
- Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ /*
+ * We should only see this in first-stage processing of UPDATE tlists
+ * (UPDATE_SOURCE), an INSERT SET tlist (VALUES_SINGLE) or from a rewritten
+ * query using rules (SELECT_TARGET).
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE ||
+ pstate->p_expr_kind == EXPR_KIND_VALUES_SINGLE ||
+ pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET);
/* We only need to transform the source if this is the first column */
if (maref->colno == 1)
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 75e25cdf48..1492ae40ee 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,14 +9,18 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
+insert into inserttest set (col1, col2, col3) = (DEFAULT, 11, 'more testing');
select * from inserttest;
- col1 | col2 | col3
-------+------+---------
+ col1 | col2 | col3
+------+------+--------------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+ | 11 | more testing
+(6 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -38,13 +42,15 @@ ERROR: INSERT has more expressions than target columns
LINE 1: insert into inserttest (col1) values (DEFAULT, DEFAULT);
^
select * from inserttest;
- col1 | col2 | col3
-------+------+---------
+ col1 | col2 | col3
+------+------+--------------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+ | 11 | more testing
+(6 rows)
--
-- VALUES test
@@ -58,10 +64,12 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
+ | 11 | more testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(9 rows)
--
-- TOASTed value test
@@ -74,11 +82,13 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
+ | 11 | 12
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(10 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 2a2085556b..a1236d8683 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..bc903d3316 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,8 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
+insert into inserttest set (col1, col2, col3) = (DEFAULT, 11, 'more testing');
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..d3e04bbad9 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -404,6 +404,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.17.1
On Fri, Nov 22, 2019 at 12:24:15PM +1300, Gareth Palmer wrote:
Attached is an updated patch with for_locking_clause added, test-cases
re-use existing tables and the comments and documentation have been
expanded.
Per the automatic patch tester, documentation included in the patch
does not build. Could you please fix that? I have moved the patch to
next CF, waiting on author.
--
Michael
On Sun, Dec 1, 2019 at 4:32 PM Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Nov 22, 2019 at 12:24:15PM +1300, Gareth Palmer wrote:
Attached is an updated patch with for_locking_clause added, test-cases
re-use existing tables and the comments and documentation have been
expanded.Per the automatic patch tester, documentation included in the patch
does not build. Could you please fix that? I have moved the patch to
next CF, waiting on author.
Attached is a fixed version.
Show quoted text
--
Michael
Attachments:
insert-set-v5.patchtext/x-diff; charset=US-ASCII; name=insert-set-v5.patchDownload
From c7c32435f0c0a1948e5c3ebd7d66f0bc415ee54e Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Mon, 2 Dec 2019 10:59:40 +0000
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 59 ++++++++++++++++++-
src/backend/parser/gram.y | 58 +++++++++++++++++-
src/backend/parser/parse_expr.c | 10 +++-
src/test/regress/expected/insert.out | 26 +++++---
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++++
src/test/regress/sql/insert.sql | 2 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
9 files changed, 177 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index e829c61642..b2f7c06f53 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,19 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = [ ROW ] ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) |
+ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( <replaceable class="parameter">sub-SELECT</replaceable> )
+ } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -254,6 +267,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -631,6 +656,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -677,6 +711,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -733,6 +777,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -743,7 +799,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5086846de..dc825c64bc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -465,7 +465,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -10984,6 +10984,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -11015,6 +11024,50 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET set_clause_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ List *values = NIL;
+ ListCell *col_cell;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET set_clause_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = $6;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11,
+ list_nth($10, 0), list_nth($10, 1),
+ NULL,
+ yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11398,6 +11451,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index eb91da2d87..dc96ce76e4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1565,8 +1565,14 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Query *qtree;
TargetEntry *tle;
- /* We should only see this in first-stage processing of UPDATE tlists */
- Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ /*
+ * We should only see this in first-stage processing of UPDATE tlists
+ * (UPDATE_SOURCE), an INSERT SET tlist (VALUES_SINGLE) or from a rewritten
+ * query using rules (SELECT_TARGET).
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE ||
+ pstate->p_expr_kind == EXPR_KIND_VALUES_SINGLE ||
+ pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET);
/* We only need to transform the source if this is the first column */
if (maref->colno == 1)
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 75e25cdf48..1492ae40ee 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,14 +9,18 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
+insert into inserttest set (col1, col2, col3) = (DEFAULT, 11, 'more testing');
select * from inserttest;
- col1 | col2 | col3
-------+------+---------
+ col1 | col2 | col3
+------+------+--------------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+ | 11 | more testing
+(6 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -38,13 +42,15 @@ ERROR: INSERT has more expressions than target columns
LINE 1: insert into inserttest (col1) values (DEFAULT, DEFAULT);
^
select * from inserttest;
- col1 | col2 | col3
-------+------+---------
+ col1 | col2 | col3
+------+------+--------------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+ | 11 | more testing
+(6 rows)
--
-- VALUES test
@@ -58,10 +64,12 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
+ | 11 | more testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(9 rows)
--
-- TOASTed value test
@@ -74,11 +82,13 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
+ | 11 | 12
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(10 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 2a2085556b..a1236d8683 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..bc903d3316 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,8 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
+insert into inserttest set (col1, col2, col3) = (DEFAULT, 11, 'more testing');
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..d3e04bbad9 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -404,6 +404,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.17.1
Hi Tom,
On 12/3/19 4:44 AM, Gareth Palmer wrote:
On Sun, Dec 1, 2019 at 4:32 PM Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Nov 22, 2019 at 12:24:15PM +1300, Gareth Palmer wrote:
Attached is an updated patch with for_locking_clause added, test-cases
re-use existing tables and the comments and documentation have been
expanded.Per the automatic patch tester, documentation included in the patch
does not build. Could you please fix that? I have moved the patch to
next CF, waiting on author.Attached is a fixed version.
Does this version of the patch address your concerns?
Regards,
--
-David
david@pgmasters.net
David Steele <david@pgmasters.net> writes:
On 12/3/19 4:44 AM, Gareth Palmer wrote:
Attached is a fixed version.
Does this version of the patch address your concerns?
No. I still find the reliance on a FROM clause being present
to be pretty arbitrary. Also, I don't believe that ruleutils.c
requires no changes, because it's not going to be possible to
transform every usage of this syntax to old-style. I tried to
prove the point with this trivial example:
regression=# create table foo (f1 int ,f2 int, f3 int);
CREATE TABLE
regression=# create table bar (f1 int ,f2 int, f3 int);
CREATE TABLE
regression=# create rule r1 as on insert to foo do instead
regression-# insert into bar set (f1,f2,f3) = (select f1,f2,f3 from foo);
intending to show that the rule decompilation was bogus, but
I didn't get that far because the parser crashed:
TRAP: FailedAssertion("pstate->p_multiassign_exprs == NIL", File: "parse_target.c", Line: 287)
postgres: postgres regression [local] CREATE RULE(ExceptionalCondition+0x55)[0x8fb6e5]
postgres: postgres regression [local] CREATE RULE[0x5bd0c3]
postgres: postgres regression [local] CREATE RULE[0x583def]
postgres: postgres regression [local] CREATE RULE(transformStmt+0x2d5)[0x582665]
postgres: postgres regression [local] CREATE RULE(transformRuleStmt+0x2ad)[0x5bf2ad]
postgres: postgres regression [local] CREATE RULE(DefineRule+0x17)[0x793847]
If I do it like this, I get a different assertion:
regression=# insert into bar set (f1,f2,f3) = (select f1,f2,f3) from foo;
server closed the connection unexpectedly
TRAP: FailedAssertion("exprKind == EXPR_KIND_UPDATE_SOURCE", File: "parse_target.c", Line: 209)
postgres: postgres regression [local] INSERT(ExceptionalCondition+0x55)[0x8fb6e5]
postgres: postgres regression [local] INSERT(transformTargetList+0x1a7)[0x5bd277]
postgres: postgres regression [local] INSERT(transformStmt+0xbe0)[0x582f70]
postgres: postgres regression [local] INSERT[0x5839f3]
postgres: postgres regression [local] INSERT(transformStmt+0x2d5)[0x582665]
postgres: postgres regression [local] INSERT(transformTopLevelStmt+0xd)[0x58411d]
postgres: postgres regression [local] INSERT(parse_analyze+0x69)[0x584269]
No doubt that's all fixable, but the realization that some cases of
this syntax are *not* just syntactic sugar for standards-compliant
syntax is giving me pause. Do we really want to get out front of
the SQL committee on extending INSERT in an incompatible way?
regards, tom lane
I wrote:
No doubt that's all fixable, but the realization that some cases of
this syntax are *not* just syntactic sugar for standards-compliant
syntax is giving me pause. Do we really want to get out front of
the SQL committee on extending INSERT in an incompatible way?
One compromise that might be worth thinking about is to disallow
multiassignments in this syntax, so as to (1) avoid the possibility
of generating something that can't be represented by standard INSERT
and (2) get something done in time for v13. The end of March is not
that far off. Perhaps somebody would come back and extend it later,
or perhaps not.
A slightly more ambitious compromise would be to allow multiassignment
only when the source can be pulled apart into independent subexpressions,
comparable to the restriction we used to have in UPDATE itself (before
8f889b108 or thereabouts).
In either case the transformation could be done right in gram.y and
a helpful error thrown for unsupported cases.
regards, tom lane
On 2020-03-24 18:57, Tom Lane wrote:
No doubt that's all fixable, but the realization that some cases of
this syntax are*not* just syntactic sugar for standards-compliant
syntax is giving me pause. Do we really want to get out front of
the SQL committee on extending INSERT in an incompatible way?
What is the additional functionality that we are considering adding here?
The thread started out proposing a more convenient syntax, but it seems
to go deeper now and perhaps not everyone is following.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 2020-03-24 18:57, Tom Lane wrote:
No doubt that's all fixable, but the realization that some cases of
this syntax are*not* just syntactic sugar for standards-compliant
syntax is giving me pause. Do we really want to get out front of
the SQL committee on extending INSERT in an incompatible way?
What is the additional functionality that we are considering adding here?
The thread started out proposing a more convenient syntax, but it seems
to go deeper now and perhaps not everyone is following.
AIUI, the proposal is to allow INSERT commands to be written
using an UPDATE-like syntax, for example
INSERT INTO table SET col1 = value1, col2 = value2, ... [ FROM ... ]
where everything after FROM is the same as it is in SELECT. My initial
belief was that this was strictly equivalent to what you could do with
a target-column-names list in standard INSERT, viz
INSERT INTO table (col1, col2, ...) VALUES (value1, value2, ...);
or
INSERT INTO table (col1, col2, ...) SELECT value1, value2, ... FROM ...
but it's arguably more legible/convenient because the column names
are written next to their values.
However, that rewriting falls down for certain multiassignment cases
where you have a row source that can't be decomposed, such as my
example
INSERT INTO table SET (col1, col2) = (SELECT value1, value2 FROM ...),
... [ FROM ... ]
So, just as we found for UPDATE, multiassignment syntax is strictly
stronger than plain column-by-column assignment.
There are some secondary issues about which variants of this syntax
will allow a column value to be written as DEFAULT, and perhaps
about whether set-returning functions work. But the major point
right now is about whether its's possible to rewrite to standard
syntax.
regards, tom lane
On 26/03/2020, at 3:17 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 2020-03-24 18:57, Tom Lane wrote:
No doubt that's all fixable, but the realization that some cases of
this syntax are*not* just syntactic sugar for standards-compliant
syntax is giving me pause. Do we really want to get out front of
the SQL committee on extending INSERT in an incompatible way?What is the additional functionality that we are considering adding here?
The thread started out proposing a more convenient syntax, but it seems
to go deeper now and perhaps not everyone is following.AIUI, the proposal is to allow INSERT commands to be written
using an UPDATE-like syntax, for exampleINSERT INTO table SET col1 = value1, col2 = value2, ... [ FROM ... ]
where everything after FROM is the same as it is in SELECT. My initial
belief was that this was strictly equivalent to what you could do with
a target-column-names list in standard INSERT, vizINSERT INTO table (col1, col2, ...) VALUES (value1, value2, ...);
or
INSERT INTO table (col1, col2, ...) SELECT value1, value2, ... FROM ...but it's arguably more legible/convenient because the column names
are written next to their values.However, that rewriting falls down for certain multiassignment cases
where you have a row source that can't be decomposed, such as my
exampleINSERT INTO table SET (col1, col2) = (SELECT value1, value2 FROM ...),
... [ FROM ... ]So, just as we found for UPDATE, multiassignment syntax is strictly
stronger than plain column-by-column assignment.There are some secondary issues about which variants of this syntax
will allow a column value to be written as DEFAULT, and perhaps
about whether set-returning functions work. But the major point
right now is about whether its's possible to rewrite to standard
syntax.regards, tom lane
Attached is v6 of the patch.
As per the suggestion the SET clause list is checked for any
MultiAssigmentRef nodes and to report an error if any are found.
For example, the rule definition that previously caused a parser crash
would now produce the following error:
vagrant=> create rule r1 as on insert to foo do instead
vagrant-> insert into bar set (f1,f2,f3) = (select f1,f2,f3 from foo);
ERROR: INSERT SET syntax does not support multi-assignment of columns.
LINE 2: insert into bar set (f1,f2,f3) = (select f1,f2,f3 from foo);
^
HINT: Specify the column assignments separately.
Requiring a FROM clause was a way to differentiate between an INSERT
with VALUES() which does allow DEFAULT and an INSERT with SELECT which
does not.
The idea was that it would help the user understand that they were writing
a different type of query and that DEFAULT would not be allowed in that
context.
To show what it would look like without that requirement I have removed
it from the v6 patch. In the first example works but the second one will
generate an error.
INSERT INTO t SET c1 = 1 WHERE true;
INSERT INTO t SET c1 = DEFAULT WHERE true;
Attachments:
insert-set-v6.patchapplication/octet-stream; name=insert-set-v6.patch; x-unix-mode=0644Download
From 488b267335dd17a4cc51cc281ffb8e0d32fbee09 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Wed, 25 Mar 2020 18:57:58 +1300
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 56 +++++++++++++++-
src/backend/parser/gram.y | 65 ++++++++++++++++++-
src/backend/parser/parse_expr.c | 8 ++-
src/test/regress/expected/insert.out | 13 ++--
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 ++++++
src/test/regress/sql/insert.sql | 1 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
9 files changed, 169 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index e829c61642..3542f98d02 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -254,6 +264,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -631,6 +653,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -677,6 +708,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -733,6 +774,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -743,7 +796,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f956c..31a02389a0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -465,7 +465,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -11029,6 +11029,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -11060,6 +11069,57 @@ insert_column_item:
}
;
+insert_set_clause:
+ SET set_clause_list from_clause where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ List *values = NIL;
+ ListCell *col_cell;
+ ResTarget *res_col;
+
+ foreach(col_cell, $2)
+ {
+ res_col = (ResTarget *) lfirst(col_cell);
+
+ if (IsA(res_col->val, MultiAssignRef))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INSERT SET does not support multi-assignment of columns."),
+ errhint("Specify the column assignments separately."),
+ parser_errposition(@2)));
+ }
+
+ if ($3 == NULL)
+ {
+ foreach(col_cell, $2)
+ {
+ res_col = (ResTarget *) lfirst(col_cell);
+ values = lappend(values, res_col->val);
+ }
+ n->valuesLists = list_make1(values);
+ }
+ else
+ {
+ n->targetList = $2;
+ n->fromClause = $3;
+ }
+
+ n->whereClause = $4;
+ n->groupClause = $5;
+ n->havingClause = $6;
+ n->windowClause = $7;
+ insertSelectOptions(n, $8, $10,
+ list_nth($9, 0), list_nth($9, 1),
+ NULL,
+ yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11443,6 +11503,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 831db4af95..644f1919fd 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1572,8 +1572,12 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
Query *qtree;
TargetEntry *tle;
- /* We should only see this in first-stage processing of UPDATE tlists */
- Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE);
+ /*
+ * We should only see this in first-stage processing of UPDATE tlists
+ * (UPDATE_SOURCE), an INSERT SET tlist (VALUES_SINGLE).
+ */
+ Assert(pstate->p_expr_kind == EXPR_KIND_UPDATE_SOURCE ||
+ pstate->p_expr_kind == EXPR_KIND_VALUES_SINGLE);
/* We only need to transform the source if this is the first column */
if (maref->colno == 1)
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 45d77ba3a5..057147d76d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,6 +9,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
col1 | col2 | col3
------+------+---------
@@ -16,7 +17,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -44,7 +46,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- VALUES test
@@ -58,10 +61,11 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(8 rows)
--
-- TOASTed value test
@@ -74,11 +78,12 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(9 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 67eaeb4f3e..7ec234b186 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..0306669167 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..d3e04bbad9 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -404,6 +404,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.17.1
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed
It builds failed by applying to the latest code version, and I try head
'73025140885c889410b9bfc4a30a3866396fc5db' which work well.
The new status of this patch is: Waiting on Author
Hi Movead,
On 22/04/2020, at 2:40 PM, movead li <movead.li@highgo.ca> wrote:
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passedIt builds failed by applying to the latest code version, and I try head
'73025140885c889410b9bfc4a30a3866396fc5db' which work well.The new status of this patch is: Waiting on Author
Thank you for the review, attached is v7 of the patch which should
apply correcly to HEAD.
This version now uses it's own production rule for the SET clause to
avoid the issue with MultiAssigmentRef nodes in the targetList.
Attachments:
insert-set-v7.patchapplication/octet-stream; name=insert-set-v7.patch; x-unix-mode=0644Download
From 12f361865276439b513cd1c17197889334b1b2b6 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth@internetnz.net.nz>
Date: Thu, 23 Apr 2020 03:24:48 +0000
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 56 +++++++++++++-
src/backend/parser/gram.y | 76 ++++++++++++++++++-
src/test/regress/expected/insert.out | 13 +++-
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++
src/test/regress/sql/insert.sql | 1 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
8 files changed, 172 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index a6cec6b02e..07930da71f 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -264,6 +274,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -644,6 +666,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -690,6 +721,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -746,6 +787,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -756,7 +809,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c78f2d1b5..1997d345a2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -402,7 +402,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
qualified_name_list any_name any_name_list type_name_list
any_operator expr_list attrs
target_list opt_target_list insert_column_list set_target_list
- set_clause_list set_clause
+ set_clause_list set_clause insert_set_list
def_list operator_def_list indirection opt_indirection
reloption_list group_clause TriggerFuncArgs opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -475,7 +475,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -509,7 +509,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <range> relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
-%type <target> target_el set_target insert_column_item
+%type <target> target_el set_target insert_column_item insert_set_item
%type <str> generic_option_name
%type <node> generic_option_arg
@@ -11064,6 +11064,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -11095,6 +11104,64 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET insert_set_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ListCell *col_cell;
+ List *values = NIL;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET insert_set_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = $6;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11, $10, NULL, yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
+insert_set_list:
+ insert_set_item
+ { $$ = list_make1($1); }
+ | insert_set_list ',' insert_set_item
+ { $$ = lappend($1, $3); }
+ ;
+
+insert_set_item:
+ insert_column_item '=' a_expr
+ {
+ $$ = $1;
+ $$->val = $3;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11478,6 +11545,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 45d77ba3a5..057147d76d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,6 +9,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
col1 | col2 | col3
------+------+---------
@@ -16,7 +17,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -44,7 +46,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- VALUES test
@@ -58,10 +61,11 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(8 rows)
--
-- TOASTed value test
@@ -74,11 +78,12 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(9 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b23e..4d6538b1fb 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 67eaeb4f3e..7ec234b186 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -952,6 +952,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 23885f638c..0306669167 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 43691cd335..e0eb2df0c8 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f85645efde..d3e04bbad9 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -404,6 +404,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.17.1
On 4/23/20 8:04 PM, Gareth Palmer wrote:
Thank you for the review, attached is v7 of the patch which should
apply correcly to HEAD.This version now uses it's own production rule for the SET clause to
avoid the issue with MultiAssigmentRef nodes in the targetList.
Ibrar, Movead, you are the reviewers of this patch. Do you think all of
Tom's and Peter's concerns have been addressed?
If so, please mark as Ready for Committer so somebody can have a look.
If not, what remains to be done?
Regards,
--
-David
david@pgmasters.net
On 4/23/20 8:04 PM, Gareth Palmer wrote:
Thank you for the review, attached is v7 of the patch which should
apply correcly to HEAD.
Hello Gareth,
This patch no longer applies to HEAD, can you please submit a rebased version?
Thanks,
Rachel
Hello Rachel,
On Wed, 22 Sept 2021 at 17:13, Rachel Heaton <rachelmheaton@gmail.com> wrote:
On 4/23/20 8:04 PM, Gareth Palmer wrote:
Thank you for the review, attached is v7 of the patch which should
apply correcly to HEAD.Hello Gareth,
This patch no longer applies to HEAD, can you please submit a rebased version?
Attached is a rebased version that should apply to HEAD.
Gareth
Show quoted text
Thanks,
Rachel
Attachments:
insert-set-v8.patchtext/x-patch; charset=US-ASCII; name=insert-set-v8.patchDownload
From 7e222b4068e445a723f1692c5cdeec99d498a161 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth.palmer3@gmail.com>
Date: Wed, 22 Sep 2021 05:09:28 +0000
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 56 +++++++++++++-
src/backend/parser/gram.y | 77 ++++++++++++++++++-
src/test/regress/expected/insert.out | 13 +++-
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++
src/test/regress/sql/insert.sql | 1 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
8 files changed, 173 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index 2973b72b81..63c0579d4b 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -263,6 +273,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -643,6 +665,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -689,6 +720,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -745,6 +786,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -755,7 +808,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e3068a374e..ea5f93fb33 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -412,7 +412,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
any_operator expr_list attrs
distinct_clause opt_distinct_clause
target_list opt_target_list insert_column_list set_target_list
- set_clause_list set_clause
+ set_clause_list set_clause insert_set_list
def_list operator_def_list indirection opt_indirection
reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -483,7 +483,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -519,7 +519,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <range> relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
-%type <target> target_el set_target insert_column_item
+%type <target> target_el set_target insert_column_item insert_set_item
%type <str> generic_option_name
%type <node> generic_option_arg
@@ -11037,6 +11037,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -11068,6 +11077,65 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET insert_set_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ListCell *col_cell;
+ List *values = NIL;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET insert_set_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = ($6)->list;
+ n->groupDistinct = ($6)->distinct;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11, $10, NULL, yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
+insert_set_list:
+ insert_set_item
+ { $$ = list_make1($1); }
+ | insert_set_list ',' insert_set_item
+ { $$ = lappend($1, $3); }
+ ;
+
+insert_set_item:
+ insert_column_item '=' a_expr
+ {
+ $$ = $1;
+ $$->val = $3;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11457,6 +11525,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..231542a7cf 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,6 +9,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
col1 | col2 | col3
------+------+---------
@@ -16,7 +17,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -44,7 +46,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- VALUES test
@@ -58,10 +61,11 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(8 rows)
--
-- TOASTed value test
@@ -74,11 +78,12 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(9 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 66d8633e3e..0a342ca5e9 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index a3a2e383e3..5329d6fd32 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1780,6 +1780,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bfaa8a3b27..1e4ecb37db 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 23d5778b82..95223bd831 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 46668a903e..5fef9f7542 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -787,6 +787,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.25.1
Since this feature adds INSERT OVERRIDING SET syntax, it is recommended to add some related testcases.
Regards
Wenjing
Show quoted text
2021年9月22日 07:38,Rachel Heaton <rachelmheaton@gmail.com> 写道:
On 4/23/20 8:04 PM, Gareth Palmer wrote:
Thank you for the review, attached is v7 of the patch which should
apply correcly to HEAD.Hello Gareth,
This patch no longer applies to HEAD, can you please submit a rebased version?
Thanks,
Rachel
Attachments:
0001-insert-overriding-set-case.patchapplication/octet-stream; name=0001-insert-overriding-set-case.patch; x-unix-mode=0644Download
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7b..5dd211e4563 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -160,6 +160,28 @@ SELECT * FROM itest5;
16 | iii
(21 rows)
+TRUNCATE TABLE itest5;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -1, b = 'aa';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'bb';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET b = 'cc';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -1, b = 'aaa';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'bbb';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING USER VALUE SET b = 'ccc';
+SELECT * FROM itest5;
+ a | b
+----+-----
+ -1 | aa
+ 17 | bb
+ 18 |
+ 19 | cc
+ 20 | aaa
+ 21 | bbb
+ 22 |
+ 23 | ccc
+(8 rows)
+
DROP TABLE itest5;
INSERT INTO itest3 VALUES (DEFAULT, 'a');
INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 52800f265c2..a659d9e9405 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -77,6 +77,21 @@ INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
SELECT * FROM itest5;
+
+TRUNCATE TABLE itest5;
+
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -1, b = 'aa';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'bb';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET b = 'cc';
+
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -1, b = 'aaa';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'bbb';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING USER VALUE SET b = 'ccc';
+
+SELECT * FROM itest5;
+
DROP TABLE itest5;
INSERT INTO itest3 VALUES (DEFAULT, 'a');
--
2.32.0 (Apple Git-132)
Hi,
On Fri, Jan 21, 2022 at 05:24:51PM +0800, wenjing zeng wrote:
Since this feature adds INSERT OVERRIDING SET syntax, it is recommended to add some related testcases.
Thanks for proposing some more tests.
Note that your patch caused Gareth's patches to break under the cfbot.
http://cfbot.cputube.org/gareth-palmer.html
You have to either include the pre-requisite patches as 0001, and your patch as
0002 (as I'm doing now), or name your patch something other than *.diff or
*.patch, so cfbot doesn't think it's a new version of the patch to be tested.
Thanks,
--
Justin
Attachments:
0001-Implement-INSERT-SET-syntax.patchtext/x-diff; charset=us-asciiDownload
From 757b3013c58cc33bffd509988833588fc0806d6b Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth.palmer3@gmail.com>
Date: Wed, 22 Sep 2021 05:09:28 +0000
Subject: [PATCH 1/2] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 56 +++++++++++++-
src/backend/parser/gram.y | 77 ++++++++++++++++++-
src/test/regress/expected/insert.out | 13 +++-
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++
src/test/regress/sql/insert.sql | 1 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
8 files changed, 173 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index 2973b72b815..63c0579d4b7 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -263,6 +273,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -643,6 +665,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -689,6 +720,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -745,6 +786,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -755,7 +808,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5966712ce1..0bd9add8d2e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -433,7 +433,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
any_operator expr_list attrs
distinct_clause opt_distinct_clause
target_list opt_target_list insert_column_list set_target_list
- set_clause_list set_clause
+ set_clause_list set_clause insert_set_list
def_list operator_def_list indirection opt_indirection
reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -503,7 +503,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
@@ -540,7 +540,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
-%type <target> target_el set_target insert_column_item
+%type <target> target_el set_target insert_column_item insert_set_item
%type <str> generic_option_name
%type <node> generic_option_arg
@@ -11197,6 +11197,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -11228,6 +11237,65 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET insert_set_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ListCell *col_cell;
+ List *values = NIL;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET insert_set_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = ($6)->list;
+ n->groupDistinct = ($6)->distinct;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11, $10, NULL, yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
+insert_set_list:
+ insert_set_item
+ { $$ = list_make1($1); }
+ | insert_set_list ',' insert_set_item
+ { $$ = lappend($1, $3); }
+ ;
+
+insert_set_item:
+ insert_column_item '=' a_expr
+ {
+ $$ = $1;
+ $$->val = $3;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -11617,6 +11685,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc221..231542a7cf3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,6 +9,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
col1 | col2 | col3
------+------+---------
@@ -16,7 +17,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -44,7 +46,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- VALUES test
@@ -58,10 +61,11 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(8 rows)
--
-- TOASTed value test
@@ -74,11 +78,12 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(9 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 66d8633e3ec..0a342ca5e92 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index f15ece3bd18..e3a6e34d32a 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1780,6 +1780,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bfaa8a3b277..1e4ecb37dbe 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 23d5778b821..95223bd8313 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 7ff9de97a5f..6ec7815e02b 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -787,6 +787,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.17.1
0002-Since-this-feature-adds-INSERT-OVERRIDING-SET-syntax.patchtext/x-diff; charset=us-asciiDownload
From 4c42f9fb4f4b596da9e1fc1b4d15009b6eb035a1 Mon Sep 17 00:00:00 2001
From: wenjing zeng <wjzeng2012@gmail.com>
Date: Fri, 21 Jan 2022 17:24:51 +0800
Subject: [PATCH 2/2] Since this feature adds INSERT OVERRIDING SET syntax, it
is recommended to add some related testcases.
Jan 21 wenjing zeng ( 111) Re: [PATCH] Implement INSERT SET syntax
---
src/test/regress/expected/identity.out | 22 ++++++++++++++++++++++
src/test/regress/sql/identity.sql | 15 +++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7b..5dd211e4563 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -160,6 +160,28 @@ SELECT * FROM itest5;
16 | iii
(21 rows)
+TRUNCATE TABLE itest5;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -1, b = 'aa';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'bb';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET b = 'cc';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -1, b = 'aaa';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'bbb';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING USER VALUE SET b = 'ccc';
+SELECT * FROM itest5;
+ a | b
+----+-----
+ -1 | aa
+ 17 | bb
+ 18 |
+ 19 | cc
+ 20 | aaa
+ 21 | bbb
+ 22 |
+ 23 | ccc
+(8 rows)
+
DROP TABLE itest5;
INSERT INTO itest3 VALUES (DEFAULT, 'a');
INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 52800f265c2..a659d9e9405 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -77,6 +77,21 @@ INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
SELECT * FROM itest5;
+
+TRUNCATE TABLE itest5;
+
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -1, b = 'aa';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'bb';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET b = 'cc';
+
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -1, b = 'aaa';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'bbb';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT;
+INSERT INTO itest5 OVERRIDING USER VALUE SET b = 'ccc';
+
+SELECT * FROM itest5;
+
DROP TABLE itest5;
INSERT INTO itest3 VALUES (DEFAULT, 'a');
--
2.17.1
Justin Pryzby <pryzby@telsasoft.com> writes:
You have to either include the pre-requisite patches as 0001, and your patch as
0002 (as I'm doing now), or name your patch something other than *.diff or
*.patch, so cfbot doesn't think it's a new version of the patch to be tested.
This patch has been basically ignored for a full two years now.
(Remarkably, it's still passing in the cfbot.)
I have to think that that means there's just not enough interest
to justify committing it. Should we mark it rejected and move on?
If not, what needs to happen to get it unstuck?
regards, tom lane
On Wed, Mar 23, 2022 at 5:33 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Justin Pryzby <pryzby@telsasoft.com> writes:
You have to either include the pre-requisite patches as 0001, and your patch as
0002 (as I'm doing now), or name your patch something other than *.diff or
*.patch, so cfbot doesn't think it's a new version of the patch to be tested.This patch has been basically ignored for a full two years now.
(Remarkably, it's still passing in the cfbot.)I have to think that that means there's just not enough interest
to justify committing it. Should we mark it rejected and move on?
If not, what needs to happen to get it unstuck?
I can help with review and/or other work here. Please give me a
couple of weeks.
.m
On Thu, Apr 7, 2022 at 11:29 AM Marko Tiikkaja <marko@joh.to> wrote:
I can help with review and/or other work here. Please give me a
couple of weeks.
Hi Marko, did you get a chance to pick up this patchset? If not, no
worries; I can mark this RwF and we can try again in a future
commitfest.
Thanks,
--Jacob
Hello,
Here is a new version of the patch that applies to HEAD.
It also adds some regression tests for overriding {system,user} values
based on Wenjing Zeng's work.
Gareth
Show quoted text
On Thu, 14 Jul 2022 at 22:40, Jacob Champion <jchampion@timescale.com> wrote:
On Thu, Apr 7, 2022 at 11:29 AM Marko Tiikkaja <marko@joh.to> wrote:
I can help with review and/or other work here. Please give me a
couple of weeks.Hi Marko, did you get a chance to pick up this patchset? If not, no
worries; I can mark this RwF and we can try again in a future
commitfest.Thanks,
--Jacob
Attachments:
insert-set-v9.patchtext/x-patch; charset=US-ASCII; name=insert-set-v9.patchDownload
From f1ab7edadac846883b9c12f02ecc6c64dd293060 Mon Sep 17 00:00:00 2001
From: Gareth Palmer <gareth.palmer3@gmail.com>
Date: Thu, 14 Jul 2022 01:47:17 +0000
Subject: [PATCH] Implement INSERT SET syntax
Allow the target column and values of an INSERT statement to be specified
using a SET clause in the same manner as that of an UPDATE statement.
The advantage of using the INSERT SET style is that the columns and values
are kept together, which can make changing or removing a column or value
from a large list easier.
A simple example that uses SET instead of a VALUES() clause:
INSERT INTO t SET c1 = 'foo', c2 = 'bar', c3 = 'baz';
Values can also be sourced from other tables similar to the INSERT INTO
SELECT FROM syntax:
INSERT INTO t SET c1 = x.c1, c2 = x.c2 FROM x WHERE x.c2 > 10 LIMIT 10;
INSERT SET is not part of any SQL standard, however this syntax is also
implemented by MySQL. Their implementation does not support specifying a
FROM clause.
---
doc/src/sgml/ref/insert.sgml | 56 +++++++++++++-
src/backend/parser/gram.y | 77 ++++++++++++++++++-
src/test/regress/expected/identity.out | 42 ++++++----
src/test/regress/expected/insert.out | 13 +++-
src/test/regress/expected/insert_conflict.out | 2 +
src/test/regress/expected/with.out | 20 +++++
src/test/regress/sql/identity.sql | 9 +++
src/test/regress/sql/insert.sql | 1 +
src/test/regress/sql/insert_conflict.sql | 3 +
src/test/regress/sql/with.sql | 9 +++
10 files changed, 210 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index a9af9959c0..b774b6ce74 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -28,6 +28,16 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
[ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
+INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ]
+ [ OVERRIDING { SYSTEM | USER} VALUE ]
+ SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } } [, ...]
+ [ FROM <replaceable class="parameter">from_clause</replaceable> ]
+ [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ]
+ [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
+
+
<phrase>where <replaceable class="parameter">conflict_target</replaceable> can be one of:</phrase>
( { <replaceable class="parameter">index_column_name</replaceable> | ( <replaceable class="parameter">index_expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WHERE <replaceable class="parameter">index_predicate</replaceable> ]
@@ -263,6 +273,18 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">from_clause</replaceable></term>
+ <listitem>
+ <para>
+ A list of table expressions, allowing columns from other tables
+ to be used as values in the <literal>expression</literal>.
+ Refer to the <xref linkend="sql-select"/> statement for a
+ description of the syntax.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFAULT</literal></term>
<listitem>
@@ -650,6 +672,15 @@ INSERT INTO films (code, title, did, date_prod, kind) VALUES
</programlisting>
</para>
+ <para>
+ Insert a row using <command>SET</command> syntax:
+
+<programlisting>
+INSERT INTO films SET code = 'MH832', title = 'Blade Runner',
+ did = 201, date_prod = DEFAULT, kind = 'SciFi';
+</programlisting>
+ </para>
+
<para>
This example inserts some rows into table
<literal>films</literal> from a table <literal>tmp_films</literal>
@@ -696,6 +727,16 @@ WITH upd AS (
INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
</programlisting>
</para>
+ <para>
+ Insert multiple rows into <literal>employees_log</literal> containing
+the hours worked by each employee from <literal>time_sheets</literal>.
+<programlisting>
+INSERT INTO employees_log SET id = time_sheets.employee,
+ total_hours = sum(time_sheets.hours) FROM time_sheets
+ WHERE time_sheets.date ≥ '2019-11-15' GROUP BY time_sheets.employee;
+</programlisting>
+ </para>
+
<para>
Insert or update new distributors as appropriate. Assumes a unique
index has been defined that constrains values appearing in the
@@ -752,6 +793,18 @@ INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
ON CONFLICT (did) WHERE is_active DO NOTHING;
</programlisting></para>
+ <para>
+ Insert a new film into <literal>watched_films</literal> or increment the
+ number of times seen. Returns the new seen count, example assumes a
+ unique index has been defined that constrains the values appearing in
+ the <literal>title</literal> and <literal>year</literal> columns and
+ that <literal>seen_count</literal> defaults to 1.
+<programlisting>
+INSERT INTO watched_films SET title = 'Akira', year = 1988
+ ON CONFLICT (title, year) DO UPDATE SET seen_count = watched_films.seen_count + 1
+ RETURNING watched_films.seen_count;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
@@ -762,7 +815,8 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
the <literal>RETURNING</literal> clause is a
<productname>PostgreSQL</productname> extension, as is the ability
to use <literal>WITH</literal> with <command>INSERT</command>, and the ability to
- specify an alternative action with <literal>ON CONFLICT</literal>.
+ specify an alternative action with <literal>ON CONFLICT</literal>, and the
+ ability to specify the inserted columns using <literal>SET</literal>.
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c018140afe..a995875449 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -442,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
distinct_clause opt_distinct_clause
target_list opt_target_list insert_column_list set_target_list
merge_values_clause
- set_clause_list set_clause
+ set_clause_list set_clause insert_set_list
def_list operator_def_list indirection opt_indirection
reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
@@ -512,7 +512,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList
%type <defelt> SeqOptElem
-%type <istmt> insert_rest
+%type <istmt> insert_rest insert_set_clause
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
%type <mergewhen> merge_insert merge_update merge_delete
@@ -553,7 +553,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <range> extended_relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
-%type <target> target_el set_target insert_column_item
+%type <target> target_el set_target insert_column_item insert_set_item
%type <str> generic_option_name
%type <node> generic_option_arg
@@ -12063,6 +12063,15 @@ insert_rest:
$$->override = $5;
$$->selectStmt = $7;
}
+ | insert_set_clause
+ {
+ $$ = $1;
+ }
+ | OVERRIDING override_kind VALUE_P insert_set_clause
+ {
+ $$ = $4;
+ $$->override = $2;
+ }
| DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
@@ -12094,6 +12103,65 @@ insert_column_item:
}
;
+/*
+ * There are two rules here to handle the two different types of INSERT.
+ * INSERT using VALUES and INSERT using SELECT. They can't be combined
+ * because only the VALUES syntax allows specifying DEFAULT.
+ */
+insert_set_clause:
+ SET insert_set_list
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ ListCell *col_cell;
+ List *values = NIL;
+
+ foreach(col_cell, $2)
+ {
+ ResTarget *res_col = (ResTarget *) lfirst(col_cell);
+
+ values = lappend(values, res_col->val);
+ }
+
+ n->valuesLists = list_make1(values);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ | SET insert_set_list FROM from_list where_clause group_clause
+ having_clause window_clause opt_sort_clause opt_select_limit
+ opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $2;
+ n->fromClause = $4;
+ n->whereClause = $5;
+ n->groupClause = ($6)->list;
+ n->groupDistinct = ($6)->distinct;
+ n->havingClause = $7;
+ n->windowClause = $8;
+ insertSelectOptions(n, $9, $11, $10, NULL, yyscanner);
+ $$ = makeNode(InsertStmt);
+ $$->cols = $2;
+ $$->selectStmt = (Node *) n;
+ }
+ ;
+
+insert_set_list:
+ insert_set_item
+ { $$ = list_make1($1); }
+ | insert_set_list ',' insert_set_item
+ { $$ = lappend($1, $3); }
+ ;
+
+insert_set_item:
+ insert_column_item '=' a_expr
+ {
+ $$ = $1;
+ $$->val = $3;
+ }
+ ;
+
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
@@ -12646,6 +12714,9 @@ select_clause:
*
* NOTE: only the leftmost component SelectStmt should have INTO.
* However, this is not checked by the grammar; parse analysis must check it.
+ *
+ * NOTE: insert_set_clause also has SELECT-like syntax so if you add any
+ * clauses after from_clause here you may need to add them there as well.
*/
simple_select:
SELECT opt_all_clause opt_target_list
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 5f03d8e14f..08df7aec3a 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -124,41 +124,55 @@ ERROR: cannot insert a non-DEFAULT value into column "a"
DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
HINT: Use OVERRIDING SYSTEM VALUE to override.
INSERT INTO itest5 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -- ok
+INSERT INTO itest5 SET a = 4, b = 'a'; -- error
+ERROR: cannot insert a non-DEFAULT value into column "a"
+DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
+HINT: Use OVERRIDING SYSTEM VALUE to override.
+INSERT INTO itest5 SET a = DEFAULT, b = 'd'; -- ok
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-1, 'aa');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-2, 'bb'), (-3, 'cc');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'dd'), (-4, 'ee');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-5, 'ff'), (DEFAULT, 'gg');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'hh'), (DEFAULT, 'ii');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -6, b = 'jj';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'kk';
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-1, 'aaa');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-2, 'bbb'), (-3, 'ccc');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'ddd'), (-4, 'eee');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -6, b = 'jjj';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'kkk';
SELECT * FROM itest5;
a | b
----+-----
1 | a
2 | b
3 | c
+ 4 | d
-1 | aa
-2 | bb
-3 | cc
- 4 | dd
+ 5 | dd
-4 | ee
-5 | ff
- 5 | gg
- 6 | hh
- 7 | ii
- 8 | aaa
- 9 | bbb
- 10 | ccc
- 11 | ddd
- 12 | eee
- 13 | fff
- 14 | ggg
- 15 | hhh
- 16 | iii
-(21 rows)
+ 6 | gg
+ 7 | hh
+ 8 | ii
+ -6 | jj
+ 9 | kk
+ 10 | aaa
+ 11 | bbb
+ 12 | ccc
+ 13 | ddd
+ 14 | eee
+ 15 | fff
+ 16 | ggg
+ 17 | hhh
+ 18 | iii
+ 19 | jjj
+ 20 | kkk
+(26 rows)
DROP TABLE itest5;
INSERT INTO itest3 VALUES (DEFAULT, 'a');
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index dd4354fc7d..3dd6ef855c 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -9,6 +9,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
col1 | col2 | col3
------+------+---------
@@ -16,7 +17,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- insert with similar expression / target_list values (all fail)
@@ -44,7 +46,8 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
-(4 rows)
+ | 9 | testing
+(5 rows)
--
-- VALUES test
@@ -58,10 +61,11 @@ select * from inserttest;
| 5 | testing
| 5 | test
| 7 | testing
+ | 9 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
-(7 rows)
+(8 rows)
--
-- TOASTed value test
@@ -74,11 +78,12 @@ select col1, col2, char_length(col3) from inserttest;
| 5 | 7
| 5 | 4
| 7 | 7
+ | 9 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
-(8 rows)
+(9 rows)
drop table inserttest;
--
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 66d8633e3e..0a342ca5e9 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -236,6 +236,8 @@ insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key)
insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 30dd900e11..9a10453b67 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1790,6 +1790,26 @@ SELECT * FROM y;
10
(10 rows)
+TRUNCATE TABLE y;
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+SELECT * FROM y;
+ a
+----
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+(10 rows)
+
DROP TABLE y;
--
-- error cases
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 9b8db2e4a3..f844d7ebfa 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -64,18 +64,27 @@ INSERT INTO itest5 VALUES (DEFAULT, 'b'), (3, 'c'); -- error
INSERT INTO itest5 VALUES (2, 'b'), (DEFAULT, 'c'); -- error
INSERT INTO itest5 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -- ok
+INSERT INTO itest5 SET a = 4, b = 'a'; -- error
+INSERT INTO itest5 SET a = DEFAULT, b = 'd'; -- ok
+
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-1, 'aa');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-2, 'bb'), (-3, 'cc');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'dd'), (-4, 'ee');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-5, 'ff'), (DEFAULT, 'gg');
INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'hh'), (DEFAULT, 'ii');
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = -6, b = 'jj';
+INSERT INTO itest5 OVERRIDING SYSTEM VALUE SET a = DEFAULT, b = 'kk';
+
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-1, 'aaa');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-2, 'bbb'), (-3, 'ccc');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'ddd'), (-4, 'eee');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg');
INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii');
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = -6, b = 'jjj';
+INSERT INTO itest5 OVERRIDING USER VALUE SET a = DEFAULT, b = 'kkk';
+
SELECT * FROM itest5;
DROP TABLE itest5;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bdcffd0314..8265c8d993 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -7,6 +7,7 @@ insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
+insert into inserttest set col1 = DEFAULT, col2 = 9;
select * from inserttest;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 23d5778b82..95223bd831 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -97,6 +97,9 @@ insert into insertconflicttest
values (1, 'Apple'), (2, 'Orange')
on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key);
+-- Using insert set syntax
+insert into insertconflicttest set key = 1, fruit = 'Banana' on conflict (key) do update set fruit = excluded.fruit;
+
-- Give good diagnostic message when EXCLUDED.* spuriously referenced from
-- RETURNING:
insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 5c52561a8a..b8c68a3e29 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -797,6 +797,15 @@ DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
SELECT * FROM y;
+TRUNCATE TABLE y;
+
+WITH t AS (
+ SELECT generate_series(1, 10) AS a
+)
+INSERT INTO y SET a = t.a+20 FROM t;
+
+SELECT * FROM y;
+
DROP TABLE y;
--
--
2.25.1
As discussed in [1]/messages/by-id/flat/0ab66589-2f71-69b3-2002-49e821740b0d@timescale.com, we're taking this opportunity to return some
patchsets that don't appear to be getting enough reviewer interest.
This is not a rejection, since we don't necessarily think there's
anything unacceptable about the entry, but it differs from a standard
"Returned with Feedback" in that there's probably not much actionable
feedback at all. Rather than code changes, what this patch needs is more
community interest. You might
- ask people for help with your approach,
- see if there are similar patches that your code could supplement,
- get interested parties to agree to review your patch in a CF, or
- possibly present the functionality in a way that's easier to review
overall.
(Doing these things is no guarantee that there will be interest, but
it's hopefully better than endlessly rebasing a patchset that is not
receiving any feedback from the community.)
Once you think you've built up some community support and the patchset
is ready for review, you (or any interested party) can resurrect the
patch entry by visiting
https://commitfest.postgresql.org/38/2218/
and changing the status to "Needs Review", and then changing the
status again to "Move to next CF". (Don't forget the second step;
hopefully we will have streamlined this in the near future!)
Thanks,
--Jacob
[1]: /messages/by-id/flat/0ab66589-2f71-69b3-2002-49e821740b0d@timescale.com
/messages/by-id/flat/0ab66589-2f71-69b3-2002-49e821740b0d@timescale.com