From 683f011884f22436777b1d2daf9e7d8bb0b06da4 Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Wed, 3 Sep 2025 12:12:33 +0900 Subject: [PATCH 1/7] Change default mapping for BLOB column for Db2 from "VARBINARY(32672)" to "BLOB(2G)" [skip ci] --- ...tAdminIntegrationTestWithJdbcDatabase.java | 9 +- ...bcAdminCaseSensitivityIntegrationTest.java | 9 +- .../jdbc/JdbcAdminIntegrationTest.java | 9 +- ...baseCrossPartitionScanIntegrationTest.java | 5 + ...tipleClusteringKeyScanIntegrationTest.java | 7 +- ...seMultiplePartitionKeyIntegrationTest.java | 5 +- ...DatabaseSecondaryIndexIntegrationTest.java | 15 ++ ...ingleClusteringKeyScanIntegrationTest.java | 7 +- ...baseSinglePartitionKeyIntegrationTest.java | 5 +- ...nAdminIntegrationTestWithJdbcDatabase.java | 9 +- .../JdbcTransactionAdminIntegrationTest.java | 9 +- .../java/com/scalar/db/common/CoreError.java | 12 ++ .../db/common/checker/OperationChecker.java | 15 ++ .../scalar/db/storage/jdbc/RdbEngineDb2.java | 8 +- .../common/checker/OperationCheckerTest.java | 37 ++++ .../scalar/db/storage/jdbc/JdbcAdminTest.java | 158 ++++++++++++------ ...ibutedStorageAdminIntegrationTestBase.java | 44 +++-- ...CrossPartitionScanIntegrationTestBase.java | 10 ++ ...edTransactionAdminIntegrationTestBase.java | 44 +++-- 19 files changed, 325 insertions(+), 92 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 7078581c2e..b2593fc13a 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 @@ -22,11 +22,16 @@ protected AdminTestUtils getAdminTestUtils(String testName) { } @Override - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { - // "admin.createIndex()" for TEXT and BLOB columns fails (the "create index" query runs + protected boolean isCreateIndexOnTextColumnEnabled() { + // "admin.createIndex()" for TEXT column fails (the "create index" query runs // indefinitely) on the Db2 community edition docker version which we use for the CI. // However, the index creation is successful on Db2 hosted on IBM Cloud. // So we disable these tests until the issue with the Db2 community edition is resolved. return !JdbcTestUtils.isDb2(rdbEngine); } + + @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 34d20d1906..2a684ecc90 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 @@ -22,10 +22,15 @@ protected AdminTestUtils getAdminTestUtils(String testName) { } @Override - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { - // "admin.createIndex()" for TEXT and BLOB columns fails (the "create index" query runs + protected boolean isCreateIndexOnTextColumnEnabled() { + // "admin.createIndex()" for TEXT column fails (the "create index" query runs // indefinitely) on Db2 community edition version but works on Db2 hosted on IBM Cloud. // So we disable these tests until the issue is resolved. return !JdbcTestUtils.isDb2(rdbEngine); } + + @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 dffdbddf5a..718456fbec 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 @@ -21,10 +21,15 @@ protected AdminTestUtils getAdminTestUtils(String testName) { } @Override - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { - // "admin.createIndex()" for TEXT and BLOB columns fails (the "create index" query runs + protected boolean isCreateIndexOnTextColumnEnabled() { + // "admin.createIndex()" for TEXT columns fails (the "create index" query runs // indefinitely) on Db2 community edition version but works on Db2 hosted on IBM Cloud. // So we disable these tests until the issue is resolved. return !JdbcTestUtils.isDb2(rdbEngine); } + + @Override + protected boolean isIndexOnBlobColumnSupported() { + return !JdbcTestUtils.isDb2(rdbEngine); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java index 83ed7a4eb1..1e85cd44d5 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java @@ -80,4 +80,9 @@ protected Stream provideColumnsForCNFConditionsTest() { } return Stream.of(Arguments.of(allColumnNames)); } + + @Override + protected boolean isOrderingOnBlobColumnSupported() { + return !JdbcTestUtils.isDb2(rdbEngine); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java index 5dc47a8f7f..2247c67e0b 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java @@ -94,9 +94,14 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) @Override protected List getDataTypes() { // TIMESTAMP WITH TIME ZONE type cannot be used as a primary key in Oracle + // BLOB type cannot be used as a clustering key in Db2 return JdbcTestUtils.filterDataTypes( super.getDataTypes(), rdbEngine, - ImmutableMap.of(RdbEngineOracle.class, ImmutableList.of(DataType.TIMESTAMPTZ))); + ImmutableMap.of( + RdbEngineOracle.class, + ImmutableList.of(DataType.TIMESTAMPTZ), + RdbEngineDb2.class, + ImmutableList.of(DataType.BLOB))); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java index c4943d8478..82919a2f72 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java @@ -88,6 +88,7 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) protected List getDataTypes() { // TIMESTAMP WITH TIME ZONE type cannot be used as a primary key in Oracle // FLOAT and DOUBLE types cannot be used as partition key in Yugabyte + // BLOB type cannot be used as a partition key in Db2 return JdbcTestUtils.filterDataTypes( super.getDataTypes(), rdbEngine, @@ -95,6 +96,8 @@ protected List getDataTypes() { RdbEngineOracle.class, ImmutableList.of(DataType.TIMESTAMPTZ), RdbEngineYugabyte.class, - ImmutableList.of(DataType.FLOAT, DataType.DOUBLE))); + ImmutableList.of(DataType.FLOAT, DataType.DOUBLE), + RdbEngineDb2.class, + ImmutableList.of(DataType.BLOB))); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java index 4bff0bed6d..16fc9182c3 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java @@ -1,12 +1,17 @@ package com.scalar.db.storage.jdbc; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.scalar.db.api.DistributedStorageSecondaryIndexIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.io.Column; import com.scalar.db.io.DataType; import com.scalar.db.util.TestUtils; +import java.util.Arrays; import java.util.Properties; import java.util.Random; +import java.util.Set; public class JdbcDatabaseSecondaryIndexIntegrationTest extends DistributedStorageSecondaryIndexIntegrationTestBase { @@ -68,4 +73,14 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) } return super.getColumnWithMaxValue(columnName, dataType); } + + @Override + protected Set getSecondaryIndexTypes() { + // BLOB type cannot be used as a secondary index in Db2 + return Sets.newHashSet( + JdbcTestUtils.filterDataTypes( + Arrays.asList(DataType.values()), + rdbEngine, + ImmutableMap.of(RdbEngineDb2.class, ImmutableList.of(DataType.BLOB)))); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java index d1f1f97bc9..4f77dafed1 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java @@ -70,9 +70,14 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) @Override protected List getClusteringKeyTypes() { // TIMESTAMP WITH TIME ZONE type cannot be used as a primary key in Oracle + // BLOB type cannot be used as a clustering key in Db2 return JdbcTestUtils.filterDataTypes( super.getClusteringKeyTypes(), rdbEngine, - ImmutableMap.of(RdbEngineOracle.class, ImmutableList.of(DataType.TIMESTAMPTZ))); + ImmutableMap.of( + RdbEngineOracle.class, + ImmutableList.of(DataType.TIMESTAMPTZ), + RdbEngineDb2.class, + ImmutableList.of(DataType.BLOB))); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java index 598936d760..480d384ec8 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java @@ -71,6 +71,7 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) protected List getPartitionKeyTypes() { // TIMESTAMP WITH TIME ZONE type cannot be used as a primary key in Oracle // FLOAT and DOUBLE types cannot be used as partition key in Yugabyte + // BLOB type cannot be used as a partition key in Db2 return JdbcTestUtils.filterDataTypes( super.getPartitionKeyTypes(), rdbEngine, @@ -78,6 +79,8 @@ protected List getPartitionKeyTypes() { RdbEngineOracle.class, ImmutableList.of(DataType.TIMESTAMPTZ), RdbEngineYugabyte.class, - ImmutableList.of(DataType.FLOAT, DataType.DOUBLE))); + ImmutableList.of(DataType.FLOAT, DataType.DOUBLE), + RdbEngineDb2.class, + ImmutableList.of(DataType.BLOB))); } } 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 78385ebd68..225e866b78 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 @@ -22,11 +22,16 @@ protected AdminTestUtils getAdminTestUtils(String testName) { } @Override - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { - // "admin.createIndex()" for TEXT and BLOB columns fails (the "create index" query runs + protected boolean isCreateIndexOnTextColumnEnabled() { + // "admin.createIndex()" for TEXT column fails (the "create index" query runs // indefinitely) on the Db2 community edition docker version which we use for the CI. // However, the index creation is successful on Db2 hosted on IBM Cloud. // So we disable these tests until the issue with the Db2 community edition is resolved. return !JdbcTestUtils.isDb2(rdbEngine); } + + @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 1a73a8d28a..23d8c82717 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 @@ -95,11 +95,16 @@ protected AdminTestUtils getAdminTestUtils(String testName) { } @Override - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { - // "admin.createIndex()" for TEXT and BLOB columns fails (the "create index" query runs + protected boolean isCreateIndexOnTextColumnEnabled() { + // "admin.createIndex()" for TEXT column fails (the "create index" query runs // indefinitely) on the Db2 community edition docker version which we use for the CI. // However, the index creation is successful on Db2 hosted on IBM Cloud. // So we disable these tests until the issue with the Db2 community edition is resolved. return !JdbcTestUtils.isDb2(rdbEngine); } + + @Override + protected boolean isIndexOnBlobColumnSupported() { + return !JdbcTestUtils.isDb2(rdbEngine); + } } 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 b4e5fbe682..7f13f8d8a8 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -678,6 +678,18 @@ public enum CoreError implements ScalarDbError { "Mutations across multiple storages are not allowed. Mutations: %s", "", ""), + DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED( + Category.USER_ERROR, + "0216", + "With Db2, using a BLOB column as partition key, clustering key or secondary index is not supported.", + "", + ""), + DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED( + Category.USER_ERROR, + "0217", + "With Db2, setting an ordering on a BLOB column when using a cross partition scan operation is not supported. Ordering: %s", + "", + ""), // // Errors for the concurrency error category diff --git a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java index 2754f0e471..a3ef1f4b3e 100644 --- a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java +++ b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java @@ -8,6 +8,7 @@ import com.scalar.db.api.Operation; import com.scalar.db.api.Put; import com.scalar.db.api.Scan; +import com.scalar.db.api.Scan.Ordering; import com.scalar.db.api.ScanAll; import com.scalar.db.api.Selection; import com.scalar.db.api.Selection.Conjunction; @@ -19,11 +20,14 @@ import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.Column; +import com.scalar.db.io.DataType; import com.scalar.db.io.Key; +import com.scalar.db.storage.jdbc.JdbcConfig; import com.scalar.db.util.ScalarDbUtils; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import javax.annotation.concurrent.ThreadSafe; @@ -159,6 +163,17 @@ private void check(ScanAll scanAll) throws ExecutionException { throw new IllegalArgumentException( CoreError.OPERATION_CHECK_ERROR_CROSS_PARTITION_SCAN_ORDERING.buildMessage(scanAll)); } + Optional orderingOnBlobColumn = + scanAll.getOrderings().stream() + .filter( + ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB) + .findFirst(); + if (orderingOnBlobColumn.isPresent() + && new JdbcConfig(config).getJdbcUrl().startsWith("jdbc:db2:")) { + throw new IllegalArgumentException( + CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage( + orderingOnBlobColumn.get())); + } checkOrderings(scanAll, metadata); if (!config.isCrossPartitionScanFilteringEnabled() && !scanAll.getConjunctions().isEmpty()) { 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 cc52d349b2..626ab8ae75 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 @@ -65,7 +65,7 @@ public String getDataTypeForEngine(DataType scalarDbDataType) { case BIGINT: return "BIGINT"; case BLOB: - return "VARBINARY(32672)"; + return "BLOB(2G)"; case BOOLEAN: return "BOOLEAN"; case FLOAT: @@ -358,7 +358,8 @@ public String getDataTypeForKey(DataType dataType) { case TEXT: return "VARCHAR(" + keyColumnSize + ") NOT NULL"; case BLOB: - return "VARBINARY(" + keyColumnSize + ") NOT NULL"; + throw new UnsupportedOperationException( + CoreError.DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); default: return getDataTypeForEngine(dataType) + " NOT NULL"; } @@ -371,7 +372,8 @@ public String getDataTypeForSecondaryIndex(DataType dataType) { case TEXT: return "VARCHAR(" + keyColumnSize + ")"; case BLOB: - return "VARBINARY(" + keyColumnSize + ")"; + throw new UnsupportedOperationException( + CoreError.DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); default: return null; } diff --git a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java index e2a6e0febf..230d5e18d5 100644 --- a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java +++ b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Properties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -416,6 +417,42 @@ public void whenCheckingScanOperationWithEmptyOrdering_shouldNotThrowAnyExceptio assertThatCode(() -> operationChecker.check(scan)).doesNotThrowAnyException(); } + @Test + public void + whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrderingOnBlobColumnWithDb2_shouldThrowIllegalArgumentException() + throws ExecutionException { + // Arrange + when(metadataManager.getTableMetadata(any())) + .thenReturn( + TableMetadata.newBuilder() + .addColumn(PKEY1, DataType.BLOB) + .addColumn(COL1, DataType.INT) + .addColumn(COL2, DataType.BLOB) + .addPartitionKey(PKEY1) + .build()); + Scan scan = + Scan.newBuilder() + .namespace(NAMESPACE) + .table(TABLE_NAME) + .all() + .ordering(Scan.Ordering.asc(COL1)) + .ordering(Scan.Ordering.desc(COL2)) + .build(); + when(databaseConfig.isCrossPartitionScanEnabled()).thenReturn(true); + when(databaseConfig.isCrossPartitionScanOrderingEnabled()).thenReturn(true); + when(databaseConfig.getContactPoints()) + .thenReturn(Collections.singletonList("jdbc:db2://localhost:50000/test_db")); + when(databaseConfig.getStorage()).thenReturn("jdbc"); + when(databaseConfig.getProperties()).thenReturn(new Properties()); + + operationChecker = new OperationChecker(databaseConfig, metadataManager, storageInfoProvider); + + // Act Assert + assertThatThrownBy(() -> operationChecker.check(scan)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContainingAll("Db2", "ordering", COL2); + } + @Test public void whenCheckingScanOperationWithInvalidProjections_shouldThrowIllegalArgumentException() { 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 ee7480a99a..0574bc4a92 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 @@ -522,8 +522,8 @@ public void createTableInternal_ForSqlite_withInvalidTableName_ShouldThrowExecut public void createTableInternal_ForMysql_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.MYSQL, - "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(128),`c4` VARBINARY(128),`c2` BIGINT,`c5` INT,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c4` ASC))", - "CREATE INDEX `index_my_ns_foo_table_c4` ON `my_ns`.`foo_table` (`c4`)", + "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(128),`c5` INT,`c2` BIGINT,`c4` LONGBLOB,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c5` ASC))", + "CREATE INDEX `index_my_ns_foo_table_c5` ON `my_ns`.`foo_table` (`c5`)", "CREATE INDEX `index_my_ns_foo_table_c1` ON `my_ns`.`foo_table` (`c1`)"); } @@ -534,8 +534,8 @@ public void createTableInternal_ForMysql_ShouldCreateTableAndIndexes() throws SQ when(config.getMysqlVariableKeyColumnSize()).thenReturn(64); createTableInternal_ForX_CreateTableAndIndexes( new RdbEngineMysql(config), - "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(64),`c4` VARBINARY(64),`c2` BIGINT,`c5` INT,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c4` ASC))", - "CREATE INDEX `index_my_ns_foo_table_c4` ON `my_ns`.`foo_table` (`c4`)", + "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(64),`c5` INT,`c2` BIGINT,`c4` LONGBLOB,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c5` ASC))", + "CREATE INDEX `index_my_ns_foo_table_c5` ON `my_ns`.`foo_table` (`c5`)", "CREATE INDEX `index_my_ns_foo_table_c1` ON `my_ns`.`foo_table` (`c1`)"); } @@ -543,9 +543,9 @@ public void createTableInternal_ForMysql_ShouldCreateTableAndIndexes() throws SQ public void createTableInternal_ForPostgresql_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.POSTGRESQL, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN,\"c1\" VARCHAR(10485760),\"c4\" BYTEA,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE PRECISION,\"c7\" REAL,\"c8\" DATE,\"c9\" TIME,\"c10\" TIMESTAMP,\"c11\" TIMESTAMP WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN,\"c1\" VARCHAR(10485760),\"c5\" INT,\"c2\" BIGINT,\"c4\" BYTEA,\"c6\" DOUBLE PRECISION,\"c7\" REAL,\"c8\" DATE,\"c9\" TIME,\"c10\" TIMESTAMP,\"c11\" TIMESTAMP WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -553,8 +553,8 @@ public void createTableInternal_ForPostgresql_ShouldCreateTableAndIndexes() thro public void createTableInternal_ForSqlServer_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.SQL_SERVER, - "CREATE TABLE [my_ns].[foo_table]([c3] BIT,[c1] VARCHAR(8000),[c4] VARBINARY(8000),[c2] BIGINT,[c5] INT,[c6] FLOAT,[c7] FLOAT(24),[c8] DATE,[c9] TIME(6),[c10] DATETIME2(3),[c11] DATETIMEOFFSET(3), PRIMARY KEY ([c3] ASC,[c1] DESC,[c4] ASC))", - "CREATE INDEX [index_my_ns_foo_table_c4] ON [my_ns].[foo_table] ([c4])", + "CREATE TABLE [my_ns].[foo_table]([c3] BIT,[c1] VARCHAR(8000),[c5] INT,[c2] BIGINT,[c4] VARBINARY(8000),[c6] FLOAT,[c7] FLOAT(24),[c8] DATE,[c9] TIME(6),[c10] DATETIME2(3),[c11] DATETIMEOFFSET(3), PRIMARY KEY ([c3] ASC,[c1] DESC,[c5] ASC))", + "CREATE INDEX [index_my_ns_foo_table_c5] ON [my_ns].[foo_table] ([c5])", "CREATE INDEX [index_my_ns_foo_table_c1] ON [my_ns].[foo_table] ([c1])"); } @@ -562,10 +562,10 @@ public void createTableInternal_ForSqlServer_ShouldCreateTableAndIndexes() throw public void createTableInternal_ForOracle_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.ORACLE, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c4\" RAW(128),\"c2\" NUMBER(16),\"c5\" NUMBER(10),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c4\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -576,10 +576,10 @@ public void createTableInternal_ForOracle_ShouldCreateTableAndIndexes() throws S when(config.getOracleVariableKeyColumnSize()).thenReturn(64); createTableInternal_ForX_CreateTableAndIndexes( new RdbEngineOracle(config), - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(64),\"c4\" RAW(64),\"c2\" NUMBER(16),\"c5\" NUMBER(10),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c4\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(64),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -587,8 +587,8 @@ public void createTableInternal_ForOracle_ShouldCreateTableAndIndexes() throws S public void createTableInternal_ForSqlite_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.SQLITE, - "CREATE TABLE \"my_ns$foo_table\"(\"c3\" BOOLEAN,\"c1\" TEXT,\"c4\" BLOB,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE,\"c7\" FLOAT,\"c8\" INT,\"c9\" BIGINT,\"c10\" BIGINT,\"c11\" BIGINT, PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns$foo_table\" (\"c4\")", + "CREATE TABLE \"my_ns$foo_table\"(\"c3\" BOOLEAN,\"c1\" TEXT,\"c5\" INT,\"c2\" BIGINT,\"c4\" BLOB,\"c6\" DOUBLE,\"c7\" FLOAT,\"c8\" INT,\"c9\" BIGINT,\"c10\" BIGINT,\"c11\" BIGINT, PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE INDEX \"index_my_ns_foo_table_c5\" ON \"my_ns$foo_table\" (\"c5\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns$foo_table\" (\"c1\")"); } @@ -597,9 +597,9 @@ public void createTableInternal_ForDb2_ShouldCreateTableAndIndexes() throws SQLE when(config.getDb2VariableKeyColumnSize()).thenReturn(64); createTableInternal_ForX_CreateTableAndIndexes( new RdbEngineDb2(config), - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(64) NOT NULL,\"c4\" VARBINARY(64) NOT NULL,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(64) NOT NULL,\"c5\" INT NOT NULL,\"c2\" BIGINT,\"c4\" BLOB(2G),\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -609,9 +609,9 @@ public void createTableInternal_ForDb2_ShouldCreateTableAndIndexes() throws SQLE throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.DB2, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(128) NOT NULL,\"c4\" VARBINARY(128) NOT NULL,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(128) NOT NULL,\"c5\" INT NOT NULL,\"c2\" BIGINT,\"c4\" BLOB(2G),\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -630,7 +630,7 @@ private void createTableInternal_ForX_CreateTableAndIndexes( TableMetadata.newBuilder() .addPartitionKey("c3") .addClusteringKey("c1", Order.DESC) - .addClusteringKey("c4", Order.ASC) + .addClusteringKey("c5", Order.ASC) .addColumn("c1", DataType.TEXT) .addColumn("c2", DataType.BIGINT) .addColumn("c3", DataType.BOOLEAN) @@ -643,7 +643,7 @@ private void createTableInternal_ForX_CreateTableAndIndexes( .addColumn("c10", DataType.TIMESTAMP) .addColumn("c11", DataType.TIMESTAMPTZ) .addSecondaryIndex("c1") - .addSecondaryIndex("c4") + .addSecondaryIndex("c5") .build(); List mockedStatements = new ArrayList<>(); @@ -672,8 +672,8 @@ public void createTableInternal_IfNotExistsForMysql_ShouldCreateTableAndIndexesI throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.MYSQL, - "CREATE TABLE IF NOT EXISTS `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(128),`c4` VARBINARY(128),`c2` BIGINT,`c5` INT,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c4` ASC))", - "CREATE INDEX `index_my_ns_foo_table_c4` ON `my_ns`.`foo_table` (`c4`)", + "CREATE TABLE IF NOT EXISTS `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(128),`c5` INT,`c2` BIGINT,`c4` LONGBLOB,`c6` DOUBLE,`c7` REAL,`c8` DATE,`c9` TIME(6),`c10` DATETIME(3),`c11` DATETIME(3), PRIMARY KEY (`c3` ASC,`c1` DESC,`c5` ASC))", + "CREATE INDEX `index_my_ns_foo_table_c5` ON `my_ns`.`foo_table` (`c5`)", "CREATE INDEX `index_my_ns_foo_table_c1` ON `my_ns`.`foo_table` (`c1`)"); } @@ -682,9 +682,9 @@ public void createTableInternal_IfNotExistsForPostgresql_ShouldCreateTableAndInd throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.POSTGRESQL, - "CREATE TABLE IF NOT EXISTS \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN,\"c1\" VARCHAR(10485760),\"c4\" BYTEA,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE PRECISION,\"c7\" REAL,\"c8\" DATE,\"c9\" TIME,\"c10\" TIMESTAMP,\"c11\" TIMESTAMP WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE UNIQUE INDEX IF NOT EXISTS \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE TABLE IF NOT EXISTS \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN,\"c1\" VARCHAR(10485760),\"c5\" INT,\"c2\" BIGINT,\"c4\" BYTEA,\"c6\" DOUBLE PRECISION,\"c7\" REAL,\"c8\" DATE,\"c9\" TIME,\"c10\" TIMESTAMP,\"c11\" TIMESTAMP WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE UNIQUE INDEX IF NOT EXISTS \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -693,8 +693,8 @@ public void createTableInternal_IfNotExistsForSqlServer_ShouldCreateTableAndInde throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.SQL_SERVER, - "CREATE TABLE [my_ns].[foo_table]([c3] BIT,[c1] VARCHAR(8000),[c4] VARBINARY(8000),[c2] BIGINT,[c5] INT,[c6] FLOAT,[c7] FLOAT(24),[c8] DATE,[c9] TIME(6),[c10] DATETIME2(3),[c11] DATETIMEOFFSET(3), PRIMARY KEY ([c3] ASC,[c1] DESC,[c4] ASC))", - "CREATE INDEX [index_my_ns_foo_table_c4] ON [my_ns].[foo_table] ([c4])", + "CREATE TABLE [my_ns].[foo_table]([c3] BIT,[c1] VARCHAR(8000),[c5] INT,[c2] BIGINT,[c4] VARBINARY(8000),[c6] FLOAT,[c7] FLOAT(24),[c8] DATE,[c9] TIME(6),[c10] DATETIME2(3),[c11] DATETIMEOFFSET(3), PRIMARY KEY ([c3] ASC,[c1] DESC,[c5] ASC))", + "CREATE INDEX [index_my_ns_foo_table_c5] ON [my_ns].[foo_table] ([c5])", "CREATE INDEX [index_my_ns_foo_table_c1] ON [my_ns].[foo_table] ([c1])"); } @@ -703,10 +703,10 @@ public void createTableInternal_IfNotExistsForOracle_ShouldCreateTableAndIndexes throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.ORACLE, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c4\" RAW(128),\"c2\" NUMBER(16),\"c5\" NUMBER(10),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c4\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -715,8 +715,8 @@ public void createTableInternal_IfNotExistsForSqlite_ShouldCreateTableAndIndexes throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.SQLITE, - "CREATE TABLE IF NOT EXISTS \"my_ns$foo_table\"(\"c3\" BOOLEAN,\"c1\" TEXT,\"c4\" BLOB,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE,\"c7\" FLOAT,\"c8\" INT,\"c9\" BIGINT,\"c10\" BIGINT,\"c11\" BIGINT, PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c4\" ON \"my_ns$foo_table\" (\"c4\")", + "CREATE TABLE IF NOT EXISTS \"my_ns$foo_table\"(\"c3\" BOOLEAN,\"c1\" TEXT,\"c5\" INT,\"c2\" BIGINT,\"c4\" BLOB,\"c6\" DOUBLE,\"c7\" FLOAT,\"c8\" INT,\"c9\" BIGINT,\"c10\" BIGINT,\"c11\" BIGINT, PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c5\" ON \"my_ns$foo_table\" (\"c5\")", "CREATE INDEX IF NOT EXISTS \"index_my_ns_foo_table_c1\" ON \"my_ns$foo_table\" (\"c1\")"); } @@ -725,9 +725,9 @@ public void createTableInternal_IfNotExistsForDb2_ShouldCreateTableAndIndexesIfN throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.DB2, - "CREATE TABLE IF NOT EXISTS \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(128) NOT NULL,\"c4\" VARBINARY(128) NOT NULL,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", - "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", - "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", + "CREATE TABLE IF NOT EXISTS \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN NOT NULL,\"c1\" VARCHAR(128) NOT NULL,\"c5\" INT NOT NULL,\"c2\" BIGINT,\"c4\" BLOB(2G),\"c6\" DOUBLE,\"c7\" REAL,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3), PRIMARY KEY (\"c3\",\"c1\",\"c5\"))", + "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", + "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")"); } @@ -747,7 +747,7 @@ private void createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExist TableMetadata.newBuilder() .addPartitionKey("c3") .addClusteringKey("c1", Order.DESC) - .addClusteringKey("c4", Order.ASC) + .addClusteringKey("c5", Order.ASC) .addColumn("c1", DataType.TEXT) .addColumn("c2", DataType.BIGINT) .addColumn("c3", DataType.BOOLEAN) @@ -760,7 +760,7 @@ private void createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExist .addColumn("c10", DataType.TIMESTAMP) .addColumn("c11", DataType.TIMESTAMPTZ) .addSecondaryIndex("c1") - .addSecondaryIndex("c4") + .addSecondaryIndex("c5") .build(); List mockedStatements = new ArrayList<>(); @@ -1416,7 +1416,7 @@ public void createTable_ShouldCallCreateTableAndAddTableMetadataCorrectly(RdbEng TableMetadata.newBuilder() .addPartitionKey("c3") .addClusteringKey("c1", Order.DESC) - .addClusteringKey("c4", Order.ASC) + .addClusteringKey("c5", Order.ASC) .addColumn("c1", DataType.TEXT) .addColumn("c2", DataType.BIGINT) .addColumn("c3", DataType.BOOLEAN) @@ -1429,7 +1429,7 @@ public void createTable_ShouldCallCreateTableAndAddTableMetadataCorrectly(RdbEng .addColumn("c10", DataType.TIMESTAMP) .addColumn("c11", DataType.TIMESTAMPTZ) .addSecondaryIndex("c1") - .addSecondaryIndex("c4") + .addSecondaryIndex("c5") .build(); when(connection.createStatement()).thenReturn(mock(Statement.class)); when(dataSource.getConnection()).thenReturn(connection); @@ -1455,7 +1455,7 @@ public void repairTable_ShouldCallCreateTableAndAddTableMetadataCorrectly(RdbEng TableMetadata.newBuilder() .addPartitionKey("c3") .addClusteringKey("c1", Order.DESC) - .addClusteringKey("c4", Order.ASC) + .addClusteringKey("c5", Order.ASC) .addColumn("c1", DataType.TEXT) .addColumn("c2", DataType.BIGINT) .addColumn("c3", DataType.BOOLEAN) @@ -1468,7 +1468,7 @@ public void repairTable_ShouldCallCreateTableAndAddTableMetadataCorrectly(RdbEng .addColumn("c10", DataType.TIMESTAMP) .addColumn("c11", DataType.TIMESTAMPTZ) .addSecondaryIndex("c1") - .addSecondaryIndex("c4") + .addSecondaryIndex("c5") .build(); when(connection.createStatement()).thenReturn(mock(Statement.class)); when(dataSource.getConnection()).thenReturn(connection); @@ -1497,7 +1497,7 @@ public void repairTable_WhenTableAlreadyExistsWithoutIndex_ShouldCreateIndex(Rdb .addColumn("c1", DataType.INT) .addColumn("c2", DataType.TEXT) .addColumn("c3", DataType.BOOLEAN) - .addColumn("c4", DataType.BLOB) + .addColumn("c4", DataType.DATE) .addSecondaryIndex("c3") .addSecondaryIndex("c4") .build(); @@ -4077,6 +4077,70 @@ void hasDifferentClusteringOrders_GivenBothAscAndDescOrders_ShouldReturnTrue() { assertThat(hasDifferentClusteringOrders(metadata2)).isTrue(); } + @Test + void createTableInternal_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() { + // Arrange + TableMetadata metadata1 = + TableMetadata.newBuilder().addPartitionKey("pk").addColumn("pk", DataType.BLOB).build(); + TableMetadata metadata2 = + TableMetadata.newBuilder() + .addPartitionKey("pk") + .addClusteringKey("ck") + .addColumn("pk", DataType.INT) + .addColumn("ck", DataType.INT) + .addColumn("ck", DataType.BLOB) + .build(); + TableMetadata metadata3 = + TableMetadata.newBuilder() + .addPartitionKey("pk") + .addColumn("pk", DataType.INT) + .addColumn("col", DataType.BLOB) + .addSecondaryIndex("col") + .build(); + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.DB2); + + // Act Assert + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata1, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "key"); + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata2, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "key"); + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata3, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "index"); + } + + @Test + void createIndex_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() + throws SQLException { + // Arrange + String namespace = "my_ns"; + String table = "my_tbl"; + String indexColumn = "index_col"; + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.DB2); + + PreparedStatement selectStatement = mock(PreparedStatement.class); + ResultSet resultSet = + mockResultSet( + new SelectAllFromMetadataTableResultSetMocker.Row( + "pk", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.BLOB.toString(), null, null, false)); + when(selectStatement.executeQuery()).thenReturn(resultSet); + when(connection.prepareStatement(any())).thenReturn(selectStatement); + Statement statement = mock(Statement.class); + + when(dataSource.getConnection()).thenReturn(connection); + when(connection.createStatement()).thenReturn(statement); + + // Act Assert + assertThatThrownBy( + () -> admin.createIndex(namespace, table, indexColumn, Collections.emptyMap())) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "index"); + } + // Utility class used to mock ResultSet for a "select * from" query on the metadata table static class SelectAllFromMetadataTableResultSetMocker implements org.mockito.stubbing.Answer { 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 761555cc4b..fa2bca176a 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 @@ -655,31 +655,39 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre // Act admin.createIndex(namespace1, getTable4(), getColumnName2(), options); + if (isCreateIndexOnTextColumnEnabled()) { + admin.createIndex(namespace1, getTable4(), getColumnName3(), options); + } admin.createIndex(namespace1, getTable4(), getColumnName4(), options); admin.createIndex(namespace1, getTable4(), getColumnName5(), options); admin.createIndex(namespace1, getTable4(), getColumnName6(), options); if (isIndexOnBooleanColumnSupported()) { admin.createIndex(namespace1, getTable4(), getColumnName7(), options); } + if (isIndexOnBlobColumnSupported()) { + admin.createIndex(namespace1, getTable4(), getColumnName8(), options); + } admin.createIndex(namespace1, getTable4(), getColumnName10(), options); admin.createIndex(namespace1, getTable4(), getColumnName11(), options); admin.createIndex(namespace1, getTable4(), getColumnName12(), options); if (isTimestampTypeSupported()) { admin.createIndex(namespace1, getTable4(), getColumnName13(), options); } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - admin.createIndex(namespace1, getTable4(), getColumnName3(), options); - admin.createIndex(namespace1, getTable4(), getColumnName8(), options); - } // Assert assertThat(admin.indexExists(namespace1, getTable4(), getColumnName2())).isTrue(); + if (isCreateIndexOnTextColumnEnabled()) { + assertThat(admin.indexExists(namespace1, getTable4(), getColumnName3())).isTrue(); + } assertThat(admin.indexExists(namespace1, getTable4(), getColumnName4())).isTrue(); assertThat(admin.indexExists(namespace1, getTable4(), getColumnName5())).isTrue(); assertThat(admin.indexExists(namespace1, getTable4(), getColumnName6())).isTrue(); if (isIndexOnBooleanColumnSupported()) { assertThat(admin.indexExists(namespace1, getTable4(), getColumnName7())).isTrue(); } + if (isIndexOnBlobColumnSupported()) { + assertThat(admin.indexExists(namespace1, getTable4(), getColumnName8())).isTrue(); + } assertThat(admin.indexExists(namespace1, getTable4(), getColumnName9())).isTrue(); assertThat(admin.indexExists(namespace1, getTable4(), getColumnName10())).isTrue(); assertThat(admin.indexExists(namespace1, getTable4(), getColumnName11())).isTrue(); @@ -687,10 +695,6 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre if (isTimestampTypeSupported()) { assertThat(admin.indexExists(namespace1, getTable4(), getColumnName13())).isTrue(); } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - assertThat(admin.indexExists(namespace1, getTable4(), getColumnName3())).isTrue(); - assertThat(admin.indexExists(namespace1, getTable4(), getColumnName8())).isTrue(); - } Set actualSecondaryIndexNames = admin.getTableMetadata(namespace1, getTable4()).getSecondaryIndexNames(); @@ -712,9 +716,13 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre assertThat(actualSecondaryIndexNames).contains(getColumnName13()); indexCount++; } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - assertThat(actualSecondaryIndexNames).contains(getColumnName3(), getColumnName8()); - indexCount += 2; + if (isCreateIndexOnTextColumnEnabled()) { + assertThat(actualSecondaryIndexNames).contains(getColumnName3()); + indexCount++; + } + if (isIndexOnBlobColumnSupported()) { + assertThat(actualSecondaryIndexNames).contains(getColumnName8()); + indexCount++; } assertThat(actualSecondaryIndexNames).hasSize(indexCount); @@ -829,7 +837,6 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() .addSecondaryIndex(getColumnName4()) .addSecondaryIndex(getColumnName5()) .addSecondaryIndex(getColumnName6()) - .addSecondaryIndex(getColumnName8()) .addSecondaryIndex(getColumnName9()) .addSecondaryIndex(getColumnName10()) .addSecondaryIndex(getColumnName11()) @@ -841,6 +848,9 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() metadataBuilder.addColumn(getColumnName13(), DataType.TIMESTAMP); metadataBuilder.addSecondaryIndex(getColumnName13()); } + if (isIndexOnBlobColumnSupported()) { + metadataBuilder.addSecondaryIndex(getColumnName8()); + } admin.createTable(namespace1, getTable4(), metadataBuilder.build(), options); storage = storageFactory.getStorage(); PutBuilder.Buildable put = @@ -878,7 +888,9 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() if (isIndexOnBooleanColumnSupported()) { admin.dropIndex(namespace1, getTable4(), getColumnName7()); } - admin.dropIndex(namespace1, getTable4(), getColumnName8()); + if (isIndexOnBlobColumnSupported()) { + admin.dropIndex(namespace1, getTable4(), getColumnName8()); + } admin.dropIndex(namespace1, getTable4(), getColumnName10()); admin.dropIndex(namespace1, getTable4(), getColumnName11()); admin.dropIndex(namespace1, getTable4(), getColumnName12()); @@ -1047,11 +1059,15 @@ protected boolean isIndexOnBooleanColumnSupported() { return true; } + protected boolean isIndexOnBlobColumnSupported() { + return true; + } + protected boolean isTimestampTypeSupported() { return true; } - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { + protected boolean isCreateIndexOnTextColumnEnabled() { return true; } } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java index af0746e41b..ab9787512c 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java @@ -924,7 +924,13 @@ public void scan_WithOrderingForNonPrimaryColumns_ShouldReturnProperResult() List> testCallables = new ArrayList<>(); for (DataType firstColumnType : columnTypes.keySet()) { + if (firstColumnType == DataType.BLOB && !isOrderingOnBlobColumnSupported()) { + continue; + } for (DataType secondColumnType : columnTypes.get(firstColumnType)) { + if (secondColumnType == DataType.BLOB && !isOrderingOnBlobColumnSupported()) { + continue; + } testCallables.add( () -> { random.get().setSeed(seed); @@ -1252,4 +1258,8 @@ public String toString() { protected boolean isTimestampTypeSupported() { return true; } + + protected boolean isOrderingOnBlobColumnSupported() { + 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 8fb7037a66..c2cf82214a 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 @@ -559,31 +559,39 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre // Act admin.createIndex(namespace1, TABLE4, COL_NAME2, options); + if (isCreateIndexOnTextColumnEnabled()) { + admin.createIndex(namespace1, TABLE4, COL_NAME3, options); + } admin.createIndex(namespace1, TABLE4, COL_NAME4, options); admin.createIndex(namespace1, TABLE4, COL_NAME5, options); admin.createIndex(namespace1, TABLE4, COL_NAME6, options); if (isIndexOnBooleanColumnSupported()) { admin.createIndex(namespace1, TABLE4, COL_NAME7, options); } + if (isIndexOnBlobColumnSupported()) { + admin.createIndex(namespace1, TABLE4, COL_NAME8, options); + } admin.createIndex(namespace1, TABLE4, COL_NAME10, options); admin.createIndex(namespace1, TABLE4, COL_NAME11, options); admin.createIndex(namespace1, TABLE4, COL_NAME12, options); if (isTimestampTypeSupported()) { admin.createIndex(namespace1, TABLE4, COL_NAME13, options); } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - admin.createIndex(namespace1, TABLE4, COL_NAME3, options); - admin.createIndex(namespace1, TABLE4, COL_NAME8, options); - } // Assert assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME2)).isTrue(); + if (isCreateIndexOnTextColumnEnabled()) { + assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME3)).isTrue(); + } assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME4)).isTrue(); assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME5)).isTrue(); assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME6)).isTrue(); if (isIndexOnBooleanColumnSupported()) { assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME7)).isTrue(); } + if (isIndexOnBlobColumnSupported()) { + assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME8)).isTrue(); + } assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME9)).isTrue(); assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME10)).isTrue(); assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME11)).isTrue(); @@ -591,10 +599,6 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre if (isTimestampTypeSupported()) { assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME13)).isTrue(); } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME3)).isTrue(); - assertThat(admin.indexExists(namespace1, TABLE4, COL_NAME8)).isTrue(); - } Set actualSecondaryIndexNames = admin.getTableMetadata(namespace1, TABLE4).getSecondaryIndexNames(); @@ -609,9 +613,13 @@ public void createIndex_ForAllDataTypesWithExistingData_ShouldCreateIndexesCorre assertThat(actualSecondaryIndexNames).contains(COL_NAME13); indexCount++; } - if (isCreateIndexOnTextAndBlobColumnsEnabled()) { - assertThat(actualSecondaryIndexNames).contains(COL_NAME3, COL_NAME8); - indexCount += 2; + if (isCreateIndexOnTextColumnEnabled()) { + assertThat(actualSecondaryIndexNames).contains(COL_NAME3); + indexCount += 1; + } + if (isIndexOnBlobColumnSupported()) { + assertThat(actualSecondaryIndexNames).contains(COL_NAME8); + indexCount += 1; } assertThat(actualSecondaryIndexNames).hasSize(indexCount); @@ -718,7 +726,6 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() .addSecondaryIndex(COL_NAME4) .addSecondaryIndex(COL_NAME5) .addSecondaryIndex(COL_NAME6) - .addSecondaryIndex(COL_NAME8) .addSecondaryIndex(COL_NAME9) .addSecondaryIndex(COL_NAME9) .addSecondaryIndex(COL_NAME10) @@ -727,6 +734,9 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() if (isIndexOnBooleanColumnSupported()) { metadataBuilder = metadataBuilder.addSecondaryIndex(COL_NAME7); } + if (isIndexOnBlobColumnSupported()) { + metadataBuilder = metadataBuilder.addSecondaryIndex(COL_NAME8); + } if (isTimestampTypeSupported()) { metadataBuilder.addColumn(COL_NAME13, DataType.TIMESTAMP); metadataBuilder.addSecondaryIndex(COL_NAME13); @@ -767,7 +777,9 @@ public void dropIndex_ForAllDataTypesWithExistingData_ShouldDropIndexCorrectly() if (isIndexOnBooleanColumnSupported()) { admin.dropIndex(namespace1, TABLE4, COL_NAME7); } - admin.dropIndex(namespace1, TABLE4, COL_NAME8); + if (isIndexOnBlobColumnSupported()) { + admin.dropIndex(namespace1, TABLE4, COL_NAME8); + } admin.dropIndex(namespace1, TABLE4, COL_NAME10); admin.dropIndex(namespace1, TABLE4, COL_NAME11); admin.dropIndex(namespace1, TABLE4, COL_NAME12); @@ -1013,11 +1025,15 @@ protected boolean isIndexOnBooleanColumnSupported() { return true; } + protected boolean isIndexOnBlobColumnSupported() { + return true; + } + protected boolean isTimestampTypeSupported() { return true; } - protected boolean isCreateIndexOnTextAndBlobColumnsEnabled() { + protected boolean isCreateIndexOnTextColumnEnabled() { return true; } } From 47dd9d5d082579adf212c2c5f8aabc5c0701efbb Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Thu, 4 Sep 2025 14:51:50 +0900 Subject: [PATCH 2/7] Trigger Build From b199319da988c70b427b8f6dcee677df6359ec5b Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Fri, 19 Sep 2025 09:41:31 +0900 Subject: [PATCH 3/7] Update check for cross partition scan with ordering on blob column --- .../db/common/checker/OperationChecker.java | 19 +--- .../scalar/db/storage/jdbc/JdbcDatabase.java | 4 +- .../db/storage/jdbc/JdbcOperationChecker.java | 29 +++++ .../scalar/db/storage/jdbc/RdbEngineDb2.java | 18 ++++ .../db/storage/jdbc/RdbEngineStrategy.java | 13 +++ .../common/checker/OperationCheckerTest.java | 42 ++++---- .../jdbc/JdbcOperationCheckerTest.java | 46 ++++++++ .../db/storage/jdbc/RdbEngineDb2Test.java | 102 ++++++++++++++++++ 8 files changed, 235 insertions(+), 38 deletions(-) create mode 100644 core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java create mode 100644 core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java create mode 100644 core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineDb2Test.java diff --git a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java index a3ef1f4b3e..430915b5e2 100644 --- a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java +++ b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java @@ -8,7 +8,6 @@ import com.scalar.db.api.Operation; import com.scalar.db.api.Put; import com.scalar.db.api.Scan; -import com.scalar.db.api.Scan.Ordering; import com.scalar.db.api.ScanAll; import com.scalar.db.api.Selection; import com.scalar.db.api.Selection.Conjunction; @@ -20,14 +19,11 @@ import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.Column; -import com.scalar.db.io.DataType; import com.scalar.db.io.Key; -import com.scalar.db.storage.jdbc.JdbcConfig; import com.scalar.db.util.ScalarDbUtils; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; import javax.annotation.concurrent.ThreadSafe; @@ -163,17 +159,7 @@ private void check(ScanAll scanAll) throws ExecutionException { throw new IllegalArgumentException( CoreError.OPERATION_CHECK_ERROR_CROSS_PARTITION_SCAN_ORDERING.buildMessage(scanAll)); } - Optional orderingOnBlobColumn = - scanAll.getOrderings().stream() - .filter( - ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB) - .findFirst(); - if (orderingOnBlobColumn.isPresent() - && new JdbcConfig(config).getJdbcUrl().startsWith("jdbc:db2:")) { - throw new IllegalArgumentException( - CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage( - orderingOnBlobColumn.get())); - } + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata); checkOrderings(scanAll, metadata); if (!config.isCrossPartitionScanFilteringEnabled() && !scanAll.getConjunctions().isEmpty()) { @@ -183,6 +169,9 @@ && new JdbcConfig(config).getJdbcUrl().startsWith("jdbc:db2:")) { checkConjunctions(scanAll, metadata); } + protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) {}; + private void checkProjections(Selection selection, TableMetadata metadata) { for (String projection : selection.getProjections()) { if (!metadata.getColumnNames().contains(projection)) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java index 5fca851925..75e7ab92c4 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java @@ -59,8 +59,8 @@ public JdbcDatabase(DatabaseConfig databaseConfig) { TableMetadataManager tableMetadataManager = new TableMetadataManager(jdbcAdmin, databaseConfig.getMetadataCacheExpirationTimeSecs()); OperationChecker operationChecker = - new OperationChecker( - databaseConfig, tableMetadataManager, new StorageInfoProvider(jdbcAdmin)); + new JdbcOperationChecker( + databaseConfig, tableMetadataManager, new StorageInfoProvider(jdbcAdmin), rdbEngine); jdbcService = new JdbcService( diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java new file mode 100644 index 0000000000..93288804cf --- /dev/null +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java @@ -0,0 +1,29 @@ +package com.scalar.db.storage.jdbc; + +import com.scalar.db.api.ScanAll; +import com.scalar.db.api.TableMetadata; +import com.scalar.db.common.StorageInfoProvider; +import com.scalar.db.common.TableMetadataManager; +import com.scalar.db.common.checker.OperationChecker; +import com.scalar.db.config.DatabaseConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class JdbcOperationChecker extends OperationChecker { + private final RdbEngineStrategy rdbEngine; + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public JdbcOperationChecker( + DatabaseConfig config, + TableMetadataManager tableMetadataManager, + StorageInfoProvider storageInfoProvider, + RdbEngineStrategy rdbEngine) { + super(config, tableMetadataManager, storageInfoProvider); + this.rdbEngine = rdbEngine; + } + + @Override + protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) { + rdbEngine.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata); + } +} 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 626ab8ae75..421c3969a4 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 @@ -6,6 +6,8 @@ import com.google.common.collect.ImmutableMap; import com.ibm.db2.jcc.DB2BaseDataSource; import com.scalar.db.api.LikeExpression; +import com.scalar.db.api.Scan.Ordering; +import com.scalar.db.api.ScanAll; import com.scalar.db.api.TableMetadata; import com.scalar.db.common.CoreError; import com.scalar.db.exception.storage.ExecutionException; @@ -32,6 +34,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -511,4 +514,19 @@ private String getProjection(String columnName, DataType dataType) { } return enclose(columnName); } + + @Override + public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) { + Optional orderingOnBlobColumn = + scanAll.getOrderings().stream() + .filter( + ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB) + .findFirst(); + if (orderingOnBlobColumn.isPresent()) { + throw new UnsupportedOperationException( + CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage( + orderingOnBlobColumn.get())); + } + } } 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 39edb7ec60..56cfbd6f11 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 @@ -1,6 +1,7 @@ package com.scalar.db.storage.jdbc; import com.scalar.db.api.LikeExpression; +import com.scalar.db.api.ScanAll; import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; @@ -246,4 +247,16 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) throws SQLException { connection.setReadOnly(readOnly); } + + /** + * Throws an exception if a cross-partition scan operation with ordering on a blob column is + * specified and is not supported in the underlying storage. + * + * @param scanAll the ScanAll operation + * @param metadata the table metadata + * @throws UnsupportedOperationException if the ScanAll operation contains an ordering on a blob + * column, and it is not supported in the underlying storage + */ + default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) {} } diff --git a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java index 230d5e18d5..20adf13bc3 100644 --- a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java +++ b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java @@ -5,6 +5,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchException; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.scalar.db.api.ConditionBuilder; @@ -17,6 +19,7 @@ import com.scalar.db.api.Put; import com.scalar.db.api.Scan; import com.scalar.db.api.Scan.Ordering; +import com.scalar.db.api.ScanAll; import com.scalar.db.api.StorageInfo; import com.scalar.db.api.TableMetadata; import com.scalar.db.api.Update; @@ -32,7 +35,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Properties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -418,18 +420,17 @@ public void whenCheckingScanOperationWithEmptyOrdering_shouldNotThrowAnyExceptio } @Test - public void - whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrderingOnBlobColumnWithDb2_shouldThrowIllegalArgumentException() - throws ExecutionException { + public void whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrdering_shouldNotThrow() + throws ExecutionException { // Arrange - when(metadataManager.getTableMetadata(any())) - .thenReturn( - TableMetadata.newBuilder() - .addColumn(PKEY1, DataType.BLOB) - .addColumn(COL1, DataType.INT) - .addColumn(COL2, DataType.BLOB) - .addPartitionKey(PKEY1) - .build()); + TableMetadata metadata = + TableMetadata.newBuilder() + .addColumn(PKEY1, DataType.BLOB) + .addColumn(COL1, DataType.INT) + .addColumn(COL2, DataType.BLOB) + .addPartitionKey(PKEY1) + .build(); + when(metadataManager.getTableMetadata(any())).thenReturn(metadata); Scan scan = Scan.newBuilder() .namespace(NAMESPACE) @@ -440,17 +441,16 @@ public void whenCheckingScanOperationWithEmptyOrdering_shouldNotThrowAnyExceptio .build(); when(databaseConfig.isCrossPartitionScanEnabled()).thenReturn(true); when(databaseConfig.isCrossPartitionScanOrderingEnabled()).thenReturn(true); - when(databaseConfig.getContactPoints()) - .thenReturn(Collections.singletonList("jdbc:db2://localhost:50000/test_db")); - when(databaseConfig.getStorage()).thenReturn("jdbc"); - when(databaseConfig.getProperties()).thenReturn(new Properties()); - operationChecker = new OperationChecker(databaseConfig, metadataManager, storageInfoProvider); + operationChecker = + spy(new OperationChecker(databaseConfig, metadataManager, storageInfoProvider)); - // Act Assert - assertThatThrownBy(() -> operationChecker.check(scan)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContainingAll("Db2", "ordering", COL2); + // Act + operationChecker.check(scan); + + // Assert + verify(operationChecker) + .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported((ScanAll) scan, metadata); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java new file mode 100644 index 0000000000..2bf81e4dda --- /dev/null +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java @@ -0,0 +1,46 @@ +package com.scalar.db.storage.jdbc; + +import static org.mockito.Mockito.verify; + +import com.scalar.db.api.ScanAll; +import com.scalar.db.api.TableMetadata; +import com.scalar.db.common.StorageInfoProvider; +import com.scalar.db.common.TableMetadataManager; +import com.scalar.db.config.DatabaseConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class JdbcOperationCheckerTest { + + @Mock private DatabaseConfig databaseConfig; + @Mock private TableMetadataManager tableMetadataManager; + @Mock private StorageInfoProvider storageInfoProvider; + @Mock private RdbEngineStrategy rdbEngine; + @Mock private ScanAll scanAll; + @Mock private TableMetadata tableMetadata; + private JdbcOperationChecker operationChecker; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this).close(); + + operationChecker = + new JdbcOperationChecker( + databaseConfig, tableMetadataManager, storageInfoProvider, rdbEngine); + } + + @Test + public void + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_ShouldDelegateToRdbEngine() { + // Arrange + // Act + operationChecker.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + scanAll, tableMetadata); + + // Assert + verify(rdbEngine) + .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, tableMetadata); + } +} diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineDb2Test.java b/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineDb2Test.java new file mode 100644 index 0000000000..afc2295300 --- /dev/null +++ b/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineDb2Test.java @@ -0,0 +1,102 @@ +package com.scalar.db.storage.jdbc; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +import com.scalar.db.api.Scan; +import com.scalar.db.api.ScanAll; +import com.scalar.db.api.TableMetadata; +import com.scalar.db.io.DataType; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class RdbEngineDb2Test { + + @Mock private ScanAll scanAll; + @Mock private TableMetadata metadata; + + private RdbEngineDb2 rdbEngineDb2; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this).close(); + rdbEngineDb2 = new RdbEngineDb2(); + } + + @Test + public void + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithBlobOrdering_ShouldThrowException() { + // Arrange + Scan.Ordering blobOrdering = Scan.Ordering.asc("blob_column"); + Scan.Ordering intOrdering = Scan.Ordering.desc("int_column"); + + when(scanAll.getOrderings()).thenReturn(Arrays.asList(intOrdering, blobOrdering)); + when(metadata.getColumnDataType("blob_column")).thenReturn(DataType.BLOB); + when(metadata.getColumnDataType("int_column")).thenReturn(DataType.INT); + + // Act & Assert + assertThatThrownBy( + () -> + rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + scanAll, metadata)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContaining("blob_column"); + } + + @Test + public void + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithoutBlobOrdering_ShouldNotThrowException() { + // Arrange + Scan.Ordering intOrdering = Scan.Ordering.asc("int_column"); + Scan.Ordering textOrdering = Scan.Ordering.desc("text_column"); + + when(scanAll.getOrderings()).thenReturn(Arrays.asList(intOrdering, textOrdering)); + when(metadata.getColumnDataType("int_column")).thenReturn(DataType.INT); + when(metadata.getColumnDataType("text_column")).thenReturn(DataType.TEXT); + + // Act & Assert + assertThatCode( + () -> + rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + scanAll, metadata)) + .doesNotThrowAnyException(); + } + + @Test + public void + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithNoOrderings_ShouldNotThrowException() { + // Arrange + when(scanAll.getOrderings()).thenReturn(Arrays.asList()); + + // Act & Assert + assertThatCode( + () -> + rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + scanAll, metadata)) + .doesNotThrowAnyException(); + } + + @Test + public void + throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithMultipleBlobOrderings_ShouldThrowForFirst() { + // Arrange + Scan.Ordering blobOrdering1 = Scan.Ordering.asc("blob_column1"); + Scan.Ordering blobOrdering2 = Scan.Ordering.desc("blob_column2"); + + when(scanAll.getOrderings()).thenReturn(Arrays.asList(blobOrdering1, blobOrdering2)); + when(metadata.getColumnDataType("blob_column1")).thenReturn(DataType.BLOB); + when(metadata.getColumnDataType("blob_column2")).thenReturn(DataType.BLOB); + + // Act & Assert + assertThatThrownBy( + () -> + rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + scanAll, metadata)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContaining("blob_column1"); + } +} From 396ad319a66c0981b85f8c08081652047c09e8d7 Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Fri, 19 Sep 2025 14:07:32 +0900 Subject: [PATCH 4/7] Apply brfrn169 suggestion --- .../db/common/checker/OperationChecker.java | 8 ++------ .../db/storage/jdbc/JdbcOperationChecker.java | 4 ++-- .../db/common/checker/OperationCheckerTest.java | 15 ++++----------- .../db/storage/jdbc/JdbcOperationCheckerTest.java | 6 ++---- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java index 430915b5e2..8a977be66f 100644 --- a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java +++ b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java @@ -159,8 +159,7 @@ private void check(ScanAll scanAll) throws ExecutionException { throw new IllegalArgumentException( CoreError.OPERATION_CHECK_ERROR_CROSS_PARTITION_SCAN_ORDERING.buildMessage(scanAll)); } - throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata); - checkOrderings(scanAll, metadata); + checkOrderingsForScanAll(scanAll, metadata); if (!config.isCrossPartitionScanFilteringEnabled() && !scanAll.getConjunctions().isEmpty()) { throw new IllegalArgumentException( @@ -169,9 +168,6 @@ private void check(ScanAll scanAll) throws ExecutionException { checkConjunctions(scanAll, metadata); } - protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( - ScanAll scanAll, TableMetadata metadata) {}; - private void checkProjections(Selection selection, TableMetadata metadata) { for (String projection : selection.getProjections()) { if (!metadata.getColumnNames().contains(projection)) { @@ -262,7 +258,7 @@ private void checkOrderings(Scan scan, TableMetadata metadata) { } } - private void checkOrderings(ScanAll scanAll, TableMetadata metadata) { + protected void checkOrderingsForScanAll(ScanAll scanAll, TableMetadata metadata) { for (Scan.Ordering ordering : scanAll.getOrderings()) { if (!metadata.getColumnNames().contains(ordering.getColumnName())) { throw new IllegalArgumentException( diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java index 93288804cf..8cf440d4a7 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java @@ -22,8 +22,8 @@ public JdbcOperationChecker( } @Override - protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( - ScanAll scanAll, TableMetadata metadata) { + protected void checkOrderingsForScanAll(ScanAll scanAll, TableMetadata metadata) { + super.checkOrderingsForScanAll(scanAll, metadata); rdbEngine.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata); } } diff --git a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java index 20adf13bc3..e734368dca 100644 --- a/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java +++ b/core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java @@ -4,9 +4,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.scalar.db.api.ConditionBuilder; @@ -19,7 +18,6 @@ import com.scalar.db.api.Put; import com.scalar.db.api.Scan; import com.scalar.db.api.Scan.Ordering; -import com.scalar.db.api.ScanAll; import com.scalar.db.api.StorageInfo; import com.scalar.db.api.TableMetadata; import com.scalar.db.api.Update; @@ -442,15 +440,10 @@ public void whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrderin when(databaseConfig.isCrossPartitionScanEnabled()).thenReturn(true); when(databaseConfig.isCrossPartitionScanOrderingEnabled()).thenReturn(true); - operationChecker = - spy(new OperationChecker(databaseConfig, metadataManager, storageInfoProvider)); - - // Act - operationChecker.check(scan); + operationChecker = new OperationChecker(databaseConfig, metadataManager, storageInfoProvider); - // Assert - verify(operationChecker) - .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported((ScanAll) scan, metadata); + // Act Assert + assertDoesNotThrow(() -> operationChecker.check(scan)); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java index 2bf81e4dda..1793086e2e 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java @@ -32,12 +32,10 @@ public void setUp() throws Exception { } @Test - public void - throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_ShouldDelegateToRdbEngine() { + public void checkOrderingsForScanAll_ShouldInvokeFurtherCheckOnRdbEngine() { // Arrange // Act - operationChecker.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( - scanAll, tableMetadata); + operationChecker.checkOrderingsForScanAll(scanAll, tableMetadata); // Assert verify(rdbEngine) From 897a4b6600b0703e3f17d43541d3f59287388c8c Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Mon, 22 Sep 2025 10:52:19 +0900 Subject: [PATCH 5/7] Rename error codes --- core/src/main/java/com/scalar/db/common/CoreError.java | 4 ++-- .../java/com/scalar/db/storage/jdbc/RdbEngineDb2.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 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 55ab5ef94d..3b830bfd73 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -736,13 +736,13 @@ public enum CoreError implements ScalarDbError { "The BOOLEAN type is not supported for index columns in DynamoDB. Column: %s", "", ""), - DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED( + JDBC_DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED( Category.USER_ERROR, "0227", "With Db2, using a BLOB column as partition key, clustering key or secondary index is not supported.", "", ""), - DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED( + JDBC_DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED( Category.USER_ERROR, "0228", "With Db2, setting an ordering on a BLOB column when using a cross partition scan operation is not supported. Ordering: %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 810ea66a53..aed243dc89 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 @@ -390,7 +390,7 @@ public String getDataTypeForKey(DataType dataType) { return "VARCHAR(" + keyColumnSize + ") NOT NULL"; case BLOB: throw new UnsupportedOperationException( - CoreError.DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); + CoreError.JDBC_DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); default: return getDataTypeForEngine(dataType) + " NOT NULL"; } @@ -404,7 +404,7 @@ public String getDataTypeForSecondaryIndex(DataType dataType) { return "VARCHAR(" + keyColumnSize + ")"; case BLOB: throw new UnsupportedOperationException( - CoreError.DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); + CoreError.JDBC_DB2_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); default: return null; } @@ -563,8 +563,8 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( .findFirst(); if (orderingOnBlobColumn.isPresent()) { throw new UnsupportedOperationException( - CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage( - orderingOnBlobColumn.get())); + CoreError.JDBC_DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED + .buildMessage(orderingOnBlobColumn.get())); } } } From 795b2e621348b13346b08a3c2014432a441ae4a4 Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Mon, 22 Sep 2025 10:58:25 +0900 Subject: [PATCH 6/7] Fix unit test --- core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java | 1 - 1 file changed, 1 deletion(-) 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 7165a52b62..ec62a3892b 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 @@ -4381,7 +4381,6 @@ void createTableInternal_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperat .addPartitionKey("pk") .addClusteringKey("ck") .addColumn("pk", DataType.INT) - .addColumn("ck", DataType.INT) .addColumn("ck", DataType.BLOB) .build(); TableMetadata metadata3 = From 7a0675d2d629c6daa8f55366137453832cb68ab9 Mon Sep 17 00:00:00 2001 From: Vincent Guilpain Date: Wed, 24 Sep 2025 15:14:08 +0900 Subject: [PATCH 7/7] For Oracle, change the data type for BLOB column to support storing up to 2GB --- .github/workflows/ci.yaml | 311 +++--------------- core/build.gradle | 3 + ...tAdminIntegrationTestWithJdbcDatabase.java | 2 +- ...bcAdminCaseSensitivityIntegrationTest.java | 2 +- .../jdbc/JdbcAdminImportTestUtils.java | 2 +- .../jdbc/JdbcAdminIntegrationTest.java | 2 +- ...dbcDatabaseColumnValueIntegrationTest.java | 185 +++++++++++ ...aseConditionalMutationIntegrationTest.java | 5 + ...baseCrossPartitionScanIntegrationTest.java | 7 +- ...tipleClusteringKeyScanIntegrationTest.java | 2 +- ...seMultiplePartitionKeyIntegrationTest.java | 2 +- ...DatabaseSecondaryIndexIntegrationTest.java | 6 +- ...ingleClusteringKeyScanIntegrationTest.java | 2 +- ...baseSinglePartitionKeyIntegrationTest.java | 2 +- ...nAdminIntegrationTestWithJdbcDatabase.java | 2 +- .../JdbcTransactionAdminIntegrationTest.java | 2 +- .../java/com/scalar/db/common/CoreError.java | 18 + .../db/common/checker/OperationChecker.java | 2 +- .../db/storage/jdbc/JdbcOperationChecker.java | 10 + .../db/storage/jdbc/RdbEngineOracle.java | 86 ++++- .../db/storage/jdbc/RdbEngineStrategy.java | 18 + .../jdbc/query/PreparedStatementBinder.java | 4 +- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 74 ++++- .../jdbc/JdbcOperationCheckerTest.java | 67 +++- ...StorageColumnValueIntegrationTestBase.java | 36 +- ...onditionalMutationIntegrationTestBase.java | 198 ++++++----- ...CrossPartitionScanIntegrationTestBase.java | 38 ++- 27 files changed, 688 insertions(+), 400 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index db0c6b3b74..f0aacff56c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -137,13 +137,7 @@ jobs: ports: - 9042:9042 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -179,7 +173,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestCassandra' task - run: ./gradlew integrationTestCassandra ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestCassandra --tests CassandraColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -201,13 +195,7 @@ jobs: ports: - 9042:9042 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -243,7 +231,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestCassandra' task - run: ./gradlew integrationTestCassandra ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestCassandra --tests CassandraColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -261,13 +249,7 @@ jobs: SET_UP_INT_TEST_RUNTIME_JDK_WHEN_NOT_ORACLE_8_OR_11: "${{ (github.event_name == 'workflow_dispatch' && !(inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' && inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'temurin') && !(inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' && (inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' || inputs.INT_TEST_JAVA_RUNTIME_VERSION == '11'))) && 'true' || 'false' }}" SET_UP_INT_TEST_RUNTIME_ORACLE_JDK_8_OR_11: "${{ ((inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' || inputs.INT_TEST_JAVA_RUNTIME_VERSION == '11') && inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle') && 'true' || 'false' }}" - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -341,7 +323,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestCosmos' task - run: ./gradlew.bat integrationTestCosmos "-Dscalardb.cosmos.uri=https://localhost:8081/" "-Dscalardb.cosmos.password=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" "-Dfile.encoding=UTF-8" ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew.bat integrationTestCosmos "--tests" "CosmosColumnValueIntegrationTest" "-Dscalardb.cosmos.uri=https://localhost:8081/" "-Dscalardb.cosmos.password=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" "-Dfile.encoding=UTF-8" - name: Upload Gradle test reports if: always() @@ -360,13 +342,7 @@ jobs: ports: - 8000:8000 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -402,7 +378,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestDynamo' task - run: ./gradlew integrationTestDynamo ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestDynamo --tests DynamoDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -415,13 +391,7 @@ jobs: name: MySQL 5.7 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run MySQL 5.7 @@ -461,7 +431,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql - name: Upload Gradle test reports if: always() @@ -474,13 +444,7 @@ jobs: name: MySQL 8.0 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run MySQL 8.0 @@ -520,7 +484,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql - name: Upload Gradle test reports if: always() @@ -533,13 +497,7 @@ jobs: name: MySQL 8.4 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run MySQL 8.4 @@ -579,7 +537,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:mysql://localhost:3306/ -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql - name: Upload Gradle test reports if: always() @@ -601,13 +559,7 @@ jobs: ports: - 5432:5432 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -643,7 +595,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -665,13 +617,7 @@ jobs: ports: - 5432:5432 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -707,7 +653,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -729,13 +675,7 @@ jobs: ports: - 5432:5432 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -771,7 +711,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -793,13 +733,7 @@ jobs: ports: - 5432:5432 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -835,7 +769,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -857,13 +791,7 @@ jobs: ports: - 5432:5432 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -899,7 +827,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest - name: Upload Gradle test reports if: always() @@ -921,13 +849,7 @@ jobs: ports: - 1521:1521 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -963,7 +885,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/ORCLPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/ORCLPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle - name: Upload Gradle test reports if: always() @@ -992,13 +914,7 @@ jobs: --health-timeout 5s --health-retries 120 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1034,7 +950,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/XEPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/XEPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle - name: Upload Gradle test reports if: always() @@ -1047,13 +963,7 @@ jobs: name: Oracle 23 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Free up ~14GB of disk space by removing the Android SDK @@ -1118,7 +1028,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/FREEPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:oracle:thin:@//localhost:1521/FREEPDB1 -Dscalardb.jdbc.username=SYSTEM -Dscalardb.jdbc.password=Oracle - name: Stop Oracle 23 container if: always() @@ -1140,13 +1050,7 @@ jobs: SET_UP_INT_TEST_RUNTIME_JDK_WHEN_NOT_ORACLE_8_OR_11: "${{ (github.event_name == 'workflow_dispatch' && !(inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' && inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'temurin') && !(inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' && (inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' || inputs.INT_TEST_JAVA_RUNTIME_VERSION == '11'))) && 'true' || 'false' }}" SET_UP_INT_TEST_RUNTIME_ORACLE_JDK_8_OR_11: "${{ ((inputs.INT_TEST_JAVA_RUNTIME_VERSION == '8' || inputs.INT_TEST_JAVA_RUNTIME_VERSION == '11') && inputs.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle') && 'true' || 'false' }}" - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1203,7 +1107,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew.bat integrationTestJdbc "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" "-Dfile.encoding=UTF-8" "-Dscalardb.jdbc.username=sa" "-Dscalardb.jdbc.password=SqlServer17" ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew.bat integrationTestJdbc "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" "-Dfile.encoding=UTF-8" "-Dscalardb.jdbc.username=sa" "-Dscalardb.jdbc.password=SqlServer17" - name: Upload Gradle test reports if: always() @@ -1227,13 +1131,7 @@ jobs: - 1433:1433 options: --name sqlserver19 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1273,7 +1171,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" -Dscalardb.jdbc.username=sa -Dscalardb.jdbc.password=SqlServer19 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" -Dscalardb.jdbc.username=sa -Dscalardb.jdbc.password=SqlServer19 - name: Upload Gradle test reports if: always() @@ -1297,13 +1195,7 @@ jobs: - 1433:1433 options: --name sqlserver22 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1343,7 +1235,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" -Dscalardb.jdbc.username=sa -Dscalardb.jdbc.password=SqlServer22 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest "-Dscalardb.jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=test_db;encrypt=true;trustServerCertificate=true" -Dscalardb.jdbc.username=sa -Dscalardb.jdbc.password=SqlServer22 - name: Upload Gradle test reports if: always() @@ -1356,13 +1248,7 @@ jobs: name: SQLite 3 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1401,7 +1287,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:sqlite:integration.sqlite3?busy_timeout=50000 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:sqlite:integration.sqlite3?busy_timeout=50000 - name: Upload Gradle test reports if: always() @@ -1414,13 +1300,7 @@ jobs: name: MariaDB 10 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run MariaDB 10.11 @@ -1460,7 +1340,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mariadb://localhost:3306 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:mariadb://localhost:3306 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql - name: Upload Gradle test reports if: always() @@ -1473,13 +1353,7 @@ jobs: name: MariaDB 11.4 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run MariaDB 11.4 @@ -1519,7 +1393,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mariadb://localhost:3306 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:mariadb://localhost:3306 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password=mysql - name: Upload Gradle test reports if: always() @@ -1532,13 +1406,7 @@ jobs: name: YugabyteDB 2 integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - name: Run YugabyteDB 2 @@ -1578,7 +1446,7 @@ jobs: uses: gradle/actions/setup-gradle@v4 - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:yugabytedb://localhost:5433/?load-balance=any -Dscalardb.jdbc.username=yugabyte -Dscalardb.jdbc.password=yugabyte -Dscalar.db.jdbc.connection_pool.max_total=12 -Dscalar.db.jdbc.table_metadata.connection_pool.max_total=4 -Dscalar.db.jdbc.admin.connection_pool.max_total=4 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url=jdbc:yugabytedb://localhost:5433/?load-balance=any -Dscalardb.jdbc.username=yugabyte -Dscalardb.jdbc.password=yugabyte -Dscalar.db.jdbc.connection_pool.max_total=12 -Dscalar.db.jdbc.table_metadata.connection_pool.max_total=4 -Dscalar.db.jdbc.admin.connection_pool.max_total=4 - name: Upload Gradle test reports if: always() @@ -1603,13 +1471,7 @@ jobs: - 50000:50000 options: --privileged --name db2 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1655,7 +1517,7 @@ jobs: echo "Container is ready" - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url="jdbc:db2://localhost:50000/test_db" -Dscalardb.jdbc.username=db2inst1 -Dscalardb.jdbc.password=db2inst1 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url="jdbc:db2://localhost:50000/test_db" -Dscalardb.jdbc.username=db2inst1 -Dscalardb.jdbc.password=db2inst1 - name: Upload Gradle test reports if: always() @@ -1680,13 +1542,7 @@ jobs: - 50000:50000 options: --privileged --name db2 - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true + steps: - uses: actions/checkout@v5 @@ -1732,7 +1588,7 @@ jobs: echo "Container is ready" - name: Execute Gradle 'integrationTestJdbc' task - run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url="jdbc:db2://localhost:50000/test_db" -Dscalardb.jdbc.username=db2inst1 -Dscalardb.jdbc.password=db2inst1 ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + run: ./gradlew core:integrationTestJdbc --tests JdbcDatabaseColumnValueIntegrationTest -Dscalardb.jdbc.url="jdbc:db2://localhost:50000/test_db" -Dscalardb.jdbc.username=db2inst1 -Dscalardb.jdbc.password=db2inst1 - name: Upload Gradle test reports if: always() @@ -1741,74 +1597,3 @@ jobs: name: db2_12.1_integration_test_reports_${{ matrix.mode.label }} path: core/build/reports/tests/integrationTestJdbc - integration-test-for-multi-storage: - name: Multi-storage integration test (${{ matrix.mode.label }}) - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:17-alpine - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - - cassandra: - image: cassandra:3.11 - env: - MAX_HEAP_SIZE: 2048m - HEAP_NEWSIZE: 512m - ports: - - 9042:9042 - - strategy: - matrix: - mode: - - label: default - group_commit_enabled: false - - label: with_group_commit - group_commit_enabled: true - - steps: - - uses: actions/checkout@v5 - - - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) - uses: actions/setup-java@v5 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_VENDOR }} - - - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }}) to run integration test - uses: actions/setup-java@v5 - if: ${{ env.SET_UP_INT_TEST_RUNTIME_NON_ORACLE_JDK == 'true'}} - with: - java-version: ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} - distribution: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }} - - - name: Login to Oracle container registry - uses: docker/login-action@v3 - if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} - with: - registry: container-registry.oracle.com - username: ${{ secrets.OCR_USERNAME }} - password: ${{ secrets.OCR_TOKEN }} - - - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (oracle) to run the integration test - if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} - run: | - container_id=$(docker create "container-registry.oracle.com/java/jdk:${{ env.INT_TEST_JAVA_RUNTIME_VERSION }}") - docker cp -L "$container_id:/usr/java/default" /usr/lib/jvm/oracle-jdk && docker rm "$container_id" - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Execute Gradle 'integrationTestMultiStorage' task - run: ./gradlew integrationTestMultiStorage ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} - - - name: Upload Gradle test reports - uses: actions/upload-artifact@v4 - if: always() - with: - name: multi_storage_integration_test_reports_${{ matrix.mode.label }} - path: core/build/reports/tests/integrationTestMultiStorage diff --git a/core/build.gradle b/core/build.gradle index d102daef09..97e8465d2c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -208,6 +208,7 @@ task integrationTestCassandra(type: Test) { options { systemProperties(System.getProperties().findAll{it.key.toString().startsWith("scalardb")}) } + maxHeapSize = "6g" } task integrationTestCosmos(type: Test) { @@ -237,6 +238,7 @@ task integrationTestDynamo(type: Test) { options { systemProperties(System.getProperties().findAll{it.key.toString().startsWith("scalardb")}) } + maxHeapSize = "6g" maxParallelForks = 10 } @@ -249,6 +251,7 @@ task integrationTestJdbc(type: Test) { options { systemProperties(System.getProperties().findAll{it.key.toString().startsWith("scalardb")}) } + maxHeapSize = "6g" // maximum heap size } task integrationTestMultiStorage(type: Test) { 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..bd1e7b52e3 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 @@ -92,6 +92,6 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Override protected boolean isIndexOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !(JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(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..8a450dc3ad 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 @@ -100,6 +100,6 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Override protected boolean isIndexOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !(JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine)); } } 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..238d36561a 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 @@ -482,7 +482,7 @@ private LinkedHashMap prepareColumnsForDb2() { columns.put("col17", "NCLOB(512)"); columns.put("col18", "BINARY(5)"); columns.put("col19", "VARBINARY(512)"); - columns.put("col20", "BLOB(1024)"); + columns.put("col20", "BLOB(2G)"); columns.put("col21", "CHAR(5) FOR BIT DATA"); columns.put("col22", "VARCHAR(512) FOR BIT DATA"); columns.put("col23", "DATE"); 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..46812fd834 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 @@ -99,6 +99,6 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Override protected boolean isIndexOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !(JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine)); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseColumnValueIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseColumnValueIntegrationTest.java index 970aea8dc7..1b2adab9ad 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseColumnValueIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseColumnValueIntegrationTest.java @@ -1,12 +1,38 @@ package com.scalar.db.storage.jdbc; +import static org.assertj.core.api.Assertions.assertThat; + import com.scalar.db.api.DistributedStorageColumnValueIntegrationTestBase; +import com.scalar.db.api.Get; +import com.scalar.db.api.Put; +import com.scalar.db.api.PutBuilder; +import com.scalar.db.api.Result; +import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.io.BigIntColumn; +import com.scalar.db.io.BlobColumn; +import com.scalar.db.io.BooleanColumn; import com.scalar.db.io.Column; import com.scalar.db.io.DataType; +import com.scalar.db.io.DateColumn; +import com.scalar.db.io.DoubleColumn; +import com.scalar.db.io.FloatColumn; +import com.scalar.db.io.IntColumn; +import com.scalar.db.io.Key; +import com.scalar.db.io.TextColumn; +import com.scalar.db.io.TimeColumn; +import com.scalar.db.io.TimestampColumn; +import com.scalar.db.io.TimestampTZColumn; import com.scalar.db.util.TestUtils; +import java.util.Optional; import java.util.Properties; import java.util.Random; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class JdbcDatabaseColumnValueIntegrationTest extends DistributedStorageColumnValueIntegrationTestBase { @@ -68,4 +94,163 @@ protected Column getColumnWithMaxValue(String columnName, DataType dataType) } return super.getColumnWithMaxValue(columnName, dataType); } + + @ParameterizedTest() + @MethodSource("provideBlobSizes") + public void put_largeBlobData_ShouldWorkCorrectly(int blobSize, String humanReadableBlobSize) + throws ExecutionException { + String tableName = TABLE + "_large_single_blob_single"; + try { + // Arrange + TableMetadata.Builder metadata = + TableMetadata.newBuilder() + .addColumn(COL_NAME1, DataType.INT) + .addColumn(COL_NAME2, DataType.BLOB) + .addPartitionKey(COL_NAME1); + + admin.createTable(namespace, tableName, metadata.build(), true, getCreationOptions()); + admin.truncateTable(namespace, tableName); + byte[] blobData = createLargeBlob(blobSize); + Put put = + Put.newBuilder() + .namespace(namespace) + .table(tableName) + .partitionKey(Key.ofInt(COL_NAME1, 1)) + .blobValue(COL_NAME2, blobData) + .build(); + + // Act + storage.put(put); + + // Assert + Optional optionalResult = + storage.get( + Get.newBuilder() + .namespace(namespace) + .table(tableName) + .partitionKey(Key.ofInt(COL_NAME1, 1)) + .build()); + assertThat(optionalResult).isPresent(); + Result result = optionalResult.get(); + assertThat(result.getColumns().get(COL_NAME2).getBlobValueAsBytes()).isEqualTo(blobData); + } finally { + admin.dropTable(namespace, tableName, true); + } + } + + Stream provideBlobSizes() { + return Stream.of( + Arguments.of(32_766, "32,766 KB"), + Arguments.of(32_767, "32,767 KB"), + Arguments.of(100_000_000, "100 MB")); + } + + @Test + public void put_largeBlobData_WithMultipleBlobColumnsShouldWorkCorrectly() + throws ExecutionException { + String tableName = TABLE + "_large_multiples_blob"; + try { + // Arrange + TableMetadata.Builder metadata = + TableMetadata.newBuilder() + .addColumn(COL_NAME1, DataType.INT) + .addColumn(COL_NAME2, DataType.BLOB) + .addColumn(COL_NAME3, DataType.BLOB) + .addPartitionKey(COL_NAME1); + + admin.createTable(namespace, tableName, metadata.build(), true, getCreationOptions()); + admin.truncateTable(namespace, tableName); + byte[] blobDataCol2 = createLargeBlob(32_766); + byte[] blobDataCol3 = createLargeBlob(5000); + Put put = + Put.newBuilder() + .namespace(namespace) + .table(tableName) + .partitionKey(Key.ofInt(COL_NAME1, 1)) + .blobValue(COL_NAME2, blobDataCol2) + .blobValue(COL_NAME3, blobDataCol3) + .build(); + + // Act + storage.put(put); + + // Assert + Optional optionalResult = + storage.get( + Get.newBuilder() + .namespace(namespace) + .table(tableName) + .partitionKey(Key.ofInt(COL_NAME1, 1)) + .build()); + assertThat(optionalResult).isPresent(); + Result result = optionalResult.get(); + assertThat(result.getColumns().get(COL_NAME2).getBlobValueAsBytes()).isEqualTo(blobDataCol2); + assertThat(result.getColumns().get(COL_NAME3).getBlobValueAsBytes()).isEqualTo(blobDataCol3); + } finally { + admin.dropTable(namespace, tableName, true); + } + } + + @Test + public void put_largeBlobData_WithAllColumnsTypesShouldWorkCorrectly() throws ExecutionException { + // Arrange + IntColumn partitionKeyValue = (IntColumn) getColumnWithMaxValue(PARTITION_KEY, DataType.INT); + BooleanColumn col1Value = (BooleanColumn) getColumnWithMaxValue(COL_NAME1, DataType.BOOLEAN); + IntColumn col2Value = (IntColumn) getColumnWithMaxValue(COL_NAME2, DataType.INT); + BigIntColumn col3Value = (BigIntColumn) getColumnWithMaxValue(COL_NAME3, DataType.BIGINT); + FloatColumn col4Value = (FloatColumn) getColumnWithMaxValue(COL_NAME4, DataType.FLOAT); + DoubleColumn col5Value = (DoubleColumn) getColumnWithMaxValue(COL_NAME5, DataType.DOUBLE); + TextColumn col6Value = (TextColumn) getColumnWithMaxValue(COL_NAME6, DataType.TEXT); + BlobColumn col7Value = BlobColumn.of(COL_NAME7, createLargeBlob(32_766)); + DateColumn col8Value = (DateColumn) getColumnWithMaxValue(COL_NAME8, DataType.DATE); + TimeColumn col9Value = (TimeColumn) getColumnWithMaxValue(COL_NAME9, DataType.TIME); + TimestampTZColumn col10Value = + (TimestampTZColumn) getColumnWithMaxValue(COL_NAME10, DataType.TIMESTAMPTZ); + TimestampColumn column11Value = null; + if (isTimestampTypeSupported()) { + column11Value = (TimestampColumn) getColumnWithMaxValue(COL_NAME11, DataType.TIMESTAMP); + } + + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(namespace) + .table(TABLE) + .partitionKey(Key.newBuilder().add(partitionKeyValue).build()) + .value(col1Value) + .value(col2Value) + .value(col3Value) + .value(col4Value) + .value(col5Value) + .value(col6Value) + .value(col7Value) + .value(col8Value) + .value(col9Value) + .value(col10Value); + if (isTimestampTypeSupported()) { + put.value(column11Value); + } + // Act + storage.put(put.build()); + + // Assert + assertResult( + partitionKeyValue, + col1Value, + col2Value, + col3Value, + col4Value, + col5Value, + col6Value, + col7Value, + col8Value, + col9Value, + col10Value, + column11Value); + } + + private byte[] createLargeBlob(int size) { + byte[] blob = new byte[size]; + random.nextBytes(blob); + return blob; + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseConditionalMutationIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseConditionalMutationIntegrationTest.java index ecf45dd959..5faca2a730 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseConditionalMutationIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseConditionalMutationIntegrationTest.java @@ -42,4 +42,9 @@ protected Column getColumnWithRandomValue( } return super.getColumnWithRandomValue(random, columnName, dataType); } + + @Override + protected boolean isConditionOnBlobColumnSupported() { + return !JdbcTestUtils.isOracle(rdbEngine); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java index 1e85cd44d5..bedcb44fd4 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseCrossPartitionScanIntegrationTest.java @@ -83,6 +83,11 @@ protected Stream provideColumnsForCNFConditionsTest() { @Override protected boolean isOrderingOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !( JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine)); + } + + @Override + protected boolean isConditionOnBlobColumnSupported() { + return !JdbcTestUtils.isOracle(rdbEngine); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java index 2247c67e0b..9c9f85739e 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultipleClusteringKeyScanIntegrationTest.java @@ -100,7 +100,7 @@ protected List getDataTypes() { rdbEngine, ImmutableMap.of( RdbEngineOracle.class, - ImmutableList.of(DataType.TIMESTAMPTZ), + ImmutableList.of(DataType.TIMESTAMPTZ, DataType.BLOB), RdbEngineDb2.class, ImmutableList.of(DataType.BLOB))); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java index 82919a2f72..6f7c6bd4bd 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseMultiplePartitionKeyIntegrationTest.java @@ -94,7 +94,7 @@ protected List getDataTypes() { rdbEngine, ImmutableMap.of( RdbEngineOracle.class, - ImmutableList.of(DataType.TIMESTAMPTZ), + ImmutableList.of(DataType.TIMESTAMPTZ, DataType.BLOB), RdbEngineYugabyte.class, ImmutableList.of(DataType.FLOAT, DataType.DOUBLE), RdbEngineDb2.class, diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java index 16fc9182c3..984a603423 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSecondaryIndexIntegrationTest.java @@ -81,6 +81,10 @@ protected Set getSecondaryIndexTypes() { JdbcTestUtils.filterDataTypes( Arrays.asList(DataType.values()), rdbEngine, - ImmutableMap.of(RdbEngineDb2.class, ImmutableList.of(DataType.BLOB)))); + ImmutableMap.of( + RdbEngineDb2.class, + ImmutableList.of(DataType.BLOB), + RdbEngineOracle.class, + ImmutableList.of(DataType.BLOB)))); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java index 4f77dafed1..2e2ad7c67c 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSingleClusteringKeyScanIntegrationTest.java @@ -76,7 +76,7 @@ protected List getClusteringKeyTypes() { rdbEngine, ImmutableMap.of( RdbEngineOracle.class, - ImmutableList.of(DataType.TIMESTAMPTZ), + ImmutableList.of(DataType.TIMESTAMPTZ, DataType.BLOB), RdbEngineDb2.class, ImmutableList.of(DataType.BLOB))); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java index 480d384ec8..6319de3cf4 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcDatabaseSinglePartitionKeyIntegrationTest.java @@ -77,7 +77,7 @@ protected List getPartitionKeyTypes() { rdbEngine, ImmutableMap.of( RdbEngineOracle.class, - ImmutableList.of(DataType.TIMESTAMPTZ), + ImmutableList.of(DataType.TIMESTAMPTZ, DataType.BLOB), RdbEngineYugabyte.class, ImmutableList.of(DataType.FLOAT, DataType.DOUBLE), RdbEngineDb2.class, 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..dfa4722cef 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 @@ -92,6 +92,6 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Override protected boolean isIndexOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !(JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(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..7c6022dcd8 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 @@ -163,6 +163,6 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp @Override protected boolean isIndexOnBlobColumnSupported() { - return !JdbcTestUtils.isDb2(rdbEngine); + return !(JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine)); } } 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 3b830bfd73..0bfe9dda64 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -748,6 +748,24 @@ public enum CoreError implements ScalarDbError { "With Db2, setting an ordering on a BLOB column when using a cross partition scan operation is not supported. Ordering: %s", "", ""), + JDBC_ORACLE_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED( + Category.USER_ERROR, + "0229", + "With Oracle, using a BLOB column as partition key, clustering key or secondary index is not supported.", + "", + ""), + JDBC_ORACLE_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED( + Category.USER_ERROR, + "0230", + "With Oracle, setting an ordering on a BLOB column when using a cross partition scan operation is not supported. Ordering: %s", + "", + ""), + JDBC_ORACLE_CROSS_PARTITION_SCAN_CONDITION_ON_BLOB_COLUMN_NOT_SUPPORTED( + Category.USER_ERROR, + "0231", + "With Oracle, setting a condition on a BLOB column when using a cross partition scan operation is not supported. Condition: %s", + "", + ""), // // Errors for the concurrency error category diff --git a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java index 8a977be66f..0cc122656d 100644 --- a/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java +++ b/core/src/main/java/com/scalar/db/common/checker/OperationChecker.java @@ -268,7 +268,7 @@ protected void checkOrderingsForScanAll(ScanAll scanAll, TableMetadata metadata) } } - private void checkConjunctions(Selection selection, TableMetadata metadata) { + protected void checkConjunctions(Selection selection, TableMetadata metadata) { for (Conjunction conjunction : selection.getConjunctions()) { for (ConditionalExpression condition : conjunction.getConditions()) { boolean isValid; diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java index 8cf440d4a7..7644a8546d 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcOperationChecker.java @@ -1,6 +1,7 @@ package com.scalar.db.storage.jdbc; import com.scalar.db.api.ScanAll; +import com.scalar.db.api.Selection; import com.scalar.db.api.TableMetadata; import com.scalar.db.common.StorageInfoProvider; import com.scalar.db.common.TableMetadataManager; @@ -26,4 +27,13 @@ protected void checkOrderingsForScanAll(ScanAll scanAll, TableMetadata metadata) super.checkOrderingsForScanAll(scanAll, metadata); rdbEngine.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata); } + + @Override + protected void checkConjunctions(Selection selection, TableMetadata metadata) { + super.checkConjunctions(selection, metadata); + if (selection instanceof ScanAll) { + rdbEngine.throwIfCrossPartitionScanConditionOnBlobColumnNotSupported( + (ScanAll) selection, metadata); + } + } } 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 6b16df9684..7917a6ed22 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 @@ -3,7 +3,10 @@ import static com.scalar.db.util.ScalarDbUtils.getFullTableName; import com.google.common.annotations.VisibleForTesting; +import com.scalar.db.api.ConditionalExpression; import com.scalar.db.api.LikeExpression; +import com.scalar.db.api.Scan; +import com.scalar.db.api.ScanAll; import com.scalar.db.api.TableMetadata; import com.scalar.db.common.CoreError; import com.scalar.db.exception.storage.ExecutionException; @@ -12,14 +15,18 @@ import com.scalar.db.storage.jdbc.query.SelectQuery; import com.scalar.db.storage.jdbc.query.SelectWithFetchFirstNRowsOnly; import com.scalar.db.storage.jdbc.query.UpsertQuery; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.sql.Driver; import java.sql.JDBCType; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -27,6 +34,7 @@ import org.slf4j.LoggerFactory; class RdbEngineOracle extends AbstractRdbEngine { + private static final Logger logger = LoggerFactory.getLogger(RdbEngineOracle.class); private final String keyColumnSize; private final RdbEngineTimeTypeOracle timeTypeEngine; @@ -237,7 +245,7 @@ public String getDataTypeForEngine(DataType scalarDbDataType) { case BIGINT: return "NUMBER(16)"; case BLOB: - return "RAW(2000)"; + return "BLOB"; case BOOLEAN: return "NUMBER(1)"; case DOUBLE: @@ -268,7 +276,8 @@ public String getDataTypeForKey(DataType dataType) { case TEXT: return "VARCHAR2(" + keyColumnSize + ")"; case BLOB: - return "RAW(" + keyColumnSize + ")"; + throw new UnsupportedOperationException( + CoreError.JDBC_ORACLE_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); default: return null; } @@ -431,9 +440,82 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { return createIndexSql; } + @Override + @Nullable + public String getDataTypeForSecondaryIndex(DataType dataType) { + if (dataType == DataType.BLOB) { + throw new UnsupportedOperationException( + CoreError.JDBC_ORACLE_INDEX_OR_KEY_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage()); + } else { + return super.getDataTypeForSecondaryIndex(dataType); + } + } + + @Override + public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) { + Optional orderingOnBlobColumn = + scanAll.getOrderings().stream() + .filter( + ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB) + .findFirst(); + if (orderingOnBlobColumn.isPresent()) { + throw new UnsupportedOperationException( + CoreError.JDBC_ORACLE_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED + .buildMessage(orderingOnBlobColumn.get())); + } + } + + @Override + public void throwIfCrossPartitionScanConditionOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) { + Optional conditionalExpression = + scanAll.getConjunctions().stream() + .flatMap(conjunction -> conjunction.getConditions().stream()) + .filter( + condition -> + metadata.getColumnDataType(condition.getColumn().getName()) == DataType.BLOB) + .findFirst(); + if (conditionalExpression.isPresent()) { + throw new UnsupportedOperationException( + CoreError.JDBC_ORACLE_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED + .buildMessage(conditionalExpression.get())); + } + } + @Override public RdbEngineTimeTypeStrategy getTimeTypeStrategy() { return timeTypeEngine; } + + @Override + public void bindBlobColumnToPreparedStatement(PreparedStatement preparedStatement, int index, byte[] bytes) + throws SQLException { + // When writing to the BLOB data type with a BLOB size greater than 32766 using a MERGE INTO + // statement, an internal error ORA-03137 on the server side occurs so we needed to use a + // workaround. Below is a detailed explanation of the workaround. + // + // Depending on the byte array size, the JDBC driver automatically choose one the following mode + // to transfer the BLOB data to the server: + // - DIRECT: the most efficient mode. It's used when the byte array length is less than 32767. + // - STREAM: this mode is less efficient. It's used when the byte array length is greater than + // 32766. + // - LOB BINDING: this mode is the least efficient. It's used when an input stream without + // specifying the length is specified. + // + // Through testing, it was conjectured that when the driver selects the STREAM mode, the error + // ORA-03137 occurs. So, we work around the issue by making sure to use the driver in a way so + // that it should never selects the STREAM mode. + // For more details about the modes, see the following documentation: + // https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/LOBs-and-BFiles.html#GUID-8FD40D53-8D64-4187-9F6F-FF78242188AD + if (bytes.length <= 32766) { + // the DIRECT mode is used to send BLOB data of small size + preparedStatement.setBytes(index, bytes); + } else { + // the LOB BINDING mode is used to send BLOB data of large size + InputStream inputStream = new ByteArrayInputStream(bytes); + preparedStatement.setBinaryStream(index, inputStream); + } + } } 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 52fc0b7153..81c430ff75 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 @@ -14,6 +14,7 @@ import java.sql.Connection; import java.sql.Driver; import java.sql.JDBCType; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; @@ -221,6 +222,11 @@ default OffsetDateTime encode(TimestampTZColumn column) { return column.getTimestampTZValue().atOffset(ZoneOffset.UTC); } + default void bindBlobColumnToPreparedStatement(PreparedStatement preparedStatement, int index, byte[] bytes) + throws SQLException { + preparedStatement.setBytes(index, bytes); + } + default DateColumn parseDateColumn(ResultSet resultSet, String columnName) throws SQLException { return DateColumn.of(columnName, resultSet.getObject(columnName, LocalDate.class)); } @@ -298,4 +304,16 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) */ default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( ScanAll scanAll, TableMetadata metadata) {} + + /** + * Throws an exception if a cross-partition scan operation with a condition on a blob column is + * specified and is not supported in the underlying storage. + * + * @param scanAll the ScanAll operation + * @param metadata the table metadata + * @throws UnsupportedOperationException if the ScanAll operation contains a condition on a blob + * column, and it is not supported in the underlying storage + */ + default void throwIfCrossPartitionScanConditionOnBlobColumnNotSupported( + ScanAll scanAll, TableMetadata metadata) {} } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/query/PreparedStatementBinder.java b/core/src/main/java/com/scalar/db/storage/jdbc/query/PreparedStatementBinder.java index 906446f512..f44386b9d6 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/query/PreparedStatementBinder.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/query/PreparedStatementBinder.java @@ -17,8 +17,10 @@ import com.scalar.db.io.TimestampTZColumn; import com.scalar.db.storage.jdbc.RdbEngineStrategy; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.ByteArrayInputStream; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Objects; import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe @@ -130,7 +132,7 @@ public void visit(BlobColumn column) { if (column.hasNullValue()) { preparedStatement.setNull(index++, getSqlType(column.getName())); } else { - preparedStatement.setBytes(index++, column.getBlobValueAsBytes()); + rdbEngine.bindBlobColumnToPreparedStatement(preparedStatement, index++, column.getBlobValueAsBytes()); } } catch (SQLException e) { sqlException = e; 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 ec62a3892b..0c6436fe24 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 @@ -562,7 +562,7 @@ public void createTableInternal_ForSqlServer_ShouldCreateTableAndIndexes() throw public void createTableInternal_ForOracle_ShouldCreateTableAndIndexes() throws SQLException { createTableInternal_ForX_CreateTableAndIndexes( RdbEngine.ORACLE, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" BLOB,\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", @@ -576,7 +576,7 @@ public void createTableInternal_ForOracle_ShouldCreateTableAndIndexes() throws S when(config.getOracleVariableKeyColumnSize()).thenReturn(64); createTableInternal_ForX_CreateTableAndIndexes( new RdbEngineOracle(config), - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(64),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(64),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" BLOB,\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", @@ -703,7 +703,7 @@ public void createTableInternal_IfNotExistsForOracle_ShouldCreateTableAndIndexes throws SQLException { createTableInternal_IfNotExistsForX_createTableAndIndexesIfNotExists( RdbEngine.ORACLE, - "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" RAW(2000),\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", + "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" NUMBER(1),\"c1\" VARCHAR2(128),\"c5\" NUMBER(10),\"c2\" NUMBER(16),\"c4\" BLOB,\"c6\" BINARY_DOUBLE,\"c7\" BINARY_FLOAT,\"c8\" DATE,\"c9\" TIMESTAMP(6),\"c10\" TIMESTAMP(3),\"c11\" TIMESTAMP(3) WITH TIME ZONE, PRIMARY KEY (\"c3\",\"c1\",\"c5\")) ROWDEPENDENCIES", "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c5\" ASC)", "CREATE INDEX \"my_ns\".\"index_my_ns_foo_table_c5\" ON \"my_ns\".\"foo_table\" (\"c5\")", @@ -4372,7 +4372,8 @@ void hasDifferentClusteringOrders_GivenBothAscAndDescOrders_ShouldReturnTrue() { } @Test - void createTableInternal_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() { + void + createTableInternal_Db2_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() { // Arrange TableMetadata metadata1 = TableMetadata.newBuilder().addPartitionKey("pk").addColumn("pk", DataType.BLOB).build(); @@ -4405,7 +4406,7 @@ void createTableInternal_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperat } @Test - void createIndex_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() + void createIndex_Db2_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() throws SQLException { // Arrange String namespace = "my_ns"; @@ -4434,6 +4435,69 @@ void createIndex_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationExcep .hasMessageContainingAll("BLOB", "index"); } + void + createTableInternal_Oracle_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() { + // Arrange + TableMetadata metadata1 = + TableMetadata.newBuilder().addPartitionKey("pk").addColumn("pk", DataType.BLOB).build(); + TableMetadata metadata2 = + TableMetadata.newBuilder() + .addPartitionKey("pk") + .addClusteringKey("ck") + .addColumn("pk", DataType.INT) + .addColumn("ck", DataType.BLOB) + .build(); + TableMetadata metadata3 = + TableMetadata.newBuilder() + .addPartitionKey("pk") + .addColumn("pk", DataType.INT) + .addColumn("col", DataType.BLOB) + .addSecondaryIndex("col") + .build(); + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.ORACLE); + + // Act Assert + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata1, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "key"); + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata2, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "key"); + assertThatThrownBy(() -> admin.createTableInternal(connection, "ns", "tbl", metadata3, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "index"); + } + + @Test + void createIndex_Oracle_WithBlobColumnAsKeyOrIndex_ShouldThrowUnsupportedOperationException() + throws SQLException { + // Arrange + String namespace = "my_ns"; + String table = "my_tbl"; + String indexColumn = "index_col"; + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.ORACLE); + + PreparedStatement selectStatement = mock(PreparedStatement.class); + ResultSet resultSet = + mockResultSet( + new SelectAllFromMetadataTableResultSetMocker.Row( + "pk", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.BLOB.toString(), null, null, false)); + when(selectStatement.executeQuery()).thenReturn(resultSet); + when(connection.prepareStatement(any())).thenReturn(selectStatement); + Statement statement = mock(Statement.class); + + when(dataSource.getConnection()).thenReturn(connection); + when(connection.createStatement()).thenReturn(statement); + + // Act Assert + assertThatThrownBy( + () -> admin.createIndex(namespace, table, indexColumn, Collections.emptyMap())) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContainingAll("BLOB", "index"); + } + // Utility class used to mock ResultSet for a "select * from" query on the metadata table static class SelectAllFromMetadataTableResultSetMocker implements org.mockito.stubbing.Answer { diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java index 1793086e2e..4c28f6e9de 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcOperationCheckerTest.java @@ -1,12 +1,17 @@ package com.scalar.db.storage.jdbc; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import com.scalar.db.api.Scan; import com.scalar.db.api.ScanAll; import com.scalar.db.api.TableMetadata; import com.scalar.db.common.StorageInfoProvider; import com.scalar.db.common.TableMetadataManager; import com.scalar.db.config.DatabaseConfig; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -19,6 +24,7 @@ public class JdbcOperationCheckerTest { @Mock private StorageInfoProvider storageInfoProvider; @Mock private RdbEngineStrategy rdbEngine; @Mock private ScanAll scanAll; + @Mock private Scan scan; @Mock private TableMetadata tableMetadata; private JdbcOperationChecker operationChecker; @@ -32,7 +38,7 @@ public void setUp() throws Exception { } @Test - public void checkOrderingsForScanAll_ShouldInvokeFurtherCheckOnRdbEngine() { + public void checkOrderingsForScanAll_ShouldInvokeAdditionalCheckOnRdbEngine() { // Arrange // Act operationChecker.checkOrderingsForScanAll(scanAll, tableMetadata); @@ -41,4 +47,63 @@ public void checkOrderingsForScanAll_ShouldInvokeFurtherCheckOnRdbEngine() { verify(rdbEngine) .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, tableMetadata); } + + @Test + public void checkOrderingsForScanAll_WhenAdditionalCheckThrows_ShouldPropagateException() { + // Arrange + Exception exception = new RuntimeException(); + doThrow(exception) + .when(rdbEngine) + .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( + any(ScanAll.class), any(TableMetadata.class)); + + // Act + Assertions.assertThatThrownBy( + () -> operationChecker.checkOrderingsForScanAll(scanAll, tableMetadata)) + .isEqualTo(exception); + + // Assert + verify(rdbEngine) + .throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, tableMetadata); + } + + @Test + public void checkConjunctions_WithScanAll_ShouldInvokeAdditionalCheckOnRdbEngine() { + // Arrange + // Act + operationChecker.checkConjunctions(scanAll, tableMetadata); + + // Assert + verify(rdbEngine) + .throwIfCrossPartitionScanConditionOnBlobColumnNotSupported(scanAll, tableMetadata); + } + + @Test + public void checkConjunctions_WithScan_ShouldNotInvokeAdditionalCheckOnRdbEngine() { + // Arrange + // Act + operationChecker.checkConjunctions(scan, tableMetadata); + + // Assert + verify(rdbEngine, never()) + .throwIfCrossPartitionScanConditionOnBlobColumnNotSupported(any(), any()); + } + + @Test + public void checkConjunctions_WithScanAll_WhenAdditionalCheckThrows_ShouldPropagateException() { + // Arrange + Exception exception = new RuntimeException(); + doThrow(exception) + .when(rdbEngine) + .throwIfCrossPartitionScanConditionOnBlobColumnNotSupported( + any(ScanAll.class), any(TableMetadata.class)); + + // Act + Assertions.assertThatThrownBy(() -> operationChecker.checkConjunctions(scanAll, tableMetadata)) + .isEqualTo(exception); + + // Assert + verify(rdbEngine) + .throwIfCrossPartitionScanConditionOnBlobColumnNotSupported(scanAll, tableMetadata); + } } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageColumnValueIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageColumnValueIntegrationTestBase.java index 9f8ab2a11f..6dd3c5b69b 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageColumnValueIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageColumnValueIntegrationTestBase.java @@ -49,26 +49,26 @@ public abstract class DistributedStorageColumnValueIntegrationTestBase { private static final String TEST_NAME = "storage_col_val"; private static final String NAMESPACE = "int_test_" + TEST_NAME; - private static final String TABLE = "test_table"; - private static final String PARTITION_KEY = "pkey"; - private static final String COL_NAME1 = "c1"; - private static final String COL_NAME2 = "c2"; - private static final String COL_NAME3 = "c3"; - private static final String COL_NAME4 = "c4"; - private static final String COL_NAME5 = "c5"; - private static final String COL_NAME6 = "c6"; - private static final String COL_NAME7 = "c7"; - private static final String COL_NAME8 = "c8"; - private static final String COL_NAME9 = "c9"; - private static final String COL_NAME10 = "c10"; - private static final String COL_NAME11 = "c11"; + protected static final String TABLE = "test_table"; + protected static final String PARTITION_KEY = "pkey"; + protected static final String COL_NAME1 = "c1"; + protected static final String COL_NAME2 = "c2"; + protected static final String COL_NAME3 = "c3"; + protected static final String COL_NAME4 = "c4"; + protected static final String COL_NAME5 = "c5"; + protected static final String COL_NAME6 = "c6"; + protected static final String COL_NAME7 = "c7"; + protected static final String COL_NAME8 = "c8"; + protected static final String COL_NAME9 = "c9"; + protected static final String COL_NAME10 = "c10"; + protected static final String COL_NAME11 = "c11"; private static final int ATTEMPT_COUNT = 50; - private static final Random random = new Random(); + protected static final Random random = new Random(); - private DistributedStorageAdmin admin; - private DistributedStorage storage; - private String namespace; + protected DistributedStorageAdmin admin; + protected DistributedStorage storage; + protected String namespace; private long seed; @@ -685,7 +685,7 @@ public void put_forTimeRelatedTypesWithVariousJvmTimezone_ShouldPutCorrectly( } } - private void assertResult( + protected void assertResult( IntColumn partitionKeyValue, BooleanColumn col1Value, IntColumn col2Value, diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageConditionalMutationIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageConditionalMutationIntegrationTestBase.java index e3bddc9cc6..c49166e604 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageConditionalMutationIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageConditionalMutationIntegrationTestBase.java @@ -121,13 +121,15 @@ private void createTable() throws ExecutionException { .addColumn(COL_NAME4, DataType.FLOAT) .addColumn(COL_NAME5, DataType.DOUBLE) .addColumn(COL_NAME6, DataType.TEXT) - .addColumn(COL_NAME7, DataType.BLOB) - .addColumn(COL_NAME8, DataType.DATE) - .addColumn(COL_NAME9, DataType.TIME) - .addColumn(COL_NAME10, DataType.TIMESTAMPTZ) + .addColumn(COL_NAME7, DataType.DATE) + .addColumn(COL_NAME8, DataType.TIME) + .addColumn(COL_NAME9, DataType.TIMESTAMPTZ) .addPartitionKey(PARTITION_KEY); if (isTimestampTypeSupported()) { - tableMetadata.addColumn(COL_NAME11, DataType.TIMESTAMP); + tableMetadata.addColumn(COL_NAME10, DataType.TIMESTAMP); + } + if (isConditionOnBlobColumnSupported()) { + tableMetadata.addColumn(COL_NAME11, DataType.BLOB); } Map options = getCreationOptions(); @@ -179,6 +181,9 @@ protected List getOperatorAndDataTypeListForTest() { if (!isTimestampTypeSupported()) { dataTypes.remove(DataType.TIMESTAMP); } + if (!isConditionOnBlobColumnSupported()) { + dataTypes.remove(DataType.BLOB); + } List ret = new ArrayList<>(); for (Operator operator : Operator.values()) { @@ -546,9 +551,11 @@ private void put_withPutIf_shouldPutProperly( COL_NAME6, COL_NAME7, COL_NAME8, - COL_NAME9, - COL_NAME10); + COL_NAME9); if (isTimestampTypeSupported()) { + columnNames.add(COL_NAME10); + } + if (isConditionOnBlobColumnSupported()) { columnNames.add(COL_NAME11); } assertThat(result.get().getContainedColumnNames()) @@ -589,22 +596,24 @@ private void put_withPutIf_shouldPutProperly( assertThat(result.get().getText(COL_NAME6)) .describedAs(description) .isEqualTo(expected.get(COL_NAME6).getTextValue()); - assertThat(result.get().getBlob(COL_NAME7)) + assertThat(result.get().getDate(COL_NAME7)) .describedAs(description) - .isEqualTo(expected.get(COL_NAME7).getBlobValue()); - assertThat(result.get().getDate(COL_NAME8)) + .isEqualTo(expected.get(COL_NAME7).getDateValue()); + assertThat(result.get().getTime(COL_NAME8)) .describedAs(description) - .isEqualTo(expected.get(COL_NAME8).getDateValue()); - assertThat(result.get().getTime(COL_NAME9)) + .isEqualTo(expected.get(COL_NAME8).getTimeValue()); + assertThat(result.get().getTimestampTZ(COL_NAME9)) .describedAs(description) - .isEqualTo(expected.get(COL_NAME9).getTimeValue()); - assertThat(result.get().getTimestampTZ(COL_NAME10)) - .describedAs(description) - .isEqualTo(expected.get(COL_NAME10).getTimestampTZValue()); + .isEqualTo(expected.get(COL_NAME9).getTimestampTZValue()); if (isTimestampTypeSupported()) { - assertThat(result.get().getTimestamp(COL_NAME11)) + assertThat(result.get().getTimestamp(COL_NAME10)) .describedAs(description) - .isEqualTo(expected.get(COL_NAME11).getTimestampValue()); + .isEqualTo(expected.get(COL_NAME10).getTimestampValue()); + } + if (isConditionOnBlobColumnSupported()) { + assertThat(result.get().getBlob(COL_NAME11)) + .describedAs(description) + .isEqualTo(expected.get(COL_NAME11).getBlobValue()); } } @@ -637,9 +646,11 @@ public void put_withPutIfExistsWhenRecordExists_shouldPutProperly() throws Execu COL_NAME6, COL_NAME7, COL_NAME8, - COL_NAME9, - COL_NAME10); + COL_NAME9); if (isTimestampTypeSupported()) { + columnNames.add(COL_NAME10); + } + if (isConditionOnBlobColumnSupported()) { columnNames.add(COL_NAME11); } assertThat(result.get().getContainedColumnNames()).isEqualTo(columnNames); @@ -649,14 +660,16 @@ public void put_withPutIfExistsWhenRecordExists_shouldPutProperly() throws Execu assertThat(result.get().getFloat(COL_NAME4)).isEqualTo(put.getFloatValue(COL_NAME4)); assertThat(result.get().getDouble(COL_NAME5)).isEqualTo(put.getDoubleValue(COL_NAME5)); assertThat(result.get().getText(COL_NAME6)).isEqualTo(put.getTextValue(COL_NAME6)); - assertThat(result.get().getBlob(COL_NAME7)).isEqualTo(put.getBlobValue(COL_NAME7)); - assertThat(result.get().getDate(COL_NAME8)).isEqualTo(put.getDateValue(COL_NAME8)); - assertThat(result.get().getTime(COL_NAME9)).isEqualTo(put.getTimeValue(COL_NAME9)); - assertThat(result.get().getTimestampTZ(COL_NAME10)) - .isEqualTo(put.getTimestampTZValue(COL_NAME10)); + assertThat(result.get().getDate(COL_NAME7)).isEqualTo(put.getDateValue(COL_NAME7)); + assertThat(result.get().getTime(COL_NAME8)).isEqualTo(put.getTimeValue(COL_NAME8)); + assertThat(result.get().getTimestampTZ(COL_NAME9)) + .isEqualTo(put.getTimestampTZValue(COL_NAME9)); if (isTimestampTypeSupported()) { - assertThat(result.get().getTimestamp(COL_NAME11)) - .isEqualTo(put.getTimestampValue(COL_NAME11)); + assertThat(result.get().getTimestamp(COL_NAME10)) + .isEqualTo(put.getTimestampValue(COL_NAME10)); + } + if (isConditionOnBlobColumnSupported()) { + assertThat(result.get().getBlob(COL_NAME11)).isEqualTo(put.getBlobValue(COL_NAME11)); } } @@ -706,9 +719,11 @@ public void put_withPutIfNotExistsWhenRecordDoesNotExist_shouldPutProperly() COL_NAME6, COL_NAME7, COL_NAME8, - COL_NAME9, - COL_NAME10); + COL_NAME9); if (isTimestampTypeSupported()) { + columnNames.add(COL_NAME10); + } + if (isConditionOnBlobColumnSupported()) { columnNames.add(COL_NAME11); } assertThat(result.get().getContainedColumnNames()).isEqualTo(columnNames); @@ -718,14 +733,16 @@ public void put_withPutIfNotExistsWhenRecordDoesNotExist_shouldPutProperly() assertThat(result.get().getFloat(COL_NAME4)).isEqualTo(put.getFloatValue(COL_NAME4)); assertThat(result.get().getDouble(COL_NAME5)).isEqualTo(put.getDoubleValue(COL_NAME5)); assertThat(result.get().getText(COL_NAME6)).isEqualTo(put.getTextValue(COL_NAME6)); - assertThat(result.get().getBlob(COL_NAME7)).isEqualTo(put.getBlobValue(COL_NAME7)); - assertThat(result.get().getDate(COL_NAME8)).isEqualTo(put.getDateValue(COL_NAME8)); - assertThat(result.get().getTime(COL_NAME9)).isEqualTo(put.getTimeValue(COL_NAME9)); - assertThat(result.get().getTimestampTZ(COL_NAME10)) - .isEqualTo(put.getTimestampTZValue(COL_NAME10)); + assertThat(result.get().getDate(COL_NAME7)).isEqualTo(put.getDateValue(COL_NAME7)); + assertThat(result.get().getTime(COL_NAME8)).isEqualTo(put.getTimeValue(COL_NAME8)); + assertThat(result.get().getTimestampTZ(COL_NAME9)) + .isEqualTo(put.getTimestampTZValue(COL_NAME9)); if (isTimestampTypeSupported()) { - assertThat(result.get().getTimestamp(COL_NAME11)) - .isEqualTo(put.getTimestampValue(COL_NAME11)); + assertThat(result.get().getTimestamp(COL_NAME10)) + .isEqualTo(put.getTimestampValue(COL_NAME10)); + } + if (isConditionOnBlobColumnSupported()) { + assertThat(result.get().getBlob(COL_NAME1)).isEqualTo(put.getBlobValue(COL_NAME11)); } } @@ -758,9 +775,11 @@ public void put_withPutIfNotExistsWhenRecordExists_shouldThrowNoMutationExceptio COL_NAME6, COL_NAME7, COL_NAME8, - COL_NAME9, - COL_NAME10); + COL_NAME9); if (isTimestampTypeSupported()) { + columnNames.add(COL_NAME10); + } + if (isConditionOnBlobColumnSupported()) { columnNames.add(COL_NAME11); } assertThat(result.get().getContainedColumnNames()).isEqualTo(columnNames); @@ -775,17 +794,19 @@ public void put_withPutIfNotExistsWhenRecordExists_shouldThrowNoMutationExceptio .isEqualTo(initialData.get(COL_NAME5).getDoubleValue()); assertThat(result.get().getText(COL_NAME6)) .isEqualTo(initialData.get(COL_NAME6).getTextValue()); - assertThat(result.get().getBlob(COL_NAME7)) - .isEqualTo(initialData.get(COL_NAME7).getBlobValue()); - assertThat(result.get().getDate(COL_NAME8)) - .isEqualTo(initialData.get(COL_NAME8).getDateValue()); - assertThat(result.get().getTime(COL_NAME9)) - .isEqualTo(initialData.get(COL_NAME9).getTimeValue()); - assertThat(result.get().getTimestampTZ(COL_NAME10)) - .isEqualTo(initialData.get(COL_NAME10).getTimestampTZValue()); + assertThat(result.get().getDate(COL_NAME7)) + .isEqualTo(initialData.get(COL_NAME7).getDateValue()); + assertThat(result.get().getTime(COL_NAME8)) + .isEqualTo(initialData.get(COL_NAME8).getTimeValue()); + assertThat(result.get().getTimestampTZ(COL_NAME9)) + .isEqualTo(initialData.get(COL_NAME9).getTimestampTZValue()); if (isTimestampTypeSupported()) { - assertThat(result.get().getTimestamp(COL_NAME11)) - .isEqualTo(initialData.get(COL_NAME11).getTimestampValue()); + assertThat(result.get().getTimestamp(COL_NAME10)) + .isEqualTo(initialData.get(COL_NAME10).getTimestampValue()); + } + if (isConditionOnBlobColumnSupported()) { + assertThat(result.get().getBlob(COL_NAME11)) + .isEqualTo(initialData.get(COL_NAME11).getBlobValue()); } } @@ -1147,9 +1168,11 @@ private void delete_withDeleteIf_shouldPutProperly( COL_NAME6, COL_NAME7, COL_NAME8, - COL_NAME9, - COL_NAME10); + COL_NAME9); if (isTimestampTypeSupported()) { + columnNames.add(COL_NAME10); + } + if (isConditionOnBlobColumnSupported()) { columnNames.add(COL_NAME11); } assertThat(result.get().getContainedColumnNames()) @@ -1189,22 +1212,24 @@ private void delete_withDeleteIf_shouldPutProperly( assertThat(result.get().getText(COL_NAME6)) .describedAs(description) .isEqualTo(initialData.get(COL_NAME6).getTextValue()); - assertThat(result.get().getBlob(COL_NAME7)) + assertThat(result.get().getDate(COL_NAME7)) .describedAs(description) - .isEqualTo(initialData.get(COL_NAME7).getBlobValue()); - assertThat(result.get().getDate(COL_NAME8)) + .isEqualTo(initialData.get(COL_NAME7).getDateValue()); + assertThat(result.get().getTime(COL_NAME8)) .describedAs(description) - .isEqualTo(initialData.get(COL_NAME8).getDateValue()); - assertThat(result.get().getTime(COL_NAME9)) + .isEqualTo(initialData.get(COL_NAME8).getTimeValue()); + assertThat(result.get().getTimestampTZ(COL_NAME9)) .describedAs(description) - .isEqualTo(initialData.get(COL_NAME9).getTimeValue()); - assertThat(result.get().getTimestampTZ(COL_NAME10)) - .describedAs(description) - .isEqualTo(initialData.get(COL_NAME10).getTimestampTZValue()); + .isEqualTo(initialData.get(COL_NAME9).getTimestampTZValue()); if (isTimestampTypeSupported()) { - assertThat(result.get().getTimestamp(COL_NAME11)) + assertThat(result.get().getTimestamp(COL_NAME10)) + .describedAs(description) + .isEqualTo(initialData.get(COL_NAME10).getTimestampValue()); + } + if (isConditionOnBlobColumnSupported()) { + assertThat(result.get().getBlob(COL_NAME11)) .describedAs(description) - .isEqualTo(initialData.get(COL_NAME11).getTimestampValue()); + .isEqualTo(initialData.get(COL_NAME11).getBlobValue()); } } } @@ -1296,15 +1321,16 @@ private Put preparePutWithRandomValues( .value(getColumnWithRandomValue(random.get(), COL_NAME4, DataType.FLOAT)) .value(getColumnWithRandomValue(random.get(), COL_NAME5, DataType.DOUBLE)) .value(getColumnWithRandomValue(random.get(), COL_NAME6, DataType.TEXT)) - .value(getColumnWithRandomValue(random.get(), COL_NAME7, DataType.BLOB)) - .value(getColumnWithRandomValue(random.get(), COL_NAME8, DataType.DATE)) - .value(getColumnWithRandomValue(random.get(), COL_NAME9, DataType.TIME)) - .value(getColumnWithRandomValue(random.get(), COL_NAME10, DataType.TIMESTAMPTZ)) + .value(getColumnWithRandomValue(random.get(), COL_NAME7, DataType.DATE)) + .value(getColumnWithRandomValue(random.get(), COL_NAME8, DataType.TIME)) + .value(getColumnWithRandomValue(random.get(), COL_NAME9, DataType.TIMESTAMPTZ)) .consistency(Consistency.LINEARIZABLE); if (isTimestampTypeSupported()) { - put.value(getColumnWithRandomValue(random.get(), COL_NAME11, DataType.TIMESTAMP)); + put.value(getColumnWithRandomValue(random.get(), COL_NAME10, DataType.TIMESTAMP)); + } + if (isConditionOnBlobColumnSupported()) { + put.value(getColumnWithRandomValue(random.get(), COL_NAME11, DataType.BLOB)); } - return put.build(); } @@ -1418,14 +1444,16 @@ private Put preparePutWithNullValues( .floatValue(COL_NAME4, null) .doubleValue(COL_NAME5, null) .textValue(COL_NAME6, null) - .blobValue(COL_NAME7, (ByteBuffer) null) - .dateValue(COL_NAME8, null) - .timeValue(COL_NAME9, null) - .timestampTZValue(COL_NAME10, null) + .dateValue(COL_NAME7, null) + .timeValue(COL_NAME8, null) + .timestampTZValue(COL_NAME9, null) .consistency(Consistency.LINEARIZABLE); if (isTimestampTypeSupported()) { - put.timestampValue(COL_NAME11, null); + put.timestampValue(COL_NAME10, null); + } + if (isConditionOnBlobColumnSupported()) { + put.blobValue(COL_NAME11, (ByteBuffer) null); } return put.build(); @@ -1473,12 +1501,14 @@ private Map> putInitialDataWithoutValues( .put(COL_NAME4, FloatColumn.ofNull(COL_NAME4)) .put(COL_NAME5, DoubleColumn.ofNull(COL_NAME5)) .put(COL_NAME6, TextColumn.ofNull(COL_NAME6)) - .put(COL_NAME7, BlobColumn.ofNull(COL_NAME7)) - .put(COL_NAME8, DateColumn.ofNull(COL_NAME8)) - .put(COL_NAME9, TimeColumn.ofNull(COL_NAME9)) - .put(COL_NAME10, TimestampTZColumn.ofNull(COL_NAME10)); + .put(COL_NAME7, DateColumn.ofNull(COL_NAME7)) + .put(COL_NAME8, TimeColumn.ofNull(COL_NAME8)) + .put(COL_NAME9, TimestampTZColumn.ofNull(COL_NAME9)); if (isTimestampTypeSupported()) { - columns.put(COL_NAME11, TimestampColumn.ofNull(COL_NAME11)); + columns.put(COL_NAME10, TimestampColumn.ofNull(COL_NAME10)); + } + if (isConditionOnBlobColumnSupported()) { + columns.put(COL_NAME11, BlobColumn.ofNull(COL_NAME11)); } return columns.build(); @@ -1526,15 +1556,15 @@ private String getColumnName(DataType dataType) { return COL_NAME5; case TEXT: return COL_NAME6; - case BLOB: - return COL_NAME7; case DATE: - return COL_NAME8; + return COL_NAME7; case TIME: - return COL_NAME9; + return COL_NAME8; case TIMESTAMPTZ: - return COL_NAME10; + return COL_NAME9; case TIMESTAMP: + return COL_NAME10; + case BLOB: return COL_NAME11; default: throw new AssertionError(); @@ -1728,4 +1758,8 @@ public DataType getDataType() { protected boolean isTimestampTypeSupported() { return true; } + + protected boolean isConditionOnBlobColumnSupported() { + return true; + } } diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java index ab9787512c..d331877127 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageCrossPartitionScanIntegrationTestBase.java @@ -155,14 +155,14 @@ private void createTableForConditionTests() throws ExecutionException { .addColumn(COL_NAME4, DataType.DOUBLE) .addColumn(COL_NAME5, DataType.TEXT) .addColumn(COL_NAME6, DataType.BOOLEAN) - .addColumn(COL_NAME7, DataType.BLOB) - .addColumn(COL_NAME8, DataType.DATE) - .addColumn(COL_NAME9, DataType.TIME) - .addColumn(COL_NAME10, DataType.TIMESTAMPTZ) + .addColumn(COL_NAME7, DataType.DATE) + .addColumn(COL_NAME8, DataType.TIME) + .addColumn(COL_NAME9, DataType.TIMESTAMPTZ) .addPartitionKey(PARTITION_KEY_NAME); if (isTimestampTypeSupported()) { - tableMetadata.addColumn(COL_NAME11, DataType.TIMESTAMP); + tableMetadata.addColumn(COL_NAME10, DataType.TIMESTAMP); } + tableMetadata.addColumn(COL_NAME11, DataType.BLOB); Map options = getCreationOptions(); admin.createNamespace(getNamespaceName(), true, options); @@ -375,12 +375,14 @@ protected List> prepareNonKeyColumns(int i) { columns.add(DoubleColumn.of(COL_NAME4, i)); columns.add(TextColumn.of(COL_NAME5, String.valueOf(i))); columns.add(BooleanColumn.of(COL_NAME6, i % 2 == 0)); - columns.add(BlobColumn.of(COL_NAME7, String.valueOf(i).getBytes(StandardCharsets.UTF_8))); - columns.add(DateColumn.of(COL_NAME8, DateColumn.MIN_VALUE.plusDays(i))); - columns.add(TimeColumn.of(COL_NAME9, TimeColumn.MIN_VALUE.plusSeconds(i))); - columns.add(TimestampTZColumn.of(COL_NAME10, TimestampTZColumn.MIN_VALUE.plusSeconds(i))); + columns.add(DateColumn.of(COL_NAME7, DateColumn.MIN_VALUE.plusDays(i))); + columns.add(TimeColumn.of(COL_NAME8, TimeColumn.MIN_VALUE.plusSeconds(i))); + columns.add(TimestampTZColumn.of(COL_NAME9, TimestampTZColumn.MIN_VALUE.plusSeconds(i))); if (isTimestampTypeSupported()) { - columns.add(TimestampColumn.of(COL_NAME11, TimestampColumn.MIN_VALUE.plusSeconds(i))); + columns.add(TimestampColumn.of(COL_NAME10, TimestampColumn.MIN_VALUE.plusSeconds(i))); + } + if (isConditionOnBlobColumnSupported()) { + columns.add(BlobColumn.of(COL_NAME11, String.valueOf(i).getBytes(StandardCharsets.UTF_8))); } return columns; } @@ -393,12 +395,14 @@ private List> prepareNullColumns() { columns.add(DoubleColumn.ofNull(COL_NAME4)); columns.add(TextColumn.ofNull(COL_NAME5)); columns.add(BooleanColumn.ofNull(COL_NAME6)); - columns.add(BlobColumn.ofNull(COL_NAME7)); - columns.add(DateColumn.ofNull(COL_NAME8)); - columns.add(TimeColumn.ofNull(COL_NAME9)); - columns.add(TimestampTZColumn.ofNull(COL_NAME10)); + columns.add(DateColumn.ofNull(COL_NAME7)); + columns.add(TimeColumn.ofNull(COL_NAME8)); + columns.add(TimestampTZColumn.ofNull(COL_NAME9)); if (isTimestampTypeSupported()) { - columns.add(TimestampColumn.ofNull(COL_NAME11)); + columns.add(TimestampColumn.ofNull(COL_NAME10)); + } + if (isConditionOnBlobColumnSupported()) { + columns.add(BlobColumn.ofNull(COL_NAME11)); } return columns; } @@ -1262,4 +1266,8 @@ protected boolean isTimestampTypeSupported() { protected boolean isOrderingOnBlobColumnSupported() { return true; } + + protected boolean isConditionOnBlobColumnSupported() { + return true; + } }