-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[CALCITE-7746] Review operation safety on arithmetic on dates and intervals #5228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
715fbcb
fb3bc2a
f5c9777
694dbc9
f63d380
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| import org.apache.calcite.sql.SqlOperator; | ||
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; | ||
| import org.apache.calcite.sql.parser.SqlParserPos; | ||
| import org.apache.calcite.sql.type.IntervalSqlType; | ||
| import org.apache.calcite.sql.type.SqlTypeCoercionRule; | ||
| import org.apache.calcite.sql.type.SqlTypeFamily; | ||
| import org.apache.calcite.sql.type.SqlTypeName; | ||
|
|
@@ -90,6 +91,8 @@ public class RexSimplify { | |
| final RexUnknownAs defaultUnknownAs; | ||
| final boolean predicateElimination; | ||
| private final RexExecutor executor; | ||
| /** Lazily initialized, to be obtained with {@link #getSafeRexVisitor()}. */ | ||
| private @Nullable SafeRexVisitor safeRexVisitor; | ||
|
|
||
| private static final Strong STRONG = new Strong(); | ||
|
|
||
|
|
@@ -184,6 +187,14 @@ private RexSimplify withPredicateElimination(boolean predicateElimination) { | |
| predicateElimination, paranoid, executor); | ||
| } | ||
|
|
||
| /** Gets the {@link SafeRexVisitor} (lazily initialized). */ | ||
| private SafeRexVisitor getSafeRexVisitor() { | ||
| if (this.safeRexVisitor == null) { | ||
| this.safeRexVisitor = new SafeRexVisitor(this); | ||
| } | ||
| return this.safeRexVisitor; | ||
| } | ||
|
|
||
| /** Simplifies a boolean expression, always preserving its type and its | ||
| * nullability. | ||
| * | ||
|
|
@@ -1212,7 +1223,7 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs unknownAs) { | |
| // simplifies to "operand0 IS NOT NULL AND operand1 IS NOT NULL"; | ||
| // this branch PRESERVES the operand subtrees, so it only | ||
| // needs SHALLOW safety of the outer operator | ||
| if (!SafeRexVisitor.INSTANCE.isShallowSafe(a)) { | ||
| if (!this.getSafeRexVisitor().isShallowSafe(a)) { | ||
| return simplifiedResult; | ||
| } | ||
| final List<RexNode> operands = new ArrayList<>(); | ||
|
|
@@ -1279,7 +1290,7 @@ private RexNode simplifyIs(RexCall call, RexUnknownAs unknownAs) { | |
| return rexBuilder.makeLiteral(false); | ||
| case ANY: | ||
| // See symmetric comment in simplifyIsNotNull | ||
| if (!SafeRexVisitor.INSTANCE.isShallowSafe(a)) { | ||
| if (!this.getSafeRexVisitor().isShallowSafe(a)) { | ||
| return simplifiedResult; | ||
| } | ||
| final List<RexNode> operands = new ArrayList<>(); | ||
|
|
@@ -1543,17 +1554,28 @@ private static List<RexNode> toCaseOperands(List<CaseBranch> branches) { | |
| } | ||
|
|
||
| /** | ||
| * Decides whether it is safe to flatten the given CASE part into ANDs/ORs. | ||
| * Visitor to analyze a given {@link RexNode} and decide whether | ||
| * it is safe to unwind, see {@link #isSafeExpression(RexNode)}. | ||
| */ | ||
| enum SafeRexVisitor implements RexVisitor<Boolean> { | ||
| INSTANCE; | ||
| private static class SafeRexVisitor implements RexVisitor<Boolean> { | ||
|
|
||
| private static final SafeRexVisitor INSTANCE = new SafeRexVisitor(); | ||
|
|
||
| @SuppressWarnings("ImmutableEnumChecker") | ||
| private final Set<SqlKind> safeOps; | ||
| @SuppressWarnings("ImmutableEnumChecker") | ||
| private final ImmutableSet<SqlOperator> safeOperators; | ||
|
|
||
| SafeRexVisitor() { | ||
| // Optional RexSimplify, if present it can be used for simplifications during isSafe computation | ||
| private final @Nullable RexSimplify rexSimplify; | ||
|
|
||
| private SafeRexVisitor() { | ||
| this(null); | ||
| } | ||
|
|
||
| SafeRexVisitor(@Nullable RexSimplify rexSimplify) { | ||
| this.rexSimplify = rexSimplify; | ||
|
|
||
| ImmutableSet.Builder<SqlOperator> builder = ImmutableSet.builder(); | ||
| builder.addAll(SqlStdOperatorTable.QUANTIFY_OPERATORS); | ||
| safeOperators = builder.build(); | ||
|
|
@@ -1614,16 +1636,23 @@ private boolean isSafe(RexCall call, boolean deep) { | |
| SqlKind sqlKind = call.getKind(); | ||
| SqlOperator sqlOperator = call.getOperator(); | ||
|
|
||
| if (SqlKind.CHECKED_ARITHMETIC.contains(sqlKind)) { | ||
| // Checked arithmetic throws on overflow, so it is only safe when the | ||
| // arithmetic is never performed, i.e. when an operand is NULL. | ||
| if (SqlKind.CHECKED_ARITHMETIC.contains(sqlKind) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not thrilled about this: we are leaking details about specific operators in a class (RexSimplify) which should be rather general. This was already there in the previous solution, but this makes it worse. On the other hand I am not sure I have a better proposal. The safety should really be a property that a RexCall can report - based on inspecting it's argument types.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is a third party cannot add new operators without modifying this visitor
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this system looks a bit ad-hoc, but it is the one we have in place (until we decide a big re-work on this mechanism). Theoretically a third party can add new operators and override the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The API should take a RexCall which can access types.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes.... I guess in SqlOperator we could deprecate And start using instead: |
||
| || (SqlKind.BINARY_ARITHMETIC.contains(sqlKind) | ||
| && call.operands.stream().anyMatch(o -> o.getType() instanceof IntervalSqlType))) { | ||
| // Checked arithmetic and binary arithmetic on intervals can throw at runtime, | ||
| // so it is only safe when the arithmetic is never performed, i.e. when an operand is NULL | ||
| if (deep) { | ||
| boolean areOperandsSafe = RexVisitorImpl.visitArrayAnd(this, call.operands); | ||
| if (!areOperandsSafe) { | ||
| return false; | ||
| } | ||
| } | ||
| return call.operands.stream().anyMatch(o -> RexUtil.isNullLiteral(o, true)); | ||
| return call.operands.stream() | ||
| .anyMatch( | ||
| o -> RexUtil.isNullLiteral( | ||
| // Simplify operand as much as possible | ||
| this.rexSimplify == null ? o : this.rexSimplify.simplify(o), | ||
| true)); | ||
| } | ||
|
|
||
| switch (sqlKind) { | ||
|
|
@@ -1734,7 +1763,16 @@ boolean isShallowSafe(RexNode node) { | |
| * | ||
| * <p>Checked arithmetic is unsafe too, because it throws on overflow | ||
| */ | ||
| static boolean isSafeExpression(RexNode r) { | ||
| private boolean isSafeExpression(RexNode r) { | ||
| return r.accept(this.getSafeRexVisitor()); | ||
| } | ||
|
|
||
| /** | ||
| * Static equivalent of the method above, without any RexSimplify in the context | ||
| * (thus less powerful); to be used by classes on the same package that need to perform | ||
| * a "basic" isSafe computation without having a RexSimplify at hand. | ||
| */ | ||
| static boolean isSafe(RexNode r) { | ||
| return r.accept(SafeRexVisitor.INSTANCE); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this javadoc still accurate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked outdated indeed. I've updated it.