From cbffe37aa9ea8ab4303a0723605b1b8def1b966b Mon Sep 17 00:00:00 2001
From: Bradley Ayers <bradley.ayers@gmail.com>
Date: Fri, 1 Apr 2022 00:25:16 +1100
Subject: [PATCH v1] =?UTF-8?q?Add=20IF=20EXISTS=20support=20to=20ALTER=20C?=
 =?UTF-8?q?OLUMN=20=E2=80=A6=20{SET|DROP}=20NOT=20NULL?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This brings the ergonomics of the ADD COLUMN IF EXISTS command to the
ALTER TABLE ... SET/DROP NOT NULL commands.
---
 src/backend/commands/tablecmds.c          | 50 ++++++++++++++++-------
 src/backend/parser/gram.y                 | 20 +++++++++
 src/test/regress/expected/alter_table.out | 12 ++++++
 src/test/regress/sql/alter_table.sql      | 10 +++++
 4 files changed, 78 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e83f375b5..b28c5fc8ad 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -422,13 +422,13 @@ static bool check_for_column_name_collision(Relation rel, const char *colname,
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
-static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
 static void ATPrepSetNotNull(List **wqueue, Relation rel,
 							 AlterTableCmd *cmd, bool recurse, bool recursing,
 							 LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
-									  const char *colName, LOCKMODE lockmode);
+									  const char *colName, bool missing_ok, LOCKMODE lockmode);
 static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
 							   const char *colName, LOCKMODE lockmode);
 static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
@@ -4915,10 +4915,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 			address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			address = ATExecDropNotNull(rel, cmd->name, lockmode);
+			address = ATExecDropNotNull(rel, cmd->name, cmd->missing_ok, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			address = ATExecSetNotNull(tab, rel, cmd->name, cmd->missing_ok, lockmode);
 			break;
 		case AT_CheckNotNull:	/* check column is already marked NOT NULL */
 			ATExecCheckNotNull(tab, rel, cmd->name, lockmode);
@@ -7128,7 +7128,7 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
  * nullable, InvalidObjectAddress is returned.
  */
 static ObjectAddress
-ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
+ATExecDropNotNull(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute attTup;
@@ -7145,10 +7145,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	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))));
+	{
+		if (!missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colName, RelationGetRelationName(rel))));
+		else
+		{
+			ereport(NOTICE,
+					(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
+							colName, RelationGetRelationName(rel))));
+			table_close(attr_rel, RowExclusiveLock);
+			return InvalidObjectAddress;
+		}
+	}
 	attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 	attnum = attTup->attnum;
 
@@ -7336,7 +7347,7 @@ ATPrepSetNotNull(List **wqueue, Relation rel,
  */
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
-				 const char *colName, LOCKMODE lockmode)
+				 const char *colName, bool missing_ok, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	AttrNumber	attnum;
@@ -7351,10 +7362,21 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 	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))));
+	{
+		if (!missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colName, RelationGetRelationName(rel))));
+		else
+		{
+			ereport(NOTICE,
+					(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
+							colName, RelationGetRelationName(rel))));
+			table_close(attr_rel, RowExclusiveLock);
+			return InvalidObjectAddress;
+		}
+	}
 
 	attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53b..aa816247d7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2247,6 +2247,16 @@ alter_table_cmd:
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropNotNull;
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> ALTER [COLUMN] IF EXISTS <colname> DROP NOT NULL */
+			| ALTER opt_column IF_P EXISTS ColId DROP NOT NULL_P
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropNotNull;
+					n->name = $5;
+					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> ALTER [COLUMN] <colname> SET NOT NULL */
@@ -2255,6 +2265,16 @@ alter_table_cmd:
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_SetNotNull;
 					n->name = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> ALTER [COLUMN] IF EXISTS <colname> SET NOT NULL */
+			| ALTER opt_column IF_P EXISTS ColId SET NOT NULL_P
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetNotNull;
+					n->name = $5;
+					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> ALTER [COLUMN] <colname> DROP EXPRESSION */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 16e0475663..cc6c9065d1 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1787,6 +1787,18 @@ alter table dropColumnExists drop column non_existing; --fail
 ERROR:  column "non_existing" of relation "dropcolumnexists" does not exist
 alter table dropColumnExists drop column if exists non_existing; --succeed
 NOTICE:  column "non_existing" of relation "dropcolumnexists" does not exist, skipping
+-- ALTER TABLE ... ALTER COLUMN IF EXISTS ... SET NOT NULL test
+create table setNotNullColumnExists ();
+alter table setNotNullColumnExists alter column non_existing set not null; --fail
+ERROR:  column "non_existing" of relation "setnotnullcolumnexists" does not exist
+alter table setNotNullColumnExists alter column if exists non_existing set not null; --succeed
+NOTICE:  column "non_existing" of relation "setnotnullcolumnexists" does not exist, skipping
+-- ALTER TABLE ... ALTER COLUMN IF EXISTS ... DROP NOT NULL test
+create table dropNotNullColumnExists ();
+alter table dropNotNullColumnExists alter column non_existing drop not null; --fail
+ERROR:  column "non_existing" of relation "dropnotnullcolumnexists" does not exist
+alter table dropNotNullColumnExists alter column if exists non_existing drop not null; --succeed
+NOTICE:  column "non_existing" of relation "dropnotnullcolumnexists" does not exist, skipping
 select relname, attname, attinhcount, attislocal
 from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
 where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ac894c0602..18e1440b8c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1271,6 +1271,16 @@ create table dropColumnExists ();
 alter table dropColumnExists drop column non_existing; --fail
 alter table dropColumnExists drop column if exists non_existing; --succeed
 
+-- ALTER TABLE ... ALTER COLUMN IF EXISTS ... SET NOT NULL test
+create table setNotNullColumnExists ();
+alter table setNotNullColumnExists alter column non_existing set not null; --fail
+alter table setNotNullColumnExists alter column if exists non_existing set not null; --succeed
+
+-- ALTER TABLE ... ALTER COLUMN IF EXISTS ... DROP NOT NULL test
+create table dropNotNullColumnExists ();
+alter table dropNotNullColumnExists alter column non_existing drop not null; --fail
+alter table dropNotNullColumnExists alter column if exists non_existing drop not null; --succeed
+
 select relname, attname, attinhcount, attislocal
 from pg_class join pg_attribute on (pg_class.oid = pg_attribute.attrelid)
 where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped
-- 
2.34.1

