This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new bdb0b2ad3e [Cherry-pick to branch-1.3] [#14056] fix(catalog-jdbc): 
Allow changing nullable column without default to NOT NULL (#14057) (#14195)
bdb0b2ad3e is described below

commit bdb0b2ad3e842af2f10730d33585c6126257db19
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Oct 26 14:49:48 2026 +0800

    [Cherry-pick to branch-1.3] [#14056] fix(catalog-jdbc): Allow changing 
nullable column without default to NOT NULL (#14057) (#14195)
    
    **Cherry-pick Information:**
    - Original commit: 993ed4038aeae8b5e3e30b6eda4f7293f6aa7d84
    - Target branch: `branch-1.3`
    - Status: ✅ Conflicts resolved
    
    ---------
    
    Co-authored-by: Yuhui <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
    Co-authored-by: diqiu50 <[email protected]>
---
 .../operations/ClickHouseTableOperations.java      | 33 ++++-------
 .../operations/TestClickHouseTableOperations.java  | 43 ++++++++++----
 .../operation/OceanBaseTableOperations.java        | 34 ++++-------
 .../operation/TestOceanBaseTableOperations.java    | 22 ++++---
 .../jdbc/operation/JdbcTableOperations.java        | 65 +++++++++++++++++++--
 .../jdbc/operation/TestJdbcTableOperations.java    | 65 +++++++++++++++++++++
 .../doris/operation/DorisTableOperations.java      |  8 ++-
 .../TestDorisTableOperationsSqlGeneration.java     | 35 +++++++++--
 .../mysql/operation/MysqlTableOperations.java      | 34 ++++-------
 .../mysql/operation/TestMysqlTableOperations.java  | 22 ++++---
 .../operation/PostgreSqlTableOperations.java       | 20 ++++++-
 .../operation/TestPostgreSqlTableOperations.java   | 67 ++++++++++++++++++++++
 12 files changed, 337 insertions(+), 111 deletions(-)

diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
index 2f7322bda4..92564dcf05 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
@@ -1080,7 +1080,8 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
       } else if (change instanceof TableChange.UpdateColumnDefaultValue 
updateColumnDefaultValue) {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         alterSql.add(
-            updateColumnDefaultValueFieldDefinition(updateColumnDefaultValue, 
lazyLoadTable));
+            updateColumnDefaultValueFieldDefinition(
+                updateColumnDefaultValue, lazyLoadTable, changes));
 
       } else if (change instanceof TableChange.UpdateColumnType 
updateColumnType) {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
@@ -1106,7 +1107,7 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         alterSql.add(
             updateColumnNullabilityDefinition(
-                (TableChange.UpdateColumnNullability) change, lazyLoadTable));
+                (TableChange.UpdateColumnNullability) change, lazyLoadTable, 
changes));
 
       } else if (change instanceof TableChange.AddIndex addIndex) {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
@@ -1294,20 +1295,11 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnNullabilityDefinition(
-      TableChange.UpdateColumnNullability change, JdbcTable table) {
-    validateUpdateColumnNullable(change, table);
+      TableChange.UpdateColumnNullability change, JdbcTable table, 
TableChange... changes) {
+    JdbcColumn column = validateUpdateColumnNullable(change, table);
 
     String col = change.fieldName()[0];
-    JdbcColumn column = getJdbcColumnFromTable(table, col);
-    JdbcColumn updateColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withDefaultValue(column.defaultValue())
-            .withNullable(change.nullable())
-            .withType(column.dataType())
-            .withComment(column.comment())
-            .withAutoIncrement(column.autoIncrement())
-            .build();
+    JdbcColumn updateColumn = withPendingDefaultValueAndNullability(column, 
changes);
 
     return "%s %s %s"
         .formatted(
@@ -1460,7 +1452,9 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnDefaultValueFieldDefinition(
-      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue, JdbcTable 
jdbcTable) {
+      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue,
+      JdbcTable jdbcTable,
+      TableChange... changes) {
     if (updateColumnDefaultValue.fieldName().length > 1) {
       throw new 
UnsupportedOperationException(CLICKHOUSE_NOT_SUPPORT_NESTED_COLUMN_MSG);
     }
@@ -1468,14 +1462,7 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
     String col = updateColumnDefaultValue.fieldName()[0];
     JdbcColumn column = getJdbcColumnFromTable(jdbcTable, col);
     StringBuilder sqlBuilder = new StringBuilder(MODIFY_COLUMN + 
quoteIdentifier(col));
-    JdbcColumn newColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withType(column.dataType())
-            .withNullable(column.nullable())
-            .withComment(column.comment())
-            .withDefaultValue(updateColumnDefaultValue.getNewDefaultValue())
-            .build();
+    JdbcColumn newColumn = withPendingDefaultValueAndNullability(column, 
changes);
 
     return appendColumnDefinition(newColumn, sqlBuilder).toString();
   }
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
index 8967a19636..c184258ba5 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
@@ -1783,7 +1783,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
   }
 
   @Test
-  public void testAlterTableNullabilityValidationFails() {
+  public void testAlterTableNullabilityDropsNullDefault() {
     StubClickHouseTableOperations ops = new StubClickHouseTableOperations();
     ops.initialize(
         null,
@@ -1793,15 +1793,38 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
         new HashMap<>());
     ops.setTable(buildStubTableWithNullDefault());
 
-    Assertions.assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            ops.buildAlterSql(
-                "db",
-                "tbl",
-                new TableChange[] {
-                  TableChange.updateColumnNullability(new String[] {"c3"}, 
false)
-                }));
+    String sql =
+        ops.buildAlterSql(
+            "db",
+            "tbl",
+            new TableChange[] {TableChange.updateColumnNullability(new 
String[] {"c3"}, false)});
+    Assertions.assertTrue(sql.replaceAll("\\s+", " ").contains("MODIFY COLUMN 
`c3` String"), sql);
+    Assertions.assertFalse(sql.contains("Nullable"), sql);
+    Assertions.assertFalse(sql.contains("DEFAULT"), sql);
+  }
+
+  @Test
+  public void testAlterTableNullabilityWithDefaultValueInSameRequest() {
+    StubClickHouseTableOperations ops = new StubClickHouseTableOperations();
+    ops.initialize(
+        null,
+        new ClickHouseExceptionConverter(),
+        new ClickHouseTypeConverter(),
+        new ClickHouseColumnDefaultValueConverter(),
+        new HashMap<>());
+    ops.setTable(buildStubTableWithNullDefault());
+
+    TableChange setDefault =
+        TableChange.updateColumnDefaultValue(new String[] {"c3"}, 
Literals.stringLiteral("abc"));
+    TableChange setNotNull = TableChange.updateColumnNullability(new String[] 
{"c3"}, false);
+    // Every regenerated column definition carries both the new default and 
NOT NULL, whichever
+    // order the changes come in.
+    for (TableChange[] changes :
+        new TableChange[][] {{setDefault, setNotNull}, {setNotNull, 
setDefault}}) {
+      String sql = ops.buildAlterSql("db", "tbl", changes);
+      Assertions.assertFalse(sql.contains("Nullable"), sql);
+      Assertions.assertEquals(2, StringUtils.countMatches(sql, "DEFAULT 
'abc'"), sql);
+    }
   }
 
   @Test
diff --git 
a/catalogs-contrib/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseTableOperations.java
index 99b0a85b1c..799affe6e7 100644
--- 
a/catalogs-contrib/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseTableOperations.java
@@ -257,7 +257,8 @@ public class OceanBaseTableOperations extends 
JdbcTableOperations {
         TableChange.UpdateColumnDefaultValue updateColumnDefaultValue =
             (TableChange.UpdateColumnDefaultValue) change;
         alterSql.add(
-            updateColumnDefaultValueFieldDefinition(updateColumnDefaultValue, 
lazyLoadTable));
+            updateColumnDefaultValueFieldDefinition(
+                updateColumnDefaultValue, lazyLoadTable, changes));
       } else if (change instanceof TableChange.UpdateColumnType) {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         TableChange.UpdateColumnType updateColumnType = 
(TableChange.UpdateColumnType) change;
@@ -283,7 +284,7 @@ public class OceanBaseTableOperations extends 
JdbcTableOperations {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         alterSql.add(
             updateColumnNullabilityDefinition(
-                (TableChange.UpdateColumnNullability) change, lazyLoadTable));
+                (TableChange.UpdateColumnNullability) change, lazyLoadTable, 
changes));
       } else if (change instanceof TableChange.AddIndex) {
         alterSql.add(addIndexDefinition((TableChange.AddIndex) change));
       } else if (change instanceof TableChange.DeleteIndex) {
@@ -368,19 +369,10 @@ public class OceanBaseTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnNullabilityDefinition(
-      TableChange.UpdateColumnNullability change, JdbcTable table) {
-    validateUpdateColumnNullable(change, table);
+      TableChange.UpdateColumnNullability change, JdbcTable table, 
TableChange... changes) {
+    JdbcColumn column = validateUpdateColumnNullable(change, table);
     String col = change.fieldName()[0];
-    JdbcColumn column = getJdbcColumnFromTable(table, col);
-    JdbcColumn updateColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withDefaultValue(column.defaultValue())
-            .withNullable(change.nullable())
-            .withType(column.dataType())
-            .withComment(column.comment())
-            .withAutoIncrement(column.autoIncrement())
-            .build();
+    JdbcColumn updateColumn = withPendingDefaultValueAndNullability(column, 
changes);
     return MODIFY_COLUMN
         + BACK_QUOTE
         + col
@@ -591,22 +583,16 @@ public class OceanBaseTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnDefaultValueFieldDefinition(
-      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue, JdbcTable 
jdbcTable) {
+      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue,
+      JdbcTable jdbcTable,
+      TableChange... changes) {
     if (updateColumnDefaultValue.fieldName().length > 1) {
       throw new 
UnsupportedOperationException(OCEANBASE_NOT_SUPPORT_NESTED_COLUMN_MSG);
     }
     String col = updateColumnDefaultValue.fieldName()[0];
     JdbcColumn column = getJdbcColumnFromTable(jdbcTable, col);
     StringBuilder sqlBuilder = new StringBuilder(MODIFY_COLUMN + BACK_QUOTE + 
col + BACK_QUOTE);
-    JdbcColumn newColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withType(column.dataType())
-            .withNullable(column.nullable())
-            .withComment(column.comment())
-            .withAutoIncrement(column.autoIncrement())
-            .withDefaultValue(updateColumnDefaultValue.getNewDefaultValue())
-            .build();
+    JdbcColumn newColumn = withPendingDefaultValueAndNullability(column, 
changes);
     return appendColumnDefinition(newColumn, sqlBuilder).toString();
   }
 
diff --git 
a/catalogs-contrib/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseTableOperations.java
index 3a07734d6c..237b4e378a 100644
--- 
a/catalogs-contrib/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseTableOperations.java
@@ -550,14 +550,20 @@ public class TestOceanBaseTableOperations extends 
TestOceanBase {
     assertionsTableInfo(
         tableName, newComment, columns, properties, indexes, 
Transforms.EMPTY_TRANSFORM, load);
 
-    TableChange updateColumn =
-        TableChange.updateColumnNullability(new String[] {col3.name()}, 
!col3.nullable());
-    IllegalArgumentException exception =
-        Assertions.assertThrows(
-            IllegalArgumentException.class,
-            () -> TABLE_OPERATIONS.alterTable(TEST_DB_NAME, tableName, 
updateColumn));
-    Assertions.assertTrue(
-        exception.getMessage().contains("with null default value cannot be 
changed to not null"));
+    // A nullable column whose default is NULL can be changed to not null.
+    TABLE_OPERATIONS.alterTable(
+        TEST_DB_NAME,
+        tableName,
+        TableChange.updateColumnNullability(new String[] {col3.name()}, 
false));
+    load = TABLE_OPERATIONS.load(TEST_DB_NAME, tableName);
+    JdbcColumn updatedCol3 =
+        (JdbcColumn)
+            Arrays.stream(load.columns())
+                .filter(c -> c.name().equals(col3.name()))
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Column not found: " + 
col3.name()));
+    Assertions.assertFalse(updatedCol3.nullable());
+    Assertions.assertEquals(Column.DEFAULT_VALUE_NOT_SET, 
updatedCol3.defaultValue());
   }
 
   @Test
diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
index 7f9b9cfa14..98b54eef43 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcTableOperations.java
@@ -587,17 +587,70 @@ public abstract class JdbcTableOperations implements 
TableOperation {
     return null != lazyLoadCreateTable ? lazyLoadCreateTable : 
load(databaseName, tableName);
   }
 
-  protected void validateUpdateColumnNullable(
+  /**
+   * Validates a nullability change and returns the column it applies to.
+   *
+   * @param change the nullability change
+   * @param table the table that contains the column
+   * @return the current column
+   */
+  protected JdbcColumn validateUpdateColumnNullable(
       TableChange.UpdateColumnNullability change, JdbcTable table) {
     if (change.fieldName().length > 1) {
       throw new UnsupportedOperationException("Nested column names are not 
supported");
     }
-    String col = change.fieldName()[0];
-    JdbcColumn column = getJdbcColumnFromTable(table, col);
-    if (!change.nullable() && column.defaultValue().equals(Literals.NULL)) {
-      throw new IllegalArgumentException(
-          "column " + col + " with null default value cannot be changed to not 
null");
+    return getJdbcColumnFromTable(table, change.fieldName()[0]);
+  }
+
+  /**
+   * Returns a copy of the column with the default value and nullability it 
has after all default
+   * value and nullability changes to it in the same alter request are 
applied. Dialects that
+   * regenerate the whole column definition for one change use it, so that the 
regenerated
+   * definition does not revert another change to the same column. A NULL 
default conflicts with NOT
+   * NULL: the existing NULL default is dropped when the column ends up 
non-nullable, while a NULL
+   * default requested in the same alter request is rejected.
+   *
+   * @param column the current column
+   * @param changes all changes in the alter request
+   * @return the column with the pending default value and nullability
+   * @throws IllegalArgumentException if the request sets a NULL default on a 
column that ends up
+   *     non-nullable
+   */
+  protected static JdbcColumn withPendingDefaultValueAndNullability(
+      JdbcColumn column, TableChange... changes) {
+    Expression defaultValue = column.defaultValue();
+    boolean defaultValueChanged = false;
+    boolean nullable = column.nullable();
+    for (TableChange change : changes) {
+      if (!(change instanceof TableChange.ColumnChange)) {
+        continue;
+      }
+      String[] fieldName = ((TableChange.ColumnChange) change).fieldName();
+      if (fieldName.length != 1 || !fieldName[0].equals(column.name())) {
+        continue;
+      }
+      if (change instanceof TableChange.UpdateColumnDefaultValue) {
+        defaultValue = ((TableChange.UpdateColumnDefaultValue) 
change).getNewDefaultValue();
+        defaultValueChanged = true;
+      } else if (change instanceof TableChange.UpdateColumnNullability) {
+        nullable = ((TableChange.UpdateColumnNullability) change).nullable();
+      }
     }
+    if (!nullable && Literals.NULL.equals(defaultValue)) {
+      Preconditions.checkArgument(
+          !defaultValueChanged,
+          "Column %s cannot have a NULL default value because it is not 
nullable",
+          column.name());
+      defaultValue = DEFAULT_VALUE_NOT_SET;
+    }
+    return JdbcColumn.builder()
+        .withName(column.name())
+        .withType(column.dataType())
+        .withComment(column.comment())
+        .withNullable(nullable)
+        .withAutoIncrement(column.autoIncrement())
+        .withDefaultValue(defaultValue)
+        .build();
   }
 
   /**
diff --git 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/TestJdbcTableOperations.java
 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/TestJdbcTableOperations.java
index ff8fcf8bb7..9fa0549001 100644
--- 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/TestJdbcTableOperations.java
+++ 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/TestJdbcTableOperations.java
@@ -46,6 +46,7 @@ import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
+import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.indexes.Indexes;
 import org.apache.gravitino.rel.types.Type;
 import org.apache.gravitino.rel.types.Types;
@@ -245,6 +246,70 @@ public class TestJdbcTableOperations {
     
Assertions.assertTrue(JDBC_TABLE_OPERATIONS.isMySQLDriverVersionSupported("org.sqlite.JDBC"));
   }
 
+  @Test
+  public void testWithPendingDefaultValueAndNullability() {
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col_1")
+            .withType(Types.IntegerType.get())
+            .withComment("comment")
+            .withNullable(true)
+            .withDefaultValue(Literals.NULL)
+            .build();
+    String[] fieldName = {"col_1"};
+
+    // A NULL default is dropped when the column becomes not null.
+    JdbcColumn pending =
+        JdbcTableOperations.withPendingDefaultValueAndNullability(
+            column, TableChange.updateColumnNullability(fieldName, false));
+    Assertions.assertFalse(pending.nullable());
+    Assertions.assertEquals(Column.DEFAULT_VALUE_NOT_SET, 
pending.defaultValue());
+    Assertions.assertEquals("comment", pending.comment());
+    Assertions.assertEquals(Types.IntegerType.get(), pending.dataType());
+
+    // A new default in the same request is kept, whichever order the changes 
come in.
+    TableChange setDefault =
+        TableChange.updateColumnDefaultValue(fieldName, 
Literals.integerLiteral(5));
+    TableChange setNotNull = TableChange.updateColumnNullability(fieldName, 
false);
+    for (TableChange[] changes :
+        new TableChange[][] {{setDefault, setNotNull}, {setNotNull, 
setDefault}}) {
+      pending = 
JdbcTableOperations.withPendingDefaultValueAndNullability(column, changes);
+      Assertions.assertFalse(pending.nullable());
+      Assertions.assertEquals(Literals.integerLiteral(5), 
pending.defaultValue());
+    }
+
+    // Changes to other columns are ignored.
+    pending =
+        JdbcTableOperations.withPendingDefaultValueAndNullability(
+            column,
+            TableChange.updateColumnNullability(new String[] {"col_2"}, false),
+            TableChange.updateColumnDefaultValue(
+                new String[] {"col_2"}, Literals.integerLiteral(5)));
+    Assertions.assertTrue(pending.nullable());
+    Assertions.assertEquals(Literals.NULL, pending.defaultValue());
+
+    // A NULL default requested for a column that ends up not null is rejected.
+    TableChange setNullDefault = 
TableChange.updateColumnDefaultValue(fieldName, Literals.NULL);
+    for (TableChange[] changes :
+        new TableChange[][] {{setNullDefault, setNotNull}, {setNotNull, 
setNullDefault}}) {
+      Assertions.assertThrows(
+          IllegalArgumentException.class,
+          () -> 
JdbcTableOperations.withPendingDefaultValueAndNullability(column, changes));
+    }
+    JdbcColumn notNullColumn =
+        JdbcColumn.builder()
+            .withName("col_1")
+            .withType(Types.IntegerType.get())
+            .withNullable(false)
+            .withDefaultValue(Literals.integerLiteral(5))
+            .build();
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            JdbcTableOperations.withPendingDefaultValueAndNullability(
+                notNullColumn, setNullDefault));
+  }
+
   private static JdbcColumn[] generateRandomColumn(int minSize, int maxSize) {
     Random r = new Random();
     String prefixColName = "col_";
diff --git 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
index b3627a50b1..24ce6ae61f 100644
--- 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
+++ 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
@@ -956,9 +956,13 @@ public class DorisTableOperations extends 
JdbcTableOperations {
 
   private String updateColumnNullabilityDefinition(
       TableChange.UpdateColumnNullability change, JdbcTable table, 
Optional<String> dorisVersion) {
-    validateUpdateColumnNullable(change, table);
+    JdbcColumn column = validateUpdateColumnNullable(change, table);
     String col = change.fieldName()[0];
-    JdbcColumn column = getJdbcColumnFromTable(table, col);
+    // Doris schema change does not allow converting a nullable column to 
non-nullable.
+    Preconditions.checkArgument(
+        change.nullable() || !column.nullable(),
+        "Doris does not support changing nullable column %s to not null",
+        col);
     JdbcColumn updateColumn =
         JdbcColumn.builder()
             .withName(col)
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
index 79f6d5dfbe..d08fcf4c7f 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
@@ -147,6 +147,31 @@ public class TestDorisTableOperationsSqlGeneration {
     }
   }
 
+  /** Verifies a nullable column cannot be changed to not null. */
+  @Test
+  public void testUpdateColumnNullabilityRejectsNullableToNotNull() {
+    TestableDorisTableOperations ops = new TestableDorisTableOperations();
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col1")
+            .withType(Types.IntegerType.get())
+            .withNullable(true)
+            .withDefaultValue(Literals.NULL)
+            .build();
+    ops.setTableForAlter(
+        JdbcTable.builder().withName("test_table").withColumns(new 
JdbcColumn[] {column}).build());
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                ops.alterTableSql(
+                    "test_table",
+                    TableChange.updateColumnNullability(new String[] {"col1"}, 
false)));
+    Assertions.assertEquals(
+        "Doris does not support changing nullable column col1 to not null", 
exception.getMessage());
+  }
+
   /** Verifies string defaults and unchanged column attributes are preserved. 
*/
   @Test
   public void testUpdateColumnTypePreservesStringDefaultAndColumnAttributes() {
@@ -233,7 +258,7 @@ public class TestDorisTableOperationsSqlGeneration {
           JdbcColumn.builder()
               .withName("col1")
               .withType(Types.VarCharType.of(64))
-              .withNullable(true)
+              .withNullable(false)
               .withComment("column comment")
               .withDefaultValue(Literals.varcharLiteral(64, value))
               .build();
@@ -246,7 +271,7 @@ public class TestDorisTableOperationsSqlGeneration {
       String sql =
           ops.alterTableSql(
               "test_table",
-              TableChange.updateColumnNullability(new String[] {"col1"}, 
false),
+              TableChange.updateColumnNullability(new String[] {"col1"}, true),
               TableChange.updateColumnPosition(
                   new String[] {"col1"}, TableChange.ColumnPosition.first()),
               TableChange.updateColumnType(new String[] {"col1"}, 
Types.VarCharType.of(128)));
@@ -257,13 +282,13 @@ public class TestDorisTableOperationsSqlGeneration {
               .fromGravitinoForModifyColumn(column.defaultValue(), isDoris3x, 
isDoris3x);
       Assertions.assertEquals(
           "ALTER TABLE `test_table`\n"
-              + "MODIFY COLUMN `col1` varchar(64) NOT NULL DEFAULT "
+              + "MODIFY COLUMN `col1` varchar(64) NULL DEFAULT "
               + expectedDefault
               + " COMMENT 'column comment' ,\n"
-              + "MODIFY COLUMN `col1` varchar(64) NULL DEFAULT "
+              + "MODIFY COLUMN `col1` varchar(64) NOT NULL DEFAULT "
               + expectedDefault
               + " COMMENT 'column comment' FIRST,\n"
-              + "MODIFY COLUMN `col1` varchar(128) NULL DEFAULT "
+              + "MODIFY COLUMN `col1` varchar(128) NOT NULL DEFAULT "
               + expectedDefault
               + " COMMENT 'column comment' ;",
           sql);
diff --git 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
index 5e755cb222..2b4a6d1f6c 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
@@ -344,7 +344,8 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
         TableChange.UpdateColumnDefaultValue updateColumnDefaultValue =
             (TableChange.UpdateColumnDefaultValue) change;
         alterSql.add(
-            updateColumnDefaultValueFieldDefinition(updateColumnDefaultValue, 
lazyLoadTable));
+            updateColumnDefaultValueFieldDefinition(
+                updateColumnDefaultValue, lazyLoadTable, changes));
       } else if (change instanceof TableChange.UpdateColumnType) {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         TableChange.UpdateColumnType updateColumnType = 
(TableChange.UpdateColumnType) change;
@@ -370,7 +371,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
         lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
         alterSql.add(
             updateColumnNullabilityDefinition(
-                (TableChange.UpdateColumnNullability) change, lazyLoadTable));
+                (TableChange.UpdateColumnNullability) change, lazyLoadTable, 
changes));
       } else if (change instanceof TableChange.AddIndex) {
         alterSql.add(addIndexDefinition((TableChange.AddIndex) change));
       } else if (change instanceof TableChange.DeleteIndex) {
@@ -455,19 +456,10 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnNullabilityDefinition(
-      TableChange.UpdateColumnNullability change, JdbcTable table) {
-    validateUpdateColumnNullable(change, table);
+      TableChange.UpdateColumnNullability change, JdbcTable table, 
TableChange... changes) {
+    JdbcColumn column = validateUpdateColumnNullable(change, table);
     String col = change.fieldName()[0];
-    JdbcColumn column = getJdbcColumnFromTable(table, col);
-    JdbcColumn updateColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withDefaultValue(column.defaultValue())
-            .withNullable(change.nullable())
-            .withType(column.dataType())
-            .withComment(column.comment())
-            .withAutoIncrement(column.autoIncrement())
-            .build();
+    JdbcColumn updateColumn = withPendingDefaultValueAndNullability(column, 
changes);
     return MODIFY_COLUMN
         + BACK_QUOTE
         + col
@@ -686,22 +678,16 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
   }
 
   private String updateColumnDefaultValueFieldDefinition(
-      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue, JdbcTable 
jdbcTable) {
+      TableChange.UpdateColumnDefaultValue updateColumnDefaultValue,
+      JdbcTable jdbcTable,
+      TableChange... changes) {
     if (updateColumnDefaultValue.fieldName().length > 1) {
       throw new 
UnsupportedOperationException(MYSQL_NOT_SUPPORT_NESTED_COLUMN_MSG);
     }
     String col = updateColumnDefaultValue.fieldName()[0];
     JdbcColumn column = getJdbcColumnFromTable(jdbcTable, col);
     StringBuilder sqlBuilder = new StringBuilder(MODIFY_COLUMN + quote(col));
-    JdbcColumn newColumn =
-        JdbcColumn.builder()
-            .withName(col)
-            .withType(column.dataType())
-            .withNullable(column.nullable())
-            .withComment(column.comment())
-            .withAutoIncrement(column.autoIncrement())
-            .withDefaultValue(updateColumnDefaultValue.getNewDefaultValue())
-            .build();
+    JdbcColumn newColumn = withPendingDefaultValueAndNullability(column, 
changes);
     return appendColumnDefinition(newColumn, sqlBuilder).toString();
   }
 
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
index dd32224d32..68d37eb09c 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
@@ -521,14 +521,20 @@ public class TestMysqlTableOperations extends TestMysql {
     assertionsTableInfo(
         tableName, newComment, columns, properties, indexes, 
Transforms.EMPTY_TRANSFORM, load);
 
-    TableChange updateColumn =
-        TableChange.updateColumnNullability(new String[] {col3.name()}, 
!col3.nullable());
-    IllegalArgumentException exception =
-        Assertions.assertThrows(
-            IllegalArgumentException.class,
-            () -> TABLE_OPERATIONS.alterTable(TEST_DB_NAME.toString(), 
tableName, updateColumn));
-    Assertions.assertTrue(
-        exception.getMessage().contains("with null default value cannot be 
changed to not null"));
+    // A nullable column whose default is NULL can be changed to not null.
+    TABLE_OPERATIONS.alterTable(
+        TEST_DB_NAME.toString(),
+        tableName,
+        TableChange.updateColumnNullability(new String[] {col3.name()}, 
false));
+    load = TABLE_OPERATIONS.load(TEST_DB_NAME.toString(), tableName);
+    JdbcColumn updatedCol3 =
+        (JdbcColumn)
+            Arrays.stream(load.columns())
+                .filter(c -> c.name().equals(col3.name()))
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Column not found: " + 
col3.name()));
+    Assertions.assertFalse(updatedCol3.nullable());
+    Assertions.assertEquals(Column.DEFAULT_VALUE_NOT_SET, 
updatedCol3.defaultValue());
   }
 
   @Test
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
index 9ea122c3e4..21c0f4db2a 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
@@ -55,6 +55,7 @@ import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
+import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.types.Types;
@@ -369,9 +370,26 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
             (TableChange.UpdateColumnNullability) change;
 
         lazyLoadTable = getOrCreateTable(schemaName, tableName, lazyLoadTable);
-        validateUpdateColumnNullable(updateColumnNullability, lazyLoadTable);
+        JdbcColumn column = 
validateUpdateColumnNullable(updateColumnNullability, lazyLoadTable);
 
         
alterSql.add(updateColumnNullabilityDefinition(updateColumnNullability, 
tableName));
+        // A NULL default conflicts with NOT NULL, so drop it when the column 
becomes non-nullable,
+        // unless the same request sets a new default value for the column.
+        JdbcColumn pendingColumn = 
withPendingDefaultValueAndNullability(column, changes);
+        if (Literals.NULL.equals(column.defaultValue())
+            && 
Column.DEFAULT_VALUE_NOT_SET.equals(pendingColumn.defaultValue())) {
+          alterSql.add(
+              ALTER_TABLE
+                  + PG_QUOTE
+                  + tableName
+                  + PG_QUOTE
+                  + " "
+                  + ALTER_COLUMN
+                  + PG_QUOTE
+                  + updateColumnNullability.fieldName()[0]
+                  + PG_QUOTE
+                  + " DROP DEFAULT;");
+        }
       } else if (change instanceof TableChange.AddIndex) {
         alterSql.add(addIndexDefinition(tableName, (TableChange.AddIndex) 
change));
       } else if (change instanceof TableChange.DeleteIndex) {
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
index 13382aa471..6dfa17fe6e 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
@@ -22,10 +22,12 @@ import com.google.common.collect.Maps;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import javax.sql.DataSource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
@@ -37,6 +39,7 @@ import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlColumnDefault
 import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlTypeConverter;
 import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.literals.Literals;
@@ -238,6 +241,26 @@ public class TestPostgreSqlTableOperations extends 
TestPostgreSql {
     assertionsTableInfo(
         newName, tableComment, alterColumns, properties, null, 
Transforms.EMPTY_TRANSFORM, load);
 
+    // a nullable column with a NULL default can be changed back to not null, 
and the NULL
+    // default is dropped
+    TABLE_OPERATIONS.alterTable(
+        TEST_DB_NAME,
+        newName,
+        TableChange.updateColumnDefaultValue(new String[] {"col_2"}, 
Literals.NULL));
+    TABLE_OPERATIONS.alterTable(
+        TEST_DB_NAME, newName, TableChange.updateColumnNullability(new 
String[] {"col_2"}, false));
+    load = TABLE_OPERATIONS.load(TEST_DB_NAME, newName);
+    alterColumns.set(
+        1,
+        JdbcColumn.builder()
+            .withName("col_2")
+            .withType(Types.DoubleType.get())
+            .withNullable(false)
+            .withComment("set test key")
+            .build());
+    assertionsTableInfo(
+        newName, tableComment, alterColumns, properties, null, 
Transforms.EMPTY_TRANSFORM, load);
+
     // delete column
     TABLE_OPERATIONS.alterTable(
         TEST_DB_NAME,
@@ -269,6 +292,50 @@ public class TestPostgreSqlTableOperations extends 
TestPostgreSql {
     Assertions.assertFalse(TABLE_OPERATIONS.drop(TEST_DB_NAME, newName));
   }
 
+  @Test
+  public void testUpdateColumnDefaultValueAndNullabilityTogether() {
+    String tableName = 
RandomNameUtils.genRandomName("default_not_null_table_");
+    JdbcColumn[] columns = {
+      JdbcColumn.builder()
+          .withName("col_1")
+          .withType(Types.IntegerType.get())
+          .withNullable(true)
+          .withComment("set default first")
+          .build(),
+      JdbcColumn.builder()
+          .withName("col_2")
+          .withType(Types.IntegerType.get())
+          .withNullable(true)
+          .withComment("set not null first")
+          .build()
+    };
+    TABLE_OPERATIONS.create(
+        TEST_DB_NAME,
+        tableName,
+        columns,
+        "test_comment",
+        new HashMap<>(),
+        null,
+        Distributions.NONE,
+        Indexes.EMPTY_INDEXES);
+
+    TABLE_OPERATIONS.alterTable(
+        TEST_DB_NAME,
+        tableName,
+        TableChange.updateColumnDefaultValue(new String[] {"col_1"}, 
Literals.integerLiteral(5)),
+        TableChange.updateColumnNullability(new String[] {"col_1"}, false),
+        TableChange.updateColumnNullability(new String[] {"col_2"}, false),
+        TableChange.updateColumnDefaultValue(new String[] {"col_2"}, 
Literals.integerLiteral(7)));
+
+    JdbcTable load = TABLE_OPERATIONS.load(TEST_DB_NAME, tableName);
+    Map<String, Column> loadedColumns =
+        Arrays.stream(load.columns()).collect(Collectors.toMap(Column::name, c 
-> c));
+    Assertions.assertFalse(loadedColumns.get("col_1").nullable());
+    Assertions.assertEquals(Literals.integerLiteral(5), 
loadedColumns.get("col_1").defaultValue());
+    Assertions.assertFalse(loadedColumns.get("col_2").nullable());
+    Assertions.assertEquals(Literals.integerLiteral(7), 
loadedColumns.get("col_2").defaultValue());
+  }
+
   @Test
   public void testClearCommentsWithNull() {
     String tableName = RandomNameUtils.genRandomName("clear_comment_");

Reply via email to