From f06d99b1d217d5564b70dff5e6f34267c48ecbc6 Mon Sep 17 00:00:00 2001
From: Vitaly Burovoy <vitaly.burovoy@gmail.com>
Date: Fri, 7 Apr 2017 05:00:54 +0000
Subject: [PATCH] Implement SET IDENTITY ... IF NOT EXISTS

Delete ADD GENERATED syntax since it does not confirm with the standard
and there is no way to add "identity property" twice to a single column.

The SET IDENTITY ... IF NOT EXISTS seems better and works similar way
as SET DEFAULT does.
---
 doc/src/sgml/ref/alter_table.sgml      |  20 ++++--
 src/backend/commands/tablecmds.c       | 118 ++++++++-------------------------
 src/backend/parser/gram.y              |  41 ++++++------
 src/backend/parser/parse_utilcmd.c     | 102 +++++++++++++++++-----------
 src/bin/pg_dump/pg_dump.c              |  39 ++++++-----
 src/include/nodes/parsenodes.h         |   1 -
 src/test/regress/expected/identity.out |  17 +++--
 src/test/regress/sql/identity.sql      |  14 ++--
 8 files changed, 163 insertions(+), 189 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 56ea830..847573c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -46,8 +46,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP DEFAULT
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET | DROP } NOT NULL
-    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]
-    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET GENERATED { ALWAYS | BY DEFAULT } | SET <replaceable>sequence_option</replaceable> | RESTART [ [ WITH ] <replaceable class="parameter">restart</replaceable> ] } [...]
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET GENERATED { ALWAYS | BY DEFAULT } [ IF NOT EXISTS ] [ identity_sequence_option [ ... ] ]
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> identity_sequence_option [ ... ]
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP IDENTITY [ IF EXISTS ]
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
@@ -85,6 +85,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
 
+<phrase>and <replaceable class="PARAMETER">identity_sequence_option</replaceable> is:</phrase>
+    { SET sequence_option | RESTART [ [ WITH ] restart }
+
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -191,7 +194,6 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY</literal></term>
     <term><literal>SET GENERATED { ALWAYS | BY DEFAULT }</literal></term>
     <term><literal>DROP IDENTITY [ IF EXISTS ]</literal></term>
     <listitem>
@@ -202,6 +204,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      </para>
 
      <para>
+      If <literal>SET GENERATED ... IF NOT EXISTS</literal> is specified and the
+      column is not an identity column, no error is thrown.  In this case the
+      column is set as an <firstterm>identity column</firstterm>.
+     </para>
+
+     <para>
       If <literal>DROP IDENTITY IF EXISTS</literal> is specified and the
       column is not an identity column, no error is thrown.  In this case a
       notice is issued instead.
@@ -1195,8 +1203,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
-    The actions for identity columns (<literal>ADD
-    GENERATED</literal>, <literal>SET</literal> etc., <literal>DROP
+    The actions for identity columns (<literal>SET</literal> etc., <literal>DROP
     IDENTITY</literal>), as well as the actions
     <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
     and <literal>TABLESPACE</> never recurse to descendant tables;
@@ -1411,7 +1418,8 @@ ALTER TABLE cities
    The forms <literal>ADD</literal> (without <literal>USING INDEX</literal>),
    <literal>DROP [COLUMN]</>, <literal>DROP IDENTITY</literal>, <literal>RESTART</literal>,
    <literal>SET DEFAULT</>, <literal>SET DATA TYPE</literal> (without <literal>USING</literal>),
-   <literal>SET GENERATED</literal>, and <literal>SET <replaceable>sequence_option</replaceable></literal>
+   <literal>SET GENERATED</literal> (without <literal>IF NOT EXISTS</literal>),
+   and <literal>SET <replaceable>sequence_option</replaceable></literal>
    conform with the SQL standard.  The other forms are
    <productname>PostgreSQL</productname> extensions of the SQL standard.
    Also, the ability to specify more than one manipulation in a single
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 60f8b7f..e930a58 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -361,10 +361,8 @@ static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode);
-static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
-					Node *def, LOCKMODE lockmode);
 static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
