diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java index 3a27e8e633..24dbca8db4 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java @@ -1,9 +1,15 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; + import com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase; +import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.io.DataType; import java.sql.SQLException; +import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Properties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -47,16 +53,51 @@ protected List createExistingDatabaseWithAllDataTypes() throws SQLExce return testUtils.createExistingDatabaseWithAllDataTypes(getNamespace()); } + @Override + protected List getIntCompatibleColumnNamesOnExistingDatabase(String table) { + return testUtils.getIntCompatibleColumnNamesOnExistingDatabase(table); + } + + @Override + protected List getFloatCompatibleColumnNamesOnExistingDatabase(String table) { + return testUtils.getFloatCompatibleColumnNamesOnExistingDatabase(table); + } + @Override protected void dropNonImportableTable(String table) throws SQLException { testUtils.dropTable(getNamespace(), table); } + @SuppressWarnings("unused") + private boolean isOracle() { + return JdbcEnv.isOracle(); + } + + @SuppressWarnings("unused") + private boolean isSqlServer() { + return JdbcEnv.isSqlServer(); + } + + @SuppressWarnings("unused") + private boolean isDb2() { + return JdbcEnv.isDb2(); + } + @SuppressWarnings("unused") private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isColumnTypeConversionToTextNotFullySupported() { + return JdbcEnv.isDb2() || JdbcEnv.isSqlServer() || JdbcEnv.isOracle() || JdbcEnv.isSqlite(); + } + + @SuppressWarnings("unused") + private boolean isWideningColumnTypeConversionNotFullySupported() { + return JdbcEnv.isOracle() || JdbcEnv.isSqlite(); + } + @Test @Override @DisabledIf("isSqlite") @@ -71,4 +112,155 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } + + @Test + @Override + @DisabledIf("isColumnTypeConversionToTextNotFullySupported") + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly() + throws Exception { + super + .alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly(); + } + + @Test + @EnabledIf("isSqlServer") + public void + alterColumnType_SqlServer_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + TableMetadata metadata = testData.getTableMetadata(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (Objects.equals(column, "col16")) { + // Conversion from IMAGE to VARCHAR(8000) is not supported in SQL Server engine + continue; + } + admin.alterColumnType(getNamespace(), testData.getTableName(), column, DataType.TEXT); + } + } + + // Assert + TableMetadata newMetadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(newMetadata).isNotNull(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (Objects.equals(column, "col16")) { + continue; + } + assertThat(newMetadata.getColumnDataType(column)).isEqualTo(DataType.TEXT); + } + } + } + } + } + + @Test + @EnabledIf("isDb2") + public void + alterColumnType_Db2_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + TableMetadata metadata = testData.getTableMetadata(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (metadata.getColumnDataType(column).equals(DataType.BLOB)) { + // Conversion from BLOB to TEXT is not supported in Db2 engine + continue; + } + admin.alterColumnType(getNamespace(), testData.getTableName(), column, DataType.TEXT); + } + } + + // Assert + TableMetadata newMetadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(newMetadata).isNotNull(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (metadata.getColumnDataType(column).equals(DataType.BLOB)) { + continue; + } + assertThat(newMetadata.getColumnDataType(column)).isEqualTo(DataType.TEXT); + } + } + } + } + } + + @Test + @Override + @DisabledIf("isWideningColumnTypeConversionNotFullySupported") + public void alterColumnType_WideningConversion_ForImportedTable_ShouldAlterProperly() + throws Exception { + super.alterColumnType_WideningConversion_ForImportedTable_ShouldAlterProperly(); + } + + @Test + @EnabledIf("isOracle") + public void alterColumnType_Oracle_WideningConversion_ForImportedTable_ShouldAlterProperly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + for (String intCompatibleColumn : + getIntCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + admin.alterColumnType( + getNamespace(), testData.getTableName(), intCompatibleColumn, DataType.BIGINT); + } + // Conversion from FLOAT TO DOUBLE is not supported in Oracle engine + + // Assert + TableMetadata metadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(metadata).isNotNull(); + for (String intCompatibleColumn : + getIntCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + assertThat(metadata.getColumnDataType(intCompatibleColumn)).isEqualTo(DataType.BIGINT); + } + } + } + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java index 333185686a..1ac5ee1529 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java @@ -146,6 +146,38 @@ public List createExistingDatabaseWithAllDataTypes(String namespace) } } + public List getIntCompatibleColumnNamesOnExistingDatabase(String table) { + if (JdbcTestUtils.isMysql(rdbEngine)) { + return getIntCompatibleColumnNamesOnExistingMysqlDatabase(table); + } else if (JdbcTestUtils.isPostgresql(rdbEngine)) { + return getIntCompatibleColumnNamesOnExistingPostgresDatabase(table); + } else if (JdbcTestUtils.isOracle(rdbEngine)) { + return getIntCompatibleColumnNamesOnExistingOracleDatabase(table); + } else if (JdbcTestUtils.isSqlServer(rdbEngine)) { + return getIntCompatibleColumnNamesOnExistingSqlServerDatabase(table); + } else if (JdbcTestUtils.isDb2(rdbEngine)) { + return getIntCompatibleColumnNamesOnExistingDb2Database(table); + } else { + throw new AssertionError("Unsupported database engine: " + rdbEngine); + } + } + + public List getFloatCompatibleColumnNamesOnExistingDatabase(String table) { + if (JdbcTestUtils.isMysql(rdbEngine)) { + return getFloatCompatibleColumnNamesOnExistingMysqlDatabase(table); + } else if (JdbcTestUtils.isPostgresql(rdbEngine)) { + return getFloatCompatibleColumnNamesOnExistingPostgresDatabase(table); + } else if (JdbcTestUtils.isOracle(rdbEngine)) { + return getFloatCompatibleColumnNamesOnExistingOracleDatabase(table); + } else if (JdbcTestUtils.isSqlServer(rdbEngine)) { + return getFloatCompatibleColumnNamesOnExistingSqlServerDatabase(table); + } else if (JdbcTestUtils.isDb2(rdbEngine)) { + return getFloatCompatibleColumnNamesOnExistingDb2Database(table); + } else { + throw new AssertionError("Unsupported database engine: " + rdbEngine); + } + } + public void dropTable(String namespace, String table) throws SQLException { String dropTable = "DROP TABLE " + rdbEngine.encloseFullTableName(namespace, table); execute(dropTable); @@ -612,6 +644,22 @@ private List createExistingMysqlDatabaseWithAllDataTypes(String namesp return ImmutableList.copyOf(data); } + private ImmutableList getIntCompatibleColumnNamesOnExistingMysqlDatabase(String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col02", "col04", "col05", "col06"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + + private ImmutableList getFloatCompatibleColumnNamesOnExistingMysqlDatabase(String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col08"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + private List createExistingPostgresDatabaseWithAllDataTypes(String namespace) throws SQLException { List data = new ArrayList<>(); @@ -646,6 +694,24 @@ private List createExistingPostgresDatabaseWithAllDataTypes(String nam return ImmutableList.copyOf(data); } + private ImmutableList getIntCompatibleColumnNamesOnExistingPostgresDatabase( + String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col02", "col03"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + + private ImmutableList getFloatCompatibleColumnNamesOnExistingPostgresDatabase( + String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col05"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + private List createExistingOracleDatabaseWithAllDataTypes(String namespace) throws SQLException { List data = new ArrayList<>(); @@ -697,6 +763,27 @@ private List createExistingOracleDatabaseWithAllDataTypes(String names return ImmutableList.copyOf(data); } + private ImmutableList getIntCompatibleColumnNamesOnExistingOracleDatabase(String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of(); + } else if (table.equals(SUPPORTED_TABLE_NAME + "_long_raw")) { + return ImmutableList.of(); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + + private ImmutableList getFloatCompatibleColumnNamesOnExistingOracleDatabase( + String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col04"); + } else if (table.equals(SUPPORTED_TABLE_NAME + "_long_raw")) { + return ImmutableList.of(); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + private List createExistingSqlServerDatabaseWithAllDataTypes(String namespace) throws SQLException { List data = new ArrayList<>(); @@ -723,6 +810,24 @@ private List createExistingSqlServerDatabaseWithAllDataTypes(String na return ImmutableList.copyOf(data); } + private ImmutableList getIntCompatibleColumnNamesOnExistingSqlServerDatabase( + String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col02", "col03", "col04"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + + private ImmutableList getFloatCompatibleColumnNamesOnExistingSqlServerDatabase( + String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col06"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + private List createExistingDb2DatabaseWithAllDataTypes(String namespace) throws SQLException { List data = new ArrayList<>(); @@ -749,6 +854,22 @@ private List createExistingDb2DatabaseWithAllDataTypes(String namespac return ImmutableList.copyOf(data); } + private ImmutableList getIntCompatibleColumnNamesOnExistingDb2Database(String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col01", "col02"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + + private ImmutableList getFloatCompatibleColumnNamesOnExistingDb2Database(String table) { + if (table.equals(SUPPORTED_TABLE_NAME)) { + return ImmutableList.of("col04", "col05"); + } else { + throw new IllegalArgumentException("Table does not exist: " + table); + } + } + private void executeCreateTableSql(List data) throws SQLException { String[] sqls = data.stream().map(JdbcTestData::getCreateTableSql).toArray(String[]::new); execute(sqls); 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 361f3f9941..f00916a0d3 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 @@ -58,6 +58,10 @@ public static boolean isOracle() { return System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL).startsWith("jdbc:oracle:"); } + public static boolean isSqlServer() { + return System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL).startsWith("jdbc:sqlserver:"); + } + public static boolean isSqlite() { return System.getProperty(PROP_JDBC_URL, DEFAULT_JDBC_URL).startsWith("jdbc:sqlite:"); } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java index 0442896e81..e9eb568267 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java @@ -33,7 +33,7 @@ public abstract class DistributedStorageAdminImportTableIntegrationTestBase { private static final String TEST_NAME = "storage_admin_import_table"; private static final String NAMESPACE = "int_test_" + TEST_NAME; - private final List testDataList = new ArrayList<>(); + protected final List testDataList = new ArrayList<>(); protected DistributedStorageAdmin admin; protected DistributedStorage storage; @@ -91,6 +91,8 @@ protected void afterEach() { } catch (Exception e) { logger.warn("Failed to close admin", e); } + + testDataList.clear(); } @AfterAll @@ -98,6 +100,12 @@ protected void afterAll() throws Exception {} protected abstract List createExistingDatabaseWithAllDataTypes() throws SQLException; + protected abstract List getIntCompatibleColumnNamesOnExistingDatabase(String table) + throws SQLException; + + protected abstract List getFloatCompatibleColumnNamesOnExistingDatabase(String table) + throws SQLException; + protected abstract void dropNonImportableTable(String table) throws Exception; @Test @@ -130,6 +138,90 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx .isInstanceOf(UnsupportedOperationException.class); } + @Test + public void + alterColumnType_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + TableMetadata metadata = testData.getTableMetadata(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + admin.alterColumnType(getNamespace(), testData.getTableName(), column, DataType.TEXT); + } + } + + // Assert + TableMetadata newMetadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(newMetadata).isNotNull(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + assertThat(newMetadata.getColumnDataType(column)).isEqualTo(DataType.TEXT); + } + } + } + } + } + + @Test + public void alterColumnType_WideningConversion_ForImportedTable_ShouldAlterProperly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + for (String intCompatibleColumn : + getIntCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + admin.alterColumnType( + getNamespace(), testData.getTableName(), intCompatibleColumn, DataType.BIGINT); + } + for (String floatCompatibleColumn : + getFloatCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + admin.alterColumnType( + getNamespace(), testData.getTableName(), floatCompatibleColumn, DataType.DOUBLE); + } + + // Assert + TableMetadata metadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(metadata).isNotNull(); + for (String intCompatibleColumn : + getIntCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + assertThat(metadata.getColumnDataType(intCompatibleColumn)).isEqualTo(DataType.BIGINT); + } + for (String floatCompatibleColumn : + getFloatCompatibleColumnNamesOnExistingDatabase(testData.getTableName())) { + assertThat(metadata.getColumnDataType(floatCompatibleColumn)).isEqualTo(DataType.DOUBLE); + } + } + } + } + private void importTable_ForImportableTable_ShouldImportProperly( String table, Map overrideColumnsType, TableMetadata metadata) throws ExecutionException {