[fix](Nereids): tolerate DateLike overflow in SQL CAST/CONVERT (#24943)
- explicit type cast, we need tolerate overflow and convert it to be NULL - implicit type cast, throw exception
This commit is contained in:
@ -1425,7 +1425,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
public Expression visitCast(DorisParser.CastContext ctx) {
|
||||
return ParserUtils.withOrigin(ctx, () -> {
|
||||
DataType dataType = ((DataType) typedVisit(ctx.dataType())).conversion();
|
||||
Expression cast = new Cast(getExpression(ctx.expression()), dataType);
|
||||
Expression cast = new Cast(getExpression(ctx.expression()), dataType, true);
|
||||
return processCast(cast, dataType);
|
||||
});
|
||||
}
|
||||
@ -1470,7 +1470,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
public Expression visitConvertType(DorisParser.ConvertTypeContext ctx) {
|
||||
return ParserUtils.withOrigin(ctx, () -> {
|
||||
DataType dataType = ((DataType) typedVisit(ctx.type)).conversion();
|
||||
Expression cast = new Cast(getExpression(ctx.argument), dataType);
|
||||
Expression cast = new Cast(getExpression(ctx.argument), dataType, true);
|
||||
return processCast(cast, dataType);
|
||||
});
|
||||
}
|
||||
|
||||
@ -352,7 +352,12 @@ public class FoldConstantRuleOnFE extends AbstractExpressionRewriteRule {
|
||||
try {
|
||||
return ((DateLikeType) dataType).fromString(((StringLikeLiteral) child).getStringValue());
|
||||
} catch (AnalysisException t) {
|
||||
return new NullLiteral(dataType);
|
||||
if (cast.isExplicitType()) {
|
||||
return new NullLiteral(dataType);
|
||||
} else {
|
||||
// If cast is from type coercion, we don't use NULL literal and will throw exception.
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
@ -33,16 +33,31 @@ import java.util.Objects;
|
||||
*/
|
||||
public class Cast extends Expression implements UnaryExpression {
|
||||
|
||||
// CAST can be from SQL Query or Type Coercion.
|
||||
private final boolean isExplicitType;
|
||||
|
||||
private final DataType targetType;
|
||||
|
||||
public Cast(Expression child, DataType targetType, boolean isExplicitType) {
|
||||
super(ImmutableList.of(child));
|
||||
this.targetType = Objects.requireNonNull(targetType, "targetType can not be null");
|
||||
this.isExplicitType = isExplicitType;
|
||||
}
|
||||
|
||||
public Cast(Expression child, DataType targetType) {
|
||||
super(ImmutableList.of(child));
|
||||
this.targetType = Objects.requireNonNull(targetType, "targetType can not be null");
|
||||
this.isExplicitType = false;
|
||||
}
|
||||
|
||||
private Cast(List<Expression> child, DataType targetType) {
|
||||
private Cast(List<Expression> child, DataType targetType, boolean isExplicitType) {
|
||||
super(child);
|
||||
this.targetType = Objects.requireNonNull(targetType, "targetType can not be null");
|
||||
this.isExplicitType = isExplicitType;
|
||||
}
|
||||
|
||||
public boolean isExplicitType() {
|
||||
return isExplicitType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,7 +87,7 @@ public class Cast extends Expression implements UnaryExpression {
|
||||
@Override
|
||||
public Cast withChildren(List<Expression> children) {
|
||||
Preconditions.checkArgument(children.size() == 1);
|
||||
return new Cast(children, getDataType());
|
||||
return new Cast(children, targetType, isExplicitType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -83,7 +83,7 @@ public class BuiltinFunctionBuilder extends FunctionBuilder {
|
||||
if (isVariableLength) {
|
||||
return builderMethod.newInstance(toVariableLengthArguments(arguments));
|
||||
} else {
|
||||
return builderMethod.newInstance(arguments.stream().toArray(Object[]::new));
|
||||
return builderMethod.newInstance(arguments.toArray());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
String argString = arguments.stream()
|
||||
|
||||
@ -18,9 +18,12 @@
|
||||
package org.apache.doris.nereids.trees.expressions.functions.scalar;
|
||||
|
||||
import org.apache.doris.catalog.FunctionSignature;
|
||||
import org.apache.doris.nereids.trees.expressions.Cast;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
|
||||
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
|
||||
import org.apache.doris.nereids.trees.expressions.shape.TernaryExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
|
||||
import org.apache.doris.nereids.types.DateTimeType;
|
||||
@ -49,7 +52,18 @@ public class ConvertTz extends ScalarFunction
|
||||
* constructor with 3 arguments.
|
||||
*/
|
||||
public ConvertTz(Expression arg0, Expression arg1, Expression arg2) {
|
||||
super("convert_tz", arg0, arg1, arg2);
|
||||
super("convert_tz", castDateTime(arg0), arg1, arg2);
|
||||
}
|
||||
|
||||
private static Expression castDateTime(Expression arg0) {
|
||||
// https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_convert-tz
|
||||
// convert_tz() should be explicit cast, so we create a explicit cast here
|
||||
try {
|
||||
return arg0 instanceof StringLikeLiteral ? new Cast(arg0, DateTimeV2Type.forTypeFromString(
|
||||
((StringLikeLiteral) arg0).getStringValue()), true) : arg0;
|
||||
} catch (Exception e) {
|
||||
return new NullLiteral(DateTimeV2Type.SYSTEM_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -101,17 +101,27 @@ public class DateTimeLiteral extends DateLiteral {
|
||||
* determine scale by datetime string
|
||||
*/
|
||||
public static int determineScale(String s) {
|
||||
TemporalAccessor dateTime = parse(s);
|
||||
int microSecond = DateUtils.getOrDefault(dateTime, ChronoField.MICRO_OF_SECOND);
|
||||
|
||||
if (microSecond == 0) {
|
||||
if (!s.contains("-") && !s.contains(":")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scale = 6;
|
||||
while (microSecond % 10 == 0) {
|
||||
s = normalize(s);
|
||||
if (s.length() <= 19 || s.charAt(19) != '.') {
|
||||
return 0;
|
||||
}
|
||||
// from index 19 find the index of first char which is not digit
|
||||
int scale = 0;
|
||||
for (int i = 20; i < s.length(); i++) {
|
||||
if (!Character.isDigit(s.charAt(i))) {
|
||||
break;
|
||||
}
|
||||
scale++;
|
||||
}
|
||||
// trim the tailing zero
|
||||
for (int i = 19 + scale; i >= 19; i--) {
|
||||
if (s.charAt(i) != '0') {
|
||||
break;
|
||||
}
|
||||
scale--;
|
||||
microSecond /= 10;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
@ -83,9 +83,6 @@ public class DateTimeV2Type extends DateLikeType {
|
||||
* may be we need to check for validity?
|
||||
*/
|
||||
public static DateTimeV2Type forTypeFromString(String s) {
|
||||
if (!s.contains(".")) {
|
||||
return DateTimeV2Type.SYSTEM_DEFAULT;
|
||||
}
|
||||
int scale = DateTimeLiteral.determineScale(s);
|
||||
return DateTimeV2Type.of(scale);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user