-					Node *def, LOCKMODE lockmode);
+					bool missing_ok, Node *def, LOCKMODE lockmode);
 static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
 static void ATPrepSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
@@ -3231,7 +3229,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableRowSecurity:
 			case AT_ForceRowSecurity:
 			case AT_NoForceRowSecurity:
-			case AT_AddIdentity:
 			case AT_DropIdentity:
 			case AT_SetIdentity:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3464,10 +3461,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
 			break;
-		case AT_AddIdentity:
-			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-			pass = AT_PASS_ADD_CONSTR;
-			break;
 		case AT_DropIdentity:
 			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
 			pass = AT_PASS_DROP;
@@ -3801,11 +3794,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
 			address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
-		case AT_AddIdentity:
-			address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
-			break;
 		case AT_SetIdentity:
-			address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
+			address = ATExecSetIdentity(rel, cmd->name, cmd->missing_ok,
+										cmd->def, lockmode);
 			break;
 		case AT_DropIdentity:
 			address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode);
@@ -5853,84 +5844,13 @@ ATExecColumnDefault(Relation rel, const char *colName,
 }
 
 /*
- * ALTER TABLE ALTER COLUMN ADD IDENTITY
- *
- * Return the address of the affected column.
- */
-static ObjectAddress
-ATExecAddIdentity(Relation rel, const char *colName,
-				  Node *def, LOCKMODE lockmode)
-{
-	Relation	attrelation;
-	HeapTuple	tuple;
-	Form_pg_attribute attTup;
-	AttrNumber	attnum;
-	ObjectAddress address;
-	ColumnDef  *cdef = castNode(ColumnDef, def);
-
-	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
-
-	tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
-	if (!HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_COLUMN),
-				 errmsg("column \"%s\" of relation \"%s\" does not exist",
-						colName, RelationGetRelationName(rel))));
-	attTup = (Form_pg_attribute) GETSTRUCT(tuple);
-	attnum = attTup->attnum;
-
-	/* Can't alter a system attribute */
-	if (attnum <= 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot alter system column \"%s\"",
-						colName)));
-
-	/*
-	 * Creating a column as identity implies NOT NULL, so adding the identity
-	 * to an existing column that is not NOT NULL would create a state that
-	 * cannot be reproduced without contortions.
-	 */
-	if (!attTup->attnotnull)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
-						colName, RelationGetRelationName(rel))));
-
-	if (attTup->attidentity)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" is already an identity column",
-						colName, RelationGetRelationName(rel))));
-
-	if (attTup->atthasdef)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" already has a default value",
-						colName, RelationGetRelationName(rel))));
-
-	attTup->attidentity = cdef->identity;
-	CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
-
-	InvokeObjectPostAlterHook(RelationRelationId,
-							  RelationGetRelid(rel),
-							  attTup->attnum);
-	ObjectAddressSubSet(address, RelationRelationId,
-						RelationGetRelid(rel), attnum);
-	heap_freetuple(tuple);
-
-	heap_close(attrelation, RowExclusiveLock);
-
-	return address;
-}
-
-/*
  * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
  *
  * Return the address of the affected column.
  */
 static ObjectAddress
-ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode)
+ATExecSetIdentity(Relation rel, const char *colName, bool missing_ok,
+				  Node *def, LOCKMODE lockmode)
 {
 	ListCell   *option;
 	DefElem	   *generatedEl = NULL;
@@ -5981,10 +5901,30 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 						colName)));
 
 	if (!attTup->attidentity)
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
-						colName, RelationGetRelationName(rel))));
+	{
+		if(!missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
+							colName, RelationGetRelationName(rel))));
+
+		/*
+		 * Creating a column as identity implies NOT NULL, so adding the identity
+		 * to an existing column that is not NOT NULL would create a state that
+		 * cannot be reproduced without contortions.
+		 */
+		if (!attTup->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
+							colName, RelationGetRelationName(rel))));
+
+		if (attTup->atthasdef)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("column \"%s\" of relation \"%s\" already has a default value",
+							colName, RelationGetRelationName(rel))));
+	}
 
 	if (generatedEl)
 	{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 29ca5f1..8010952 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -292,7 +292,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 	   replica_identity partition_cmd
 %type <list>	alter_table_cmds alter_type_cmds
-%type <list>    alter_identity_column_option_list
+%type <list>    alter_identity_column_options alter_identity_column_option_list
 %type <defelt>  alter_identity_column_option
 
 %type <dbehavior>	opt_drop_behavior
@@ -2132,32 +2132,28 @@ alter_table_cmd:
 					n->def = (Node *) makeString($6);
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> ADD GENERATED ... AS IDENTITY ... */
-			| ALTER opt_column ColId ADD_P GENERATED generated_when AS IDENTITY_P OptParenthesizedSeqOptList
-				{
-					AlterTableCmd *n = makeNode(AlterTableCmd);
-					Constraint *c = makeNode(Constraint);
-
-					c->contype = CONSTR_IDENTITY;
-					c->generated_when = $6;
-					c->options = $9;
-					c->location = @5;
-
-					n->subtype = AT_AddIdentity;
-					n->name = $3;
-					n->def = (Node *) c;
-
-					$$ = (Node *)n;
-				}
 			/* ALTER TABLE <name> ALTER [COLUMN] <colname> SET <sequence options>/RESET */
 			| ALTER opt_column ColId alter_identity_column_option_list
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_SetIdentity;
 					n->name = $3;
+					n->missing_ok = false;
 					n->def = (Node *) $4;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> ALTER [COLUMN] <colname> SET GENERATED <when> [IF NOT EXISTS] SET <sequence options>/RESET */
+			| ALTER opt_column ColId SET GENERATED generated_when opt_if_not_exists alter_identity_column_options
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					List *generated = (List *)makeDefElem("generated", (Node *) makeInteger($6), @6);
+
+					n->subtype = AT_SetIdentity;
+					n->name = $3;
+					n->missing_ok = $7;
+					n->def = (Node *) lappend($8, generated);
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ALTER [COLUMN] <colname> DROP IDENTITY */
 			| ALTER opt_column ColId DROP IDENTITY_P
 				{
@@ -2613,6 +2609,11 @@ reloption_elem:
 				}
 		;
 
+alter_identity_column_options:
+			alter_identity_column_option_list		{ $$ = $1; }
+			| /* EMPTY */							{ $$ = NIL; }
+			;
+
 alter_identity_column_option_list:
 			alter_identity_column_option
 				{ $$ = list_make1($1); }
@@ -2640,10 +2641,6 @@ alter_identity_column_option:
 								 parser_errposition(@2)));
 					$$ = $2;
 				}
-			| SET GENERATED generated_when
-				{
-					$$ = makeDefElem("generated", (Node *) makeInteger($3), @1);
-				}
 		;
 
 ForValues:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9266996..315e56f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2783,43 +2783,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
