|
35 | 35 | import com.scalar.db.exception.storage.NoMutationException; |
36 | 36 | import com.scalar.db.exception.storage.RetriableExecutionException; |
37 | 37 | import com.scalar.db.io.Column; |
| 38 | +import com.scalar.db.util.ScalarDbUtils; |
38 | 39 | import java.sql.Connection; |
39 | 40 | import java.sql.SQLException; |
40 | 41 | import java.util.ArrayList; |
@@ -395,7 +396,21 @@ private List<Put> dividePutForSourceTables(Put put, VirtualTableInfo virtualTabl |
395 | 396 | putBuilderForLeftSourceTable.condition(ConditionBuilder.putIf(leftExpressions)); |
396 | 397 | } |
397 | 398 | 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 | + } |
399 | 414 | } |
400 | 415 | } |
401 | 416 | } |
@@ -464,7 +479,27 @@ private List<Delete> divideDeleteForSourceTables(Delete delete, VirtualTableInfo |
464 | 479 | deleteBuilderForLeftSourceTable.condition(ConditionBuilder.deleteIf(leftExpressions)); |
465 | 480 | } |
466 | 481 | 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 | + } |
468 | 503 | } |
469 | 504 | } |
470 | 505 | } |
|
0 commit comments