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 1d86c7b6a1..fdb5e3437b 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 @@ -966,6 +966,89 @@ private Column prepareGenericColumnValue(String columnName, DataType columnTy } } + /** + * Returns the supported data types for primary key columns in the current database engine. + * + *

Currently, this method returns only those data types that are mapped to the ScalarDB + * primitive types: BOOLEAN, INT, BIGINT, FLOAT, DOUBLE, and TEXT. + * + * @return a map of supported data types for primary key columns + */ + public Map getSupportedDataTypeMapForPrimaryKey() { + if (JdbcTestUtils.isMysql(rdbEngine)) { + return ImmutableMap.builder() + .put("BOOLEAN", DataType.BOOLEAN) + .put("INT", DataType.INT) + .put("INT UNSIGNED", DataType.BIGINT) + .put("TINYINT", DataType.INT) + .put("SMALLINT", DataType.INT) + .put("MEDIUMINT", DataType.INT) + .put("BIGINT", DataType.BIGINT) + .put("FLOAT", DataType.FLOAT) + .put("DOUBLE", DataType.DOUBLE) + .put("CHAR(8)", DataType.TEXT) + .put("VARCHAR(512)", DataType.TEXT) + .build(); + } else if (JdbcTestUtils.isPostgresql(rdbEngine)) { + return ImmutableMap.builder() + .put("boolean", DataType.BOOLEAN) + .put("smallint", DataType.INT) + .put("integer", DataType.INT) + .put("bigint", DataType.BIGINT) + .put("real", DataType.FLOAT) + .put("double precision", DataType.DOUBLE) + .put("char(3)", DataType.TEXT) + .put("varchar(512)", DataType.TEXT) + .put("text", DataType.TEXT) + .build(); + } else if (JdbcTestUtils.isOracle(rdbEngine)) { + return ImmutableMap.builder() + .put("NUMERIC(15,0)", DataType.BIGINT) + .put("NUMERIC(15,2)", DataType.DOUBLE) + .put("FLOAT(53)", DataType.DOUBLE) + .put("BINARY_FLOAT", DataType.FLOAT) + .put("BINARY_DOUBLE", DataType.DOUBLE) + .put("CHAR(3)", DataType.TEXT) + .put("VARCHAR2(512)", DataType.TEXT) + .put("NCHAR(3)", DataType.TEXT) + .put("NVARCHAR2(512)", DataType.TEXT) + .build(); + } else if (JdbcTestUtils.isSqlServer(rdbEngine)) { + return ImmutableMap.builder() + .put("bit", DataType.BOOLEAN) + .put("tinyint", DataType.INT) + .put("smallint", DataType.INT) + .put("int", DataType.INT) + .put("bigint", DataType.BIGINT) + .put("real", DataType.FLOAT) + .put("float", DataType.DOUBLE) + .put("char(3)", DataType.TEXT) + .put("varchar(512)", DataType.TEXT) + .put("nchar(3)", DataType.TEXT) + .put("nvarchar(512)", DataType.TEXT) + .build(); + } else if (JdbcTestUtils.isDb2(rdbEngine)) { + return ImmutableMap.builder() + .put("SMALLINT", DataType.INT) + .put("INT", DataType.INT) + .put("BIGINT", DataType.BIGINT) + .put("REAL", DataType.FLOAT) + .put("FLOAT(24)", DataType.FLOAT) + .put("DOUBLE", DataType.DOUBLE) + .put("FLOAT", DataType.DOUBLE) + .put("FLOAT(25)", DataType.DOUBLE) + .put("CHAR(3)", DataType.TEXT) + .put("VARCHAR(512)", DataType.TEXT) + .put("GRAPHIC(3)", DataType.TEXT) + .put("VARGRAPHIC(32)", DataType.TEXT) + .put("NCHAR(3)", DataType.TEXT) + .put("NVARCHAR(32)", DataType.TEXT) + .build(); + } else { + throw new AssertionError("Unsupported database engine: " + rdbEngine); + } + } + public void close() throws SQLException { dataSource.close(); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseVirtualTablesIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseVirtualTablesIntegrationTest.java index e0258820d9..41b5e3591c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseVirtualTablesIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseVirtualTablesIntegrationTest.java @@ -1,13 +1,213 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; + import com.scalar.db.api.DistributedStorageVirtualTablesIntegrationTestBase; +import com.scalar.db.api.Put; +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.api.VirtualTableJoinType; +import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.io.DataType; +import com.scalar.db.io.Key; +import com.scalar.db.util.ScalarDbUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Properties; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JdbcDatabaseVirtualTablesIntegrationTest extends DistributedStorageVirtualTablesIntegrationTestBase { + private static final Logger logger = + LoggerFactory.getLogger(JdbcDatabaseVirtualTablesIntegrationTest.class); + + private JdbcAdminImportTestUtils testUtils; + private RdbEngineStrategy rdbEngine; + @Override protected Properties getProperties(String testName) { - return JdbcEnv.getProperties(testName); + Properties properties = JdbcEnv.getProperties(testName); + JdbcConfig config = new JdbcConfig(new DatabaseConfig(properties)); + rdbEngine = RdbEngineFactory.create(config); + testUtils = new JdbcAdminImportTestUtils(properties); + return properties; + } + + @Override + public void afterAll() { + try { + super.afterAll(); + } catch (Exception e) { + logger.warn("Failed to call super.afterAll", e); + } + + try { + if (testUtils != null) { + testUtils.close(); + } + } catch (Exception e) { + logger.warn("Failed to close test utils", e); + } + } + + @SuppressFBWarnings("SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE") + @DisabledIf("com.scalar.db.storage.jdbc.JdbcEnv#isSqlite") + @Test + public void createVirtualTable_WithImportedTableHavingVariousPrimaryKeyTypes_ShouldWorkProperly() + throws Exception { + for (Map.Entry entry : + testUtils.getSupportedDataTypeMapForPrimaryKey().entrySet()) { + String dataTypeName = entry.getKey(); + DataType dataType = entry.getValue(); + String tableBaseName = + dataTypeName.replaceAll("[()]", "").replaceAll("[\\s,]", "_").toLowerCase(); + String importedTableName = tableBaseName + "_imported"; + String anotherTableName = tableBaseName + "_another"; + String vtableInnerTableName = tableBaseName + "_vtable_inner"; + String vtableLeftOuterTableName = tableBaseName + "_vtable_left_outer"; + + String createTableSql = + "CREATE TABLE " + + rdbEngine.encloseFullTableName(namespace, importedTableName) + + " (" + + rdbEngine.enclose("pk") + + " " + + dataTypeName + + " PRIMARY KEY" + + (JdbcEnv.isDb2() ? " NOT NULL" : "") + + "," + + rdbEngine.enclose("col1") + + " VARCHAR(100))"; + + try { + // Create a left source table to be imported + testUtils.execute(createTableSql); + + // Import the left source table + admin.importTable(namespace, importedTableName, Collections.emptyMap()); + + // Create a right source table + admin.createTable( + namespace, + anotherTableName, + TableMetadata.newBuilder() + .addColumn("pk", dataType) + .addColumn("col2", DataType.TEXT) + .addPartitionKey("pk") + .build()); + + // Create a virtual table that joins the above two source tables with different join types + admin.createVirtualTable( + namespace, + vtableInnerTableName, + namespace, + importedTableName, + namespace, + anotherTableName, + VirtualTableJoinType.INNER); + admin.createVirtualTable( + namespace, + vtableLeftOuterTableName, + namespace, + importedTableName, + namespace, + anotherTableName, + VirtualTableJoinType.LEFT_OUTER); + + // Verify that the virtual tables are created successfully + TableMetadata expectedMetadata = + TableMetadata.newBuilder() + .addColumn("pk", dataType) + .addColumn("col1", DataType.TEXT) + .addColumn("col2", DataType.TEXT) + .addPartitionKey("pk") + .build(); + assertThat(admin.getTableMetadata(namespace, vtableInnerTableName)) + .isEqualTo(expectedMetadata); + assertThat(admin.getTableMetadata(namespace, vtableLeftOuterTableName)) + .isEqualTo(expectedMetadata); + + // Put data into the virtual table + Key partitionKey1 = getPartitionKey(dataType, 1); + Key partitionKey2 = getPartitionKey(dataType, 2); + storage.put( + Put.newBuilder() + .namespace(namespace) + .table(vtableInnerTableName) + .partitionKey(partitionKey1) + .textValue("col1", "value1") + .textValue("col2", "value2") + .build()); + storage.put( + Put.newBuilder() + .namespace(namespace) + .table(vtableInnerTableName) + .partitionKey(partitionKey2) + .textValue("col1", "value3") + .textValue("col2", "value4") + .build()); + + // Scan data from the virtual table and verify + try (Scanner scanner = + storage.scan( + Scan.newBuilder().namespace(namespace).table(vtableInnerTableName).all().build())) { + List results = scanner.all(); + assertThat(results).hasSize(2); + + // Verify results in any order + assertThat(results) + .anySatisfy( + result -> { + Assertions.assertThat( + ScalarDbUtils.getPartitionKey(result, expectedMetadata)) + .isEqualTo(partitionKey1); + assertThat(result.getText("col1")).isEqualTo("value1"); + assertThat(result.getText("col2")).isEqualTo("value2"); + }) + .anySatisfy( + result -> { + Assertions.assertThat( + ScalarDbUtils.getPartitionKey(result, expectedMetadata)) + .isEqualTo(partitionKey2); + assertThat(result.getText("col1")).isEqualTo("value3"); + assertThat(result.getText("col2")).isEqualTo("value4"); + }); + } + } finally { + // Drop the created tables + admin.dropTable(namespace, vtableInnerTableName, true); + admin.dropTable(namespace, vtableLeftOuterTableName, true); + admin.dropTable(namespace, anotherTableName, true); + admin.dropTable(namespace, importedTableName, true); + } + } + } + + private Key getPartitionKey(DataType dataType, int index) { + switch (dataType) { + case BOOLEAN: + return Key.ofBoolean("pk", index == 1); + case INT: + return Key.ofInt("pk", index); + case BIGINT: + return Key.ofBigInt("pk", index); + case FLOAT: + return Key.ofFloat("pk", (float) index); + case DOUBLE: + return Key.ofDouble("pk", index); + case TEXT: + return Key.ofText("pk", String.valueOf(index * 100)); + default: + throw new AssertionError("Unsupported data type: " + dataType); + } } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java index be99398212..0c6c7d8a5c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java @@ -15,7 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@DisabledIf("isSqlite") +@DisabledIf("com.scalar.db.storage.jdbc.JdbcEnv#isSqlite") public class JdbcSchemaLoaderImportIntegrationTest extends SchemaLoaderImportIntegrationTestBase { private static final Logger logger = @@ -196,11 +196,6 @@ public void afterAll() { } } - @SuppressWarnings("unused") - private static boolean isSqlite() { - return JdbcEnv.isSqlite(); - } - @Override protected void waitForDifferentSessionDdl() { if (JdbcTestUtils.isYugabyte(rdbEngine)) { diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageVirtualTablesIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageVirtualTablesIntegrationTestBase.java index cb8d07ddd1..e298df84b5 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageVirtualTablesIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageVirtualTablesIntegrationTestBase.java @@ -34,9 +34,9 @@ public abstract class DistributedStorageVirtualTablesIntegrationTestBase { private static final String RIGHT_SOURCE_TABLE = "right_source_table"; private static final String VIRTUAL_TABLE = "virtual_table"; - private DistributedStorageAdmin admin; - private DistributedStorage storage; - private String namespace; + protected DistributedStorageAdmin admin; + protected DistributedStorage storage; + protected String namespace; @BeforeAll public void beforeAll() throws Exception {