Hi,

Here's a patch for $SUBJECT. I'll probably work on the docs a bit more before the next CF, but I thought I'd post it anyway.


.m
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index e710cf4..33e577b 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -22,12 +22,21 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 [ 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> [, ...] ) ]
-    { DEFAULT VALUES | VALUES ( { <replaceable 
class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | 
<replaceable class="PARAMETER">query</replaceable> }
+INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ AS 
<replaceable class="parameter">alias</replaceable> ]
+    {
+        [ column_list ] VALUES ( { <replaceable 
class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] |
+        [ column_list ] <replaceable class="PARAMETER">query</replaceable> |
+        DEFAULT VALUES |
+        SET <replaceable class="PARAMETER">column_name</replaceable> = { 
<replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...]
+    }
     [ 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>
+<phrase>where <replaceable class="parameter">column_list</replaceable> 
is:</phrase>
+
+    ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] )
+
+<phrase>and <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> ]
     ON CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
@@ -53,13 +62,26 @@ INSERT INTO <replaceable 
class="PARAMETER">table_name</replaceable> [ AS <replac
   </para>
 
   <para>
-   The target column names can be listed in any order.  If no list of
-   column names is given at all, the default is all the columns of the
-   table in their declared order; or the first <replaceable>N</> column
-   names, if there are only <replaceable>N</> columns supplied by the
-   <literal>VALUES</> clause or <replaceable>query</>.  The values
-   supplied by the <literal>VALUES</> clause or <replaceable>query</> are
-   associated with the explicit or implicit column list left-to-right.
+   The target column names in a <replaceable>column_list</> can be
+   listed in any order.  If no <replaceable>column_list</> is given at
+   all (and the <literal>SET</> syntax is not used), the default is all
+   the columns of the table in their declared order; or the first
+   <replaceable>N</> column names, if there are only <replaceable>N</>
+   columns supplied by the <literal>VALUES</> clause or
+   <replaceable>query</>.  The values supplied by the <literal>VALUES</>
+   clause or <replaceable>query</> are associated with the explicit or
+   implicit column list left-to-right.
+  </para>
+
+  <para>
+    Instead of a <replaceable>column_list</> and a <literal>VALUES</>
+    clause, a <literal>SET</> clause similar to that of an
+    <command>UPDATE</command> can be used instead.  The advantage of the
+    <literal>SET</> clause is that instead of matching the elements in
+    the two lists by ordinal position, the column name and the
+    expression to assign to that column are visually next to each other.
+    This can make long column assignment lists significantly more
+    readable.
   </para>
 
   <para>
@@ -691,13 +713,13 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad 
International')
   <para>
    <command>INSERT</command> conforms to the SQL standard, except that
    the <literal>RETURNING</> clause is a
-   <productname>PostgreSQL</productname> extension, as is the ability
-   to use <literal>WITH</> with <command>INSERT</>, and the ability to
-   specify an alternative action with <literal>ON CONFLICT</>.
-   Also, the case in
-   which a column name list is omitted, but not all the columns are
-   filled from the <literal>VALUES</> clause or <replaceable>query</>,
-   is disallowed by the standard.
+   <productname>PostgreSQL</productname> extension, as is the
+   <literal>SET</> clause when used instead of a VALUES clause, the
+   ability to use <literal>WITH</> with <command>INSERT</>, and the
+   ability to specify an alternative action with <literal>ON
+   CONFLICT</>.  Also, the case in which a column name list is omitted,
+   but not all the columns are filled from the <literal>VALUES</> clause
+   or <replaceable>query</>, is disallowed by the standard.
   </para>
 
   <para>
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 29c8c4e..55c4cb3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -466,8 +466,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                                                stmt->onConflictClause->action 
== ONCONFLICT_UPDATE);
 
        /*
-        * We have three cases to deal with: DEFAULT VALUES (selectStmt == 
NULL),
-        * VALUES list, or general SELECT input.  We special-case VALUES, both 
for
+        * We have four cases to deal with: DEFAULT VALUES (selectStmt == NULL 
and
+        * cols == NIL), SET syntax (selectStmt == NULL but cols != NIL), VALUES
+        * list, or general SELECT input.  We special-case VALUES, both for
         * efficiency and so we can handle DEFAULT specifications.
         *
         * The grammar allows attaching ORDER BY, LIMIT, FOR UPDATE, or WITH to 
a
@@ -522,7 +523,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
        /*
         * Determine which variant of INSERT we have.
         */