-			case AT_AddIdentity:
-				{
-					Constraint  *def = castNode(Constraint, cmd->def);
-					ColumnDef *newdef = makeNode(ColumnDef);
-					AttrNumber	attnum;
-
-					newdef->colname = cmd->name;
-					newdef->identity = def->generated_when;
-					cmd->def = (Node *) newdef;
-
-					attnum = get_attnum(relid, cmd->name);
-					/* if attribute not found, something will error about it later */
-					if (attnum != InvalidAttrNumber)
-						generateSerialExtraStmts(&cxt, newdef,
-												 get_atttype(relid, attnum),
-												 def->options, true,
-												 NULL, NULL);
-
-					newcmds = lappend(newcmds, cmd);
-					break;
-				}
-
 			case AT_SetIdentity:
 				{
 					/*
-					 * Create an ALTER SEQUENCE statement for the internal
-					 * sequence of the identity column.
+					 * Create an CREATE/ALTER SEQUENCE statement for the
+					 * internal sequence of the identity column.
 					 */
 					ListCell   *lc;
 					List	   *newseqopts = NIL;
 					List	   *newdef = NIL;
+					List	   *seqname = NIL;
 					List	   *seqlist;
 					AttrNumber	attnum;
 
 					/*
-					 * Split options into those handled by ALTER SEQUENCE and
-					 * those for ALTER TABLE proper.
+					 * Split options into those handled by CREATE/ALTER SEQUENCE
+					 * and those for ALTER TABLE proper.
 					 */
 					foreach(lc, castNode(List, cmd->def))
 					{
@@ -2827,6 +2806,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 						if (strcmp(def->defname, "generated") == 0)
 							newdef = lappend(newdef, def);
+						else if (strcmp(def->defname, "sequence_name") == 0)
+							seqname = lappend(seqname, def);
 						else
 							newseqopts = lappend(newseqopts, def);
 					}
@@ -2835,25 +2816,66 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 					if (attnum)
 					{
-						seqlist = getOwnedSequences(relid, attnum);
-						if (seqlist)
+						/*
+						 * cmd->missing_ok can be set to true iff "generated"
+						 * is present
+						 */
+						if (!get_attidentity(relid, attnum) && cmd->missing_ok)
 						{
-							AlterSeqStmt *seqstmt;
-							Oid			seq_relid;
+							/* CREATE SEQUENCE */
+							ColumnDef *newcoldef = makeNode(ColumnDef);
+							newcoldef->colname = cmd->name;
 
-							seqstmt = makeNode(AlterSeqStmt);
-							seq_relid = linitial_oid(seqlist);
-							seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-															 get_rel_name(seq_relid), -1);
-							seqstmt->options = newseqopts;
-							seqstmt->for_identity = true;
-							seqstmt->missing_ok = false;
+							newseqopts = list_concat(newseqopts, seqname);
+							generateSerialExtraStmts(&cxt, newcoldef,
+													 get_atttype(relid, attnum),
+													 newseqopts, true,
+													 NULL, NULL);
+						}
+						else
+						{
+							/* ALTER SEQUENCE */
+							seqlist = getOwnedSequences(relid, attnum);
+							if (seqlist)
+							{
+								AlterSeqStmt *seqstmt;
+								Oid			seq_relid;
 
-							cxt.alist = lappend(cxt.alist, seqstmt);
+								seqstmt = makeNode(AlterSeqStmt);
+								seq_relid = linitial_oid(seqlist);
+								seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+																 get_rel_name(seq_relid), -1);
+								seqstmt->options = newseqopts;
+								seqstmt->for_identity = true;
+								seqstmt->missing_ok = false;
+
+								cxt.alist = lappend(cxt.alist, seqstmt);
+
+								if (seqname)
+								{
+									/*
+									 * ALTER SEQUENCE ... RENAME TO ...
+									 * if the old name differs from the new one.
+									 */
+									char *new_seqname = NameListToString(defGetQualifiedName(linitial(seqname)));
+									if (strcmp(new_seqname, seqstmt->sequence->relname) != 0)
+									{
+										RenameStmt *n = makeNode(RenameStmt);
+										n->renameType = OBJECT_SEQUENCE;
+										n->relation = seqstmt->sequence;
+										n->subname = NULL;
+										n->newname = new_seqname;
+										n->missing_ok = false;
+
+										cxt.alist = lappend(cxt.alist, n);
+									}
+								}
+							}
 						}
 					}
-					/* If column was not found or was not an identity column, we
-					 * just let the ALTER TABLE command error out later. */
+					/* If column was not found or was not an identity column and
+					 * IF NOT EXISTS is not present, we just let the ALTER TABLE
+					 * command error out later. */
 
 					cmd->def = (Node *) newdef;
 					newcmds = lappend(newcmds, cmd);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 65a2f23..d9cf2f6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -16246,6 +16246,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 			   *seqtype;
 	bool		cycled;
 	bool		is_ascending;
