Skip to content

Commit 5c13edb

Browse files
committed
Revisit handling IS_NULL conditions on right source table columns in LEFT_OUTER virtual tables
1 parent e03a522 commit 5c13edb

File tree

5 files changed

+682
-2
lines changed

5 files changed

+682
-2
lines changed

core/src/main/java/com/scalar/db/common/CoreError.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,12 @@ public enum CoreError implements ScalarDbError {
10091009
"Source tables cannot be dropped while virtual tables depending on them exist. Source table: %s; Virtual tables: %s",
10101010
"",
10111011
""),
1012+
DELETE_IF_IS_NULL_FOR_RIGHT_SOURCE_TABLE_NOT_ALLOWED_FOR_LEFT_OUTER_VIRTUAL_TABLES(
1013+
Category.USER_ERROR,
1014+
"0276",
1015+
"The DeleteIf IS_NULL condition for right source table columns is not allowed in LEFT_OUTER virtual tables. Virtual table: %s",
1016+
"",
1017+
""),
10121018

10131019
//
10141020
// Errors for the concurrency error category

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.scalar.db.exception.storage.NoMutationException;
3636
import com.scalar.db.exception.storage.RetriableExecutionException;
3737
import com.scalar.db.io.Column;
38+
import com.scalar.db.util.ScalarDbUtils;
3839
import java.sql.Connection;
3940
import java.sql.SQLException;
4041
import java.util.ArrayList;
@@ -395,7 +396,21 @@ private List<Put> dividePutForSourceTables(Put put, VirtualTableInfo virtualTabl
395396
putBuilderForLeftSourceTable.condition(ConditionBuilder.putIf(leftExpressions));
396397
}
397398
if (!rightExpressions.isEmpty()) {
398-
putBuilderForRightSourceTable.condition(ConditionBuilder.putIf(rightExpressions));
399+
if (virtualTableInfo.getJoinType() == VirtualTableJoinType.LEFT_OUTER
400+
&& JdbcOperationAttributes
401+
.isLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(put)
402+
&& rightExpressions.stream()
403+
.allMatch(e -> e.getOperator() == ConditionalExpression.Operator.IS_NULL)) {
404+
// In a LEFT_OUTER join, when all conditions on the right source table columns are
405+
// IS_NULL, we cannot distinguish whether we should check for the existence of a
406+
// right-side record with NULL values or for the case where the right-side record does
407+
// not exist at all. Therefore, this behavior is controlled by the operation attribute.
408+
// By default, we convert the condition to PutIfNotExists, assuming that the more common
409+
// use case is to check that the right-side record does not exist.
410+
putBuilderForRightSourceTable.condition(ConditionBuilder.putIfNotExists());
411+
} else {
412+
putBuilderForRightSourceTable.condition(ConditionBuilder.putIf(rightExpressions));
413+
}
399414
}
400415
}
401416
}
@@ -464,7 +479,27 @@ private List<Delete> divideDeleteForSourceTables(Delete delete, VirtualTableInfo
464479
deleteBuilderForLeftSourceTable.condition(ConditionBuilder.deleteIf(leftExpressions));
465480
}
466481
if (!rightExpressions.isEmpty()) {
467-
deleteBuilderForRightSourceTable.condition(ConditionBuilder.deleteIf(rightExpressions));
482+
if (virtualTableInfo.getJoinType() == VirtualTableJoinType.LEFT_OUTER
483+
&& !JdbcOperationAttributes
484+
.isLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(delete)
485+
&& rightExpressions.stream()
486+
.allMatch(e -> e.getOperator() == ConditionalExpression.Operator.IS_NULL)) {
487+
// In a LEFT_OUTER join, when all conditions on the right source table columns are
488+
// IS_NULL, we cannot distinguish whether we should check for the existence of a
489+
// right-side record with NULL values or for the case where the right-side record does
490+
// not exist at all. This makes the delete operation semantically ambiguous. Therefore,
491+
// this behavior is controlled by the operation attribute. By default, we disallow this
492+
// operation to prevent unintended behavior.
493+
assert delete.forNamespace().isPresent() && delete.forTable().isPresent();
494+
throw new IllegalArgumentException(
495+
CoreError
496+
.DELETE_IF_IS_NULL_FOR_RIGHT_SOURCE_TABLE_NOT_ALLOWED_FOR_LEFT_OUTER_VIRTUAL_TABLES
497+
.buildMessage(
498+
ScalarDbUtils.getFullTableName(
499+
delete.forNamespace().get(), delete.forTable().get())));
500+
} else {
501+
deleteBuilderForRightSourceTable.condition(ConditionBuilder.deleteIf(rightExpressions));
502+
}
468503
}
469504
}
470505
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.scalar.db.storage.jdbc;
2+
3+
import com.scalar.db.api.Delete;
4+
import com.scalar.db.api.Put;
5+
import java.util.Map;
6+
7+
/** A utility class to manipulate the operation attributes for JDBC. */
8+
public final class JdbcOperationAttributes {
9+
private static final String OPERATION_ATTRIBUTE_PREFIX = "jdbc-";
10+
11+
public static final String
12+
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED =
13+
OPERATION_ATTRIBUTE_PREFIX
14+
+ "left-outer-virtual-table-put-if-is-null-on-right-columns-conversion-enabled";
15+
16+
public static final String LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_FOR_RIGHT_ALLOWED =
17+
OPERATION_ATTRIBUTE_PREFIX
18+
+ "left-outer-virtual-table-delete-if-is-null-on-right-columns-allowed";
19+
20+
private JdbcOperationAttributes() {}
21+
22+
public static boolean isLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(Put put) {
23+
return put.getAttribute(
24+
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED)
25+
.map(Boolean::parseBoolean)
26+
.orElse(true);
27+
}
28+
29+
public static void setLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(
30+
Map<String, String> attributes, boolean enabled) {
31+
attributes.put(
32+
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED,
33+
Boolean.toString(enabled));
34+
}
35+
36+
public static boolean isLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(Delete delete) {
37+
return delete
38+
.getAttribute(LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_FOR_RIGHT_ALLOWED)
39+
.map(Boolean::parseBoolean)
40+
.orElse(false);
41+
}
42+
43+
public static void setLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(
44+
Map<String, String> attributes, boolean allowed) {
45+
attributes.put(
46+
LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_FOR_RIGHT_ALLOWED, Boolean.toString(allowed));
47+
}
48+
}

0 commit comments

Comments
 (0)