Skip to content

Commit b199319

Browse files
committed
Update check for cross partition scan with ordering on blob column
1 parent 47dd9d5 commit b199319

File tree

8 files changed

+235
-38
lines changed

8 files changed

+235
-38
lines changed

core/src/main/java/com/scalar/db/common/checker/OperationChecker.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.scalar.db.api.Operation;
99
import com.scalar.db.api.Put;
1010
import com.scalar.db.api.Scan;
11-
import com.scalar.db.api.Scan.Ordering;
1211
import com.scalar.db.api.ScanAll;
1312
import com.scalar.db.api.Selection;
1413
import com.scalar.db.api.Selection.Conjunction;
@@ -20,14 +19,11 @@
2019
import com.scalar.db.config.DatabaseConfig;
2120
import com.scalar.db.exception.storage.ExecutionException;
2221
import com.scalar.db.io.Column;
23-
import com.scalar.db.io.DataType;
2422
import com.scalar.db.io.Key;
25-
import com.scalar.db.storage.jdbc.JdbcConfig;
2623
import com.scalar.db.util.ScalarDbUtils;
2724
import java.util.Iterator;
2825
import java.util.LinkedHashSet;
2926
import java.util.List;
30-
import java.util.Optional;
3127
import java.util.function.Supplier;
3228
import javax.annotation.concurrent.ThreadSafe;
3329

@@ -163,17 +159,7 @@ private void check(ScanAll scanAll) throws ExecutionException {
163159
throw new IllegalArgumentException(
164160
CoreError.OPERATION_CHECK_ERROR_CROSS_PARTITION_SCAN_ORDERING.buildMessage(scanAll));
165161
}
166-
Optional<Ordering> orderingOnBlobColumn =
167-
scanAll.getOrderings().stream()
168-
.filter(
169-
ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB)
170-
.findFirst();
171-
if (orderingOnBlobColumn.isPresent()
172-
&& new JdbcConfig(config).getJdbcUrl().startsWith("jdbc:db2:")) {
173-
throw new IllegalArgumentException(
174-
CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage(
175-
orderingOnBlobColumn.get()));
176-
}
162+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata);
177163
checkOrderings(scanAll, metadata);
178164

179165
if (!config.isCrossPartitionScanFilteringEnabled() && !scanAll.getConjunctions().isEmpty()) {
@@ -183,6 +169,9 @@ && new JdbcConfig(config).getJdbcUrl().startsWith("jdbc:db2:")) {
183169
checkConjunctions(scanAll, metadata);
184170
}
185171