+	char	   *opts_prefix = "";
 	PQExpBuffer query = createPQExpBuffer();
 	PQExpBuffer delqry = createPQExpBuffer();
 	PQExpBuffer labelq = createPQExpBuffer();
@@ -16376,14 +16377,15 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 						  "ALTER TABLE %s ",
 						  fmtId(owning_tab->dobj.name));
 		appendPQExpBuffer(query,
-						  "ALTER COLUMN %s ADD GENERATED ",
+						  "ALTER COLUMN %s SET GENERATED ",
 						  fmtId(owning_tab->attnames[tbinfo->owning_col - 1]));
 		if (owning_tab->attidentity[tbinfo->owning_col - 1] == ATTRIBUTE_IDENTITY_ALWAYS)
 			appendPQExpBuffer(query, "ALWAYS");
 		else if (owning_tab->attidentity[tbinfo->owning_col - 1] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 			appendPQExpBuffer(query, "BY DEFAULT");
-		appendPQExpBuffer(query, " AS IDENTITY (\n    SEQUENCE NAME %s\n",
+		appendPQExpBuffer(query, " IF NOT EXISTS\n    SET SEQUENCE NAME %s\n",
 						  fmtId(tbinfo->dobj.name));
+		opts_prefix = "SET ";
 	}
 	else
 	{
@@ -16396,28 +16398,35 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
 	}
 
 	if (fout->remoteVersion >= 80400)
-		appendPQExpBuffer(query, "    START WITH %s\n", startv);
+		appendPQExpBuffer(query, "    %sSTART WITH %s\n", opts_prefix, startv);
 
-	appendPQExpBuffer(query, "    INCREMENT BY %s\n", incby);
+	appendPQExpBuffer(query, "    %sINCREMENT BY %s\n", opts_prefix, incby);
 
 	if (minv)
-		appendPQExpBuffer(query, "    MINVALUE %s\n", minv);
+		appendPQExpBuffer(query, "    %sMINVALUE %s\n", opts_prefix, minv);
 	else
-		appendPQExpBufferStr(query, "    NO MINVALUE\n");
+		appendPQExpBuffer(query, "    %sNO MINVALUE\n", opts_prefix);
 
 	if (maxv)
-		appendPQExpBuffer(query, "    MAXVALUE %s\n", maxv);
+		appendPQExpBuffer(query, "    %sMAXVALUE %s\n", opts_prefix, maxv);
 	else
-		appendPQExpBufferStr(query, "    NO MAXVALUE\n");
+		appendPQExpBuffer(query, "    %sNO MAXVALUE\n", opts_prefix);
 
-	appendPQExpBuffer(query,
-					  "    CACHE %s%s",
-					  cache, (cycled ? "\n    CYCLE" : ""));
-
-	if (tbinfo->is_identity_sequence)
-		appendPQExpBufferStr(query, "\n);\n");
+	if (!tbinfo->is_identity_sequence)
+		appendPQExpBuffer(query,
+						  "    CACHE %s%s",
+						  cache, (cycled ? "\n    CYCLE" : ""));
 	else
-		appendPQExpBufferStr(query, ";\n");
+	{
+		appendPQExpBuffer(query, "    %sCACHE %s\n", opts_prefix, cache);
+
+		if (cycled)
+			appendPQExpBuffer(query, "    %sCYCLE", opts_prefix);
+		else
+			appendPQExpBuffer(query, "    %sNO CYCLE", opts_prefix);
+	}
+
+	appendPQExpBufferStr(query, ";\n");
 
 	appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name));
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9f57388..a40963a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1726,7 +1726,6 @@ typedef enum AlterTableType
 	AT_GenericOptions,			/* OPTIONS (...) */
 	AT_AttachPartition,			/* ATTACH PARTITION */
 	AT_DetachPartition,			/* DETACH PARTITION */
-	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity				/* DROP IDENTITY */
 } AlterTableType;
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 88b56da..a6b0e92 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -7,8 +7,6 @@ SELECT attrelid, attname, attidentity FROM pg_attribute WHERE attidentity NOT IN
 CREATE TABLE itest1 (a int generated by default as identity, b text);
 CREATE TABLE itest2 (a bigint generated always as identity, b text);
 CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text);
-ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error
-ERROR:  column "a" of relation "itest3" is already an identity column
 SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2;
  table_name | column_name | column_default | is_nullable | is_identity | identity_generation | identity_start | identity_increment |  identity_maximum   | identity_minimum | identity_cycle 
 ------------+-------------+----------------+-------------+-------------+---------------------+----------------+--------------------+---------------------+------------------+----------------
@@ -27,15 +25,16 @@ SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE
 (0 rows)
 
 CREATE TABLE itest4 (a int, b text);
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;  -- error, requires NOT NULL
 ERROR:  column "a" of relation "itest4" must be declared NOT NULL before identity can be added
 ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- ok
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED BY DEFAULT;  -- error, must be identity without "IF NOT EXISTS"
+ERROR:  column "a" of relation "itest4" is not an identity column
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED BY DEFAULT IF NOT EXISTS;  -- ok
 ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL;  -- error, disallowed
 ERROR:  column "a" of relation "itest4" is an identity column
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, already set
-ERROR:  column "a" of relation "itest4" is already an identity column
-ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY;  -- error, wrong data type
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;  -- ok, just update
+ALTER TABLE itest4 ALTER COLUMN b SET GENERATED ALWAYS IF NOT EXISTS;  -- error, wrong data type
 ERROR:  identity column type must be smallint, integer, or bigint
 -- for later
 ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT '';
@@ -217,7 +216,7 @@ ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1;
 ERROR:  column "a" of relation "itest1" is an identity column
 -- fail, not allowed, already has a default
 CREATE TABLE itest5 (a serial, b text);
-ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest5 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;
 ERROR:  column "a" of relation "itest5" already has a default value
 ALTER TABLE itest3 ALTER COLUMN a TYPE int;
 SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regclass;
@@ -286,7 +285,7 @@ SELECT * FROM itest7c;
 
 CREATE TABLE itest7d (a int not null);
 CREATE TABLE itest7e () INHERITS (itest7d);
-ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest7d ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;
 ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY;  -- error
 ERROR:  cannot recursively add identity column to table that has child tables
 SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2;
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index a7e7b15..4db78c5 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -5,7 +5,6 @@ SELECT attrelid, attname, attidentity FROM pg_attribute WHERE attidentity NOT IN
 CREATE TABLE itest1 (a int generated by default as identity, b text);
 CREATE TABLE itest2 (a bigint generated always as identity, b text);
 CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text);
-ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error
 
 SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2;
 
@@ -13,12 +12,13 @@ SELECT table_name, column_name, column_default, is_nullable, is_identity, identi
 SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%';
 
 CREATE TABLE itest4 (a int, b text);
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;  -- error, requires NOT NULL
 ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- ok
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED BY DEFAULT;  -- error, must be identity without "IF NOT EXISTS"
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED BY DEFAULT IF NOT EXISTS;  -- ok
 ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL;  -- error, disallowed
-ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, already set
-ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY;  -- error, wrong data type
+ALTER TABLE itest4 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;  -- ok, just update
+ALTER TABLE itest4 ALTER COLUMN b SET GENERATED ALWAYS IF NOT EXISTS;  -- error, wrong data type
 
 -- for later
 ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT '';
@@ -124,7 +124,7 @@ ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1;
 
 -- fail, not allowed, already has a default
 CREATE TABLE itest5 (a serial, b text);
-ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest5 ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;
 
 ALTER TABLE itest3 ALTER COLUMN a TYPE int;
 SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regclass;
@@ -170,7 +170,7 @@ SELECT * FROM itest7c;
 
 CREATE TABLE itest7d (a int not null);
 CREATE TABLE itest7e () INHERITS (itest7d);
-ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest7d ALTER COLUMN a SET GENERATED ALWAYS IF NOT EXISTS;
 ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY;  -- error
 
 SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2;
-- 
2.7.3

