From c062e08c923b265862fe6b92fb94a141853ebb94 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Sat, 20 Sep 2025 11:36:24 +0900 Subject: [PATCH 01/12] Add alter column type --- .../main/java/com/scalar/db/api/Admin.java | 13 ++ .../common/CommonDistributedStorageAdmin.java | 36 ++++ .../java/com/scalar/db/common/CoreError.java | 42 +++++ .../DecoratedDistributedTransactionAdmin.java | 7 + .../com/scalar/db/service/AdminService.java | 7 + .../db/storage/cassandra/CassandraAdmin.java | 8 + .../scalar/db/storage/cosmos/CosmosAdmin.java | 11 +- .../scalar/db/storage/dynamo/DynamoAdmin.java | 8 + .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 31 ++++ .../db/storage/jdbc/RdbEngineMysql.java | 2 +- .../db/storage/jdbc/RdbEnginePostgresql.java | 2 +- .../db/storage/jdbc/RdbEngineSqlServer.java | 8 +- .../db/storage/jdbc/RdbEngineSqlite.java | 7 + .../db/storage/jdbc/RdbEngineStrategy.java | 7 + .../multistorage/MultiStorageAdmin.java | 7 + .../consensuscommit/ConsensusCommitAdmin.java | 22 +++ .../jdbc/JdbcTransactionAdmin.java | 7 + .../SingleCrudOperationTransactionAdmin.java | 7 + .../com/scalar/db/util/ScalarDbUtils.java | 28 ++++ .../storage/cassandra/CassandraAdminTest.java | 4 + .../db/storage/cosmos/CosmosAdminTest.java | 3 + .../storage/dynamo/DynamoAdminTestBase.java | 3 + .../scalar/db/storage/jdbc/JdbcAdminTest.java | 155 +++++++++++++++++- .../multistorage/MultiStorageAdminTest.java | 64 ++++++++ .../ConsensusCommitAdminTestBase.java | 24 +++ .../jdbc/JdbcTransactionAdminTest.java | 15 ++ ...ngleCrudOperationTransactionAdminTest.java | 16 ++ ...ibutedStorageAdminIntegrationTestBase.java | 124 ++++++++++++++ 28 files changed, 659 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/scalar/db/api/Admin.java b/core/src/main/java/com/scalar/db/api/Admin.java index 729853d06a..247117d935 100644 --- a/core/src/main/java/com/scalar/db/api/Admin.java +++ b/core/src/main/java/com/scalar/db/api/Admin.java @@ -534,6 +534,19 @@ default void dropColumnFromTable( void renameColumn(String namespace, String table, String oldColumnName, String newColumnName) throws ExecutionException; + /** + * Alters the data type of existing column of an existing table. + * + * @param namespace the table namespace + * @param table the table name + * @param columnName the name of the column to alter + * @param newColumnType the new data type of the column + * @throws IllegalArgumentException if the table or the column does not exist + * @throws ExecutionException if the operation fails + */ + void alterColumnType(String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException; + /** * Imports an existing table that is not managed by ScalarDB. * diff --git a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java index 1758c297ae..d239bf57d6 100644 --- a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java @@ -363,6 +363,42 @@ public void renameColumn( } } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + TableMetadata tableMetadata = getTableMetadata(namespace, table); + if (tableMetadata == null) { + throw new IllegalArgumentException( + CoreError.TABLE_NOT_FOUND.buildMessage(ScalarDbUtils.getFullTableName(namespace, table))); + } + + if (!tableMetadata.getColumnNames().contains(columnName)) { + throw new IllegalArgumentException( + CoreError.COLUMN_NOT_FOUND2.buildMessage( + ScalarDbUtils.getFullTableName(namespace, table), columnName)); + } + + DataType currentColumnType = tableMetadata.getColumnDataType(columnName); + if (currentColumnType == newColumnType) { + return; + } + if (!ScalarDbUtils.isTypeConversionSupported(currentColumnType, newColumnType)) { + throw new IllegalArgumentException( + CoreError.INVALID_COLUMN_TYPE_CONVERSION.buildMessage( + currentColumnType, newColumnType, columnName)); + } + + try { + admin.alterColumnType(namespace, table, columnName, newColumnType); + } catch (ExecutionException e) { + throw new ExecutionException( + CoreError.ALTERING_COLUMN_TYPE_FAILED.buildMessage( + ScalarDbUtils.getFullTableName(namespace, table), columnName, newColumnType), + e); + } + } + @Override public Set getNamespaceNames() throws ExecutionException { try { diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 4137040b22..d050ba919f 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -736,6 +736,42 @@ public enum CoreError implements ScalarDbError { "The BOOLEAN type is not supported for index columns in DynamoDB. Column: %s", "", ""), + INVALID_COLUMN_TYPE_CONVERSION( + Category.USER_ERROR, + "0227", + "Invalid column type conversion from %s to %s. Column: %s", + "", + ""), + CASSANDRA_ALTER_COLUMN_TYPE_NOT_SUPPORTED( + Category.USER_ERROR, + "0228", + "Cassandra does not support the altering column type feature", + "", + ""), + COSMOS_ALTER_COLUMN_TYPE_NOT_SUPPORTED( + Category.USER_ERROR, + "0229", + "Cosmos DB does not support the altering column type feature", + "", + ""), + DYNAMO_ALTER_COLUMN_TYPE_NOT_SUPPORTED( + Category.USER_ERROR, + "0230", + "DynamoDB does not support the altering column type feature", + "", + ""), + JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED( + Category.USER_ERROR, + "0231", + "SQLite does not support the altering column type feature", + "", + ""), + ALTER_IMPORTED_COLUMN_TYPE_NOT_SUPPORTED( + Category.USER_ERROR, + "0232", + "Altering the data type of an imported column is not supported. Column: %s; Data type: %s; ", + "", + ""), // // Errors for the concurrency error category @@ -1030,6 +1066,12 @@ public enum CoreError implements ScalarDbError { "Renaming a column failed. Table: %s; Old column name: %s; New column name: %s", "", ""), + ALTERING_COLUMN_TYPE_FAILED( + Category.INTERNAL_ERROR, + "0061", + "Altering a column type failed. Table: %s; Column: %s; New column type: %s", + "", + ""), // // Errors for the unknown transaction status error category diff --git a/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java b/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java index e8954c002b..d757c6e6e9 100644 --- a/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/common/DecoratedDistributedTransactionAdmin.java @@ -228,6 +228,13 @@ public void renameColumn( distributedTransactionAdmin.renameColumn(namespace, table, oldColumnName, newColumnName); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + distributedTransactionAdmin.alterColumnType(namespace, table, columnName, newColumnType); + } + @Override public void importTable(String namespace, String table, Map options) throws ExecutionException { diff --git a/core/src/main/java/com/scalar/db/service/AdminService.java b/core/src/main/java/com/scalar/db/service/AdminService.java index 9f198dde3b..8b1360fbc4 100644 --- a/core/src/main/java/com/scalar/db/service/AdminService.java +++ b/core/src/main/java/com/scalar/db/service/AdminService.java @@ -107,6 +107,13 @@ public void renameColumn( admin.renameColumn(namespace, table, oldColumnName, newColumnName); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + admin.alterColumnType(namespace, table, columnName, newColumnType); + } + @Override public TableMetadata getImportTableMetadata( String namespace, String table, Map overrideColumnsType) diff --git a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java index 63f7383fbf..90c7e03928 100644 --- a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java @@ -471,6 +471,14 @@ oldColumnName, newColumnName, getFullTableName(namespace, table)), } } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + throw new UnsupportedOperationException( + CoreError.CASSANDRA_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + } + @Override public Set getNamespaceNames() throws ExecutionException { try { diff --git a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java index 9efa458369..cfd3428571 100644 --- a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java @@ -653,11 +653,20 @@ public void dropColumnFromTable(String namespace, String table, String columnNam @Override public void renameColumn( - String namespace, String table, String oldColumnName, String newColumnName) { + String namespace, String table, String oldColumnName, String newColumnName) + throws ExecutionException { throw new UnsupportedOperationException( CoreError.COSMOS_RENAME_COLUMN_NOT_SUPPORTED.buildMessage()); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + throw new UnsupportedOperationException( + CoreError.COSMOS_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + } + @Override public TableMetadata getImportTableMetadata( String namespace, String table, Map overrideColumnsType) { diff --git a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java index b1415dda82..5c8e294dae 100644 --- a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java @@ -1451,6 +1451,14 @@ public void renameColumn( CoreError.DYNAMO_RENAME_COLUMN_NOT_SUPPORTED.buildMessage()); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + throw new UnsupportedOperationException( + CoreError.DYNAMO_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + } + @Override public TableMetadata getImportTableMetadata( String namespace, String table, Map overrideColumnsType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java index dda73607e7..dc12c8d601 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java @@ -927,6 +927,37 @@ oldColumnName, newColumnName, getFullTableName(namespace, table)), } } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + try { + rdbEngine.throwIfAlterColumnTypeNotSupported(); + TableMetadata currentTableMetadata = getTableMetadata(namespace, table); + assert currentTableMetadata != null; + + TableMetadata updatedTableMetadata = + TableMetadata.newBuilder(currentTableMetadata) + .removeColumn(columnName) + .addColumn(columnName, newColumnType) + .build(); + String newStorageColumnType = getVendorDbColumnType(updatedTableMetadata, columnName); + String alterColumnTypeStatement = + rdbEngine.alterColumnTypeSql(namespace, table, columnName, newStorageColumnType); + + try (Connection connection = dataSource.getConnection()) { + execute(connection, alterColumnTypeStatement); + addTableMetadata(connection, namespace, table, updatedTableMetadata, false, true); + } + } catch (SQLException e) { + throw new ExecutionException( + String.format( + "Altering the %s column type to %s in the %s table failed", + columnName, newColumnType, getFullTableName(namespace, table)), + e); + } + } + @Override public void addRawColumnToTable( String namespace, String table, String columnName, DataType columnType) diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java index a8c35b5f57..e3b634e427 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java @@ -136,7 +136,7 @@ public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { return "ALTER TABLE " + encloseFullTableName(namespace, table) - + " MODIFY" + + " MODIFY " + enclose(columnName) + " " + columnType; diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java index fb76b2d48f..33595e361c 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java @@ -116,7 +116,7 @@ public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { return "ALTER TABLE " + encloseFullTableName(namespace, table) - + " ALTER COLUMN" + + " ALTER COLUMN " + enclose(columnName) + " TYPE " + columnType; diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java index 6bec004bba..00d196e54c 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java @@ -120,8 +120,12 @@ public String renameColumnSql( @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - // SQLServer does not require changes in column data types when making indices. - throw new AssertionError(); + return "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " ALTER COLUMN " + + enclose(columnName) + + " " + + columnType; } @Override diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java index 4d04335b74..aea1c59869 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java @@ -2,6 +2,7 @@ import com.scalar.db.api.LikeExpression; import com.scalar.db.api.TableMetadata; +import com.scalar.db.common.CoreError; import com.scalar.db.io.DataType; import com.scalar.db.io.DateColumn; import com.scalar.db.io.TimeColumn; @@ -357,4 +358,10 @@ public RdbEngineTimeTypeStrategy getTimeTypeStrategy( public void setConnectionToReadOnly(Connection connection, boolean readOnly) { // Do nothing. SQLite does not support read-only mode. } + + @Override + public void throwIfAlterColumnTypeNotSupported() { + throw new UnsupportedOperationException( + CoreError.JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java index b0826ceeb0..02d78b9bf3 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java @@ -281,6 +281,13 @@ default void throwIfDuplicatedIndexWarning(SQLWarning warning) throws SQLExcepti */ default void throwIfRenameColumnNotSupported(String columnName, TableMetadata tableMetadata) {} + /** + * Throws an exception if altering the column type is not supported in the underlying database. + * + * @throws UnsupportedOperationException if altering the column type is not supported + */ + default void throwIfAlterColumnTypeNotSupported() {} + default void setConnectionToReadOnly(Connection connection, boolean readOnly) throws SQLException { connection.setReadOnly(readOnly); diff --git a/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java b/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java index d04a58a6ea..b44eb4cf7b 100644 --- a/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java @@ -218,6 +218,13 @@ public void renameColumn( getAdmin(namespace, table).renameColumn(namespace, table, oldColumnName, newColumnName); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + getAdmin(namespace, table).alterColumnType(namespace, table, columnName, newColumnType); + } + @Override public TableMetadata getImportTableMetadata( String namespace, String table, Map overrideColumnsType) diff --git a/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java b/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java index e25ec4eb97..181a0121a6 100644 --- a/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java @@ -277,6 +277,28 @@ public void renameColumn( } } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + checkNamespace(namespace); + + TableMetadata tableMetadata = getTableMetadata(namespace, table); + if (tableMetadata == null) { + throw new IllegalArgumentException( + CoreError.TABLE_NOT_FOUND.buildMessage(ScalarDbUtils.getFullTableName(namespace, table))); + } + if (tableMetadata.getPartitionKeyNames().contains(columnName) + || tableMetadata.getClusteringKeyNames().contains(columnName)) { + admin.alterColumnType(namespace, table, columnName, newColumnType); + } else { + String beforeColumnName = getBeforeImageColumnName(columnName, tableMetadata); + + admin.alterColumnType(namespace, table, columnName, newColumnType); + admin.alterColumnType(namespace, table, beforeColumnName, newColumnType); + } + } + @Override public Set getNamespaceNames() throws ExecutionException { return admin.getNamespaceNames().stream() diff --git a/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java b/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java index ed9d9f5150..2105bcea92 100644 --- a/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java @@ -173,6 +173,13 @@ public void renameColumn( jdbcAdmin.renameColumn(namespace, table, oldColumnName, newColumnName); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + jdbcAdmin.alterColumnType(namespace, table, columnName, newColumnType); + } + @Override public Set getNamespaceNames() throws ExecutionException { return jdbcAdmin.getNamespaceNames(); diff --git a/core/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdmin.java b/core/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdmin.java index 64d3e520aa..b0c88807b6 100644 --- a/core/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdmin.java @@ -122,6 +122,13 @@ public void renameColumn( distributedStorageAdmin.renameColumn(namespace, table, oldColumnName, newColumnName); } + @Override + public void alterColumnType( + String namespace, String table, String columnName, DataType newColumnType) + throws ExecutionException { + distributedStorageAdmin.alterColumnType(namespace, table, columnName, newColumnType); + } + @Override public Set getNamespaceNames() throws ExecutionException { return distributedStorageAdmin.getNamespaceNames(); diff --git a/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java b/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java index 70d5aa1303..453f06b885 100644 --- a/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java +++ b/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java @@ -36,6 +36,7 @@ import com.scalar.db.io.BooleanColumn; import com.scalar.db.io.BooleanValue; import com.scalar.db.io.Column; +import com.scalar.db.io.DataType; import com.scalar.db.io.DoubleColumn; import com.scalar.db.io.DoubleValue; import com.scalar.db.io.FloatColumn; @@ -475,4 +476,31 @@ private static Optional getKey(Map> columns, LinkedHashSe } return Optional.of(builder.build()); } + + public static boolean isTypeConversionSupported(DataType from, DataType to) { + if (from == to) { + return true; + } + switch (from) { + case BOOLEAN: + case BIGINT: + case DOUBLE: + case BLOB: + case TIME: + case TIMESTAMPTZ: + return to == DataType.TEXT; + case INT: + return to == DataType.BIGINT || to == DataType.TEXT; + case FLOAT: + return to == DataType.DOUBLE || to == DataType.TEXT; + case DATE: + return to == DataType.TEXT || to == DataType.TIMESTAMP || to == DataType.TIMESTAMPTZ; + case TIMESTAMP: + return to == DataType.TEXT || to == DataType.TIMESTAMPTZ; + case TEXT: + return false; + default: + throw new AssertionError("Unknown data type: " + from); + } + } } diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java index b1fdca6eff..0cf323d549 100644 --- a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java @@ -984,11 +984,15 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { () -> cassandraAdmin.importTable( namespace, table, Collections.emptyMap(), Collections.emptyMap())); + Throwable thrown4 = + catchThrowable( + () -> cassandraAdmin.alterColumnType(namespace, table, column, DataType.INT)); // Assert assertThat(thrown1).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown2).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown3).isInstanceOf(UnsupportedOperationException.class); + assertThat(thrown4).isInstanceOf(UnsupportedOperationException.class); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java index c45f99fb1e..c6fab3d342 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java @@ -1152,6 +1152,8 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { Throwable thrown4 = catchThrowable(() -> admin.dropColumnFromTable(namespace, table, column)); Throwable thrown5 = catchThrowable(() -> admin.renameColumn(namespace, table, column, "newCol")); + Throwable thrown6 = + catchThrowable(() -> admin.alterColumnType(namespace, table, column, DataType.INT)); // Assert assertThat(thrown1).isInstanceOf(UnsupportedOperationException.class); @@ -1159,6 +1161,7 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { assertThat(thrown3).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown4).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown5).isInstanceOf(UnsupportedOperationException.class); + assertThat(thrown6).isInstanceOf(UnsupportedOperationException.class); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java index 9f48489cb8..35dd0f8c9d 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java @@ -1719,6 +1719,8 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { NAMESPACE, TABLE, Collections.emptyMap(), Collections.emptyMap())); Throwable thrown4 = catchThrowable(() -> admin.dropColumnFromTable(NAMESPACE, TABLE, "c1")); Throwable thrown5 = catchThrowable(() -> admin.renameColumn(NAMESPACE, TABLE, "c1", "c2")); + Throwable thrown6 = + catchThrowable(() -> admin.alterColumnType(NAMESPACE, TABLE, "c1", DataType.INT)); // Assert assertThat(thrown1).isInstanceOf(UnsupportedOperationException.class); @@ -1726,6 +1728,7 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { assertThat(thrown3).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown4).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown5).isInstanceOf(UnsupportedOperationException.class); + assertThat(thrown6).isInstanceOf(UnsupportedOperationException.class); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index ab22b91d7a..f814587cff 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -35,6 +35,7 @@ import com.mysql.cj.jdbc.exceptions.CommunicationsException; import com.scalar.db.api.Scan.Ordering.Order; import com.scalar.db.api.TableMetadata; +import com.scalar.db.common.CoreError; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; import java.sql.Connection; @@ -2447,7 +2448,7 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", - "ALTER TABLE `my_ns`.`my_tbl` MODIFY`my_column` VARCHAR(128)", + "ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` VARCHAR(128)", "CREATE INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl` (`my_column`)", "UPDATE `" + METADATA_SCHEMA @@ -2463,7 +2464,7 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN\"my_column\" TYPE VARCHAR(10485760)", + "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE VARCHAR(10485760)", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" + METADATA_SCHEMA @@ -2686,7 +2687,7 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForMysql_ShouldDropInde + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "DROP INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl`", - "ALTER TABLE `my_ns`.`my_tbl` MODIFY`my_column` LONGTEXT", + "ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` LONGTEXT", "UPDATE `" + METADATA_SCHEMA + "`.`metadata` SET `indexed`=false WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); @@ -2701,7 +2702,7 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForPostgresql_ShouldDro + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN\"my_column\" TYPE TEXT", + "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE TEXT", "UPDATE \"" + METADATA_SCHEMA + "\".\"metadata\" SET \"indexed\"=false WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); @@ -3233,6 +3234,152 @@ private void renameColumn_ForX_ShouldWorkProperly( } } + @Test + public void alterColumnType_ForMysql_ShouldWorkProperly() + throws SQLException, ExecutionException { + alterColumnType_ForX_ShouldWorkProperly( + RdbEngine.MYSQL, + "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" + + METADATA_SCHEMA + + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", + "ALTER TABLE `ns`.`table` MODIFY `c2` BIGINT", + "DELETE FROM `" + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name` = 'ns.table'", + "INSERT INTO `" + + METADATA_SCHEMA + + "`.`metadata` VALUES ('ns.table','c1','TEXT','PARTITION',NULL,false,1)", + "INSERT INTO `" + + METADATA_SCHEMA + + "`.`metadata` VALUES ('ns.table','c2','BIGINT',NULL,NULL,false,2)"); + } + + @Test + public void alterColumnType_ForOracle_ShouldWorkProperly() + throws SQLException, ExecutionException { + alterColumnType_ForX_ShouldWorkProperly( + RdbEngine.ORACLE, + "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", + "ALTER TABLE \"ns\".\"table\" MODIFY ( \"c2\" NUMBER(16) )", + "DELETE FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,0,1)", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c2','BIGINT',NULL,NULL,0,2)"); + } + + @Test + public void alterColumnType_ForPostgresql_ShouldWorkProperly() + throws SQLException, ExecutionException { + alterColumnType_ForX_ShouldWorkProperly( + RdbEngine.POSTGRESQL, + "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", + "ALTER TABLE \"ns\".\"table\" ALTER COLUMN \"c2\" TYPE BIGINT", + "DELETE FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,false,1)", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c2','BIGINT',NULL,NULL,false,2)"); + } + + @Test + public void alterColumnType_ForSqlServer_ShouldWorkProperly() + throws SQLException, ExecutionException { + alterColumnType_ForX_ShouldWorkProperly( + RdbEngine.SQL_SERVER, + "SELECT [column_name],[data_type],[key_type],[clustering_order],[indexed] FROM [" + + METADATA_SCHEMA + + "].[metadata] WHERE [full_table_name]=? ORDER BY [ordinal_position] ASC", + "ALTER TABLE [ns].[table] ALTER COLUMN [c2] BIGINT", + "DELETE FROM [" + METADATA_SCHEMA + "].[metadata] WHERE [full_table_name] = 'ns.table'", + "INSERT INTO [" + + METADATA_SCHEMA + + "].[metadata] VALUES ('ns.table','c1','TEXT','PARTITION',NULL,0,1)", + "INSERT INTO [" + + METADATA_SCHEMA + + "].[metadata] VALUES ('ns.table','c2','BIGINT',NULL,NULL,0,2)"); + } + + @Test + public void alterColumnType_ForSqlite_ShouldThrowUnsupportedOperationException() { + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE); + assertThatThrownBy(() -> admin.alterColumnType("ns", "table", "column", DataType.BIGINT)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage(CoreError.JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + } + + @Test + public void alterColumnType_ForDb2_ShouldWorkProperly() throws SQLException, ExecutionException { + alterColumnType_ForX_ShouldWorkProperly( + RdbEngine.DB2, + "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", + "ALTER TABLE \"ns\".\"table\" ALTER COLUMN \"c2\" SET DATA TYPE BIGINT", + "DELETE FROM \"" + + METADATA_SCHEMA + + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,false,1)", + "INSERT INTO \"" + + METADATA_SCHEMA + + "\".\"metadata\" VALUES ('ns.table','c2','BIGINT',NULL,NULL,false,2)"); + } + + private void alterColumnType_ForX_ShouldWorkProperly( + RdbEngine rdbEngine, String expectedGetMetadataStatement, String... expectedSqlStatements) + throws SQLException, ExecutionException { + // Arrange + String namespace = "ns"; + String table = "table"; + String columnName1 = "c1"; + String columnName2 = "c2"; + + PreparedStatement selectStatement = mock(PreparedStatement.class); + ResultSet resultSet = + mockResultSet( + new SelectAllFromMetadataTableResultSetMocker.Row( + columnName1, DataType.TEXT.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + columnName2, DataType.INT.toString(), null, null, false)); + when(selectStatement.executeQuery()).thenReturn(resultSet); + + when(connection.prepareStatement(any())).thenReturn(selectStatement); + List expectedStatements = new ArrayList<>(); + for (int i = 0; i < expectedSqlStatements.length; i++) { + Statement expectedStatement = mock(Statement.class); + expectedStatements.add(expectedStatement); + } + when(connection.createStatement()) + .thenReturn( + expectedStatements.get(0), + expectedStatements.subList(1, expectedStatements.size()).toArray(new Statement[0])); + + when(dataSource.getConnection()).thenReturn(connection); + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); + + // Act + admin.alterColumnType(namespace, table, columnName2, DataType.BIGINT); + + // Assert + verify(selectStatement).setString(1, getFullTableName(namespace, table)); + verify(connection).prepareStatement(expectedGetMetadataStatement); + for (int i = 0; i < expectedSqlStatements.length; i++) { + verify(expectedStatements.get(i)).execute(expectedSqlStatements[i]); + } + } + @Test public void getNamespaceNames_forMysql_ShouldReturnNamespaceNames() throws Exception { getNamespaceNames_forX_ShouldReturnNamespaceNames( diff --git a/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java b/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java index d084a0bafc..469070bf44 100644 --- a/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java @@ -625,6 +625,70 @@ public void renameColumn_ForTable1InNamespace2_ShouldCallRenameColumnOfAdmin2() verify(admin2).renameColumn(namespace, table, column1, column2); } + @Test + public void alterColumnType_ForTable1InNamespace1_ShouldCallAlterColumnTypeOfAdmin1() + throws ExecutionException { + // Arrange + String namespace = NAMESPACE1; + String table = TABLE1; + String columnName = "column"; + DataType columnType = DataType.BIGINT; + + // Act + multiStorageAdmin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(admin1).alterColumnType(namespace, table, columnName, columnType); + } + + @Test + public void alterColumnType_ForTable2InNamespace1_ShouldShouldCallAlterColumnTypeOfAdmin2() + throws ExecutionException { + // Arrange + String namespace = NAMESPACE1; + String table = TABLE2; + String columnName = "column"; + DataType columnType = DataType.BIGINT; + + // Act + multiStorageAdmin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(admin2).alterColumnType(namespace, table, columnName, columnType); + } + + @Test + public void alterColumnType_ForTable3InNamespace1_ShouldCallAlterColumnTypeOfDefaultAdmin() + throws ExecutionException { + // Arrange + String namespace = NAMESPACE1; + String table = TABLE3; + String columnName = "column"; + DataType columnType = DataType.BIGINT; + + // Act + multiStorageAdmin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(admin3).alterColumnType(namespace, table, columnName, columnType); + } + + @Test + public void alterColumnType_ForTable1InNamespace2_ShouldCallAlterColumnTypeOfAdmin2() + throws ExecutionException { + // Arrange + String namespace = NAMESPACE2; + String table = TABLE1; + String columnName = "col"; + DataType columnType = DataType.BIGINT; + + // Act + multiStorageAdmin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(admin2).alterColumnType(namespace, table, columnName, columnType); + } + @Test public void getNamespaceNames_WithExistingNamespacesNotInMapping_ShouldReturnExistingNamespacesInMappingAndFromDefaultAdmin() diff --git a/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java b/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java index 670186c9d2..00eeac9d73 100644 --- a/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java @@ -654,6 +654,30 @@ public void renameColumn_ShouldCallJdbcAdminProperly() throws ExecutionException Attribute.BEFORE_PREFIX + newColumnName); } + @Test + public void alterColumnType_ShouldCallJdbcAdminProperly() throws ExecutionException { + // Arrange + String columnName = "col2"; + DataType columnType = DataType.BIGINT; + TableMetadata tableMetadata = + TableMetadata.newBuilder() + .addColumn("col1", DataType.INT) + .addColumn(columnName, DataType.INT) + .addPartitionKey("col1") + .build(); + when(distributedStorageAdmin.getTableMetadata(any(), any())) + .thenReturn(ConsensusCommitUtils.buildTransactionTableMetadata(tableMetadata)); + + // Act + admin.alterColumnType(NAMESPACE, TABLE, columnName, columnType); + + // Assert + verify(distributedStorageAdmin).getTableMetadata(NAMESPACE, TABLE); + verify(distributedStorageAdmin).alterColumnType(NAMESPACE, TABLE, columnName, columnType); + verify(distributedStorageAdmin) + .alterColumnType(NAMESPACE, TABLE, Attribute.BEFORE_PREFIX + columnName, columnType); + } + @Test public void importTable_ShouldCallStorageAdminProperly() throws ExecutionException { // Arrange diff --git a/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java b/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java index fd05bdbaf9..6f395411da 100644 --- a/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java +++ b/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java @@ -257,6 +257,21 @@ public void renameColumn_ShouldCallJdbcAdminProperly() throws ExecutionException verify(jdbcAdmin).renameColumn(namespace, table, columnName1, columnName2); } + @Test + public void alterColumnType_ShouldCallJdbcAdminProperly() throws ExecutionException { + // Arrange + String namespace = "ns"; + String table = "tbl"; + String columnName = "col"; + DataType columnType = DataType.BIGINT; + + // Act + admin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(jdbcAdmin).alterColumnType(namespace, table, columnName, columnType); + } + @Test public void importTable_ShouldCallJdbcAdminProperly() throws ExecutionException { // Arrange diff --git a/core/src/test/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminTest.java b/core/src/test/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminTest.java index c01ff28d4a..d2ada4d907 100644 --- a/core/src/test/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminTest.java +++ b/core/src/test/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminTest.java @@ -263,6 +263,22 @@ public void renameColumn_ShouldCallDistributedStorageAdminProperly() throws Exec verify(distributedStorageAdmin).renameColumn(namespace, table, columnName1, columnName2); } + @Test + public void alterColumnType_ShouldCallDistributedStorageAdminProperly() + throws ExecutionException { + // Arrange + String namespace = "ns"; + String table = "tbl"; + String columnName = "col"; + DataType columnType = DataType.BIGINT; + + // Act + admin.alterColumnType(namespace, table, columnName, columnType); + + // Assert + verify(distributedStorageAdmin).alterColumnType(namespace, table, columnName, columnType); + } + @Test public void importTable_ShouldCallDistributedStorageAdminProperly() throws ExecutionException { // Arrange diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java index a4b3ac9284..9b8c1b2abc 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java @@ -1071,6 +1071,130 @@ public void renameColumn_ShouldRenameColumnCorrectly() throws ExecutionException } } + @Test + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, getTable4(), currentTableMetadata, options); + + // Act + admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName4(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName5(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName6(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName7(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName8(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName9(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName10(), DataType.TEXT); + if (isTimestampTypeSupported()) { + admin.alterColumnType(namespace1, getTable4(), getColumnName11(), DataType.TEXT); + admin.alterColumnType(namespace1, getTable4(), getColumnName12(), DataType.TEXT); + } + + // Assert + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.TEXT) + .addColumn(getColumnName4(), DataType.TEXT) + .addColumn(getColumnName5(), DataType.TEXT) + .addColumn(getColumnName6(), DataType.TEXT) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.TEXT) + .addColumn(getColumnName9(), DataType.TEXT) + .addColumn(getColumnName10(), DataType.TEXT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TEXT) + .addColumn(getColumnName12(), DataType.TEXT); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, getTable4())).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, getTable4(), true); + } + } + + @Test + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.FLOAT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName5(), DataType.DATE) + .addColumn(getColumnName6(), DataType.DATE) + .addColumn(getColumnName7(), DataType.TIMESTAMP); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, getTable4(), currentTableMetadata, options); + + // Act + admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.BIGINT); + admin.alterColumnType(namespace1, getTable4(), getColumnName4(), DataType.DOUBLE); + if (isTimestampTypeSupported()) { + admin.alterColumnType(namespace1, getTable4(), getColumnName5(), DataType.TIMESTAMP); + admin.alterColumnType(namespace1, getTable4(), getColumnName6(), DataType.TIMESTAMPTZ); + admin.alterColumnType(namespace1, getTable4(), getColumnName7(), DataType.TIMESTAMPTZ); + } + + // Assert + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.BIGINT) + .addColumn(getColumnName4(), DataType.DOUBLE) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn(getColumnName5(), DataType.TIMESTAMP) + .addColumn(getColumnName6(), DataType.TIMESTAMPTZ) + .addColumn(getColumnName7(), DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, getTable4())).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, getTable4(), true); + } + } + @Test public void getNamespaceNames_ShouldReturnCreatedNamespaces() throws ExecutionException { // Arrange From f1a935de3cf73c1144ed5361e6b1e31456f8d7d2 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Thu, 2 Oct 2025 12:33:00 +0900 Subject: [PATCH 02/12] Fix --- .../jdbc/JdbcAdminIntegrationTest.java | 258 ++++++++++++++++++ .../com/scalar/db/storage/jdbc/JdbcEnv.java | 4 + .../common/CommonDistributedStorageAdmin.java | 27 +- .../java/com/scalar/db/common/CoreError.java | 6 + .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 24 +- .../scalar/db/storage/jdbc/RdbEngineDb2.java | 37 ++- .../db/storage/jdbc/RdbEngineMysql.java | 16 +- .../db/storage/jdbc/RdbEngineOracle.java | 23 +- .../db/storage/jdbc/RdbEnginePostgresql.java | 16 +- .../db/storage/jdbc/RdbEngineSqlServer.java | 16 +- .../db/storage/jdbc/RdbEngineSqlite.java | 2 +- .../db/storage/jdbc/RdbEngineStrategy.java | 14 +- .../com/scalar/db/util/ScalarDbUtils.java | 28 -- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 55 ++-- ...ibutedStorageAdminIntegrationTestBase.java | 100 +++++-- 15 files changed, 513 insertions(+), 113 deletions(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index 677ce2881d..852e38aec1 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -1,13 +1,32 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; +import com.scalar.db.api.DistributedStorage; import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; +import com.scalar.db.api.Put; +import com.scalar.db.api.PutBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; +import com.scalar.db.api.Scanner; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -37,11 +56,28 @@ protected boolean isCreateIndexOnTextColumnEnabled() { return !JdbcTestUtils.isDb2(rdbEngine); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + @SuppressWarnings("unused") private boolean isDb2() { return JdbcEnv.isDb2(); } + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcTestUtils.isDb2(rdbEngine) + || JdbcTestUtils.isOracle(rdbEngine) + || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -97,6 +133,228 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void + alterColumnType_Oracle_AlterColumnTypeFromEachExistingDataTypeToText_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + put.timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)); + put.timestampTZValue(getColumnName12(), Instant.now()); + } + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + admin.alterColumnType(getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @Override + @DisabledIf("isOracle") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.FLOAT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + DistributedStorage storage = storageFactory.getStorage(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), expectedColumn3Value) + .floatValue(getColumnName4(), expectedColumn4Value); + storage.put(put.build()); + storage.close(); + + // Act + admin.alterColumnType(getNamespace1(), getTable4(), getColumnName3(), DataType.BIGINT); + Throwable exception = + catchThrowable( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.DOUBLE)); + + // Assert + assertThat(exception).isInstanceOf(UnsupportedOperationException.class); + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.BIGINT) + .addColumn(getColumnName4(), DataType.FLOAT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); + storage = storageFactory.getStorage(); + Scan scan = + Scan.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .build(); + try (Scanner scanner = storage.scan(scan)) { + List results = scanner.all(); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + } + storage.close(); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @EnabledIf("isSqlite") + public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + + // Act Assert + assertThatThrownBy( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + @Override protected boolean isIndexOnBlobColumnSupported() { return !JdbcTestUtils.isDb2(rdbEngine); diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java index a977a2346c..c88398250b 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcEnv.java @@ -51,6 +51,10 @@ public static Properties getPropertiesForNormalUser(String testName) { return properties; } + public static boolean isOracle() { + return System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL).startsWith("jdbc:oracle:"); + } + public static boolean isSqlite() { return System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL).startsWith("jdbc:sqlite:"); } diff --git a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java index 885b7df95b..05f640a94f 100644 --- a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java @@ -383,7 +383,7 @@ public void alterColumnType( if (currentColumnType == newColumnType) { return; } - if (!ScalarDbUtils.isTypeConversionSupported(currentColumnType, newColumnType)) { + if (!isTypeConversionSupported(currentColumnType, newColumnType)) { throw new IllegalArgumentException( CoreError.INVALID_COLUMN_TYPE_CONVERSION.buildMessage( currentColumnType, newColumnType, columnName)); @@ -522,4 +522,29 @@ public StorageInfo getStorageInfo(String namespace) throws ExecutionException { public void close() { admin.close(); } + + private boolean isTypeConversionSupported(DataType from, DataType to) { + if (from == to) { + return true; + } + switch (from) { + case BOOLEAN: + case BIGINT: + case DOUBLE: + case BLOB: + case DATE: + case TIME: + case TIMESTAMP: + case TIMESTAMPTZ: + return to == DataType.TEXT; + case INT: + return to == DataType.BIGINT || to == DataType.TEXT; + case FLOAT: + return to == DataType.DOUBLE || to == DataType.TEXT; + case TEXT: + return false; + default: + throw new AssertionError("Unknown data type: " + from); + } + } } diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 1eafcd9be7..e49aa3e8db 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -790,6 +790,12 @@ public enum CoreError implements ScalarDbError { "SQLite does not support the altering column type feature", "", ""), + JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION( + Category.USER_ERROR, + "0237", + "The storage does not support column type conversion from %s to %s. Column: %s", + "", + ""), // // Errors for the concurrency error category diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java index 4a564311e2..48d97c7717 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java @@ -748,8 +748,10 @@ private void alterToIndexColumnTypeIfNecessary( return; } - String sql = rdbEngine.alterColumnTypeSql(namespace, table, columnName, columnTypeForKey); - execute(connection, sql); + String[] sqls = rdbEngine.alterColumnTypeSql(namespace, table, columnName, columnTypeForKey); + for (String sql : sqls) { + execute(connection, sql); + } } private void alterToRegularColumnTypeIfNecessary( @@ -764,8 +766,10 @@ private void alterToRegularColumnTypeIfNecessary( } String columnType = rdbEngine.getDataTypeForEngine(indexType); - String sql = rdbEngine.alterColumnTypeSql(namespace, table, columnName, columnType); - execute(connection, sql); + String[] sqls = rdbEngine.alterColumnTypeSql(namespace, table, columnName, columnType); + for (String sql : sqls) { + execute(connection, sql); + } } @Override @@ -938,6 +942,12 @@ public void alterColumnType( rdbEngine.throwIfAlterColumnTypeNotSupported(); TableMetadata currentTableMetadata = getTableMetadata(namespace, table); assert currentTableMetadata != null; + DataType currentColumnType = currentTableMetadata.getColumnDataType(columnName); + if (!rdbEngine.isTypeConversionSupportedInternal(currentColumnType, newColumnType)) { + throw new UnsupportedOperationException( + CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + currentColumnType, newColumnType, columnName)); + } TableMetadata updatedTableMetadata = TableMetadata.newBuilder(currentTableMetadata) @@ -945,11 +955,13 @@ public void alterColumnType( .addColumn(columnName, newColumnType) .build(); String newStorageColumnType = getVendorDbColumnType(updatedTableMetadata, columnName); - String alterColumnTypeStatement = + String[] alterColumnTypeStatements = rdbEngine.alterColumnTypeSql(namespace, table, columnName, newStorageColumnType); try (Connection connection = dataSource.getConnection()) { - execute(connection, alterColumnTypeStatement); + for (String alterColumnTypeStatement : alterColumnTypeStatements) { + execute(connection, alterColumnTypeStatement); + } addTableMetadata(connection, namespace, table, updatedTableMetadata, false, true); } } catch (SQLException e) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 895d204907..9df6c92537 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -269,14 +269,17 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - return "ALTER TABLE " - + encloseFullTableName(namespace, table) - + " ALTER COLUMN " - + enclose(columnName) - + " SET DATA TYPE " - + columnType; + return new String[] { + "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " ALTER COLUMN " + + enclose(columnName) + + " SET DATA TYPE " + + columnType, + "CALL SYSPROC.ADMIN_CMD('REORG TABLE " + encloseFullTableName(namespace, table) + "')" + }; } @Override @@ -571,4 +574,24 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( .buildMessage(orderingOnBlobColumn.get())); } } + + @Override + public boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + if (from == DataType.BLOB && to == DataType.TEXT) { + return false; + } + if (from == DataType.FLOAT && to == DataType.TEXT) { + return false; + } + if (from == DataType.DATE && to == DataType.TEXT) { + return false; + } + if (from == DataType.TIME && to == DataType.TEXT) { + return false; + } + if (from == DataType.TIMESTAMP && to == DataType.TEXT) { + return false; + } + return !(from == DataType.TIMESTAMPTZ && to == DataType.TEXT); + } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java index 4bc45c006f..fed20d8a81 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java @@ -140,14 +140,16 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - return "ALTER TABLE " - + encloseFullTableName(namespace, table) - + " MODIFY " - + enclose(columnName) - + " " - + columnType; + return new String[] { + "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " MODIFY " + + enclose(columnName) + + " " + + columnType + }; } @Override diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java index 44b8028eb5..1f871a49fd 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java @@ -136,15 +136,17 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - return "ALTER TABLE " - + encloseFullTableName(namespace, table) - + " MODIFY ( " - + enclose(columnName) - + " " - + columnType - + " )"; + return new String[] { + "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " MODIFY ( " + + enclose(columnName) + + " " + + columnType + + " )" + }; } @Override @@ -440,4 +442,9 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { getTimeTypeStrategy() { return timeTypeEngine; } + + @Override + public boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + return from == DataType.INT && to == DataType.BIGINT; + } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java index d42e44e466..c7339c8451 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java @@ -120,14 +120,16 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - return "ALTER TABLE " - + encloseFullTableName(namespace, table) - + " ALTER COLUMN " - + enclose(columnName) - + " TYPE " - + columnType; + return new String[] { + "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " ALTER COLUMN " + + enclose(columnName) + + " TYPE " + + columnType + }; } @Override diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java index c2a32961a6..9b489090e3 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java @@ -127,14 +127,16 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { - return "ALTER TABLE " - + encloseFullTableName(namespace, table) - + " ALTER COLUMN " - + enclose(columnName) - + " " - + columnType; + return new String[] { + "ALTER TABLE " + + encloseFullTableName(namespace, table) + + " ALTER COLUMN " + + enclose(columnName) + + " " + + columnType + }; } @Override diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java index ab35b7e8b8..5131a294fd 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java @@ -259,7 +259,7 @@ public String renameTableSql(String namespace, String oldTableName, String newTa } @Override - public String alterColumnTypeSql( + public String[] alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { throw new AssertionError( "SQLite does not require changes in column data types when making indices"); diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java index 4b86dc6453..78bc9271ac 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java @@ -124,7 +124,7 @@ default String renameColumnSql( String renameTableSql(String namespace, String oldTableName, String newTableName); - String alterColumnTypeSql(String namespace, String table, String columnName, String columnType); + String[] alterColumnTypeSql(String namespace, String table, String columnName, String columnType); String tableExistsInternalTableCheckSql(String fullTableName); @@ -303,4 +303,16 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) */ default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( ScanAll scanAll, TableMetadata metadata) {} + + /** + * Returns whether type conversion is supported between the two data types in the underlying + * storage. + * + * @param from the source data type + * @param to the target data type + * @return true if type conversion is supported, false otherwise + */ + default boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + return true; + } } diff --git a/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java b/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java index 453f06b885..70d5aa1303 100644 --- a/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java +++ b/core/src/main/java/com/scalar/db/util/ScalarDbUtils.java @@ -36,7 +36,6 @@ import com.scalar.db.io.BooleanColumn; import com.scalar.db.io.BooleanValue; import com.scalar.db.io.Column; -import com.scalar.db.io.DataType; import com.scalar.db.io.DoubleColumn; import com.scalar.db.io.DoubleValue; import com.scalar.db.io.FloatColumn; @@ -476,31 +475,4 @@ private static Optional getKey(Map> columns, LinkedHashSe } return Optional.of(builder.build()); } - - public static boolean isTypeConversionSupported(DataType from, DataType to) { - if (from == to) { - return true; - } - switch (from) { - case BOOLEAN: - case BIGINT: - case DOUBLE: - case BLOB: - case TIME: - case TIMESTAMPTZ: - return to == DataType.TEXT; - case INT: - return to == DataType.BIGINT || to == DataType.TEXT; - case FLOAT: - return to == DataType.DOUBLE || to == DataType.TEXT; - case DATE: - return to == DataType.TEXT || to == DataType.TIMESTAMP || to == DataType.TIMESTAMPTZ; - case TIMESTAMP: - return to == DataType.TEXT || to == DataType.TIMESTAMPTZ; - case TEXT: - return false; - default: - throw new AssertionError("Unknown data type: " + from); - } - } } diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index 0a668ad663..5ab9830964 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -2448,7 +2448,7 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", - "ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` VARCHAR(128)", + new String[] {"ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` VARCHAR(128)"}, "CREATE INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl` (`my_column`)", "UPDATE `" + METADATA_SCHEMA @@ -2464,7 +2464,9 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE VARCHAR(10485760)", + new String[] { + "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE VARCHAR(10485760)" + }, "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" + METADATA_SCHEMA @@ -2480,7 +2482,7 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", - "ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(128) )", + new String[] {"ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(128) )"}, "CREATE INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" + METADATA_SCHEMA @@ -2502,7 +2504,10 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" SET DATA TYPE VARCHAR(128)", + new String[] { + "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" SET DATA TYPE VARCHAR(128)", + "CALL SYSPROC.ADMIN_CMD('REORG TABLE \"my_ns\".\"my_tbl\"')" + }, "CREATE INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" + METADATA_SCHEMA @@ -2513,7 +2518,7 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate createIndex_forColumnTypeWithRequiredAlterationForX_ShouldAlterColumnAndCreateIndexProperly( RdbEngine rdbEngine, String expectedGetTableMetadataStatement, - String expectedAlterColumnTypeStatement, + String[] expectedAlterColumnTypeStatements, String expectedCreateIndexStatement, String expectedUpdateTableMetadataStatement) throws SQLException, ExecutionException { @@ -2545,12 +2550,17 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate verify(connection).prepareStatement(expectedGetTableMetadataStatement); verify(selectStatement).setString(1, getFullTableName(namespace, table)); - verify(connection, times(3)).createStatement(); + verify(connection, times(2 + expectedAlterColumnTypeStatements.length)).createStatement(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(statement, times(3)).execute(captor.capture()); - assertThat(captor.getAllValues().get(0)).isEqualTo(expectedAlterColumnTypeStatement); - assertThat(captor.getAllValues().get(1)).isEqualTo(expectedCreateIndexStatement); - assertThat(captor.getAllValues().get(2)).isEqualTo(expectedUpdateTableMetadataStatement); + verify(statement, times(2 + expectedAlterColumnTypeStatements.length)) + .execute(captor.capture()); + for (int i = 0; i < expectedAlterColumnTypeStatements.length; i++) { + assertThat(captor.getAllValues().get(i)).isEqualTo(expectedAlterColumnTypeStatements[i]); + } + assertThat(captor.getAllValues().get(expectedAlterColumnTypeStatements.length)) + .isEqualTo(expectedCreateIndexStatement); + assertThat(captor.getAllValues().get(expectedAlterColumnTypeStatements.length + 1)) + .isEqualTo(expectedUpdateTableMetadataStatement); } @Test @@ -2687,7 +2697,7 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForMysql_ShouldDropInde + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "DROP INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl`", - "ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` LONGTEXT", + new String[] {"ALTER TABLE `my_ns`.`my_tbl` MODIFY `my_column` LONGTEXT"}, "UPDATE `" + METADATA_SCHEMA + "`.`metadata` SET `indexed`=false WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); @@ -2702,7 +2712,7 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForPostgresql_ShouldDro + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE TEXT", + new String[] {"ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" TYPE TEXT"}, "UPDATE \"" + METADATA_SCHEMA + "\".\"metadata\" SET \"indexed\"=false WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); @@ -2717,7 +2727,7 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForOracle_ShouldDropInd + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", - "ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(4000) )", + new String[] {"ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(4000) )"}, "UPDATE \"" + METADATA_SCHEMA + "\".\"metadata\" SET \"indexed\"=0 WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); @@ -2737,7 +2747,10 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForDb2_ShouldDropIndexP + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", - "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" SET DATA TYPE VARCHAR(32672)", + new String[] { + "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN \"my_column\" SET DATA TYPE VARCHAR(32672)", + "CALL SYSPROC.ADMIN_CMD('REORG TABLE \"my_ns\".\"my_tbl\"')" + }, "UPDATE \"" + METADATA_SCHEMA + "\".\"metadata\" SET \"indexed\"=false WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); @@ -2747,7 +2760,7 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr RdbEngine rdbEngine, String expectedGetTableMetadataStatement, String expectedDropIndexStatement, - String expectedAlterColumnStatement, + String[] expectedAlterColumnStatements, String expectedUpdateTableMetadataStatement) throws SQLException, ExecutionException { // Arrange @@ -2777,12 +2790,15 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr verify(connection).prepareStatement(expectedGetTableMetadataStatement); verify(selectStatement).setString(1, getFullTableName(namespace, table)); - verify(connection, times(3)).createStatement(); + verify(connection, times(2 + expectedAlterColumnStatements.length)).createStatement(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(statement, times(3)).execute(captor.capture()); + verify(statement, times(2 + expectedAlterColumnStatements.length)).execute(captor.capture()); assertThat(captor.getAllValues().get(0)).isEqualTo(expectedDropIndexStatement); - assertThat(captor.getAllValues().get(1)).isEqualTo(expectedAlterColumnStatement); - assertThat(captor.getAllValues().get(2)).isEqualTo(expectedUpdateTableMetadataStatement); + for (int i = 0; i < expectedAlterColumnStatements.length; i++) { + assertThat(captor.getAllValues().get(i + 1)).isEqualTo(expectedAlterColumnStatements[i]); + } + assertThat(captor.getAllValues().get(expectedAlterColumnStatements.length + 1)) + .isEqualTo(expectedUpdateTableMetadataStatement); } @Test @@ -3326,6 +3342,7 @@ public void alterColumnType_ForDb2_ShouldWorkProperly() throws SQLException, Exe + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"ns\".\"table\" ALTER COLUMN \"c2\" SET DATA TYPE BIGINT", + "CALL SYSPROC.ADMIN_CMD('REORG TABLE \"ns\".\"table\"')", "DELETE FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java index f824146b70..08e4673e12 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java @@ -12,14 +12,17 @@ import com.scalar.db.util.AdminTestUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -58,7 +61,7 @@ public abstract class DistributedStorageAdminIntegrationTestBase { private static final String COL_NAME13 = "c13"; private static final String COL_NAME14 = "c14"; private static final String COL_NAME15 = "c15"; - private StorageFactory storageFactory; + protected StorageFactory storageFactory; protected DistributedStorageAdmin admin; private String namespace1; private String namespace2; @@ -1113,8 +1116,8 @@ public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws Exe @Test public void alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() - throws ExecutionException { - try { + throws ExecutionException, IOException { + try (DistributedStorage storage = storageFactory.getStorage()) { // Arrange Map options = getCreationOptions(); TableMetadata.Builder currentTableMetadataBuilder = @@ -1138,6 +1141,26 @@ public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws Exe } TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(namespace1, getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(namespace1) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + put.timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)); + put.timestampTZValue(getColumnName12(), Instant.now()); + } + storage.put(put.build()); + storage.close(); // Act admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.TEXT); @@ -1182,7 +1205,7 @@ public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws Exe @Test public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() - throws ExecutionException { + throws ExecutionException, IOException { try { // Arrange Map options = getCreationOptions(); @@ -1194,23 +1217,26 @@ public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() .addColumn(getColumnName4(), DataType.FLOAT) .addPartitionKey(getColumnName1()) .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); - if (isTimestampTypeSupported()) { - currentTableMetadataBuilder - .addColumn(getColumnName5(), DataType.DATE) - .addColumn(getColumnName6(), DataType.DATE) - .addColumn(getColumnName7(), DataType.TIMESTAMP); - } TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(namespace1, getTable4(), currentTableMetadata, options); + DistributedStorage storage = storageFactory.getStorage(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(namespace1) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), expectedColumn3Value) + .floatValue(getColumnName4(), expectedColumn4Value); + storage.put(put.build()); + storage.close(); // Act admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.BIGINT); admin.alterColumnType(namespace1, getTable4(), getColumnName4(), DataType.DOUBLE); - if (isTimestampTypeSupported()) { - admin.alterColumnType(namespace1, getTable4(), getColumnName5(), DataType.TIMESTAMP); - admin.alterColumnType(namespace1, getTable4(), getColumnName6(), DataType.TIMESTAMPTZ); - admin.alterColumnType(namespace1, getTable4(), getColumnName7(), DataType.TIMESTAMPTZ); - } // Assert TableMetadata.Builder expectedTableMetadataBuilder = @@ -1221,14 +1247,23 @@ public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() .addColumn(getColumnName4(), DataType.DOUBLE) .addPartitionKey(getColumnName1()) .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); - if (isTimestampTypeSupported()) { - expectedTableMetadataBuilder - .addColumn(getColumnName5(), DataType.TIMESTAMP) - .addColumn(getColumnName6(), DataType.TIMESTAMPTZ) - .addColumn(getColumnName7(), DataType.TIMESTAMPTZ); - } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, getTable4())).isEqualTo(expectedTableMetadata); + storage = storageFactory.getStorage(); + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .build(); + try (Scanner scanner = storage.scan(scan)) { + List results = scanner.all(); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + assertThat(result.getDouble(getColumnName4())).isEqualTo(expectedColumn4Value); + } + storage.close(); } finally { admin.dropTable(namespace1, getTable4(), true); } @@ -1533,6 +1568,29 @@ public void renameTable_ForExistingTableWithIndexes_ShouldRenameTableAndIndexesC } } + @Test + public void alterColumnType_ForNonExistingTable_ShouldThrowIllegalArgumentException() { + // Arrange + + // Act Assert + assertThatThrownBy( + () -> + admin.alterColumnType( + namespace1, "nonExistingTable", getColumnName2(), DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void alterColumnType_ForNonExistingColumn_ShouldThrowIllegalArgumentException() { + // Arrange + + // Act Assert + assertThatThrownBy( + () -> + admin.alterColumnType(namespace1, getTable1(), "nonExistingColumn", DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + } + @Test public void upgrade_WhenMetadataTableExistsButNotNamespacesTable_ShouldCreateNamespacesTableAndImportExistingNamespaces() From 3f2e8751738cfd4814d6bd8ce0cc294c5883aff5 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Thu, 2 Oct 2025 14:50:59 +0900 Subject: [PATCH 03/12] Apply suggestions --- core/src/main/java/com/scalar/db/common/CoreError.java | 4 ++-- .../db/api/DistributedStorageAdminIntegrationTestBase.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index e49aa3e8db..daf249315e 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -786,13 +786,13 @@ public enum CoreError implements ScalarDbError { ""), JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, - "0236", + "0237", "SQLite does not support the altering column type feature", "", ""), JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION( Category.USER_ERROR, - "0237", + "0238", "The storage does not support column type conversion from %s to %s. Column: %s", "", ""), diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java index 08e4673e12..5026174d75 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java @@ -1160,7 +1160,6 @@ public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws Exe put.timestampTZValue(getColumnName12(), Instant.now()); } storage.put(put.build()); - storage.close(); // Act admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.TEXT); From 64fdf4f0fc29d6cce34313b3cc2ef90b63bdecce Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Thu, 2 Oct 2025 16:59:56 +0900 Subject: [PATCH 04/12] Update integration tests --- ...raAdminCaseSensitivityIntegrationTest.java | 9 + .../CassandraAdminIntegrationTest.java | 9 + ...ssandraAdminPermissionIntegrationTest.java | 5 + ...mmitAdminIntegrationTestWithCassandra.java | 9 + ...tionAdminIntegrationTestWithCassandra.java | 9 + ...sCommitAdminIntegrationTestWithCosmos.java | 9 + ...osAdminCaseSensitivityIntegrationTest.java | 9 + .../cosmos/CosmosAdminIntegrationTest.java | 9 + ...sactionAdminIntegrationTestWithCosmos.java | 9 + ...sCommitAdminIntegrationTestWithDynamo.java | 9 + ...moAdminCaseSensitivityIntegrationTest.java | 9 + .../dynamo/DynamoAdminIntegrationTest.java | 9 + .../DynamoAdminPermissionIntegrationTest.java | 5 + ...sactionAdminIntegrationTestWithDynamo.java | 9 + ...tAdminIntegrationTestWithJdbcDatabase.java | 327 +++++++++++++++ ...bcAdminCaseSensitivityIntegrationTest.java | 397 ++++++++++++++++++ .../jdbc/JdbcAdminIntegrationTest.java | 165 +++++++- .../JdbcAdminPermissionIntegrationTest.java | 15 + ...nAdminIntegrationTestWithJdbcDatabase.java | 327 +++++++++++++++ .../JdbcTransactionAdminIntegrationTest.java | 344 +++++++++++++++ ...ageAdminPermissionIntegrationTestBase.java | 12 + ...edTransactionAdminIntegrationTestBase.java | 163 +++++++ ...nsensusCommitAdminIntegrationTestBase.java | 24 ++ ...onTransactionAdminIntegrationTestBase.java | 18 + 24 files changed, 1897 insertions(+), 13 deletions(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java index 0d3e242592..37dc54a29a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java @@ -77,6 +77,15 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() } } + @Override + @Disabled("Cassandra does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java index 1b7b8b93a2..083109ef5a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java @@ -76,6 +76,15 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() } } + @Override + @Disabled("Cassandra does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java index b6c1f3960e..46ab9e6108 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminPermissionIntegrationTest.java @@ -154,6 +154,11 @@ public void renameColumn_WithSufficientPermission_ShouldSucceed() throws Executi .doesNotThrowAnyException(); } + @Test + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_WithSufficientPermission_ShouldSucceed() {} + @Test @Override @Disabled("Cassandra does not support renaming tables") diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java index 46f5571e32..4b227f6e5c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java @@ -76,6 +76,15 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() } } + @Override + @Disabled("Cassandra does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java index 6099945a44..1c6dec27c6 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java @@ -71,6 +71,15 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() } } + @Override + @Disabled("Cassandra does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java index eff9d9ea7d..b011f6b813 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java @@ -68,6 +68,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("Cosmos DB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java index ec8559fb0a..8a70897d5a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java @@ -68,6 +68,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("Cosmos DB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java index b16c14e2b6..342161a5fb 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java @@ -67,6 +67,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("Cosmos DB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java index ede5138583..693dc1fcfc 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java @@ -62,6 +62,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("Cosmos DB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java index 732ac6d315..9be4c48043 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java @@ -73,6 +73,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("DynamoDB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java index 75d528738b..e464d584d0 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java @@ -73,6 +73,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("DynamoDB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java index e03fd1dd8f..a6daecb0dc 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java @@ -72,6 +72,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("DynamoDB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java index b99fadca3a..c79c8afdb5 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java @@ -70,6 +70,11 @@ public void dropColumnFromTable_WithSufficientPermission_ShouldSucceed() {} @Disabled("DynamoDB does not support renaming columns") public void renameColumn_WithSufficientPermission_ShouldSucceed() {} + @Test + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_WithSufficientPermission_ShouldSucceed() {} + @Test @Override @Disabled("DynamoDB does not support renaming tables") diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java index c2c2a3294d..623ccf850c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java @@ -67,6 +67,15 @@ public void renameColumn_ForPrimaryKeyColumn_ShouldRenameColumnCorrectly() {} @Disabled("DynamoDB does not support renaming columns") public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() {} + + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java index 1975fd8f60..bcc4fa703f 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java @@ -1,13 +1,31 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.catchThrowable; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.api.Insert; +import com.scalar.db.api.InsertBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.exception.transaction.TransactionException; import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminIntegrationTestBase; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -39,11 +57,33 @@ protected boolean isCreateIndexOnTextColumnEnabled() { return !JdbcTestUtils.isDb2(rdbEngine); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + @SuppressWarnings("unused") private boolean isDb2() { return JdbcEnv.isDb2(); } + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcTestUtils.isDb2(rdbEngine) + || JdbcTestUtils.isOracle(rdbEngine) + || JdbcTestUtils.isSqlite(rdbEngine); + } + + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -90,6 +130,293 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void + alterColumnType_Oracle_AlterColumnTypeFromEachExistingDataTypeToText_ShouldThrowUnsupportedOperationException() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @Override + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(transactionManager, insert.build()); + transactionManager.close(); + + // Act + admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); + Throwable exception = + catchThrowable(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.DOUBLE)); + + // Assert + assertThat(exception).isInstanceOf(UnsupportedOperationException.class); + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.BIGINT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + transactionManager = transactionFactory.getTransactionManager(); + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(transactionManager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + transactionManager.close(); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isSqlite") + public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Override protected boolean isIndexOnBlobColumnSupported() { return !JdbcTestUtils.isDb2(rdbEngine); diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java index 9349a58ede..37bca3b8ef 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java @@ -1,13 +1,31 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.catchThrowable; +import com.scalar.db.api.DistributedStorage; import com.scalar.db.api.DistributedStorageAdminCaseSensitivityIntegrationTestBase; +import com.scalar.db.api.Put; +import com.scalar.db.api.PutBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; +import com.scalar.db.api.Scanner; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -38,11 +56,33 @@ protected boolean isCreateIndexOnTextColumnEnabled() { return !JdbcTestUtils.isDb2(rdbEngine); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + @SuppressWarnings("unused") private boolean isDb2() { return JdbcEnv.isDb2(); } + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcTestUtils.isDb2(rdbEngine) + || JdbcTestUtils.isOracle(rdbEngine) + || JdbcTestUtils.isSqlite(rdbEngine); + } + + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -98,6 +138,363 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void + alterColumnType_Oracle_AlterColumnTypeFromEachExistingDataTypeToText_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + put.timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)); + put.timestampTZValue(getColumnName12(), Instant.now()); + } + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + put.timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)); + put.timestampTZValue(getColumnName12(), Instant.now()); + } + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.TEXT) + .addColumn(getColumnName4(), DataType.TEXT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.TEXT) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @Override + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.FLOAT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + DistributedStorage storage = storageFactory.getStorage(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), expectedColumn3Value) + .floatValue(getColumnName4(), expectedColumn4Value); + storage.put(put.build()); + storage.close(); + + // Act + admin.alterColumnType(getNamespace1(), getTable4(), getColumnName3(), DataType.BIGINT); + Throwable exception = + catchThrowable( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.DOUBLE)); + + // Assert + assertThat(exception).isInstanceOf(UnsupportedOperationException.class); + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.BIGINT) + .addColumn(getColumnName4(), DataType.FLOAT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); + storage = storageFactory.getStorage(); + Scan scan = + Scan.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .build(); + try (Scanner scanner = storage.scan(scan)) { + List results = scanner.all(); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + } + storage.close(); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @EnabledIf("isSqlite") + public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + @Override protected boolean isIndexOnBlobColumnSupported() { return !JdbcTestUtils.isDb2(rdbEngine); diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index 852e38aec1..55d8f2f907 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; import com.scalar.db.api.DistributedStorage; @@ -78,6 +77,11 @@ private boolean isColumnTypeConversionToTextNotFullySupported() { || JdbcTestUtils.isSqlite(rdbEngine); } + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -194,54 +198,189 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp storage.close(); // Act Assert - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - admin.alterColumnType(getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT); - assertThatThrownBy( + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + put.timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)); + put.timestampTZValue(getColumnName12(), Instant.now()); + } + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); if (isTimestampTypeSupported()) { - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); } + + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.TEXT) + .addColumn(getColumnName4(), DataType.TEXT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.TEXT) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); } finally { admin.dropTable(getNamespace1(), getTable4(), true); } @@ -249,7 +388,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Test @Override - @DisabledIf("isOracle") + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() throws ExecutionException, IOException { super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); @@ -345,7 +484,7 @@ public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperati admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); // Act Assert - assertThatThrownBy( + assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java index a57e2f8dfa..a75eb3d5ac 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminPermissionIntegrationTest.java @@ -5,10 +5,13 @@ import com.google.common.util.concurrent.Uninterruptibles; import com.scalar.db.api.DistributedStorageAdminPermissionIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.util.AdminTestUtils; import com.scalar.db.util.PermissionTestUtils; import java.util.Properties; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; public class JdbcAdminPermissionIntegrationTest extends DistributedStorageAdminPermissionIntegrationTestBase { @@ -68,4 +71,16 @@ private void waitForDdlCompletion() { Uninterruptibles.sleepUninterruptibly(DDL_WAIT_SECONDS, TimeUnit.SECONDS); } } + + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @Test + @Override + @DisabledIf("isSqlite") + public void alterColumnType_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + super.alterColumnType_WithSufficientPermission_ShouldSucceed(); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java index da5833d5fa..b941cb8293 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java @@ -1,13 +1,31 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.catchThrowable; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.api.Insert; +import com.scalar.db.api.InsertBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.exception.transaction.TransactionException; import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; import com.scalar.db.transaction.singlecrudoperation.SingleCrudOperationTransactionAdminIntegrationTestBase; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -39,11 +57,33 @@ protected boolean isCreateIndexOnTextColumnEnabled() { return !JdbcTestUtils.isDb2(rdbEngine); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + @SuppressWarnings("unused") private boolean isDb2() { return JdbcEnv.isDb2(); } + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcTestUtils.isDb2(rdbEngine) + || JdbcTestUtils.isOracle(rdbEngine) + || JdbcTestUtils.isSqlite(rdbEngine); + } + + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -90,6 +130,293 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void + alterColumnType_Oracle_AlterColumnTypeFromEachExistingDataTypeToText_ShouldThrowUnsupportedOperationException() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionManager.insert(insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @Override + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(transactionManager, insert.build()); + transactionManager.close(); + + // Act + admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); + Throwable exception = + catchThrowable(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.DOUBLE)); + + // Assert + assertThat(exception).isInstanceOf(UnsupportedOperationException.class); + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.BIGINT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + transactionManager = transactionFactory.getTransactionManager(); + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(transactionManager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + transactionManager.close(); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isSqlite") + public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Override protected boolean isIndexOnBlobColumnSupported() { return !JdbcTestUtils.isDb2(rdbEngine); diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java index 7dd9a519b9..bd10a376f5 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java @@ -1,18 +1,37 @@ package com.scalar.db.transaction.jdbc; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.catchThrowable; +import com.scalar.db.api.DistributedTransaction; import com.scalar.db.api.DistributedTransactionAdminIntegrationTestBase; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.api.Insert; +import com.scalar.db.api.InsertBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.exception.transaction.TransactionException; import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; import com.scalar.db.storage.jdbc.JdbcConfig; import com.scalar.db.storage.jdbc.JdbcEnv; import com.scalar.db.storage.jdbc.JdbcTestUtils; import com.scalar.db.storage.jdbc.RdbEngineFactory; import com.scalar.db.storage.jdbc.RdbEngineStrategy; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.List; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Disabled; @@ -110,11 +129,33 @@ protected boolean isCreateIndexOnTextColumnEnabled() { return !JdbcTestUtils.isDb2(rdbEngine); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + @SuppressWarnings("unused") private boolean isDb2() { return JdbcEnv.isDb2(); } + @SuppressWarnings("unused") + private boolean isSqlite() { + return JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcTestUtils.isDb2(rdbEngine) + || JdbcTestUtils.isOracle(rdbEngine) + || JdbcTestUtils.isSqlite(rdbEngine); + } + + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); + } + @Test @Override @DisabledIf("isDb2") @@ -161,8 +202,311 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void + alterColumnType_Oracle_AlterColumnTypeFromEachExistingDataTypeToText_ShouldThrowUnsupportedOperationException() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + if (isTimestampTypeSupported()) { + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } + + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @Override + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + super.alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(transactionManager, insert.build()); + transactionManager.close(); + + // Act + admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); + Throwable exception = + catchThrowable(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.DOUBLE)); + + // Assert + assertThat(exception).isInstanceOf(UnsupportedOperationException.class); + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.BIGINT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + transactionManager = transactionFactory.getTransactionManager(); + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(transactionManager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + transactionManager.close(); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + @EnabledIf("isSqlite") + public void alterColumnType_Sqlite_AlterColumnType_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Override protected boolean isIndexOnBlobColumnSupported() { return !JdbcTestUtils.isDb2(rdbEngine); } + + @Override + protected void transactionalInsert(DistributedTransactionManager manager, Insert insert) + throws TransactionException { + DistributedTransaction transaction = manager.start(); + transaction.insert(insert); + transaction.commit(); + } + + @Override + protected List transactionalScan(DistributedTransactionManager manager, Scan scan) + throws TransactionException { + DistributedTransaction transaction = manager.start(); + List results = transaction.scan(scan); + transaction.commit(); + return results; + } } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java index 97cc3087e2..c2b145f4c2 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminPermissionIntegrationTestBase.java @@ -348,6 +348,18 @@ public void renameColumn_WithSufficientPermission_ShouldSucceed() throws Executi .doesNotThrowAnyException(); } + @Test + public void alterColumnType_WithSufficientPermission_ShouldSucceed() throws ExecutionException { + // Arrange + createNamespaceByRoot(); + createTableByRoot(); + + // Act Assert + assertThatCode( + () -> adminForNormalUser.alterColumnType(NAMESPACE, TABLE, COL_NAME3, DataType.BIGINT)) + .doesNotThrowAnyException(); + } + @Test public void renameTable_WithSufficientPermission_ShouldSucceed() throws ExecutionException { // Arrange diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java index 11cb1f5689..2ed600dcde 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java @@ -11,10 +11,13 @@ import com.scalar.db.io.Key; import com.scalar.db.service.TransactionFactory; import com.scalar.db.util.AdminTestUtils; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Arrays; import java.util.Collections; @@ -1172,6 +1175,160 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() } } + @Test + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + try (DistributedTransactionManager transactionManager = + transactionFactory.getTransactionManager()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + currentTableMetadataBuilder + .addColumn("c11", DataType.TIMESTAMP) + .addColumn("c12", DataType.TIMESTAMPTZ); + } + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))); + if (isTimestampTypeSupported()) { + insert.timestampValue("c11", LocalDateTime.now(ZoneOffset.UTC)); + insert.timestampTZValue("c12", Instant.now()); + } + transactionalInsert(transactionManager, insert.build()); + + // Act + admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT); + if (isTimestampTypeSupported()) { + admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT); + admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT); + } + + // Assert + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.TEXT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.TEXT) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + if (isTimestampTypeSupported()) { + expectedTableMetadataBuilder + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT); + } + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + + @Test + public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() + throws ExecutionException, IOException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.FLOAT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; + float expectedColumn4Value = 4.0f; + + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(transactionManager, insert.build()); + transactionManager.close(); + + // Act + admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); + admin.alterColumnType(namespace1, TABLE4, "c4", DataType.DOUBLE); + + // Assert + TableMetadata.Builder expectedTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.BIGINT) + .addColumn("c4", DataType.DOUBLE) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + transactionManager = transactionFactory.getTransactionManager(); + List results = transactionalScan(transactionManager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + assertThat(result.getDouble("c4")).isEqualTo(expectedColumn4Value); + transactionManager.close(); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Test public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws ExecutionException { String newTableName = "new" + TABLE4; @@ -1395,4 +1552,10 @@ protected boolean isTimestampTypeSupported() { protected boolean isCreateIndexOnTextColumnEnabled() { return true; } + + protected abstract void transactionalInsert(DistributedTransactionManager manager, Insert insert) + throws TransactionException; + + protected abstract List transactionalScan( + DistributedTransactionManager manager, Scan scan) throws TransactionException; } diff --git a/integration-test/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminIntegrationTestBase.java index 4b535150dc..133141e476 100644 --- a/integration-test/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminIntegrationTestBase.java @@ -1,6 +1,13 @@ package com.scalar.db.transaction.consensuscommit; +import com.scalar.db.api.DistributedTransaction; import com.scalar.db.api.DistributedTransactionAdminIntegrationTestBase; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.api.Insert; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; +import com.scalar.db.exception.transaction.TransactionException; +import java.util.List; import java.util.Properties; public abstract class ConsensusCommitAdminIntegrationTestBase @@ -23,4 +30,21 @@ protected final Properties getProperties(String testName) { } protected abstract Properties getProps(String testName); + + @Override + protected void transactionalInsert(DistributedTransactionManager manager, Insert insert) + throws TransactionException { + DistributedTransaction transaction = manager.start(); + transaction.insert(insert); + transaction.commit(); + } + + @Override + protected List transactionalScan(DistributedTransactionManager manager, Scan scan) + throws TransactionException { + DistributedTransaction transaction = manager.start(); + List results = transaction.scan(scan); + transaction.commit(); + return results; + } } diff --git a/integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminIntegrationTestBase.java index d2d26b9da2..80e3c2f66b 100644 --- a/integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionAdminIntegrationTestBase.java @@ -1,9 +1,15 @@ package com.scalar.db.transaction.singlecrudoperation; import com.scalar.db.api.DistributedTransactionAdminIntegrationTestBase; +import com.scalar.db.api.DistributedTransactionManager; +import com.scalar.db.api.Insert; +import com.scalar.db.api.Result; +import com.scalar.db.api.Scan; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.exception.transaction.TransactionException; import com.scalar.db.util.AdminTestUtils; +import java.util.List; import java.util.Properties; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -91,4 +97,16 @@ public void dropCoordinatorTables_IfExist_CoordinatorTablesDoNotExist_ShouldNotT protected AdminTestUtils getAdminTestUtils(String testName) { throw new UnsupportedOperationException(); } + + @Override + protected void transactionalInsert(DistributedTransactionManager manager, Insert insert) + throws TransactionException { + manager.insert(insert); + } + + @Override + protected List transactionalScan(DistributedTransactionManager manager, Scan scan) + throws TransactionException { + return manager.scan(scan); + } } From e8c8ea65d9f798e229cf7cf34ccf46228b5cd4c5 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Fri, 3 Oct 2025 10:42:07 +0900 Subject: [PATCH 05/12] Apply suggestions --- ...tAdminIntegrationTestWithJdbcDatabase.java | 49 +++++++++--------- ...bcAdminCaseSensitivityIntegrationTest.java | 51 ++++++++++--------- .../jdbc/JdbcAdminIntegrationTest.java | 51 ++++++++++--------- ...nAdminIntegrationTestWithJdbcDatabase.java | 49 +++++++++--------- .../JdbcTransactionAdminIntegrationTest.java | 48 ++++++++--------- 5 files changed, 127 insertions(+), 121 deletions(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java index bcc4fa703f..4beba430c4 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java @@ -343,20 +343,20 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(namespace1, TABLE4, currentTableMetadata, options); - DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; float expectedColumn4Value = 4.0f; - - InsertBuilder.Buildable insert = - Insert.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .clusteringKey(Key.ofInt("c2", 2)) - .intValue("c3", expectedColumn3Value) - .floatValue("c4", expectedColumn4Value); - transactionalInsert(transactionManager, insert.build()); - transactionManager.close(); + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(manager, insert.build()); + } // Act admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); @@ -375,18 +375,19 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); - transactionManager = transactionFactory.getTransactionManager(); - Scan scan = - Scan.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .build(); - List results = transactionalScan(transactionManager, scan); - assertThat(results).hasSize(1); - Result result = results.get(0); - assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); - transactionManager.close(); + + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(manager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + } } finally { admin.dropTable(namespace1, TABLE4, true); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java index 37bca3b8ef..42b3d9c02a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java @@ -412,20 +412,20 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); - DistributedStorage storage = storageFactory.getStorage(); + int expectedColumn3Value = 1; float expectedColumn4Value = 4.0f; - - PutBuilder.Buildable put = - Put.newBuilder() - .namespace(getNamespace1()) - .table(getTable4()) - .partitionKey(Key.ofInt(getColumnName1(), 1)) - .clusteringKey(Key.ofInt(getColumnName2(), 2)) - .intValue(getColumnName3(), expectedColumn3Value) - .floatValue(getColumnName4(), expectedColumn4Value); - storage.put(put.build()); - storage.close(); + try (DistributedStorage storage = storageFactory.getStorage()) { + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), expectedColumn3Value) + .floatValue(getColumnName4(), expectedColumn4Value); + storage.put(put.build()); + } // Act admin.alterColumnType(getNamespace1(), getTable4(), getColumnName3(), DataType.BIGINT); @@ -448,20 +448,21 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) .isEqualTo(expectedTableMetadata); - storage = storageFactory.getStorage(); - Scan scan = - Scan.newBuilder() - .namespace(getNamespace1()) - .table(getTable4()) - .partitionKey(Key.ofInt(getColumnName1(), 1)) - .build(); - try (Scanner scanner = storage.scan(scan)) { - List results = scanner.all(); - assertThat(results).hasSize(1); - Result result = results.get(0); - assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + + try (DistributedStorage storage = storageFactory.getStorage()) { + Scan scan = + Scan.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .build(); + try (Scanner scanner = storage.scan(scan)) { + List results = scanner.all(); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + } } - storage.close(); } finally { admin.dropTable(getNamespace1(), getTable4(), true); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index 55d8f2f907..084e29a56f 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -411,20 +411,20 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); - DistributedStorage storage = storageFactory.getStorage(); + int expectedColumn3Value = 1; float expectedColumn4Value = 4.0f; - - PutBuilder.Buildable put = - Put.newBuilder() - .namespace(getNamespace1()) - .table(getTable4()) - .partitionKey(Key.ofInt(getColumnName1(), 1)) - .clusteringKey(Key.ofInt(getColumnName2(), 2)) - .intValue(getColumnName3(), expectedColumn3Value) - .floatValue(getColumnName4(), expectedColumn4Value); - storage.put(put.build()); - storage.close(); + try (DistributedStorage storage = storageFactory.getStorage()) { + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), expectedColumn3Value) + .floatValue(getColumnName4(), expectedColumn4Value); + storage.put(put.build()); + } // Act admin.alterColumnType(getNamespace1(), getTable4(), getColumnName3(), DataType.BIGINT); @@ -447,20 +447,21 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) .isEqualTo(expectedTableMetadata); - storage = storageFactory.getStorage(); - Scan scan = - Scan.newBuilder() - .namespace(getNamespace1()) - .table(getTable4()) - .partitionKey(Key.ofInt(getColumnName1(), 1)) - .build(); - try (Scanner scanner = storage.scan(scan)) { - List results = scanner.all(); - assertThat(results).hasSize(1); - Result result = results.get(0); - assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + + try (DistributedStorage storage = storageFactory.getStorage()) { + Scan scan = + Scan.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .build(); + try (Scanner scanner = storage.scan(scan)) { + List results = scanner.all(); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt(getColumnName3())).isEqualTo(expectedColumn3Value); + } } - storage.close(); } finally { admin.dropTable(getNamespace1(), getTable4(), true); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java index b941cb8293..b175e69a6d 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java @@ -343,20 +343,20 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(namespace1, TABLE4, currentTableMetadata, options); - DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; float expectedColumn4Value = 4.0f; - - InsertBuilder.Buildable insert = - Insert.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .clusteringKey(Key.ofInt("c2", 2)) - .intValue("c3", expectedColumn3Value) - .floatValue("c4", expectedColumn4Value); - transactionalInsert(transactionManager, insert.build()); - transactionManager.close(); + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(manager, insert.build()); + } // Act admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); @@ -375,18 +375,19 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); - transactionManager = transactionFactory.getTransactionManager(); - Scan scan = - Scan.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .build(); - List results = transactionalScan(transactionManager, scan); - assertThat(results).hasSize(1); - Result result = results.get(0); - assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); - transactionManager.close(); + + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(manager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + } } finally { admin.dropTable(namespace1, TABLE4, true); } diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java index bd10a376f5..0162702d48 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java @@ -415,19 +415,20 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); admin.createTable(namespace1, TABLE4, currentTableMetadata, options); - DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); + int expectedColumn3Value = 1; float expectedColumn4Value = 4.0f; - InsertBuilder.Buildable insert = - Insert.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .clusteringKey(Key.ofInt("c2", 2)) - .intValue("c3", expectedColumn3Value) - .floatValue("c4", expectedColumn4Value); - transactionalInsert(transactionManager, insert.build()); - transactionManager.close(); + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", expectedColumn3Value) + .floatValue("c4", expectedColumn4Value); + transactionalInsert(manager, insert.build()); + } // Act admin.alterColumnType(namespace1, TABLE4, "c3", DataType.BIGINT); @@ -446,18 +447,19 @@ public void alterColumnType_Oracle_WideningConversion_ShouldAlterColumnTypesCorr .addClusteringKey("c2", Scan.Ordering.Order.ASC); TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); - transactionManager = transactionFactory.getTransactionManager(); - Scan scan = - Scan.newBuilder() - .namespace(namespace1) - .table(TABLE4) - .partitionKey(Key.ofInt("c1", 1)) - .build(); - List results = transactionalScan(transactionManager, scan); - assertThat(results).hasSize(1); - Result result = results.get(0); - assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); - transactionManager.close(); + + try (DistributedTransactionManager manager = transactionFactory.getTransactionManager()) { + Scan scan = + Scan.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .build(); + List results = transactionalScan(manager, scan); + assertThat(results).hasSize(1); + Result result = results.get(0); + assertThat(result.getBigInt("c3")).isEqualTo(expectedColumn3Value); + } } finally { admin.dropTable(namespace1, TABLE4, true); } From 31c4876e92e86e0a3e27e3d2ef1d3cf0660e4d78 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Fri, 3 Oct 2025 13:56:14 +0900 Subject: [PATCH 06/12] Make primary or index key column type conversion not supported --- ...raAdminCaseSensitivityIntegrationTest.java | 4 ++ .../CassandraAdminIntegrationTest.java | 4 ++ ...mmitAdminIntegrationTestWithCassandra.java | 4 ++ ...tionAdminIntegrationTestWithCassandra.java | 4 ++ ...sCommitAdminIntegrationTestWithCosmos.java | 4 ++ ...osAdminCaseSensitivityIntegrationTest.java | 4 ++ .../cosmos/CosmosAdminIntegrationTest.java | 4 ++ ...sactionAdminIntegrationTestWithCosmos.java | 4 ++ ...sCommitAdminIntegrationTestWithDynamo.java | 4 ++ ...moAdminCaseSensitivityIntegrationTest.java | 4 ++ .../dynamo/DynamoAdminIntegrationTest.java | 4 ++ ...sactionAdminIntegrationTestWithDynamo.java | 4 ++ .../main/java/com/scalar/db/api/Admin.java | 3 +- .../common/CommonDistributedStorageAdmin.java | 8 ++++ .../java/com/scalar/db/common/CoreError.java | 18 ++++++--- ...ibutedStorageAdminIntegrationTestBase.java | 32 ++++++++++++++++ ...edTransactionAdminIntegrationTestBase.java | 37 ++++++++++++++++--- 17 files changed, 134 insertions(+), 12 deletions(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java index 37dc54a29a..75e3fe3d1d 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java @@ -86,6 +86,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cassandra does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java index 083109ef5a..7dc8c37482 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java @@ -85,6 +85,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cassandra does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java index 4b227f6e5c..ec839164ce 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/ConsensusCommitAdminIntegrationTestWithCassandra.java @@ -85,6 +85,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cassandra does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java index 1c6dec27c6..95b484b38d 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/SingleCrudOperationTransactionAdminIntegrationTestWithCassandra.java @@ -80,6 +80,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cassandra does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cassandra does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cassandra does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java index b011f6b813..864253b4fc 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/ConsensusCommitAdminIntegrationTestWithCosmos.java @@ -77,6 +77,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cosmos DB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java index 8a70897d5a..2bedab906f 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java @@ -77,6 +77,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cosmos DB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java index 342161a5fb..e7f33d6ccc 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java @@ -76,6 +76,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cosmos DB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java index 693dc1fcfc..9369098416 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/SingleCrudOperationTransactionAdminIntegrationTestWithCosmos.java @@ -71,6 +71,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("Cosmos DB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("Cosmos DB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("Cosmos DB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java index 9be4c48043..a885ee2574 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java @@ -82,6 +82,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("DynamoDB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java index e464d584d0..b1fb8fcde9 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java @@ -82,6 +82,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("DynamoDB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java index a6daecb0dc..33f8dfeb41 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java @@ -81,6 +81,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("DynamoDB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java index 623ccf850c..c971f6e29c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/SingleCrudOperationTransactionAdminIntegrationTestWithDynamo.java @@ -76,6 +76,10 @@ public void renameColumn_ForIndexKeyColumn_ShouldRenameColumnAndIndexCorrectly() @Disabled("DynamoDB does not support altering column types") public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() {} + @Override + @Disabled("DynamoDB does not support altering column types") + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() {} + @Override @Disabled("DynamoDB does not support renaming tables") public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() {} diff --git a/core/src/main/java/com/scalar/db/api/Admin.java b/core/src/main/java/com/scalar/db/api/Admin.java index cce9a0c9d1..c1cb0bcb4d 100644 --- a/core/src/main/java/com/scalar/db/api/Admin.java +++ b/core/src/main/java/com/scalar/db/api/Admin.java @@ -541,7 +541,8 @@ void renameColumn(String namespace, String table, String oldColumnName, String n * @param table the table name * @param columnName the name of the column to alter * @param newColumnType the new data type of the column - * @throws IllegalArgumentException if the table or the column does not exist + * @throws IllegalArgumentException if the table or the column does not exist, if the column is a + * primary key column or the index key column, or if the data type conversion is not supported * @throws ExecutionException if the operation fails */ void alterColumnType(String namespace, String table, String columnName, DataType newColumnType) diff --git a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java index 05f640a94f..a7ec47ffc9 100644 --- a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java @@ -379,6 +379,14 @@ public void alterColumnType( ScalarDbUtils.getFullTableName(namespace, table), columnName)); } + if (tableMetadata.getPartitionKeyNames().contains(columnName) + || tableMetadata.getClusteringKeyNames().contains(columnName) + || tableMetadata.getSecondaryIndexNames().contains(columnName)) { + throw new IllegalArgumentException( + CoreError.ALTER_PRIMARY_OR_INDEX_KEY_COLUMN_TYPE_NOT_SUPPORTED.buildMessage( + ScalarDbUtils.getFullTableName(namespace, table), columnName)); + } + DataType currentColumnType = tableMetadata.getColumnDataType(columnName); if (currentColumnType == newColumnType) { return; diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index daf249315e..963317d2c2 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -760,39 +760,45 @@ public enum CoreError implements ScalarDbError { Category.USER_ERROR, "0231", "Renaming tables is not supported in Cosmos DB", "", ""), DYNAMO_RENAME_TABLE_NOT_SUPPORTED( Category.USER_ERROR, "0232", "Renaming tables is not supported in DynamoDB", "", ""), - INVALID_COLUMN_TYPE_CONVERSION( + ALTER_PRIMARY_OR_INDEX_KEY_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, "0233", + "Altering primary key or index key column types is not supported. Table: %s; Column: %s", + "", + ""), + INVALID_COLUMN_TYPE_CONVERSION( + Category.USER_ERROR, + "0234", "Invalid column type conversion from %s to %s. Column: %s", "", ""), CASSANDRA_ALTER_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, - "0234", + "0235", "Cassandra does not support the altering column type feature", "", ""), COSMOS_ALTER_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, - "0235", + "0236", "Cosmos DB does not support the altering column type feature", "", ""), DYNAMO_ALTER_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, - "0236", + "0237", "DynamoDB does not support the altering column type feature", "", ""), JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED( Category.USER_ERROR, - "0237", + "0238", "SQLite does not support the altering column type feature", "", ""), JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION( Category.USER_ERROR, - "0238", + "0239", "The storage does not support column type conversion from %s to %s. Column: %s", "", ""), diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java index 5026174d75..bb55e887e8 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java @@ -1590,6 +1590,38 @@ public void alterColumnType_ForNonExistingColumn_ShouldThrowIllegalArgumentExcep .isInstanceOf(IllegalArgumentException.class); } + @Test + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata currentTableMetadata = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2()) + .addSecondaryIndex(getColumnName3()) + .build(); + admin.createTable(namespace1, getTable4(), currentTableMetadata, options); + + // Act Assert + assertThatThrownBy( + () -> admin.alterColumnType(namespace1, getTable4(), getColumnName1(), DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> admin.alterColumnType(namespace1, getTable4(), getColumnName2(), DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> admin.alterColumnType(namespace1, getTable4(), getColumnName3(), DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + } finally { + admin.dropTable(namespace1, getTable4(), true); + } + } + @Test public void upgrade_WhenMetadataTableExistsButNotNamespacesTable_ShouldCreateNamespacesTableAndImportExistingNamespaces() diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java index 2ed600dcde..d11275d4b0 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java @@ -36,9 +36,6 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class DistributedTransactionAdminIntegrationTestBase { - private static final Logger logger = - LoggerFactory.getLogger(DistributedTransactionAdminIntegrationTestBase.class); - protected static final String NAMESPACE_BASE_NAME = "int_test_"; protected static final String TABLE1 = "test_table1"; protected static final String TABLE2 = "test_table2"; @@ -55,11 +52,11 @@ public abstract class DistributedTransactionAdminIntegrationTestBase { protected static final String COL_NAME9 = "c9"; protected static final String COL_NAME10 = "c10"; protected static final String COL_NAME11 = "c11"; + private static final Logger logger = + LoggerFactory.getLogger(DistributedTransactionAdminIntegrationTestBase.class); private static final String COL_NAME12 = "c12"; private static final String COL_NAME13 = "c13"; private static final String COL_NAME14 = "c14"; - private static final String COL_NAME15 = "c15"; - protected static final TableMetadata TABLE_METADATA = TableMetadata.newBuilder() .addColumn(COL_NAME1, DataType.INT) @@ -83,6 +80,7 @@ public abstract class DistributedTransactionAdminIntegrationTestBase { .addSecondaryIndex(COL_NAME5) .addSecondaryIndex(COL_NAME6) .build(); + private static final String COL_NAME15 = "c15"; protected TransactionFactory transactionFactory; protected DistributedTransactionAdmin admin; protected String namespace1; @@ -1329,6 +1327,35 @@ public void alterColumnType_WideningConversion_ShouldAlterColumnTypesCorrectly() } } + @Test + public void alterColumnType_ForPrimaryKeyOrIndexKeyColumn_ShouldThrowIllegalArgumentException() + throws ExecutionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata currentTableMetadata = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addPartitionKey("c1") + .addClusteringKey("c2") + .addSecondaryIndex("c3") + .build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + + // Act Assert + assertThatThrownBy(() -> admin.alterColumnType(namespace1, TABLE4, "c1", DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> admin.alterColumnType(namespace1, TABLE4, "c2", DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .isInstanceOf(IllegalArgumentException.class); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Test public void renameTable_ForExistingTable_ShouldRenameTableCorrectly() throws ExecutionException { String newTableName = "new" + TABLE4; From 3f9e399e8ca3f99165302a03db883a31b2428db0 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Mon, 6 Oct 2025 14:37:28 +0900 Subject: [PATCH 07/12] Apply suggestion --- .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 121 +++++++++--------- .../scalar/db/storage/jdbc/RdbEngineDb2.java | 2 +- .../db/storage/jdbc/RdbEngineOracle.java | 2 +- .../db/storage/jdbc/RdbEngineStrategy.java | 2 +- 4 files changed, 63 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java index 48d97c7717..0f0c1686f7 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java @@ -44,7 +44,7 @@ @ThreadSafe public class JdbcAdmin implements DistributedStorageAdmin { public static final String METADATA_TABLE = "metadata"; - + public static final String NAMESPACES_TABLE = "namespaces"; @VisibleForTesting static final String METADATA_COL_FULL_TABLE_NAME = "full_table_name"; @VisibleForTesting static final String METADATA_COL_COLUMN_NAME = "column_name"; @VisibleForTesting static final String METADATA_COL_DATA_TYPE = "data_type"; @@ -57,7 +57,6 @@ public class JdbcAdmin implements DistributedStorageAdmin { @VisibleForTesting static final String JDBC_COL_TYPE_NAME = "TYPE_NAME"; @VisibleForTesting static final String JDBC_COL_COLUMN_SIZE = "COLUMN_SIZE"; @VisibleForTesting static final String JDBC_COL_DECIMAL_DIGITS = "DECIMAL_DIGITS"; - public static final String NAMESPACES_TABLE = "namespaces"; @VisibleForTesting static final String NAMESPACE_COL_NAMESPACE_NAME = "namespace_name"; private static final Logger logger = LoggerFactory.getLogger(JdbcAdmin.class); private static final String INDEX_NAME_PREFIX = "index"; @@ -87,6 +86,64 @@ public JdbcAdmin(BasicDataSource dataSource, JdbcConfig config) { metadataSchema = config.getMetadataSchema(); } + private static boolean hasDescClusteringOrder(TableMetadata metadata) { + return metadata.getClusteringKeyNames().stream() + .anyMatch(c -> metadata.getClusteringOrder(c) == Order.DESC); + } + + @VisibleForTesting + static boolean hasDifferentClusteringOrders(TableMetadata metadata) { + boolean hasAscOrder = false; + boolean hasDescOrder = false; + for (Order order : metadata.getClusteringOrders().values()) { + if (order == Order.ASC) { + hasAscOrder = true; + } else { + hasDescOrder = true; + } + } + return hasAscOrder && hasDescOrder; + } + + static void execute(Connection connection, String sql) throws SQLException { + execute(connection, sql, null); + } + + static void execute(Connection connection, String sql, @Nullable SqlWarningHandler handler) + throws SQLException { + if (Strings.isNullOrEmpty(sql)) { + return; + } + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + throwSqlWarningIfNeeded(handler, stmt); + } + } + + private static void throwSqlWarningIfNeeded(SqlWarningHandler handler, Statement stmt) + throws SQLException { + if (handler == null) { + return; + } + SQLWarning warning = stmt.getWarnings(); + while (warning != null) { + handler.throwSqlWarningIfNeeded(warning); + warning = warning.getNextWarning(); + } + } + + static void execute(Connection connection, String[] sqls) throws SQLException { + execute(connection, sqls, null); + } + + static void execute( + Connection connection, String[] sqls, @Nullable SqlWarningHandler warningHandler) + throws SQLException { + for (String sql : sqls) { + execute(connection, sql, warningHandler); + } + } + @Override public void createNamespace(String namespace, Map options) throws ExecutionException { @@ -702,25 +759,6 @@ private String getVendorDbColumnType(TableMetadata metadata, String columnName) } } - private static boolean hasDescClusteringOrder(TableMetadata metadata) { - return metadata.getClusteringKeyNames().stream() - .anyMatch(c -> metadata.getClusteringOrder(c) == Order.DESC); - } - - @VisibleForTesting - static boolean hasDifferentClusteringOrders(TableMetadata metadata) { - boolean hasAscOrder = false; - boolean hasDescOrder = false; - for (Order order : metadata.getClusteringOrders().values()) { - if (order == Order.ASC) { - hasAscOrder = true; - } else { - hasDescOrder = true; - } - } - return hasAscOrder && hasDescOrder; - } - @Override public void createIndex( String namespace, String table, String columnName, Map options) @@ -943,7 +981,7 @@ public void alterColumnType( TableMetadata currentTableMetadata = getTableMetadata(namespace, table); assert currentTableMetadata != null; DataType currentColumnType = currentTableMetadata.getColumnDataType(columnName); - if (!rdbEngine.isTypeConversionSupportedInternal(currentColumnType, newColumnType)) { + if (!rdbEngine.isTypeConversionSupported(currentColumnType, newColumnType)) { throw new UnsupportedOperationException( CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( currentColumnType, newColumnType, columnName)); @@ -1097,45 +1135,6 @@ private void updateTableMetadata( execute(connection, updateStatement); } - static void execute(Connection connection, String sql) throws SQLException { - execute(connection, sql, null); - } - - static void execute(Connection connection, String sql, @Nullable SqlWarningHandler handler) - throws SQLException { - if (Strings.isNullOrEmpty(sql)) { - return; - } - try (Statement stmt = connection.createStatement()) { - stmt.execute(sql); - throwSqlWarningIfNeeded(handler, stmt); - } - } - - private static void throwSqlWarningIfNeeded(SqlWarningHandler handler, Statement stmt) - throws SQLException { - if (handler == null) { - return; - } - SQLWarning warning = stmt.getWarnings(); - while (warning != null) { - handler.throwSqlWarningIfNeeded(warning); - warning = warning.getNextWarning(); - } - } - - static void execute(Connection connection, String[] sqls) throws SQLException { - execute(connection, sqls, null); - } - - static void execute( - Connection connection, String[] sqls, @Nullable SqlWarningHandler warningHandler) - throws SQLException { - for (String sql : sqls) { - execute(connection, sql, warningHandler); - } - } - private String enclose(String name) { return rdbEngine.enclose(name); } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 9df6c92537..688dfbe915 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -576,7 +576,7 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( } @Override - public boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + public boolean isTypeConversionSupported(DataType from, DataType to) { if (from == DataType.BLOB && to == DataType.TEXT) { return false; } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java index 1f871a49fd..01b7e0fc3c 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java @@ -444,7 +444,7 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { } @Override - public boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + public boolean isTypeConversionSupported(DataType from, DataType to) { return from == DataType.INT && to == DataType.BIGINT; } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java index 78bc9271ac..416b6ba64d 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java @@ -312,7 +312,7 @@ default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( * @param to the target data type * @return true if type conversion is supported, false otherwise */ - default boolean isTypeConversionSupportedInternal(DataType from, DataType to) { + default boolean isTypeConversionSupported(DataType from, DataType to) { return true; } } From d775440260c6b850c62266c8569d91d9d85f6956 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Mon, 6 Oct 2025 16:26:02 +0900 Subject: [PATCH 08/12] Apply suggestion --- .../com/scalar/db/common/CommonDistributedStorageAdmin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java index a7ec47ffc9..bfaeed741c 100644 --- a/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/common/CommonDistributedStorageAdmin.java @@ -391,7 +391,7 @@ public void alterColumnType( if (currentColumnType == newColumnType) { return; } - if (!isTypeConversionSupported(currentColumnType, newColumnType)) { + if (!isTypeConversionValid(currentColumnType, newColumnType)) { throw new IllegalArgumentException( CoreError.INVALID_COLUMN_TYPE_CONVERSION.buildMessage( currentColumnType, newColumnType, columnName)); @@ -531,7 +531,7 @@ public void close() { admin.close(); } - private boolean isTypeConversionSupported(DataType from, DataType to) { + private boolean isTypeConversionValid(DataType from, DataType to) { if (from == to) { return true; } From b41de5e17d8717bea194b572ee9561189f313820 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 7 Oct 2025 14:43:54 +0900 Subject: [PATCH 09/12] Update supported type conversion in Db2 --- ...tAdminIntegrationTestWithJdbcDatabase.java | 20 +++++++++---------- ...bcAdminCaseSensitivityIntegrationTest.java | 20 +++++++++---------- .../jdbc/JdbcAdminIntegrationTest.java | 20 +++++++++---------- ...nAdminIntegrationTestWithJdbcDatabase.java | 20 +++++++++---------- .../JdbcTransactionAdminIntegrationTest.java | 20 +++++++++---------- .../scalar/db/storage/jdbc/RdbEngineDb2.java | 17 +--------------- 6 files changed, 51 insertions(+), 66 deletions(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java index 4beba430c4..e1599f0d6d 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java @@ -274,7 +274,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) @@ -282,14 +282,14 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); if (isTimestampTypeSupported()) { assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); } TableMetadata.Builder expectedTableMetadataBuilder = @@ -298,18 +298,18 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp .addColumn("c2", DataType.INT) .addColumn("c3", DataType.TEXT) .addColumn("c4", DataType.TEXT) - .addColumn("c5", DataType.FLOAT) + .addColumn("c5", DataType.TEXT) .addColumn("c6", DataType.TEXT) .addColumn("c7", DataType.TEXT) .addColumn("c8", DataType.BLOB) - .addColumn("c9", DataType.DATE) - .addColumn("c10", DataType.TIME) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) .addPartitionKey("c1") .addClusteringKey("c2", Scan.Ordering.Order.ASC); if (isTimestampTypeSupported()) { expectedTableMetadataBuilder - .addColumn("c11", DataType.TIMESTAMP) - .addColumn("c12", DataType.TIMESTAMPTZ); + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT); } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java index 42b3d9c02a..71dad0d7a7 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java @@ -321,7 +321,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( @@ -341,23 +341,23 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); if (isTimestampTypeSupported()) { assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); } TableMetadata.Builder expectedTableMetadataBuilder = @@ -366,18 +366,18 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp .addColumn(getColumnName2(), DataType.INT) .addColumn(getColumnName3(), DataType.TEXT) .addColumn(getColumnName4(), DataType.TEXT) - .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName5(), DataType.TEXT) .addColumn(getColumnName6(), DataType.TEXT) .addColumn(getColumnName7(), DataType.TEXT) .addColumn(getColumnName8(), DataType.BLOB) - .addColumn(getColumnName9(), DataType.DATE) - .addColumn(getColumnName10(), DataType.TIME) + .addColumn(getColumnName9(), DataType.TEXT) + .addColumn(getColumnName10(), DataType.TEXT) .addPartitionKey(getColumnName1()) .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); if (isTimestampTypeSupported()) { expectedTableMetadataBuilder - .addColumn(getColumnName11(), DataType.TIMESTAMP) - .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + .addColumn(getColumnName11(), DataType.TEXT) + .addColumn(getColumnName12(), DataType.TEXT); } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index 084e29a56f..4e6e2a8b17 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -320,7 +320,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( @@ -340,23 +340,23 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); if (isTimestampTypeSupported()) { assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode( () -> admin.alterColumnType( getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); } TableMetadata.Builder expectedTableMetadataBuilder = @@ -365,18 +365,18 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp .addColumn(getColumnName2(), DataType.INT) .addColumn(getColumnName3(), DataType.TEXT) .addColumn(getColumnName4(), DataType.TEXT) - .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName5(), DataType.TEXT) .addColumn(getColumnName6(), DataType.TEXT) .addColumn(getColumnName7(), DataType.TEXT) .addColumn(getColumnName8(), DataType.BLOB) - .addColumn(getColumnName9(), DataType.DATE) - .addColumn(getColumnName10(), DataType.TIME) + .addColumn(getColumnName9(), DataType.TEXT) + .addColumn(getColumnName10(), DataType.TEXT) .addPartitionKey(getColumnName1()) .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC); if (isTimestampTypeSupported()) { expectedTableMetadataBuilder - .addColumn(getColumnName11(), DataType.TIMESTAMP) - .addColumn(getColumnName12(), DataType.TIMESTAMPTZ); + .addColumn(getColumnName11(), DataType.TEXT) + .addColumn(getColumnName12(), DataType.TEXT); } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java index b175e69a6d..e6a541aadf 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java @@ -274,7 +274,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) @@ -282,14 +282,14 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); if (isTimestampTypeSupported()) { assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); } TableMetadata.Builder expectedTableMetadataBuilder = @@ -298,18 +298,18 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp .addColumn("c2", DataType.INT) .addColumn("c3", DataType.TEXT) .addColumn("c4", DataType.TEXT) - .addColumn("c5", DataType.FLOAT) + .addColumn("c5", DataType.TEXT) .addColumn("c6", DataType.TEXT) .addColumn("c7", DataType.TEXT) .addColumn("c8", DataType.BLOB) - .addColumn("c9", DataType.DATE) - .addColumn("c10", DataType.TIME) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) .addPartitionKey("c1") .addClusteringKey("c2", Scan.Ordering.Order.ASC); if (isTimestampTypeSupported()) { expectedTableMetadataBuilder - .addColumn("c11", DataType.TIMESTAMP) - .addColumn("c12", DataType.TIMESTAMPTZ); + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT); } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java index 0162702d48..df3af3d8f9 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java @@ -346,7 +346,7 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) @@ -354,14 +354,14 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) .isInstanceOf(UnsupportedOperationException.class); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); if (isTimestampTypeSupported()) { assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) - .isInstanceOf(UnsupportedOperationException.class); + .doesNotThrowAnyException(); } TableMetadata.Builder expectedTableMetadataBuilder = @@ -370,18 +370,18 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp .addColumn("c2", DataType.INT) .addColumn("c3", DataType.TEXT) .addColumn("c4", DataType.TEXT) - .addColumn("c5", DataType.FLOAT) + .addColumn("c5", DataType.TEXT) .addColumn("c6", DataType.TEXT) .addColumn("c7", DataType.TEXT) .addColumn("c8", DataType.BLOB) - .addColumn("c9", DataType.DATE) - .addColumn("c10", DataType.TIME) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) .addPartitionKey("c1") .addClusteringKey("c2", Scan.Ordering.Order.ASC); if (isTimestampTypeSupported()) { expectedTableMetadataBuilder - .addColumn("c11", DataType.TIMESTAMP) - .addColumn("c12", DataType.TIMESTAMPTZ); + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT); } TableMetadata expectedTableMetadata = expectedTableMetadataBuilder.build(); assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 688dfbe915..51b9775d8e 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -577,21 +577,6 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( @Override public boolean isTypeConversionSupported(DataType from, DataType to) { - if (from == DataType.BLOB && to == DataType.TEXT) { - return false; - } - if (from == DataType.FLOAT && to == DataType.TEXT) { - return false; - } - if (from == DataType.DATE && to == DataType.TEXT) { - return false; - } - if (from == DataType.TIME && to == DataType.TEXT) { - return false; - } - if (from == DataType.TIMESTAMP && to == DataType.TEXT) { - return false; - } - return !(from == DataType.TIMESTAMPTZ && to == DataType.TEXT); + return from != DataType.BLOB || to != DataType.TEXT; } } From 0724fc414df27affd17e8b567098656f6ac4f830 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 7 Oct 2025 15:04:04 +0900 Subject: [PATCH 10/12] Apply suggestions --- .../java/com/scalar/db/common/CoreError.java | 2 +- .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 7 +------ .../com/scalar/db/storage/jdbc/RdbEngineDb2.java | 14 +++++++++----- .../scalar/db/storage/jdbc/RdbEngineOracle.java | 8 ++++++-- .../scalar/db/storage/jdbc/RdbEngineSqlite.java | 11 ++++++----- .../db/storage/jdbc/RdbEngineStrategy.java | 16 +++------------- ...butedTransactionAdminIntegrationTestBase.java | 8 +++++--- 7 files changed, 31 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 963317d2c2..df1497cf9f 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -799,7 +799,7 @@ public enum CoreError implements ScalarDbError { JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION( Category.USER_ERROR, "0239", - "The storage does not support column type conversion from %s to %s. Column: %s", + "The storage does not support column type conversion from %s to %s", "", ""), diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java index 0f0c1686f7..23cfd6a209 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java @@ -977,15 +977,10 @@ public void alterColumnType( String namespace, String table, String columnName, DataType newColumnType) throws ExecutionException { try { - rdbEngine.throwIfAlterColumnTypeNotSupported(); TableMetadata currentTableMetadata = getTableMetadata(namespace, table); assert currentTableMetadata != null; DataType currentColumnType = currentTableMetadata.getColumnDataType(columnName); - if (!rdbEngine.isTypeConversionSupported(currentColumnType, newColumnType)) { - throw new UnsupportedOperationException( - CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( - currentColumnType, newColumnType, columnName)); - } + rdbEngine.throwIfAlterColumnTypeNotSupported(currentColumnType, newColumnType); TableMetadata updatedTableMetadata = TableMetadata.newBuilder(currentTableMetadata) diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 51b9775d8e..7c5d4b3948 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -549,6 +549,15 @@ public void throwIfRenameColumnNotSupported(String columnName, TableMetadata tab } } + @Override + public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { + if (from == DataType.BLOB && to == DataType.TEXT) { + throw new UnsupportedOperationException( + CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + from.toString(), to.toString())); + } + } + private String getProjection(String columnName, DataType dataType) { if (dataType == DataType.DATE) { // Selecting a DATE column requires special handling. We need to cast the DATE column values @@ -574,9 +583,4 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( .buildMessage(orderingOnBlobColumn.get())); } } - - @Override - public boolean isTypeConversionSupported(DataType from, DataType to) { - return from != DataType.BLOB || to != DataType.TEXT; - } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java index 01b7e0fc3c..6a5ba879d3 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java @@ -444,7 +444,11 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { } @Override - public boolean isTypeConversionSupported(DataType from, DataType to) { - return from == DataType.INT && to == DataType.BIGINT; + public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { + if (!(from == DataType.INT && to == DataType.BIGINT)) { + throw new UnsupportedOperationException( + CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + from.toString(), to.toString())); + } } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java index 5131a294fd..df6d895417 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java @@ -358,13 +358,14 @@ public RdbEngineTimeTypeStrategy getTimeTypeStrategy( } @Override - public void setConnectionToReadOnly(Connection connection, boolean readOnly) { - // Do nothing. SQLite does not support read-only mode. + public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { + throw new UnsupportedOperationException( + CoreError.JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage( + from.toString(), to.toString())); } @Override - public void throwIfAlterColumnTypeNotSupported() { - throw new UnsupportedOperationException( - CoreError.JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); + public void setConnectionToReadOnly(Connection connection, boolean readOnly) { + // Do nothing. SQLite does not support read-only mode. } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java index 416b6ba64d..55ebc1937b 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java @@ -283,9 +283,11 @@ default void throwIfRenameColumnNotSupported(String columnName, TableMetadata ta /** * Throws an exception if altering the column type is not supported in the underlying database. * + * @param from the source data type + * @param to the target data type * @throws UnsupportedOperationException if altering the column type is not supported */ - default void throwIfAlterColumnTypeNotSupported() {} + default void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) {} default void setConnectionToReadOnly(Connection connection, boolean readOnly) throws SQLException { @@ -303,16 +305,4 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) */ default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( ScanAll scanAll, TableMetadata metadata) {} - - /** - * Returns whether type conversion is supported between the two data types in the underlying - * storage. - * - * @param from the source data type - * @param to the target data type - * @return true if type conversion is supported, false otherwise - */ - default boolean isTypeConversionSupported(DataType from, DataType to) { - return true; - } } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java index d11275d4b0..e6423ad6fa 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java @@ -36,6 +36,9 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class DistributedTransactionAdminIntegrationTestBase { + private static final Logger logger = + LoggerFactory.getLogger(DistributedTransactionAdminIntegrationTestBase.class); + protected static final String NAMESPACE_BASE_NAME = "int_test_"; protected static final String TABLE1 = "test_table1"; protected static final String TABLE2 = "test_table2"; @@ -52,11 +55,11 @@ public abstract class DistributedTransactionAdminIntegrationTestBase { protected static final String COL_NAME9 = "c9"; protected static final String COL_NAME10 = "c10"; protected static final String COL_NAME11 = "c11"; - private static final Logger logger = - LoggerFactory.getLogger(DistributedTransactionAdminIntegrationTestBase.class); private static final String COL_NAME12 = "c12"; private static final String COL_NAME13 = "c13"; private static final String COL_NAME14 = "c14"; + private static final String COL_NAME15 = "c15"; + protected static final TableMetadata TABLE_METADATA = TableMetadata.newBuilder() .addColumn(COL_NAME1, DataType.INT) @@ -80,7 +83,6 @@ public abstract class DistributedTransactionAdminIntegrationTestBase { .addSecondaryIndex(COL_NAME5) .addSecondaryIndex(COL_NAME6) .build(); - private static final String COL_NAME15 = "c15"; protected TransactionFactory transactionFactory; protected DistributedTransactionAdmin admin; protected String namespace1; From 776c08b8885f5add9394c33b817949be6801ed24 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 7 Oct 2025 15:12:23 +0900 Subject: [PATCH 11/12] Fix unit test --- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index 5ab9830964..c8636162e2 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -3327,9 +3327,28 @@ public void alterColumnType_ForSqlServer_ShouldWorkProperly() } @Test - public void alterColumnType_ForSqlite_ShouldThrowUnsupportedOperationException() { + public void alterColumnType_ForSqlite_ShouldThrowUnsupportedOperationException() + throws SQLException { + // Arrange + String namespace = "ns"; + String table = "table"; + String columnName1 = "c1"; + String columnName2 = "c2"; + + PreparedStatement selectStatement = mock(PreparedStatement.class); + ResultSet resultSet = + mockResultSet( + new SelectAllFromMetadataTableResultSetMocker.Row( + columnName1, DataType.TEXT.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + columnName2, DataType.INT.toString(), null, null, false)); + when(selectStatement.executeQuery()).thenReturn(resultSet); + when(connection.prepareStatement(any())).thenReturn(selectStatement); + when(dataSource.getConnection()).thenReturn(connection); JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE); - assertThatThrownBy(() -> admin.alterColumnType("ns", "table", "column", DataType.BIGINT)) + + // Act Assert + assertThatThrownBy(() -> admin.alterColumnType(namespace, table, columnName1, DataType.BIGINT)) .isInstanceOf(UnsupportedOperationException.class) .hasMessage(CoreError.JDBC_SQLITE_ALTER_COLUMN_TYPE_NOT_SUPPORTED.buildMessage()); } From fc51ea1744d2cbb4fa436debc987a1f15e964b6d Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 7 Oct 2025 20:02:30 +0900 Subject: [PATCH 12/12] Apply suggestions --- core/src/main/java/com/scalar/db/common/CoreError.java | 10 ++++++++-- .../java/com/scalar/db/storage/jdbc/RdbEngineDb2.java | 2 +- .../com/scalar/db/storage/jdbc/RdbEngineOracle.java | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index df1497cf9f..b5f70cbb6e 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -796,10 +796,16 @@ public enum CoreError implements ScalarDbError { "SQLite does not support the altering column type feature", "", ""), - JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION( + JDBC_ORACLE_UNSUPPORTED_COLUMN_TYPE_CONVERSION( Category.USER_ERROR, "0239", - "The storage does not support column type conversion from %s to %s", + "Oracle does not support column type conversion from %s to %s", + "", + ""), + JDBC_DB2_UNSUPPORTED_COLUMN_TYPE_CONVERSION( + Category.USER_ERROR, + "0240", + "Db2 does not support column type conversion from %s to %s", "", ""), diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 7c5d4b3948..8d0ec467eb 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -553,7 +553,7 @@ public void throwIfRenameColumnNotSupported(String columnName, TableMetadata tab public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { if (from == DataType.BLOB && to == DataType.TEXT) { throw new UnsupportedOperationException( - CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + CoreError.JDBC_DB2_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( from.toString(), to.toString())); } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java index 6a5ba879d3..4bf9bcb5a6 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java @@ -447,7 +447,7 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { if (!(from == DataType.INT && to == DataType.BIGINT)) { throw new UnsupportedOperationException( - CoreError.JDBC_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + CoreError.JDBC_ORACLE_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( from.toString(), to.toString())); } }