172+
protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
173+
ScanAll scanAll, TableMetadata metadata) {};
174+
186175
private void checkProjections(Selection selection, TableMetadata metadata) {
187176
for (String projection : selection.getProjections()) {
188177
if (!metadata.getColumnNames().contains(projection)) {

core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public JdbcDatabase(DatabaseConfig databaseConfig) {
5959
TableMetadataManager tableMetadataManager =
6060
new TableMetadataManager(jdbcAdmin, databaseConfig.getMetadataCacheExpirationTimeSecs());
6161
OperationChecker operationChecker =
62-
new OperationChecker(
63-
databaseConfig, tableMetadataManager, new StorageInfoProvider(jdbcAdmin));
62+
new JdbcOperationChecker(
63+
databaseConfig, tableMetadataManager, new StorageInfoProvider(jdbcAdmin), rdbEngine);
6464

6565
jdbcService =
6666
new JdbcService(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.scalar.db.storage.jdbc;
2+
3+
import com.scalar.db.api.ScanAll;
4+
import com.scalar.db.api.TableMetadata;
5+
import com.scalar.db.common.StorageInfoProvider;
6+
import com.scalar.db.common.TableMetadataManager;
7+
import com.scalar.db.common.checker.OperationChecker;
8+
import com.scalar.db.config.DatabaseConfig;
9+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
10+
11+
public class JdbcOperationChecker extends OperationChecker {
12+
private final RdbEngineStrategy rdbEngine;
13+
14+
@SuppressFBWarnings("EI_EXPOSE_REP2")
15+
public JdbcOperationChecker(
16+
DatabaseConfig config,
17+
TableMetadataManager tableMetadataManager,
18+
StorageInfoProvider storageInfoProvider,
19+
RdbEngineStrategy rdbEngine) {
20+
super(config, tableMetadataManager, storageInfoProvider);
21+
this.rdbEngine = rdbEngine;
22+
}
23+
24+
@Override
25+
protected void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
26+
ScanAll scanAll, TableMetadata metadata) {
27+
rdbEngine.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, metadata);
28+
}
29+
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.google.common.collect.ImmutableMap;
77
import com.ibm.db2.jcc.DB2BaseDataSource;
88
import com.scalar.db.api.LikeExpression;
9+
import com.scalar.db.api.Scan.Ordering;
10+
import com.scalar.db.api.ScanAll;
911
import com.scalar.db.api.TableMetadata;
1012
import com.scalar.db.common.CoreError;
1113
import com.scalar.db.exception.storage.ExecutionException;
@@ -32,6 +34,7 @@
3234
import java.util.Collection;
3335
import java.util.List;
3436
import java.util.Map;
37+
import java.util.Optional;
3538
import java.util.stream.Collectors;
3639
import java.util.stream.Stream;
3740
import javax.annotation.Nullable;
@@ -511,4 +514,19 @@ private String getProjection(String columnName, DataType dataType) {
511514
}
512515
return enclose(columnName);
513516
}
517+
518+
@Override
519+
public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
520+
ScanAll scanAll, TableMetadata metadata) {
521+
Optional<Ordering> orderingOnBlobColumn =
522+
scanAll.getOrderings().stream()
523+
.filter(
524+
ordering -> metadata.getColumnDataType(ordering.getColumnName()) == DataType.BLOB)
525+
.findFirst();
526+
if (orderingOnBlobColumn.isPresent()) {
527+
throw new UnsupportedOperationException(
528+
CoreError.DB2_CROSS_PARTITION_SCAN_ORDERING_ON_BLOB_COLUMN_NOT_SUPPORTED.buildMessage(
529+
orderingOnBlobColumn.get()));
530+
}
531+
}
514532
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.scalar.db.storage.jdbc;
22

33
import com.scalar.db.api.LikeExpression;
4+
import com.scalar.db.api.ScanAll;
45
import com.scalar.db.api.TableMetadata;
56
import com.scalar.db.exception.storage.ExecutionException;
67
import com.scalar.db.io.DataType;
@@ -246,4 +247,16 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly)
246247
throws SQLException {
247248
connection.setReadOnly(readOnly);
248249
}
250+
251+
/**
252+
* Throws an exception if a cross-partition scan operation with ordering on a blob column is
253+
* specified and is not supported in the underlying storage.
254+
*
255+
* @param scanAll the ScanAll operation
256+
* @param metadata the table metadata
257+
* @throws UnsupportedOperationException if the ScanAll operation contains an ordering on a blob
258+
* column, and it is not supported in the underlying storage
259+
*/
260+
default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
261+
ScanAll scanAll, TableMetadata metadata) {}
249262
}

core/src/test/java/com/scalar/db/common/checker/OperationCheckerTest.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import static org.assertj.core.api.Assertions.assertThatThrownBy;
66
import static org.assertj.core.api.Assertions.catchException;
77
import static org.mockito.Mockito.any;
8+
import static org.mockito.Mockito.spy;
9+
import static org.mockito.Mockito.verify;
810
import static org.mockito.Mockito.when;
911

1012
import com.scalar.db.api.ConditionBuilder;
@@ -17,6 +19,7 @@
1719
import com.scalar.db.api.Put;
1820
import com.scalar.db.api.Scan;
1921
import com.scalar.db.api.Scan.Ordering;
22+
import com.scalar.db.api.ScanAll;
2023
import com.scalar.db.api.StorageInfo;
2124
import com.scalar.db.api.TableMetadata;
2225
import com.scalar.db.api.Update;
@@ -32,7 +35,6 @@
3235
import java.util.Arrays;
3336
import java.util.Collections;
3437
import java.util.List;
35-
import java.util.Properties;
3638
import org.junit.jupiter.api.BeforeEach;
3739
import org.junit.jupiter.api.Test;
3840
import org.junit.jupiter.params.ParameterizedTest;
@@ -418,18 +420,17 @@ public void whenCheckingScanOperationWithEmptyOrdering_shouldNotThrowAnyExceptio
418420
}
419421

