Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/java/com/scalar/db/common/CoreError.java
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,12 @@ public enum CoreError implements ScalarDbError {
"Source tables cannot be dropped while virtual tables depending on them exist. Source table: %s; Virtual tables: %s",
"",
""),
DELETE_IF_IS_NULL_FOR_RIGHT_SOURCE_TABLE_NOT_ALLOWED_FOR_LEFT_OUTER_VIRTUAL_TABLES(
Category.USER_ERROR,
"0276",
"The DeleteIf IS_NULL condition for right source table columns is not allowed in LEFT_OUTER virtual tables. Virtual table: %s",
"",
""),

//
// Errors for the concurrency error category
Expand Down
42 changes: 40 additions & 2 deletions core/src/main/java/com/scalar/db/storage/jdbc/JdbcDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.scalar.db.exception.storage.NoMutationException;
import com.scalar.db.exception.storage.RetriableExecutionException;
import com.scalar.db.io.Column;
import com.scalar.db.util.ScalarDbUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
Expand Down Expand Up @@ -395,7 +396,19 @@ private List<Put> dividePutForSourceTables(Put put, VirtualTableInfo virtualTabl
putBuilderForLeftSourceTable.condition(ConditionBuilder.putIf(leftExpressions));
}
if (!rightExpressions.isEmpty()) {
putBuilderForRightSourceTable.condition(ConditionBuilder.putIf(rightExpressions));
if (isAllIsNullOnRightColumnsInLeftOuterJoin(virtualTableInfo, rightExpressions)
&& JdbcOperationAttributes
.isLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(put)) {
// In a LEFT_OUTER join, when all conditions on the right source table columns are
// IS_NULL, we cannot distinguish whether we should check for the existence of a
// right-side record with NULL values or for the case where the right-side record does
// not exist at all. Therefore, this behavior is controlled by the operation attribute.
// By default, we convert the condition to PutIfNotExists, assuming that the more common
// use case is to check that the right-side record does not exist.
putBuilderForRightSourceTable.condition(ConditionBuilder.putIfNotExists());
} else {
putBuilderForRightSourceTable.condition(ConditionBuilder.putIf(rightExpressions));
}
}
}
}
Expand Down Expand Up @@ -464,7 +477,25 @@ private List<Delete> divideDeleteForSourceTables(Delete delete, VirtualTableInfo
deleteBuilderForLeftSourceTable.condition(ConditionBuilder.deleteIf(leftExpressions));
}
if (!rightExpressions.isEmpty()) {
deleteBuilderForRightSourceTable.condition(ConditionBuilder.deleteIf(rightExpressions));
if (isAllIsNullOnRightColumnsInLeftOuterJoin(virtualTableInfo, rightExpressions)
&& !JdbcOperationAttributes
.isLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(delete)) {
// In a LEFT_OUTER join, when all conditions on the right source table columns are
// IS_NULL, we cannot distinguish whether we should check for the existence of a
// right-side record with NULL values or for the case where the right-side record does
// not exist at all. This makes the delete operation semantically ambiguous. Therefore,
// this behavior is controlled by the operation attribute. By default, we disallow this
// operation to prevent unintended behavior.
assert delete.forNamespace().isPresent() && delete.forTable().isPresent();
throw new IllegalArgumentException(
CoreError
.DELETE_IF_IS_NULL_FOR_RIGHT_SOURCE_TABLE_NOT_ALLOWED_FOR_LEFT_OUTER_VIRTUAL_TABLES
.buildMessage(
ScalarDbUtils.getFullTableName(
delete.forNamespace().get(), delete.forTable().get())));
} else {
deleteBuilderForRightSourceTable.condition(ConditionBuilder.deleteIf(rightExpressions));
}
}
}
}
Expand All @@ -474,6 +505,13 @@ private List<Delete> divideDeleteForSourceTables(Delete delete, VirtualTableInfo
return Arrays.asList(deleteForLeftSourceTable, deleteForRightSourceTable);
}

private boolean isAllIsNullOnRightColumnsInLeftOuterJoin(
VirtualTableInfo virtualTableInfo, List<ConditionalExpression> rightExpressions) {
return virtualTableInfo.getJoinType() == VirtualTableJoinType.LEFT_OUTER
&& rightExpressions.stream()
.allMatch(e -> e.getOperator() == ConditionalExpression.Operator.IS_NULL);
}

private void close(Connection connection) {
try {
if (connection != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.scalar.db.storage.jdbc;

import com.scalar.db.api.Delete;
import com.scalar.db.api.Put;
import java.util.Map;

/** A utility class to manipulate the operation attributes for JDBC. */
public final class JdbcOperationAttributes {
private static final String OPERATION_ATTRIBUTE_PREFIX = "jdbc-";

public static final String
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED =
OPERATION_ATTRIBUTE_PREFIX
+ "left-outer-virtual-table-put-if-is-null-on-right-columns-conversion-enabled";

public static final String LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_ON_RIGHT_COLUMNS_ALLOWED =
OPERATION_ATTRIBUTE_PREFIX
+ "left-outer-virtual-table-delete-if-is-null-on-right-columns-allowed";

private JdbcOperationAttributes() {}

public static boolean isLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(Put put) {
return put.getAttribute(
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED)
.map(Boolean::parseBoolean)
.orElse(true);
}

public static void setLeftOuterVirtualTablePutIfIsNullOnRightColumnsConversionEnabled(
Map<String, String> attributes, boolean enabled) {
attributes.put(
LEFT_OUTER_VIRTUAL_TABLE_PUT_IF_IS_NULL_ON_RIGHT_COLUMNS_CONVERSION_ENABLED,
Boolean.toString(enabled));
}

public static boolean isLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(Delete delete) {
return delete
.getAttribute(LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_ON_RIGHT_COLUMNS_ALLOWED)
.map(Boolean::parseBoolean)
.orElse(false);
}

public static void setLeftOuterVirtualTableDeleteIfIsNullOnRightColumnsAllowed(
Map<String, String> attributes, boolean allowed) {
attributes.put(
LEFT_OUTER_VIRTUAL_TABLE_DELETE_IF_IS_NULL_ON_RIGHT_COLUMNS_ALLOWED,
Boolean.toString(allowed));
}
}
Loading
Loading