So I started looking into the bug noted in [1], but before getting to multi-row inserts, I concluded that the current single-row behaviour isn't spec-compliant.
In particular, Syntax Rule 11b of section 14.11 says that an INSERT statement on a GENERATED ALWAYS identity column must specify an overriding clause, but it doesn't place any restriction on the type of overriding clause allowed. In other words it should be possible to use either OVERRIDING SYSTEM VALUE or OVERRIDING USER VALUE, but we currently throw an error unless it's the former. It's useful to allow OVERRIDING USER VALUE for precisely the example use-case given in the INSERT docs: This clause is useful for example when copying values between tables. Writing <literal>INSERT INTO tbl2 OVERRIDING USER VALUE SELECT * FROM tbl1</literal> will copy from <literal>tbl1</literal> all columns that are not identity columns in <literal>tbl2</literal> while values for the identity columns in <literal>tbl2</literal> will be generated by the sequences associated with <literal>tbl2</literal>. which currently only works for a GENERATED BY DEFAULT identity column, but should work equally well for a GENERATED ALWAYS identity column. So I propose the attached patch. Regards, Dean [1] https://postgr.es/m/CAEZATCUmSp3-8nLOpgGcPkpUEXK9TJGM%3DiA6q4E2Sn%3D%2BbwkKNA%40mail.gmail.com
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml new file mode 100644 index 22dbc07..b48a29e --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -803,8 +803,8 @@ WITH ( MODULUS <replaceable class="param <para> This clause creates the column as an <firstterm>identity column</firstterm>. It will have an implicit sequence attached to it - and the column in new rows will automatically have values from the - sequence assigned to it. + and new rows in the column will automatically have values from the + sequence assigned to them. </para> <para> @@ -815,12 +815,22 @@ WITH ( MODULUS <replaceable class="param only accepted if the <command>INSERT</command> statement specifies <literal>OVERRIDING SYSTEM VALUE</literal>. If <literal>BY DEFAULT</literal> is specified, then the user-specified value takes - precedence. See <xref linkend="sql-insert"/> for details. (In + precedence, unless the <command>INSERT</command> statement specifies + <literal>OVERRIDING USER VALUE</literal>. + See <xref linkend="sql-insert"/> for details. (In the <command>COPY</command> command, user-specified values are always used regardless of this setting.) </para> <para> + Additionally, if <literal>ALWAYS</literal> is specified, any attempt to + update the value of the column using an <command>UPDATE</command> + statement specifying any value other than <literal>DEFAULT</literal> + will be rejected. If <literal>BY DEFAULT</literal> is specified, the + system will allow values in the column to be updated. + </para> + + <para> The optional <replaceable>sequence_options</replaceable> clause can be used to override the options of the sequence. See <xref linkend="sql-createsequence"/> for details. diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml new file mode 100644 index 62e142f..8108f31 --- a/doc/src/sgml/ref/insert.sgml +++ b/doc/src/sgml/ref/insert.sgml @@ -206,10 +206,16 @@ INSERT INTO <replaceable class="paramete <term><literal>OVERRIDING SYSTEM VALUE</literal></term> <listitem> <para> - Without this clause, it is an error to specify an explicit value - (other than <literal>DEFAULT</literal>) for an identity column defined - as <literal>GENERATED ALWAYS</literal>. This clause overrides that - restriction. + If this clause is specified, then any values supplied for identity + columns will override the default sequence-generated values. + </para> + + <para> + For an identity column defined as <literal>GENERATED ALWAYS</literal>, + it is an error to insert an explicit value (other than + <literal>DEFAULT</literal>) without specifying either + <literal>OVERRIDING SYSTEM VALUE</literal> or + <literal>OVERRIDING USER VALUE</literal>. </para> </listitem> </varlistentry> @@ -219,8 +225,8 @@ INSERT INTO <replaceable class="paramete <listitem> <para> If this clause is specified, then any values supplied for identity - columns defined as <literal>GENERATED BY DEFAULT</literal> are ignored - and the default sequence-generated values are applied. + columns are ignored and the default sequence-generated values are + applied. </para> <para> diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index 7eb41ff..3dc27eb --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -821,13 +821,15 @@ rewriteTargetListIU(List *targetList, { if (att_tup->attidentity == ATTRIBUTE_IDENTITY_ALWAYS && !apply_default) { - if (override != OVERRIDING_SYSTEM_VALUE) + if (override == OVERRIDING_USER_VALUE) + apply_default = true; + else if (override != OVERRIDING_SYSTEM_VALUE) ereport(ERROR, (errcode(ERRCODE_GENERATED_ALWAYS), errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)), errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", NameStr(att_tup->attname)), - errhint("Use OVERRIDING SYSTEM VALUE to override."))); + errhint("You must specify either OVERRIDING SYSTEM VALUE or OVERRIDING USER VALUE."))); } if (att_tup->attidentity == ATTRIBUTE_IDENTITY_BY_DEFAULT && override == OVERRIDING_USER_VALUE) diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out new file mode 100644 index d7d5178..81044b2 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -119,28 +119,32 @@ SELECT * FROM itest3; -- OVERRIDING tests INSERT INTO itest1 VALUES (10, 'xyz'); -INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz'); +INSERT INTO itest1 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); +INSERT INTO itest1 OVERRIDING USER VALUE VALUES (30, 'xyz'); SELECT * FROM itest1; a | b ----+----- 1 | 2 | 10 | xyz + 20 | xyz 3 | xyz -(4 rows) +(5 rows) INSERT INTO itest2 VALUES (10, 'xyz'); ERROR: cannot insert into column "a" DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (10, 'xyz'); +HINT: You must specify either OVERRIDING SYSTEM VALUE or OVERRIDING USER VALUE. +INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); +INSERT INTO itest2 OVERRIDING USER VALUE VALUES (30, 'xyz'); SELECT * FROM itest2; a | b ----+----- 1 | 2 | - 10 | xyz -(3 rows) + 20 | xyz + 3 | xyz +(4 rows) -- UPDATE tests UPDATE itest1 SET a = 101 WHERE a = 1; @@ -149,10 +153,11 @@ SELECT * FROM itest1; a | b -----+----- 10 | xyz + 20 | xyz 3 | xyz 101 | 4 | -(4 rows) +(5 rows) UPDATE itest2 SET a = 101 WHERE a = 1; ERROR: column "a" can only be updated to DEFAULT @@ -162,9 +167,10 @@ SELECT * FROM itest2; a | b ----+----- 1 | - 10 | xyz - 3 | -(3 rows) + 20 | xyz + 3 | xyz + 4 | +(4 rows) -- COPY tests CREATE TABLE itest9 (a int GENERATED ALWAYS AS IDENTITY, b text, c bigint); @@ -240,7 +246,7 @@ SELECT * FROM itestv10; INSERT INTO itestv11 VALUES (10, 'xyz'); ERROR: cannot insert into column "a" DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. +HINT: You must specify either OVERRIDING SYSTEM VALUE or OVERRIDING USER VALUE. INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz'); SELECT * FROM itestv11; a | b diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql new file mode 100644 index a35f331..f74353f --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -65,12 +65,14 @@ SELECT * FROM itest3; -- OVERRIDING tests INSERT INTO itest1 VALUES (10, 'xyz'); -INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz'); +INSERT INTO itest1 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); +INSERT INTO itest1 OVERRIDING USER VALUE VALUES (30, 'xyz'); SELECT * FROM itest1; INSERT INTO itest2 VALUES (10, 'xyz'); -INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (10, 'xyz'); +INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); +INSERT INTO itest2 OVERRIDING USER VALUE VALUES (30, 'xyz'); SELECT * FROM itest2;