420422
@Test
421-
public void
422-
whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrderingOnBlobColumnWithDb2_shouldThrowIllegalArgumentException()
423-
throws ExecutionException {
423+
public void whenCheckingScanAllOperationWithCrossPartitionScanEnabledWithOrdering_shouldNotThrow()
424+
throws ExecutionException {
424425
// Arrange
425-
when(metadataManager.getTableMetadata(any()))
426-
.thenReturn(
427-
TableMetadata.newBuilder()
428-
.addColumn(PKEY1, DataType.BLOB)
429-
.addColumn(COL1, DataType.INT)
430-
.addColumn(COL2, DataType.BLOB)
431-
.addPartitionKey(PKEY1)
432-
.build());
426+
TableMetadata metadata =
427+
TableMetadata.newBuilder()
428+
.addColumn(PKEY1, DataType.BLOB)
429+
.addColumn(COL1, DataType.INT)
430+
.addColumn(COL2, DataType.BLOB)
431+
.addPartitionKey(PKEY1)
432+
.build();
433+
when(metadataManager.getTableMetadata(any())).thenReturn(metadata);
433434
Scan scan =
434435
Scan.newBuilder()
435436
.namespace(NAMESPACE)
@@ -440,17 +441,16 @@ public void whenCheckingScanOperationWithEmptyOrdering_shouldNotThrowAnyExceptio
440441
.build();
441442
when(databaseConfig.isCrossPartitionScanEnabled()).thenReturn(true);
442443
when(databaseConfig.isCrossPartitionScanOrderingEnabled()).thenReturn(true);
443-
when(databaseConfig.getContactPoints())
444-
.thenReturn(Collections.singletonList("jdbc:db2://localhost:50000/test_db"));
445-
when(databaseConfig.getStorage()).thenReturn("jdbc");
446-
when(databaseConfig.getProperties()).thenReturn(new Properties());
447444

448-
operationChecker = new OperationChecker(databaseConfig, metadataManager, storageInfoProvider);
445+
operationChecker =
446+
spy(new OperationChecker(databaseConfig, metadataManager, storageInfoProvider));
449447

450-
// Act Assert
451-
assertThatThrownBy(() -> operationChecker.check(scan))
452-
.isInstanceOf(IllegalArgumentException.class)
453-
.hasMessageContainingAll("Db2", "ordering", COL2);
448+
// Act
449+
operationChecker.check(scan);
450+
451+
// Assert
452+
verify(operationChecker)
453+
.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported((ScanAll) scan, metadata);
454454
}
455455

456456
@Test
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.scalar.db.storage.jdbc;
2+
3+
import static org.mockito.Mockito.verify;
4+
5+
import com.scalar.db.api.ScanAll;
6+
import com.scalar.db.api.TableMetadata;
7+
import com.scalar.db.common.StorageInfoProvider;
8+
import com.scalar.db.common.TableMetadataManager;
9+
import com.scalar.db.config.DatabaseConfig;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
import org.mockito.Mock;
13+
import org.mockito.MockitoAnnotations;
14+
15+
public class JdbcOperationCheckerTest {
16+
17+
@Mock private DatabaseConfig databaseConfig;
18+
@Mock private TableMetadataManager tableMetadataManager;
19+
@Mock private StorageInfoProvider storageInfoProvider;
20+
@Mock private RdbEngineStrategy rdbEngine;
21+
@Mock private ScanAll scanAll;
22+
@Mock private TableMetadata tableMetadata;
23+
private JdbcOperationChecker operationChecker;
24+
25+
@BeforeEach
26+
public void setUp() throws Exception {
27+
MockitoAnnotations.openMocks(this).close();
28+
29+
operationChecker =
30+
new JdbcOperationChecker(
31+
databaseConfig, tableMetadataManager, storageInfoProvider, rdbEngine);
32+
}
33+
34+
@Test
35+
public void
36+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_ShouldDelegateToRdbEngine() {
37+
// Arrange
38+
// Act
39+
operationChecker.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
40+
scanAll, tableMetadata);
41+
42+
// Assert
43+
verify(rdbEngine)
44+
.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(scanAll, tableMetadata);
45+
}
46+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.scalar.db.storage.jdbc;
2+
3+
import static org.assertj.core.api.Assertions.assertThatCode;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
import static org.mockito.Mockito.when;
6+
7+
import com.scalar.db.api.Scan;
8+
import com.scalar.db.api.ScanAll;
9+
import com.scalar.db.api.TableMetadata;
10+
import com.scalar.db.io.DataType;
11+
import java.util.Arrays;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Test;
14+
import org.mockito.Mock;
15+
import org.mockito.MockitoAnnotations;
16+
17+
public class RdbEngineDb2Test {
18+
19+
@Mock private ScanAll scanAll;
20+
@Mock private TableMetadata metadata;
21+
22+
private RdbEngineDb2 rdbEngineDb2;
23+
24+
@BeforeEach
25+
public void setUp() throws Exception {
26+
MockitoAnnotations.openMocks(this).close();
27+
rdbEngineDb2 = new RdbEngineDb2();
28+
}
29+
30+
@Test
31+
public void
32+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithBlobOrdering_ShouldThrowException() {
33+
// Arrange
34+
Scan.Ordering blobOrdering = Scan.Ordering.asc("blob_column");
35+
Scan.Ordering intOrdering = Scan.Ordering.desc("int_column");
36+
37+
when(scanAll.getOrderings()).thenReturn(Arrays.asList(intOrdering, blobOrdering));
38+
when(metadata.getColumnDataType("blob_column")).thenReturn(DataType.BLOB);
39+
when(metadata.getColumnDataType("int_column")).thenReturn(DataType.INT);
40+
41+
// Act & Assert
42+
assertThatThrownBy(
43+
() ->
44+
rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
45+
scanAll, metadata))
46+
.isInstanceOf(UnsupportedOperationException.class)
47+
.hasMessageContaining("blob_column");
48+
}
49+
50+
@Test
51+
public void
52+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithoutBlobOrdering_ShouldNotThrowException() {
53+
// Arrange
54+
Scan.Ordering intOrdering = Scan.Ordering.asc("int_column");
55+
Scan.Ordering textOrdering = Scan.Ordering.desc("text_column");
56+
57+
when(scanAll.getOrderings()).thenReturn(Arrays.asList(intOrdering, textOrdering));
58+
when(metadata.getColumnDataType("int_column")).thenReturn(DataType.INT);
59+
when(metadata.getColumnDataType("text_column")).thenReturn(DataType.TEXT);
60+
61+
// Act & Assert
62+
assertThatCode(
63+
() ->
64+
rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
65+
scanAll, metadata))
66+
.doesNotThrowAnyException();
67+
}
68+
69+
@Test
70+
public void
71+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithNoOrderings_ShouldNotThrowException() {
72+
// Arrange
73+
when(scanAll.getOrderings()).thenReturn(Arrays.asList());
74+
75+
// Act & Assert
76+
assertThatCode(
77+
() ->
78+
rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
79+
scanAll, metadata))
80+
.doesNotThrowAnyException();
81+
}
82+
83+
@Test
84+
public void
85+
throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported_WithMultipleBlobOrderings_ShouldThrowForFirst() {
86+
// Arrange
87+
Scan.Ordering blobOrdering1 = Scan.Ordering.asc("blob_column1");
88+
Scan.Ordering blobOrdering2 = Scan.Ordering.desc("blob_column2");
89+
90+
when(scanAll.getOrderings()).thenReturn(Arrays.asList(blobOrdering1, blobOrdering2));
91+
when(metadata.getColumnDataType("blob_column1")).thenReturn(DataType.BLOB);
92+
when(metadata.getColumnDataType("blob_column2")).thenReturn(DataType.BLOB);
93+
94+
// Act & Assert
95+
assertThatThrownBy(
96+
() ->
97+
rdbEngineDb2.throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
98+
scanAll, metadata))
99+
.isInstanceOf(UnsupportedOperationException.class)
100+
.hasMessageContaining("blob_column1");
101+
}
102+
}

0 commit comments

Comments
 (0)