Forbid marking an identity column as nullable.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Mar 2021 16:08:42 +0000 (11:08 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Mar 2021 16:08:42 +0000 (11:08 -0500)
GENERATED ALWAYS AS IDENTITY implies NOT NULL, but the code failed
to complain if you overrode that with "GENERATED ALWAYS AS IDENTITY
NULL".  One might think the old behavior was a feature, but it was
inconsistent because the outcome varied depending on the order of
the clauses, so it seems to have been just an oversight.

Per bug #16913 from Pavel Boev.  Back-patch to v10 where identity
columns were introduced.

Vik Fearing (minor tweaks by me)

Discussion: https://postgr.es/m/16913-3b5198410f67d8c6@postgresql.org

doc/src/sgml/ref/create_table.sgml
src/backend/parser/parse_utilcmd.c
src/test/regress/expected/identity.out
src/test/regress/sql/identity.sql

index 3b2b227683a2fe2854b36370512ac5ceb96f1c50..71703da85aef30db5198cf67e86e26dbcd7c5671 100644 (file)
@@ -840,6 +840,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       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.
+      Such a column is implicitly <literal>NOT NULL</literal>.
      </para>
 
      <para>
index 75266caeb4bb50a635a3572c3fde0a3719516702..d56f81c79ff09ced9e11cfe26b6639b55ebaeab6 100644 (file)
@@ -719,7 +719,17 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 
                    column->identity = constraint->generated_when;
                    saw_identity = true;
+
+                   /* An identity column is implicitly NOT NULL */
+                   if (saw_nullable && !column->is_not_null)
+                       ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
+                                       column->colname, cxt->relation->relname),
+                                parser_errposition(cxt->pstate,
+                                                   constraint->location)));
                    column->is_not_null = true;
+                   saw_nullable = true;
                    break;
                }
 
index fbca0333a2c85ab87d2a145741bc5cefb0665b38..99811570b7bf35fc40474cf7a856efb3ff752bac 100644 (file)
@@ -547,3 +547,16 @@ CREATE TABLE itest14 (id serial);
 ALTER TABLE itest14 ALTER id DROP DEFAULT;
 ALTER TABLE itest14 ALTER id ADD GENERATED BY DEFAULT AS IDENTITY;
 INSERT INTO itest14 (id) VALUES (DEFAULT);
+-- Identity columns must be NOT NULL (cf bug #16913)
+CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); -- fail
+ERROR:  conflicting NULL/NOT NULL declarations for column "id" of table "itest15"
+LINE 1: ...ABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL);
+                                                                 ^
+CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS IDENTITY); -- fail
+ERROR:  conflicting NULL/NOT NULL declarations for column "id" of table "itest15"
+LINE 1: CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS ID...
+                                              ^
+CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NOT NULL);
+DROP TABLE itest15;
+CREATE TABLE itest15 (id integer NOT NULL GENERATED ALWAYS AS IDENTITY);
+DROP TABLE itest15;
index cdda186720df1b281cc63b03fda008746a74eebf..52800f265c2765ab35c8314f425a8c1cbb7df367 100644 (file)
@@ -346,3 +346,12 @@ CREATE TABLE itest14 (id serial);
 ALTER TABLE itest14 ALTER id DROP DEFAULT;
 ALTER TABLE itest14 ALTER id ADD GENERATED BY DEFAULT AS IDENTITY;
 INSERT INTO itest14 (id) VALUES (DEFAULT);
+
+-- Identity columns must be NOT NULL (cf bug #16913)
+
+CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); -- fail
+CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS IDENTITY); -- fail
+CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NOT NULL);
+DROP TABLE itest15;
+CREATE TABLE itest15 (id integer NOT NULL GENERATED ALWAYS AS IDENTITY);
+DROP TABLE itest15;