-       if (selectStmt == NULL)
+       if (selectStmt == NULL && stmt->cols == NIL)
        {
                /*
                 * We have INSERT ... DEFAULT VALUES.  We can handle this case 
by
@@ -531,6 +532,25 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                 */
                exprList = NIL;
        }
+       else if (selectStmt == NULL)
+       {
+               /*
+                * INSERT INTO ... SET syntax.
+                */
+               Assert(stmt->cols != NIL);
+
+               stmt->cols = transformUpdateTargetList(pstate, stmt->cols);
+
+               exprList = NIL;
+               foreach(lc, stmt->cols)
+               {
+                       TargetEntry *rt = (TargetEntry *) lfirst(lc);
+
+                       Assert(IsA(rt, TargetEntry));
+
+                       exprList = lappend(exprList, rt->expr);
+               }
+       }
        else if (isGeneralSelect)
        {
                /*
@@ -2130,7 +2150,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 /*
  * transformUpdateTargetList -
- *     handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
+ *     handle SET clause in UPDATE / INSERT ... ON CONFLICT UPDATE / INSERT 
INTO
+ *     ... SET
  */
 static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index edf4516..8062ee3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -360,7 +360,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 multiple_set_clause
+                               insert_set_clause_list set_clause_list 
set_clause multiple_set_clause
                                ctext_expr_list ctext_row def_list 
operator_def_list indirection opt_indirection
                                reloption_list group_clause TriggerFuncArgs 
select_limit
                                opt_select_limit opclass_item_list 
opclass_drop_list
@@ -9659,6 +9659,12 @@ insert_rest:
                                        $$->cols = $2;
                                        $$->selectStmt = $4;
                                }
+                       | SET insert_set_clause_list
+                               {
+                                       $$ = makeNode(InsertStmt);
+                                       $$->cols = $2;
+                                       $$->selectStmt = NULL;
+                               }
                        | DEFAULT VALUES
                                {
                                        $$ = makeNode(InsertStmt);
@@ -9685,6 +9691,17 @@ insert_column_item:
                                }
                ;
 
+/*
+ * This is different from set_clause_list used in UPDATE because the SelectStmt
+ * syntax already does everything you might want to do in an in INSERT.
+ */
+insert_set_clause_list:
+                       single_set_clause
+                               { $$ = list_make1($1); }
+                       | insert_set_clause_list ',' single_set_clause
+                               { $$ = lappend($1,$3); }
+               ;
+
 opt_on_conflict:
                        ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list 
