This is an automated email from the ASF dual-hosted git repository.
yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 7a04352992 [#12740] fix(doris): Preserve default value on column type
change (#12747)
7a04352992 is described below
commit 7a043529925254bec1283256da10346b13b585f9
Author: StormSpirit <[email protected]>
AuthorDate: Wed Sep 30 08:51:18 2026 +0800
[#12740] fix(doris): Preserve default value on column type change (#12747)
### What changes were proposed in this pull request?
This pull request preserves the loaded Doris column default when
`TableChange.UpdateColumnType` builds the replacement column definition.
It also adds focused SQL-generation coverage for string, numeric,
`CURRENT_TIMESTAMP`, explicit `DEFAULT NULL`, unset defaults, preserved
column attributes, and nested-column rejection.
The integration coverage verifies successful default-preserving type
changes on Doris 3.0.6.2 and 4.0.6, and verifies on Doris 4.0.6 that an
unsupported `VARCHAR` to `BOOLEAN` conversion is still rejected without
changing the column type or default.
This pull request is opened as a draft so it can be reviewed in parallel
with a related Doris default-literal converter change. Before this draft
is marked ready, it will be rebased onto that change, its string and
numeric SQL expectations will be aligned with the merged Doris quoting
rules, and an exact quote/backslash default-value case will be added and
verified.
### Why are the changes needed?
The Doris catalog currently replaces the existing default with
`DEFAULT_VALUE_NOT_SET` when generating a `MODIFY COLUMN` statement for
`UpdateColumnType`. Doris treats `MODIFY COLUMN` as a complete column
definition and rejects the operation with `Can not change default value`
when the submitted definition omits an existing default.
Preserving the loaded default makes the generated definition faithful to
the unchanged column attributes while leaving type and default
compatibility validation to Doris.
Fix: #12740
### Does this PR introduce _any_ user-facing change?
Yes. A supported Doris column type change can now preserve an existing
default value instead of failing because the generated `MODIFY COLUMN`
definition omits that default. This pull request does not change any
public API or property key.
### How was this patch tested?
- `./gradlew :catalogs:catalog-jdbc-doris:spotlessCheck --console=plain
--no-daemon`
- `./gradlew rat --console=plain --no-daemon`
- `./gradlew :catalogs:catalog-jdbc-doris:test -PskipITs --console=plain
--no-daemon`
- `./gradlew :catalogs:catalog-jdbc-doris:build -x test --console=plain
--no-daemon`
- Local static precheck covering negative-test consistency, string-slice
safety, switch-case coverage, stale-comment references, orphan-test
detection, commit-message validation, regex validation, cross-module
impact, and unintended-file detection: all checks passed.
- Doris 3.0.6.2: `./gradlew :catalogs:catalog-jdbc-doris:test --tests
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris3xIT.testAlterColumnTypePreservesDefaultValue'
-PskipDockerTests=false -PdorisMultiVersionTest --console=plain
--no-daemon`
- Doris 4.0.6: `./gradlew :catalogs:catalog-jdbc-doris:test --tests
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris4xIT.testAlterColumnTypePreservesDefaultValue'
-PskipDockerTests=false -PdorisMultiVersionTest --console=plain
--no-daemon`
- Doris 4.0.6: `./gradlew :catalogs:catalog-jdbc-doris:test --tests
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris4xIT.testAlterColumnTypeRejectsInvalidConversionWithDefaultValue'
-PskipDockerTests=false -PdorisMultiVersionTest --console=plain
--no-daemon`
All listed checks and focused tests passed. The post-commit static
precheck completed all nine checks successfully.
---------
Signed-off-by: jiangxt2 <[email protected]>
---
.../DorisColumnDefaultValueConverter.java | 49 +++++
.../doris/operation/DorisTableOperations.java | 82 +++++++-
.../TestDorisColumnDefaultValueConverter.java | 40 ++++
.../doris/integration/test/CatalogDoris3xIT.java | 234 +++++++++++++++++++++
.../doris/integration/test/CatalogDoris4xIT.java | 213 +++++++++++++++++++
.../TestDorisTableOperationsSqlGeneration.java | 209 ++++++++++++++++++
6 files changed, 821 insertions(+), 6 deletions(-)
diff --git
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/converter/DorisColumnDefaultValueConverter.java
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/converter/DorisColumnDefaultValueConverter.java
index 0d0c8d033d..243211f1ef 100644
---
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/converter/DorisColumnDefaultValueConverter.java
+++
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/converter/DorisColumnDefaultValueConverter.java
@@ -32,6 +32,7 @@ import static
org.apache.gravitino.rel.Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP
import java.time.LocalDate;
import java.time.LocalDateTime;
+import javax.annotation.Nullable;
import
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
import org.apache.gravitino.rel.expressions.Expression;
@@ -88,6 +89,46 @@ public class DorisColumnDefaultValueConverter extends
JdbcColumnDefaultValueConv
return super.fromGravitino(defaultValue);
}
+ /**
+ * Converts a loaded default value for a Doris MODIFY COLUMN definition.
+ *
+ * <p>String literals containing quote or backslash characters need
Doris-specific escaping.
+ * Unparsed expressions come from native Doris metadata and are passed
through unchanged.
+ *
+ * @param defaultValue the loaded Gravitino default value
+ * @param doubleEscapeBackslashes whether Doris 3.x requires an additional
backslash-escaping
+ * layer
+ * @param tripleEscapeQuotes whether Doris 3.x MODIFY COLUMN requires three
backslashes before an
+ * embedded double quote
+ * @return the Doris SQL representation, or {@code null} when the default is
unset
+ */
+ @Nullable
+ public String fromGravitinoForModifyColumn(
+ Expression defaultValue, boolean doubleEscapeBackslashes, boolean
tripleEscapeQuotes) {
+ if (DEFAULT_VALUE_NOT_SET.equals(defaultValue)) {
+ return null;
+ }
+
+ if (defaultValue instanceof UnparsedExpression) {
+ return ((UnparsedExpression) defaultValue).unparsedExpression();
+ }
+
+ if (defaultValue instanceof Literal) {
+ Literal<?> literal = (Literal<?>) defaultValue;
+ if (literal.value() != null
+ && (literal.dataType() instanceof Types.StringType
+ || literal.dataType() instanceof Types.VarCharType
+ || literal.dataType() instanceof Types.FixedCharType)) {
+ String value = String.valueOf(literal.value());
+ if (value.indexOf('\\') >= 0 || value.indexOf('\'') >= 0 ||
value.indexOf('"') >= 0) {
+ return quoteDorisModifyLiteral(value, doubleEscapeBackslashes,
tripleEscapeQuotes);
+ }
+ }
+ }
+
+ return super.fromGravitino(defaultValue);
+ }
+
@Override
public Expression toGravitino(
JdbcTypeConverter.JdbcTypeBean columnType,
@@ -162,6 +203,14 @@ public class DorisColumnDefaultValueConverter extends
JdbcColumnDefaultValueConv
return "\"" + escaped + "\"";
}
+ private static String quoteDorisModifyLiteral(
+ String value, boolean doubleEscapeBackslashes, boolean
tripleEscapeQuotes) {
+ String escapedBackslash = doubleEscapeBackslashes ? "\\".repeat(4) :
"\\".repeat(2);
+ String escapedQuote = "\\".repeat(tripleEscapeQuotes ? 3 : 1) + "\"";
+ String escaped = value.replace("\\", escapedBackslash).replace("\"",
escapedQuote);
+ return "\"" + escaped + "\"";
+ }
+
private static String unescapeDorisLiteral(String value) {
StringBuilder result = new StringBuilder(value.length());
for (int i = 0; i < value.length(); i++) {
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 fb2cb54211..ef0ce30e62 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
@@ -78,6 +78,7 @@ import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.indexes.Indexes;
import org.apache.gravitino.rel.partitions.ListPartition;
import org.apache.gravitino.rel.partitions.RangePartition;
+import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.utils.ExceptionMessages;
/** Table operations for Apache Doris. */
@@ -840,7 +841,7 @@ public class DorisTableOperations extends
JdbcTableOperations {
TableChange.UpdateComment updateComment = null;
List<TableChange.SetProperty> setProperties = new ArrayList<>();
List<String> alterSql = new ArrayList<>();
- Optional<String> addColumnDorisVersion =
+ Optional<String> alterColumnDorisVersion =
Arrays.stream(changes)
.filter(TableChange.AddColumn.class::isInstance)
.map(TableChange.AddColumn.class::cast)
@@ -861,13 +862,23 @@ public class DorisTableOperations extends
JdbcTableOperations {
} else if (change instanceof TableChange.AddColumn) {
TableChange.AddColumn addColumn = (TableChange.AddColumn) change;
lazyLoadTable = getOrCreateTable(databaseName, tableName,
lazyLoadTable);
- alterSql.add(addColumnFieldDefinition(addColumn,
addColumnDorisVersion));
+ alterSql.add(addColumnFieldDefinition(addColumn,
alterColumnDorisVersion));
} else if (change instanceof TableChange.RenameColumn) {
throw new IllegalArgumentException("Rename column is not supported
yet");
} else if (change instanceof TableChange.UpdateColumnType) {
lazyLoadTable = getOrCreateTable(databaseName, tableName,
lazyLoadTable);
TableChange.UpdateColumnType updateColumnType =
(TableChange.UpdateColumnType) change;
- alterSql.add(updateColumnTypeFieldDefinition(updateColumnType,
lazyLoadTable));
+ if (alterColumnDorisVersion.isEmpty() &&
updateColumnType.fieldName().length == 1) {
+ JdbcColumn currentColumn =
+ getJdbcColumnFromTable(lazyLoadTable,
updateColumnType.fieldName()[0]);
+ if (requiresVersionAwareModifyEscaping(currentColumn)) {
+ alterColumnDorisVersion =
+ Optional.of(getDorisVersion("MODIFY COLUMN default literal
compatibility check"));
+ }
+ }
+ alterSql.add(
+ updateColumnTypeFieldDefinition(
+ updateColumnType, lazyLoadTable, alterColumnDorisVersion));
} else if (change instanceof TableChange.UpdateColumnComment) {
TableChange.UpdateColumnComment updateColumnComment =
(TableChange.UpdateColumnComment) change;
@@ -1066,6 +1077,25 @@ public class DorisTableOperations extends
JdbcTableOperations {
return stringValue.contains("\\") || stringValue.contains("\"\"");
}
+ private static boolean requiresVersionAwareModifyEscaping(Column column) {
+ if (!(column.defaultValue() instanceof Literal)) {
+ return false;
+ }
+
+ Literal<?> literal = (Literal<?>) column.defaultValue();
+ if (!(literal.dataType() instanceof Types.StringType
+ || literal.dataType() instanceof Types.VarCharType
+ || literal.dataType() instanceof Types.FixedCharType)) {
+ return false;
+ }
+ Object value = literal.value();
+ if (value == null) {
+ return false;
+ }
+ String stringValue = String.valueOf(value);
+ return stringValue.contains("\\") || stringValue.contains("\"");
+ }
+
private String serializeAddColumnDefaultValue(
Expression defaultValue, Optional<String> dorisVersion) {
Preconditions.checkState(
@@ -1136,7 +1166,9 @@ public class DorisTableOperations extends
JdbcTableOperations {
}
private String updateColumnTypeFieldDefinition(
- TableChange.UpdateColumnType updateColumnType, JdbcTable jdbcTable) {
+ TableChange.UpdateColumnType updateColumnType,
+ JdbcTable jdbcTable,
+ Optional<String> dorisVersion) {
if (updateColumnType.fieldName().length > 1) {
throw new UnsupportedOperationException("Doris does not support nested
column names.");
}
@@ -1148,11 +1180,49 @@ public class DorisTableOperations extends
JdbcTableOperations {
.withName(col)
.withType(updateColumnType.getNewDataType())
.withComment(column.comment())
- .withDefaultValue(DEFAULT_VALUE_NOT_SET)
+ .withDefaultValue(column.defaultValue())
.withNullable(column.nullable())
.withAutoIncrement(column.autoIncrement())
.build();
- return appendColumnDefinition(newColumn, sqlBuilder).toString();
+ return appendColumnDefinitionForModify(newColumn, sqlBuilder,
dorisVersion).toString();
+ }
+
+ private StringBuilder appendColumnDefinitionForModify(
+ JdbcColumn column, StringBuilder sqlBuilder, Optional<String>
dorisVersion) {
+
sqlBuilder.append(SPACE).append(typeConverter.fromGravitino(column.dataType())).append(SPACE);
+
+ if (column.nullable()) {
+ sqlBuilder.append("NULL ");
+ } else {
+ sqlBuilder.append("NOT NULL ");
+ }
+
+ if (!DEFAULT_VALUE_NOT_SET.equals(column.defaultValue())) {
+ Preconditions.checkState(
+ columnDefaultValueConverter instanceof
DorisColumnDefaultValueConverter,
+ "DorisColumnDefaultValueConverter is required for Doris MODIFY
COLUMN");
+ DorisColumnDefaultValueConverter converter =
+ (DorisColumnDefaultValueConverter) columnDefaultValueConverter;
+ boolean isDoris3x =
+ dorisVersion
+ .map(
+ version ->
+ isVersionAtLeast(version, 3, 0, 0) &&
!isVersionAtLeast(version, 4, 0, 0))
+ .orElse(false);
+ String defaultValue =
+ converter.fromGravitinoForModifyColumn(column.defaultValue(),
isDoris3x, isDoris3x);
+ Preconditions.checkState(defaultValue != null, "Doris default value must
not be null");
+ sqlBuilder.append("DEFAULT ").append(defaultValue).append(SPACE);
+ }
+
+ if (column.autoIncrement()) {
+ sqlBuilder.append(DORIS_AUTO_INCREMENT).append(" ");
+ }
+
+ if (StringUtils.isNotEmpty(column.comment())) {
+ sqlBuilder.append("COMMENT '").append(escapeSqlLiteral(column.comment(),
'\'')).append("' ");
+ }
+ return sqlBuilder;
}
private StringBuilder appendColumnDefinition(JdbcColumn column,
StringBuilder sqlBuilder) {
diff --git
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/converter/TestDorisColumnDefaultValueConverter.java
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/converter/TestDorisColumnDefaultValueConverter.java
index 0f494cbc79..34ff4f4c70 100644
---
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/converter/TestDorisColumnDefaultValueConverter.java
+++
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/converter/TestDorisColumnDefaultValueConverter.java
@@ -18,9 +18,11 @@
*/
package org.apache.gravitino.catalog.doris.converter;
+import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
import static
org.apache.gravitino.rel.Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP;
import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.expressions.UnparsedExpression;
import org.apache.gravitino.rel.expressions.literals.Literal;
import org.apache.gravitino.rel.expressions.literals.Literals;
import org.apache.gravitino.rel.types.Types;
@@ -138,4 +140,42 @@ public class TestDorisColumnDefaultValueConverter {
Literals.of("owner's a\"\"b \"value\"\\path",
Types.VarCharType.of(255)),
CONVERTER.toGravitino(varcharType, "owner's a\"\"b \"value\"\\\\path",
false, false));
}
+
+ /** Verifies loaded literal and native expression defaults use MODIFY COLUMN
SQL forms. */
+ @Test
+ public void testModifyColumnSerializesLoadedDefaultValues() {
+ Assertions.assertNull(
+ CONVERTER.fromGravitinoForModifyColumn(DEFAULT_VALUE_NOT_SET, false,
false));
+ Assertions.assertEquals(
+ "NULL", CONVERTER.fromGravitinoForModifyColumn(Literals.NULL, false,
false));
+ Assertions.assertEquals(
+ "7",
CONVERTER.fromGravitinoForModifyColumn(Literals.integerLiteral(7), false,
false));
+ Assertions.assertEquals(
+ "CURRENT_TIMESTAMP",
+
CONVERTER.fromGravitinoForModifyColumn(DEFAULT_VALUE_OF_CURRENT_TIMESTAMP,
false, false));
+ Assertions.assertEquals(
+ "CURRENT_DATE",
+ CONVERTER.fromGravitinoForModifyColumn(
+ UnparsedExpression.of("CURRENT_DATE"), false, false));
+
+ Assertions.assertEquals(
+ "\"owner's \\\"value\\\"\\\\path\"",
+ CONVERTER.fromGravitinoForModifyColumn(
+ Literals.of("owner's \"value\"\\path", Types.VarCharType.of(255)),
false, false));
+ }
+
+ /** Verifies adjacent backslash and quote escaping for Doris 3.x and 4.x. */
+ @Test
+ public void testModifyColumnEscapesAdjacentBackslashAndQuoteByDorisVersion()
{
+ String defaultValue = "prefix" + "\\" + "\"" + "suffix";
+
+ Assertions.assertEquals(
+ "\"" + "prefix" + "\\".repeat(3) + "\"suffix\"",
+ CONVERTER.fromGravitinoForModifyColumn(
+ Literals.of(defaultValue, Types.VarCharType.of(255)), false,
false));
+ Assertions.assertEquals(
+ "\"" + "prefix" + "\\".repeat(7) + "\"suffix\"",
+ CONVERTER.fromGravitinoForModifyColumn(
+ Literals.of(defaultValue, Types.VarCharType.of(255)), true, true));
+ }
}
diff --git
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
index a7c73db6e4..e45aeaff81 100644
---
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
+++
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
@@ -309,6 +309,240 @@ public class CatalogDoris3xIT extends BaseIT {
.untilAsserted(() -> assertEquals(0,
tc.loadTable(tid).index().length));
}
+ @Test
+ void testAlterColumnTypePreservesDefaultValue() throws SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_preserves_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String distributionColumnName = "dist_key";
+ String valueColumnName = "agg_value";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "%s INT NOT NULL DEFAULT \"7\", "
+ + "%s BIGINT NOT NULL, "
+ + "%s BIGINT SUM DEFAULT \"0\""
+ + ") AGGREGATE KEY(%s, %s) "
+ + "DISTRIBUTED BY HASH(%s) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName,
+ colName1,
+ distributionColumnName,
+ valueColumnName,
+ colName1,
+ distributionColumnName,
+ distributionColumnName);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ Column originalColumn = findColumn(tc.loadTable(tid), colName1);
+ assertEquals(Types.IntegerType.get(), originalColumn.dataType());
+ assertEquals(Literals.integerLiteral(7), originalColumn.defaultValue());
+
+ tc.alterTable(tid, TableChange.updateColumnType(new String[] {colName1},
Types.LongType.get()));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), colName1);
+ assertEquals(Types.LongType.get(), updatedColumn.dataType());
+ assertEquals(Literals.longLiteral(7L),
updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(
+ String.format(
+ "INSERT INTO %s (%s, %s) VALUES (101, 0)",
+ tableName, distributionColumnName, valueColumnName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format(
+ "SELECT %s FROM %s WHERE %s = 101",
+ colName1, tableName, distributionColumnName))) {
+ assertTrue(resultSet.next());
+ assertEquals(7L, resultSet.getLong(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypePreservesQuotedAndBackslashDefaultValue() throws
SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_special_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String logicalDefault = "owner's \"value\"\\\\path";
+ String sqlDefault = "owner's \\\"value\\\"" + "\\\\\\\\" + "path";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "col_pk BIGINT NOT NULL, "
+ + "col_data VARCHAR(32) NOT NULL DEFAULT \"%s\") "
+ + "DUPLICATE KEY(col_pk) "
+ + "DISTRIBUTED BY HASH(col_pk) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName, sqlDefault);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(32)),
+ findColumn(tc.loadTable(tid), "col_data").defaultValue());
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {"col_data"},
Types.VarCharType.of(64)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), "col_data");
+ assertEquals(Types.VarCharType.of(64), updatedColumn.dataType());
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(64)),
+ updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(String.format("INSERT INTO %s (col_pk) VALUES
(1)", tableName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT col_data FROM %s WHERE col_pk = 1",
tableName))) {
+ assertTrue(resultSet.next());
+ assertEquals(logicalDefault, resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypePreservesBackslashOnlyDefaultValue() throws
SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_backslash_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String logicalDefault = "owner's\\value";
+ String sqlDefault = "\"" + logicalDefault.replace("\\", "\\\\") + "\"";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "col_pk BIGINT NOT NULL, "
+ + "col_data VARCHAR(32) NOT NULL DEFAULT %s) "
+ + "DUPLICATE KEY(col_pk) "
+ + "DISTRIBUTED BY HASH(col_pk) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName, sqlDefault);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(32)),
+ findColumn(tc.loadTable(tid), "col_data").defaultValue());
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {"col_data"},
Types.VarCharType.of(64)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), "col_data");
+ assertEquals(Types.VarCharType.of(64), updatedColumn.dataType());
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(64)),
+ updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(String.format("INSERT INTO %s (col_pk) VALUES
(1)", tableName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT col_data FROM %s WHERE col_pk = 1",
tableName))) {
+ assertTrue(resultSet.next());
+ assertEquals(logicalDefault, resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypePreservesAdjacentBackslashQuoteDefaultValue() throws
SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_adjacent_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String logicalDefault = "prefix" + "\\" + "\"" + "suffix";
+ String sqlDefault = "\"" + logicalDefault.replace("\\",
"\\\\").replace("\"", "\\\"") + "\"";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "col_pk BIGINT NOT NULL, "
+ + "col_data VARCHAR(32) NOT NULL DEFAULT %s) "
+ + "DUPLICATE KEY(col_pk) "
+ + "DISTRIBUTED BY HASH(col_pk) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName, sqlDefault);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(32)),
+ findColumn(tc.loadTable(tid), "col_data").defaultValue());
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {"col_data"},
Types.VarCharType.of(64)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), "col_data");
+ assertEquals(Types.VarCharType.of(64), updatedColumn.dataType());
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(64)),
+ updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(String.format("INSERT INTO %s (col_pk) VALUES
(1)", tableName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT col_data FROM %s WHERE col_pk = 1",
tableName))) {
+ assertTrue(resultSet.next());
+ assertEquals(logicalDefault, resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
@Test
void testAddColumnPreservesDefaultValue() throws SQLException {
TableCatalog tc = catalog.asTableCatalog();
diff --git
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
index dff696f912..3badfd22d3 100644
---
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
+++
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
@@ -345,6 +345,219 @@ public class CatalogDoris4xIT extends BaseIT {
.untilAsserted(() -> assertEquals(0,
tc.loadTable(tid).index().length));
}
+ @Test
+ void testAlterColumnTypePreservesDefaultValue() throws SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ NameIdentifier tid =
+ NameIdentifier.of(
+ schemaName,
GravitinoITUtils.genRandomName("t_alter_type_preserves_default"));
+ Column defaultedColumn =
+ Column.of(
+ colName2,
+ Types.VarCharType.of(10),
+ "defaulted column",
+ false,
+ false,
+ Literals.of("seed", Types.VarCharType.of(10)));
+
+ tc.createTable(
+ tid,
+ new Column[] {
+ Column.of(colName1, Types.LongType.get(), "pk", false, false, null),
defaultedColumn
+ },
+ tableComment,
+ Collections.emptyMap(),
+ Transforms.EMPTY_TRANSFORM,
+ hashDist(),
+ null,
+ null);
+
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {colName2},
Types.VarCharType.of(20)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), colName2);
+ assertEquals(Types.VarCharType.of(20), updatedColumn.dataType());
+ assertEquals(
+ Literals.of("seed", Types.VarCharType.of(20)),
updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(
+ String.format("INSERT INTO %s (%s) VALUES (1)", tid.name(),
colName1));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT %s FROM %s WHERE %s = 1", colName2,
tid.name(), colName1))) {
+ assertTrue(resultSet.next());
+ assertEquals("seed", resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypePreservesQuotedAndBackslashDefaultValue() throws
SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_special_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String logicalDefault = "owner's \"value\"\\path";
+ String sqlDefault = "owner's \\\"value\\\"" + "\\\\" + "path";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "col_pk BIGINT NOT NULL, "
+ + "col_data VARCHAR(32) NOT NULL DEFAULT \"%s\") "
+ + "DUPLICATE KEY(col_pk) "
+ + "DISTRIBUTED BY HASH(col_pk) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName, sqlDefault);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(32)),
+ findColumn(tc.loadTable(tid), "col_data").defaultValue());
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {"col_data"},
Types.VarCharType.of(64)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), "col_data");
+ assertEquals(Types.VarCharType.of(64), updatedColumn.dataType());
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(64)),
+ updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(String.format("INSERT INTO %s (col_pk) VALUES
(1)", tableName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT col_data FROM %s WHERE col_pk = 1",
tableName))) {
+ assertTrue(resultSet.next());
+ assertEquals(logicalDefault, resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypePreservesAdjacentBackslashQuoteDefaultValue() throws
SQLException {
+ TableCatalog tc = catalog.asTableCatalog();
+ String tableName =
GravitinoITUtils.genRandomName("t_alter_type_adjacent_default");
+ NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+ String logicalDefault = "prefix" + "\\" + "\"" + "suffix";
+ String sqlDefault = "\"" + logicalDefault.replace("\\",
"\\\\").replace("\"", "\\\"") + "\"";
+ String createSql =
+ String.format(
+ "CREATE TABLE %s ("
+ + "col_pk BIGINT NOT NULL, "
+ + "col_data VARCHAR(32) NOT NULL DEFAULT %s) "
+ + "DUPLICATE KEY(col_pk) "
+ + "DISTRIBUTED BY HASH(col_pk) BUCKETS 1 "
+ + "PROPERTIES (\"replication_num\" = \"1\")",
+ tableName, sqlDefault);
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(createSql);
+ }
+
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(32)),
+ findColumn(tc.loadTable(tid), "col_data").defaultValue());
+ tc.alterTable(
+ tid, TableChange.updateColumnType(new String[] {"col_data"},
Types.VarCharType.of(64)));
+
+ Awaitility.await()
+ .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+ .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ Column updatedColumn = findColumn(tc.loadTable(tid), "col_data");
+ assertEquals(Types.VarCharType.of(64), updatedColumn.dataType());
+ assertEquals(
+ Literals.of(logicalDefault, Types.VarCharType.of(64)),
+ updatedColumn.defaultValue());
+ });
+
+ try (Connection connection =
+ DriverManager.getConnection(
+ jdbcUrl + schemaName, DorisContainer.USER_NAME,
DorisContainer.PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(String.format("INSERT INTO %s (col_pk) VALUES
(1)", tableName));
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ String.format("SELECT col_data FROM %s WHERE col_pk = 1",
tableName))) {
+ assertTrue(resultSet.next());
+ assertEquals(logicalDefault, resultSet.getString(1));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ @Test
+ void testAlterColumnTypeRejectsInvalidConversionWithDefaultValue() {
+ TableCatalog tc = catalog.asTableCatalog();
+ NameIdentifier tid =
+ NameIdentifier.of(
+ schemaName,
GravitinoITUtils.genRandomName("t_alter_type_rejects_invalid"));
+ Column defaultedColumn =
+ Column.of(
+ colName2,
+ Types.VarCharType.of(10),
+ "defaulted column",
+ false,
+ false,
+ Literals.of("true", Types.VarCharType.of(10)));
+
+ tc.createTable(
+ tid,
+ new Column[] {
+ Column.of(colName1, Types.LongType.get(), "pk", false, false, null),
defaultedColumn
+ },
+ tableComment,
+ Collections.emptyMap(),
+ Transforms.EMPTY_TRANSFORM,
+ hashDist(),
+ null,
+ null);
+
+ RuntimeException exception =
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ tc.alterTable(
+ tid,
+ TableChange.updateColumnType(
+ new String[] {colName2}, Types.BooleanType.get())));
+
+ assertTrue(
+ exception.getMessage().contains("Can not change VARCHAR to BOOLEAN"),
+ exception.getMessage());
+ Column unchangedColumn = findColumn(tc.loadTable(tid), colName2);
+ assertEquals(Types.VarCharType.of(10), unchangedColumn.dataType());
+ assertEquals(Literals.of("true", Types.VarCharType.of(10)),
unchangedColumn.defaultValue());
+ }
+
@Test
void testAddColumnPreservesDefaultValue() throws SQLException {
TableCatalog tc = catalog.asTableCatalog();
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 464797da76..86eea613d9 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
@@ -43,6 +43,7 @@ import org.apache.gravitino.catalog.jdbc.JdbcTable;
import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
import org.apache.gravitino.rel.TableChange;
import org.apache.gravitino.rel.expressions.NamedReference;
+import org.apache.gravitino.rel.expressions.UnparsedExpression;
import org.apache.gravitino.rel.expressions.distributions.Distribution;
import org.apache.gravitino.rel.expressions.distributions.Distributions;
import org.apache.gravitino.rel.expressions.literals.Literal;
@@ -50,6 +51,7 @@ import org.apache.gravitino.rel.expressions.literals.Literals;
import org.apache.gravitino.rel.expressions.transforms.Transforms;
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.indexes.Indexes;
+import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.rel.types.Types;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -107,6 +109,23 @@ public class TestDorisTableOperationsSqlGeneration {
return generateAlterTableSql("database", tableName, changes);
}
+ /**
+ * Generates SQL for updating the given column type.
+ *
+ * @param column the current JDBC column
+ * @param newType the requested Gravitino type
+ * @return the generated ALTER TABLE SQL
+ */
+ public String updateColumnTypeSql(JdbcColumn column, Type newType) {
+ setTableForAlter(
+ JdbcTable.builder()
+ .withName("test_table")
+ .withColumns(new JdbcColumn[] {column})
+ .build());
+ return alterTableSql(
+ "test_table", TableChange.updateColumnType(new String[]
{column.name()}, newType));
+ }
+
void setTableForAlter(JdbcTable table) {
this.tableForAlter = table;
}
@@ -135,6 +154,196 @@ public class TestDorisTableOperationsSqlGeneration {
}
}
+ /** Verifies string defaults and unchanged column attributes are preserved.
*/
+ @Test
+ public void testUpdateColumnTypePreservesStringDefaultAndColumnAttributes() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ JdbcColumn column =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.VarCharType.of(10))
+ .withComment("comment")
+ .withNullable(true)
+ .withDefaultValue(Literals.of("seed", Types.VarCharType.of(10)))
+ .build();
+
+ Assertions.assertEquals(
+ "ALTER TABLE `test_table`\n"
+ + "MODIFY COLUMN `col1` varchar(20) NULL DEFAULT 'seed' COMMENT
'comment' ;",
+ ops.updateColumnTypeSql(column, Types.VarCharType.of(20)));
+ }
+
+ /** Verifies numeric and current-timestamp defaults are preserved. */
+ @Test
+ public void
testUpdateColumnTypePreservesNumericAndCurrentTimestampDefaults() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ JdbcColumn numericColumn =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.IntegerType.get())
+ .withNullable(false)
+ .withDefaultValue(Literals.integerLiteral(7))
+ .build();
+ JdbcColumn timestampColumn =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.TimestampType.withoutTimeZone())
+ .withNullable(false)
+ .withDefaultValue(DEFAULT_VALUE_OF_CURRENT_TIMESTAMP)
+ .build();
+
+ Assertions.assertEquals(
+ "ALTER TABLE `test_table`\nMODIFY COLUMN `col1` bigint NOT NULL
DEFAULT 7 ;",
+ ops.updateColumnTypeSql(numericColumn, Types.LongType.get()));
+ Assertions.assertEquals(
+ "ALTER TABLE `test_table`\n"
+ + "MODIFY COLUMN `col1` datetime(6) NOT NULL DEFAULT
CURRENT_TIMESTAMP ;",
+ ops.updateColumnTypeSql(timestampColumn,
Types.TimestampType.withoutTimeZone(6)));
+ }
+
+ /** Verifies quoted string defaults use Doris MODIFY COLUMN escaping. */
+ @Test
+ public void testUpdateColumnTypeUsesDorisModifyEscapingForQuotedDefaults() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ Literal<?> defaultValue = Literals.of("owner's \"value\"\\path",
Types.VarCharType.of(32));
+ JdbcColumn column =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.VarCharType.of(32))
+ .withNullable(false)
+ .withDefaultValue(defaultValue)
+ .build();
+
+ String sql = ops.updateColumnTypeSql(column, Types.VarCharType.of(64));
+
+ Assertions.assertTrue(
+ sql.contains(
+ "DEFAULT "
+ + new DorisColumnDefaultValueConverter()
+ .fromGravitinoForModifyColumn(defaultValue, true, true)),
+ sql);
+ }
+
+ /** Verifies native unparsed defaults pass through without interpretation. */
+ @Test
+ public void testUpdateColumnTypePassesThroughUnparsedDefault() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ JdbcColumn column =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.DateType.get())
+ .withNullable(false)
+ .withDefaultValue(UnparsedExpression.of("CURRENT_DATE"))
+ .build();
+
+ Assertions.assertTrue(
+ ops.updateColumnTypeSql(column,
Types.DateType.get()).contains("DEFAULT CURRENT_DATE"),
+ "Native default expression should pass through unchanged");
+ }
+
+ /** Verifies explicit NULL and an unset default remain distinct in generated
SQL. */
+ @Test
+ public void testUpdateColumnTypePreservesNullAndUnsetDefaultDistinction() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ JdbcColumn nullDefault =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.IntegerType.get())
+ .withNullable(true)
+ .withDefaultValue(Literals.NULL)
+ .build();
+ JdbcColumn unsetDefault =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.IntegerType.get())
+ .withNullable(true)
+ .withDefaultValue(DEFAULT_VALUE_NOT_SET)
+ .build();
+
+ Assertions.assertTrue(
+ ops.updateColumnTypeSql(nullDefault,
Types.LongType.get()).contains("NULL DEFAULT NULL ;"));
+ Assertions.assertTrue(
+ ops.updateColumnTypeSql(unsetDefault,
Types.LongType.get()).contains("bigint NULL ;"));
+ }
+
+ /** Verifies nested column names remain unsupported for type changes. */
+ @Test
+ public void testUpdateColumnTypeRejectsNestedColumnName() {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+
+ UnsupportedOperationException exception =
+ Assertions.assertThrows(
+ UnsupportedOperationException.class,
+ () ->
+ ops.alterTableSql(
+ "test_table",
+ TableChange.updateColumnType(
+ new String[] {"parent", "child"},
Types.LongType.get())));
+
+ Assertions.assertEquals("Doris does not support nested column names.",
exception.getMessage());
+ }
+
+ /** Verifies version lookup is cached across type changes in one ALTER
request. */
+ @Test
+ public void testUpdateColumnTypeQueriesVersionOnceForAlterRequest() throws
Exception {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ DataSource versionDataSource =
mockVersionDataSource("doris-3.0.6.2-rc01-910c4249c5");
+ ops.setDataSource(versionDataSource);
+ JdbcColumn firstColumn =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.VarCharType.of(32))
+ .withDefaultValue(Literals.of("first\\value",
Types.VarCharType.of(32)))
+ .build();
+ JdbcColumn secondColumn =
+ JdbcColumn.builder()
+ .withName("col2")
+ .withType(Types.VarCharType.of(32))
+ .withDefaultValue(Literals.of("second\"value",
Types.VarCharType.of(32)))
+ .build();
+ ops.setTableForAlter(
+ JdbcTable.builder()
+ .withName("test_table")
+ .withColumns(new JdbcColumn[] {firstColumn, secondColumn})
+ .build());
+
+ ops.alterTableSql(
+ "test_table",
+ TableChange.updateColumnType(new String[] {"col1"},
Types.VarCharType.of(64)),
+ TableChange.updateColumnType(new String[] {"col2"},
Types.VarCharType.of(64)));
+
+ Mockito.verify(versionDataSource, Mockito.times(1)).getConnection();
+ }
+
+ /** Verifies ADD and MODIFY changes share one version lookup in an ALTER
request. */
+ @Test
+ public void testAddAndUpdateColumnTypeShareVersionLookup() throws Exception {
+ TestableDorisTableOperations ops = new TestableDorisTableOperations();
+ DataSource versionDataSource =
mockVersionDataSource("doris-3.0.6.2-rc01-910c4249c5");
+ ops.setDataSource(versionDataSource);
+ JdbcColumn existingColumn =
+ JdbcColumn.builder()
+ .withName("col1")
+ .withType(Types.VarCharType.of(32))
+ .withDefaultValue(Literals.of("existing\\value",
Types.VarCharType.of(32)))
+ .build();
+ ops.setTableForAlter(
+ JdbcTable.builder()
+ .withName("test_table")
+ .withColumns(new JdbcColumn[] {existingColumn})
+ .build());
+
+ ops.alterTableSql(
+ "test_table",
+ TableChange.addColumn(
+ new String[] {"col2"},
+ Types.VarCharType.of(32),
+ Literals.of("added\\value", Types.VarCharType.of(32))),
+ TableChange.updateColumnType(new String[] {"col1"},
Types.VarCharType.of(64)));
+
+ Mockito.verify(versionDataSource, Mockito.times(1)).getConnection();
+ }
+
/**
* Verifies DROP returns false for missing-table errors from different Doris
versions.
*