[
https://issues.apache.org/jira/browse/CALCITE-7627?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18111813#comment-18111813
]
zzwqqq commented on CALCITE-7627:
---------------------------------
I was thinking of using the standard Config mechanism. Here is a sketch for
bounded VARCHAR, with the existing assignment mapping and Config setup omitted.
{code:java}
@Value.Immutable(singleton = false)
@SuppressWarnings("immutables")
public interface Config extends ConverterRule.Config {
@Value.Default
default AssignmentPolicy assignmentPolicy() {
return VARCHAR_POLICY;
}
Config withAssignmentPolicy(AssignmentPolicy policy);
}
@FunctionalInterface
public interface AssignmentPolicy {
RexNode apply(AssignmentContext context);
}
{code}
AssignmentContext provides the RexBuilder, source expression, target type, and
addCheck method.
{code:java}
private static final AssignmentPolicy VARCHAR_POLICY = context -> {
final RexNode source = context.source();
final RelDataType targetType = context.targetType();
if (targetType.getSqlTypeName() != SqlTypeName.VARCHAR
|| targetType.getPrecision() == RelDataType.PRECISION_NOT_SPECIFIED) {
return source;
}
final RexBuilder rexBuilder = context.rexBuilder();
final RexNode length =
rexBuilder.makeCall(SqlStdOperatorTable.CHAR_LENGTH, source);
final RexNode fits =
rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
length,
rexBuilder.makeExactLiteral(
BigDecimal.valueOf(targetType.getPrecision())));
context.addCheck(fits, "Value exceeds " + targetType);
return source;
};
{code}
Inside AssignmentContext:
{code:java}
/** Adds a check: FALSE throws; TRUE and NULL pass. */
public void addCheck(RexNode condition, String message) {
checks.add(
rexBuilder.makeCall(SqlInternalOperators.THROW_UNLESS,
rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_FALSE, condition),
rexBuilder.makeLiteral(message)));
}
{code}
The rule applies the configured policy to each assignment, then builds a Calc
from the returned expressions and collected checks:
{code:java}
for (RelDataTypeField field : assignmentType.getFieldList()) {
final int sourceOrdinal = assignmentOffset + field.getIndex();
final AssignmentContext context =
new AssignmentContext(rexBuilder, projects.get(sourceOrdinal),
field.getType(), checks);
projects.set(sourceOrdinal, assignmentPolicy.apply(context));
}
if (!checks.isEmpty()
|| !RexUtil.isIdentity(projects, input.getRowType())) {
input = EnumerableCalc.create(input,
RexProgram.create(input.getRowType(), projects,
RexUtil.composeConjunction(rexBuilder, checks, true),
input.getRowType().getFieldNames(), rexBuilder));
}
{code}
This Calc becomes the input to EnumerableTableModify. Its expressions use the
existing Enumerable code generator and run as input rows are consumed.
An application would replace the default rule with its configured instance:
{code:java}
planner.removeRule(EnumerableRules.ENUMERABLE_TABLE_MODIFICATION_RULE);
planner.addRule(
EnumerableTableModifyRule.DEFAULT_CONFIG
.as(EnumerableTableModifyRule.Config.class)
.withAssignmentPolicy(customPolicy)
.toRule(EnumerableTableModifyRule.class));
{code}
Would you be happy with this as a starting point?
> Enumerable DML should reject assignments that may lose data
> -----------------------------------------------------------
>
> Key: CALCITE-7627
> URL: https://issues.apache.org/jira/browse/CALCITE-7627
> Project: Calcite
> Issue Type: Bug
> Reporter: zzwqqq
> Assignee: zzwqqq
> Priority: Major
> Labels: pull-request-available
>
> Enumerable DML currently accepts some assignments that may lose data.
> One example is assigning a longer string to a shorter VARCHAR column:
> {code:sql}
> CREATE TABLE dept (deptno INTEGER NOT NULL, name VARCHAR(10));
> INSERT INTO dept
> VALUES (30, 'Engineering');
> {code}
> The Enumerable/server path can insert the value now. With their default
> settings, PostgreSQL, MySQL, and Oracle reject this case:
> https://onecompiler.com/postgresql/44sf3dz68
> https://onecompiler.com/mysql/44sf3efz4
> https://onecompiler.com/oracle/44sf3ewmh
> The assignment should produce a runtime error rather than truncating or
> accepting the value.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)