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 {