Hello hello,

Here's a rebased and updated patch for $SUBJECT for the September commit fest.


.m
*** a/doc/src/sgml/ref/insert.sgml
--- b/doc/src/sgml/ref/insert.sgml
***************
*** 22,33 **** 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> }
      [ 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> ]
      ON CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
--- 22,42 ----
   <refsynopsisdiv>
  <synopsis>
  [ WITH [ RECURSIVE ] <replaceable class="parameter">with_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">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,65 **** 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.
    </para>
  
    <para>
--- 62,87 ----
    </para>
  
    <para>
!    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>
***************
*** 690,702 **** 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.
    </para>
  
    <para>
--- 712,724 ----
    <para>
     <command>INSERT</command> conforms to the SQL standard, except that
     the <literal>RETURNING</> clause is a
!    <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>
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 467,474 **** 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
  	 * efficiency and so we can handle DEFAULT specifications.
  	 *
  	 * The grammar allows attaching ORDER BY, LIMIT, FOR UPDATE, or WITH to a
--- 467,475 ----
  						stmt->onConflictClause->action == ONCONFLICT_UPDATE);
  
  	/*
! 	 * 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
***************
*** 523,529 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  	/*
  	 * Determine which variant of INSERT we have.
  	 */
! 	if (selectStmt == NULL)
  	{
  		/*
  		 * We have INSERT ... DEFAULT VALUES.  We can handle this case by
--- 524,530 ----
  	/*
  	 * Determine which variant of INSERT we have.
  	 */
! 	if (selectStmt == NULL && stmt->cols == NIL)
  	{
  		/*
  		 * We have INSERT ... DEFAULT VALUES.  We can handle this case by
***************
*** 532,537 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 533,557 ----
  		 */
  		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)
  	{
  		/*
***************
*** 2179,2185 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
  
  /*
   * transformUpdateTargetList -
!  *	handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
   */
  static List *
  transformUpdateTargetList(ParseState *pstate, List *origTlist)
--- 2199,2206 ----
  
  /*
   * transformUpdateTargetList -
!  *	handle SET clause in UPDATE / INSERT ... ON CONFLICT UPDATE / INSERT INTO
!  *	... SET
   */
  static List *
  transformUpdateTargetList(ParseState *pstate, List *origTlist)
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 362,368 **** 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
  				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
--- 362,368 ----
  				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
! 				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
***************
*** 9661,9666 **** insert_rest:
--- 9661,9672 ----
  					$$->cols = $2;
  					$$->selectStmt = $4;
  				}
+ 			| SET insert_set_clause_list
+ 				{
+ 					$$ = makeNode(InsertStmt);
+ 					$$->cols = $2;
+ 					$$->selectStmt = NULL;
+ 				}
  			| DEFAULT VALUES
  				{
  					$$ = makeNode(InsertStmt);
***************
*** 9687,9692 **** insert_column_item:
--- 9693,9709 ----
  				}
  		;
  
+ /*
+  * 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
  				{
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 406,414 **** 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.
   *
   * In an UPDATE target list, 'name' is the name of the destination column,
   * 'indirection' stores any subscripts attached to the destination, and
--- 406,416 ----
   * '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 "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
*** a/src/test/regress/expected/insert.out
--- b/src/test/regress/expected/insert.out
***************
*** 80,85 **** select col1, col2, char_length(col3) from inserttest;
--- 80,177 ----
     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;
  --
  -- check indirection (field/array assignment), cf bug #14265
***************
*** 160,162 **** Rules:
--- 252,255 ----
  drop table inserttest2;
  drop table inserttest;
  drop type insert_test_type;
+ drop type inserttestcomplextype;
*** a/src/test/regress/sql/insert.sql
--- b/src/test/regress/sql/insert.sql
***************
*** 35,40 **** insert into inserttest values(30, 50, repeat('x', 10000));
--- 35,102 ----
  
  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;
  
  --
***************
*** 84,86 **** create rule irule3 as on insert to inserttest2 do also
--- 146,149 ----
  drop table inserttest2;
  drop table inserttest;
  drop type insert_test_type;
+ 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