where_clause
                                {
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d36d9c6..380a4d4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -405,9 +405,11 @@ typedef struct A_ArrayExpr
  * 'AS ColumnLabel' clause, or NULL if there was none, and 'val' is the
  * value expression itself.  The 'indirection' field is not used.
  *
- * INSERT uses ResTarget in its target-column-names list.  Here, 'name' is
- * the name of the destination column, 'indirection' stores any subscripts
- * attached to the destination, and 'val' is not used.
+ * INSERT uses ResTarget in its "cols" list.  Here, 'name' is the name of the
+ * destination column, 'indirection' stores any subscripts attached to the
+ * destination.  'val' is only used if "cols" is from an INSERT ... SET syntax
+ * statement; otherwise "cols" only represents the column name list, and thus
+ * 'val' is unused.
  *
  * In an UPDATE target list, 'name' is the name of the destination column,
  * 'indirection' stores any subscripts attached to the destination, and
diff --git a/src/test/regress/expected/insert.out 
b/src/test/regress/expected/insert.out
index 96c7f9e..eaec247 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -80,4 +80,97 @@ select col1, col2, char_length(col3) from inserttest;
    30 |   50 |       10000
 (8 rows)
 
+--
+-- insert with SET syntax
+--
+truncate inserttest;
+create type inserttestcomplextype as (f1 int, f2 int);
+alter table inserttest add column col4 int[];
+alter table inserttest add column col5 inserttestcomplextype;
+-- these all fail
+insert into inserttest set
+    colnonexistent = 1;
+ERROR:  column "colnonexistent" of relation "inserttest" does not exist
+LINE 2:     colnonexistent = 1;
+            ^
+insert into inserttest set
+    col1 = col1;
+ERROR:  column "col1" does not exist
+LINE 2:     col1 = col1;
+                   ^
+HINT:  There is a column named "col1" in table "inserttest", but it cannot be 
referenced from this part of the query.
+insert into inserttest set
+    col4[1] = 1,
+    col4 = '{}';
+ERROR:  column "col4" specified more than once
+LINE 3:     col4 = '{}';
+            ^
+insert into inserttest set
+    col5.nonexistent = 1;
+ERROR:  cannot assign to field "nonexistent" of column "col5" because there is 
no such column in data type inserttestcomplextype
+LINE 2:     col5.nonexistent = 1;
+            ^
+insert into inserttest set
+    col5.f1 = 1,
+    col5 = '()';
+ERROR:  column "col5" specified more than once
+LINE 3:     col5 = '()';
+            ^
+insert into inserttest set
+    col1 = 1,
+    col1 = 1;
+ERROR:  column "col1" specified more than once
+LINE 3:     col1 = 1;
+            ^
+select * from inserttest;
+ col1 | col2 | col3 | col4 | col5 
+------+------+------+------+------
+(0 rows)
+
+truncate table inserttest;
+-- defaults
+insert into inserttest set
+    col1 = default,
+    col2 = default,
+    col3 = default;
+ERROR:  null value in column "col2" violates not-null constraint
+DETAIL:  Failing row contains (null, null, testing, null, null).
+insert into inserttest set
+    col2 = 3,
+    col3 = default;
+insert into inserttest set
+    col1 = default,
+    col2 = 5,
+    col3 = default;
+select * from inserttest;
+ col1 | col2 |  col3   | col4 | col5 
+------+------+---------+------+------
+      |    3 | testing |      | 
+      |    5 | testing |      | 
+(2 rows)
+
+truncate table inserttest;
+-- more complex cases
+insert into inserttest set
+    col2 = (select count(*))::int;
+insert into inserttest set
+    col2 = 1,
+    col4[1] = 1,
+    -- weird, but accepted
+    col4[1] = 2;
+insert into inserttest set
+    col2 = 1,
+    col5.f1 = 1,
+    -- weird, but accepted
+    col5.f1 = 2;
+select * from inserttest;
+ col1 | col2 |  col3   | col4 | col5 
+------+------+---------+------+------
+      |    1 | testing |      | 
+      |    1 | testing | {2}  | 
+      |    1 | testing |      | (2,)
+(3 rows)
+
+truncate table inserttest;
 drop table inserttest;
+drop type inserttestcomplextype;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a0ae850..652d4a5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -35,4 +35,67 @@ insert into inserttest values(30, 50, repeat('x', 10000));
 
 select col1, col2, char_length(col3) from inserttest;
 
+--
+-- insert with SET syntax
+--
+truncate inserttest;
+
+create type inserttestcomplextype as (f1 int, f2 int);
+alter table inserttest add column col4 int[];
+alter table inserttest add column col5 inserttestcomplextype;
+
+-- these all fail
+insert into inserttest set
+    colnonexistent = 1;
+insert into inserttest set
+    col1 = col1;
+insert into inserttest set
+    col4[1] = 1,
+    col4 = '{}';
+insert into inserttest set
+    col5.nonexistent = 1;
+insert into inserttest set
+    col5.f1 = 1,
+    col5 = '()';
+insert into inserttest set
+    col1 = 1,
+    col1 = 1;
+
+select * from inserttest;
+truncate table inserttest;
+
+-- defaults
+insert into inserttest set
+    col1 = default,
+    col2 = default,
+    col3 = default;
+insert into inserttest set
+    col2 = 3,
+    col3 = default;
+insert into inserttest set
+    col1 = default,
+    col2 = 5,
+    col3 = default;
+
+select * from inserttest;
+truncate table inserttest;
+
+-- more complex cases
+insert into inserttest set
+    col2 = (select count(*))::int;
+insert into inserttest set
+    col2 = 1,
+    col4[1] = 1,
+    -- weird, but accepted
+    col4[1] = 2;
+insert into inserttest set
+    col2 = 1,
+    col5.f1 = 1,
+    -- weird, but accepted
+    col5.f1 = 2;
+
+select * from inserttest;
+truncate table inserttest;
+
 drop table inserttest;
+drop type inserttestcomplextype;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to