-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[CALCITE-6435] SqlToRel conversion of IN expressions may lead to incorrect simplifications #3835
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -1682,7 +1682,10 @@ private <C extends Comparable<C>> RexNode simplifyAnd2ForUnknownAsFalse( | |
| final RexLiteral literal = comparison.literal; | ||
| final RexLiteral prevLiteral = | ||
| equalityConstantTerms.put(comparison.ref, literal); | ||
| if (prevLiteral != null && !literal.equals(prevLiteral)) { | ||
|
|
||
| if (prevLiteral != null | ||
| && literal.getType().equals(prevLiteral.getType()) | ||
| && !literal.equals(prevLiteral)) { | ||
| return rexBuilder.makeLiteral(false); | ||
| } | ||
| } else if (RexUtil.isReferenceOrAccess(left, true) | ||
|
|
@@ -1753,7 +1756,7 @@ private <C extends Comparable<C>> RexNode simplifyAnd2ForUnknownAsFalse( | |
| if (literal2 == null) { | ||
| continue; | ||
| } | ||
| if (!literal1.equals(literal2)) { | ||
| if (literal1.getType().equals(literal2.getType()) && !literal1.equals(literal2)) { | ||
|
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'm puzzled about the necessity of adding extra type equals checks. What specific issues might arise without them in SQL simplification? |
||
| // If an expression is equal to two different constants, | ||
| // it is not satisfiable | ||
| return rexBuilder.makeLiteral(false); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -175,6 +175,8 @@ | |
| import org.apache.calcite.sql.validate.SqlValidatorTable; | ||
| import org.apache.calcite.sql.validate.SqlValidatorUtil; | ||
| import org.apache.calcite.sql.validate.SqlWithItemTableRef; | ||
| import org.apache.calcite.sql2rel.SqlToRelConverter.Blackboard; | ||
| import org.apache.calcite.sql2rel.SqlToRelConverter.SqlIdentifierFinder; | ||
| import org.apache.calcite.tools.RelBuilder; | ||
| import org.apache.calcite.tools.RelBuilderFactory; | ||
| import org.apache.calcite.util.ImmutableBitSet; | ||
|
|
@@ -1184,16 +1186,16 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { | |
| } | ||
| final SqlNode leftKeyNode = call.operand(0); | ||
|
|
||
| final List<RexNode> leftKeys; | ||
| final List<SqlNode> leftSqlKeys; | ||
| switch (leftKeyNode.getKind()) { | ||
| case ROW: | ||
| leftKeys = new ArrayList<>(); | ||
| leftSqlKeys = new ArrayList<>(); | ||
| for (SqlNode sqlExpr : ((SqlBasicCall) leftKeyNode).getOperandList()) { | ||
| leftKeys.add(bb.convertExpression(sqlExpr)); | ||
| leftSqlKeys.add(sqlExpr); | ||
| } | ||
| break; | ||
| default: | ||
| leftKeys = ImmutableList.of(bb.convertExpression(leftKeyNode)); | ||
| leftSqlKeys = ImmutableList.of(leftKeyNode); | ||
| } | ||
|
|
||
| if (query instanceof SqlNodeList) { | ||
|
|
@@ -1204,7 +1206,7 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { | |
| subQuery.expr = | ||
| convertInToOr( | ||
| bb, | ||
| leftKeys, | ||
| leftSqlKeys, | ||
| valueList, | ||
| (SqlInOperator) call.getOperator()); | ||
| return; | ||
|
|
@@ -1215,6 +1217,10 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { | |
| // reference to Q below. | ||
| } | ||
|
|
||
| final List<RexNode> leftKeys = leftSqlKeys.stream() | ||
| .map(bb::convertExpression) | ||
| .collect(toImmutableList()); | ||
|
|
||
| // Project out the search columns from the left side | ||
|
|
||
| // Q1: | ||
|
|
@@ -1718,12 +1724,11 @@ public RelNode convertToSingleValueSubq( | |
| */ | ||
| private @Nullable RexNode convertInToOr( | ||
| final Blackboard bb, | ||
| final List<RexNode> leftKeys, | ||
| final List<SqlNode> leftKeys, | ||
| SqlNodeList valuesList, | ||
| SqlInOperator op) { | ||
| final List<RexNode> comparisons = new ArrayList<>(); | ||
| for (SqlNode rightVals : valuesList) { | ||
| RexNode rexComparison; | ||
| final SqlOperator comparisonOp; | ||
| if (op instanceof SqlQuantifyOperator) { | ||
| comparisonOp = | ||
|
|
@@ -1732,25 +1737,23 @@ public RelNode convertToSingleValueSubq( | |
| } else { | ||
| comparisonOp = SqlStdOperatorTable.EQUALS; | ||
| } | ||
| RexNode rexComparison; | ||
| if (leftKeys.size() == 1) { | ||
| rexComparison = | ||
| rexBuilder.makeCall(comparisonOp, | ||
| leftKeys.get(0), | ||
| ensureSqlType(leftKeys.get(0).getType(), | ||
| bb.convertExpression(rightVals))); | ||
| SqlCall sqlCall = | ||
| comparisonOp.createCall(rightVals.getParserPosition(), leftKeys.get(0), rightVals); | ||
| rexComparison = bb.convertExpression(sqlCall); | ||
| } else { | ||
| assert rightVals instanceof SqlCall; | ||
| final SqlBasicCall call = (SqlBasicCall) rightVals; | ||
| assert (call.getOperator() instanceof SqlRowOperator) | ||
| && call.operandCount() == leftKeys.size(); | ||
| rexComparison = | ||
| RexUtil.composeConjunction(rexBuilder, | ||
| Util.transform( | ||
| Pair.zip(leftKeys, call.getOperandList()), | ||
| pair -> rexBuilder.makeCall(comparisonOp, pair.left, | ||
| // TODO: remove requireNonNull when checkerframework issue resolved | ||
| ensureSqlType(requireNonNull(pair.left, "pair.left").getType(), | ||
| bb.convertExpression(pair.right))))); | ||
| RexUtil.composeConjunction( | ||
|
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. Is the focus solely on the SQL IN case, or might there be a need for adjustments elsewhere too?
Member
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. not really; this |
||
| rexBuilder, Util.transform( | ||
| Pair.zip(leftKeys, call.getOperandList()), | ||
| pair -> bb.convertExpression( | ||
| comparisonOp.createCall( | ||
| rightVals.getParserPosition(), pair.left, pair.right)))); | ||
| } | ||
| comparisons.add(rexComparison); | ||
| } | ||
|
|
@@ -1769,18 +1772,6 @@ public RelNode convertToSingleValueSubq( | |
| } | ||
| } | ||
|
|
||
| /** Ensures that an expression has a given {@link SqlTypeName}, applying a | ||
| * cast if necessary. If the expression already has the right type family, | ||
| * returns the expression unchanged. */ | ||
| private RexNode ensureSqlType(RelDataType type, RexNode node) { | ||
| if (type.getSqlTypeName() == node.getType().getSqlTypeName() | ||
| || (type.getSqlTypeName() == SqlTypeName.VARCHAR | ||
| && node.getType().getSqlTypeName() == SqlTypeName.CHAR)) { | ||
| return node; | ||
| } | ||
| return rexBuilder.ensureType(type, node, true); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the list size threshold under which {@link #convertInToOr} is used. | ||
| * Lists of this size or greater will instead be converted to use a join | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1030,7 +1030,7 @@ private void checkGroupBySingleSortLimit(boolean approx) { | |
| + "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], " | ||
| + "filter=[AND(" | ||
| + "=($3, 'High Top Dried Mushrooms'), " | ||
| + "SEARCH($87, Sarg['Q2', 'Q3']:CHAR(2)), " | ||
| + "SEARCH($87, Sarg['Q2':VARCHAR, 'Q3':VARCHAR]:VARCHAR), " | ||
|
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 find this change surprising.
Member
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 was surprised too ; however this is the same kind of expressions |
||
| + "=($30, 'WA'))], " | ||
| + "projects=[[$30, $29, $3]], groups=[{0, 1, 2}], aggs=[[]])\n"; | ||
| sql(sql) | ||
|
|
@@ -1072,7 +1072,7 @@ private void checkGroupBySingleSortLimit(boolean approx) { | |
| + "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], " | ||
| + "filter=[AND(" | ||
| + "=($3, 'High Top Dried Mushrooms'), " | ||
| + "SEARCH($87, Sarg['Q2', 'Q3']:CHAR(2)), " | ||
| + "SEARCH($87, Sarg['Q2':VARCHAR, 'Q3':VARCHAR]:VARCHAR), " | ||
| + "=($30, 'WA'))], " | ||
| + "projects=[[$30, $29, $3]])\n"; | ||
| sql(sql) | ||
|
|
||
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.
so two literals can be equal but their types can be different?
one could argue that this is a bug in SqlLiteral::equals.
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.
the situation was caused by two literals which had the same value; but different types (
VARCHAR;CHAR(n))actually the predicate based simplification also does this (beyond other things); but there are situations in which its disabled.
Uh oh!
There was an error while loading. Please reload this page.
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.
I think
VARCHARandCHARshould be considered equivalent ifRexLiteral.valueis the sameThere 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.
CHAR(N) indicates that the literal has to be padded with spaces, whereas VARCHAR indicates that trailing spaces are to be ignored. So two literals with identical strings but different types can have different values.