Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ private RelOptRules() {
PruneEmptyRules.WINDOW_INSTANCE,
PruneEmptyRules.JOIN_LEFT_INSTANCE,
PruneEmptyRules.JOIN_RIGHT_INSTANCE,
PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE,
PruneEmptyRules.EMPTY_TABLE_INSTANCE,
SingleValuesOptimizationRules.JOIN_LEFT_INSTANCE,
SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,20 @@
import org.apache.calcite.rel.core.Minus;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.TableModify;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.core.Values;
import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.metadata.RelMdUtil;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilderFactory;

import org.immutables.value.Value;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -223,14 +221,13 @@
* Rule that converts a {@link org.apache.calcite.rel.core.Sort}
* to empty if it has {@code LIMIT 0}.
*
* <p>Examples:
*
* <ul>
* <li>Sort[fetch=0] becomes Empty
* </ul>
* @deprecated Use {@link #EMPTY_TABLE_INSTANCE}, which uses
* {@link RelMdUtil#isRelDefinitelyEmpty} to prune any relational expression
* that is definitely empty.
*/
@Deprecated // to be removed before 2.0
public static final RelOptRule SORT_FETCH_ZERO_INSTANCE =

Check warning on line 229 in core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ-x7cboPCd1aEIgB3Pt&open=AZ-x7cboPCd1aEIgB3Pt&pullRequest=5132
SortFetchZeroRuleConfig.DEFAULT.toRule();
ZeroMaxRowsRuleConfig.DEFAULT.toRule();

/**
* Rule that converts an {@link org.apache.calcite.rel.core.Aggregate}
Expand Down Expand Up @@ -525,25 +522,6 @@
}
}

/** Configuration for a rule that prunes a Sort if it has limit 0. */
@Value.Immutable
public interface SortFetchZeroRuleConfig extends PruneEmptyRule.Config {
SortFetchZeroRuleConfig DEFAULT = ImmutableSortFetchZeroRuleConfig.of()
.withOperandSupplier(b -> b.operand(Sort.class).anyInputs())
.withDescription("PruneSortLimit0");

@Override default PruneEmptyRule toRule() {
return new RemoveEmptySingleRule(this) {
@Override public boolean matches(final RelOptRuleCall call) {
Sort sort = call.rel(0);
return sort.fetch != null
&& !(sort.fetch instanceof RexDynamicParam)
&& RexLiteral.bigDecimalValue(sort.fetch).equals(BigDecimal.ZERO);
}
};
}
}

/** Configuration for rule that prunes a join it its left input is
* empty. */
@Value.Immutable
Expand Down Expand Up @@ -684,24 +662,38 @@
}
}

/** Configuration for rule that transforms an empty relational expression into
* an empty values.
/** Configuration for rule that transforms a relational expression into an
* empty values if it is definitely empty.
*
* <p>It relies on {@link org.apache.calcite.rel.metadata.RelMdMaxRowCount} to
* derive if the relation is empty or not. If the stats are not available then
* the rule is a noop. */
* the rule is a noop.
*
* <p>{@link Values} is excluded because it is already a values and does not
* need to be replaced. {@link TableModify} is excluded because it may have
* side effects even when no rows are modified. */
@Value.Immutable
public interface ZeroMaxRowsRuleConfig extends PruneEmptyRule.Config {
Predicate<RelNode> CAN_BE_REPLACED_BY_EMPTY_VALUES = rel ->
!(rel instanceof Values) && !(rel instanceof TableModify);

ZeroMaxRowsRuleConfig DEFAULT = ImmutableZeroMaxRowsRuleConfig.of()
.withOperandSupplier(b0 -> b0.operand(TableScan.class).noInputs())
.withDescription("PruneZeroRowsTable");
.withOperandSupplier(b0 -> b0.operand(RelNode.class)
.predicate(CAN_BE_REPLACED_BY_EMPTY_VALUES)
.anyInputs())
.withDescription("PruneZeroRows");

@Override default PruneEmptyRule toRule() {
return new RemoveEmptySingleRule(this) {
@Override public boolean matches(RelOptRuleCall call) {
return new PruneEmptyRule(this) {
@Override public boolean matches(final RelOptRuleCall call) {
RelNode node = call.rel(0);
return RelMdUtil.isRelDefinitelyEmpty(call.getMetadataQuery(), node);
}

@Override public void onMatch(final RelOptRuleCall call) {
RelNode node = call.rel(0);
call.transformTo(call.builder().push(node).empty().build());
}
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ protected SortRemoveRedundantRule(final SortRemoveRedundantRule.Config config) {
// If sort is 'order by x' or 'order by x limit n', target threshold is 1.
// If sort is pure limit, the target threshold is the limit's fetch.
// If the limit's fetch is 0, we could use
// CoreRules.SORT_FETCH_ZERO_INSTANCE to deal with it, so we don't need to
// PruneEmptyRules.EMPTY_TABLE_INSTANCE to deal with it, so we don't need to
// deal with it in this rule.
final Optional<BigDecimal> rowCountThreshold = getRowCountThreshold(sort);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8068,7 +8068,7 @@ private void checkLiteral2(String expression, String expected) {
+ "from (values (1, 'a'), (2, 'bb')) as t(x, y)\n"
+ "limit 0";
final RuleSet rules =
RuleSets.ofList(PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE);
RuleSets.ofList(PruneEmptyRules.EMPTY_TABLE_INSTANCE);
final String expectedMysql = "SELECT *\n"
+ "FROM (SELECT NULL AS `X`, NULL AS `Y`) AS `t`\n"
+ "WHERE 1 = 0";
Expand Down Expand Up @@ -8136,9 +8136,11 @@ private void checkLiteral2(String expression, String expected) {
+ "limit 0";
final String sql = "SELECT SUBSTRING(y, 1, 1) FROM (" + sql0 + ") t";
final RuleSet rules =
RuleSets.ofList(PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE);
final String expected = "SELECT SUBSTRING(`Y`, 1, 1)\n"
+ "FROM (SELECT NULL AS `X`, NULL AS `Y`) AS `t`\n"
RuleSets.ofList(PruneEmptyRules.EMPTY_TABLE_INSTANCE);
// EMPTY_TABLE_INSTANCE is now a general rule that prunes any definitely
// empty expression to an empty Values, so the outer Project is also pruned.
final String expected = "SELECT *\n"
+ "FROM (SELECT NULL AS `EXPR$0`) AS `t`\n"
+ "WHERE 1 = 0";
sql(sql).optimize(rules, null).withMysql().ok(expected);
}
Expand Down
30 changes: 29 additions & 1 deletion core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5838,7 +5838,35 @@ private void checkEmptyJoin(RelOptFixture f) {

@Test void testEmptySortLimitZero() {
final String sql = "select * from emp order by deptno limit 0";
sql(sql).withRule(PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE).check();
sql(sql).withRule(PruneEmptyRules.EMPTY_TABLE_INSTANCE).check();
}

/** Tests that a Sort whose OFFSET skips at least as many rows as its input
* can produce is pruned to empty by
* {@link PruneEmptyRules#EMPTY_TABLE_INSTANCE}. */
@Test void testEmptySortOffsetGreaterThanMaxRows() {
// The input VALUES has at most 2 rows, so 'OFFSET 5' skips them all.
final String sql = "select * from (values (1, 2), (3, 4)) as t (a, b)\n"
+ "order by a\n"
+ "offset 5 rows";
sql(sql).withRule(PruneEmptyRules.EMPTY_TABLE_INSTANCE).check();
}

/** Tests that {@link PruneEmptyRules#EMPTY_TABLE_INSTANCE} prunes a Filter
* whose condition is always false, even when its input is not an empty
* Values. */
@Test void testEmptyFilterAlwaysFalse() {
final String sql = "select * from emp where false";
sql(sql).withRule(PruneEmptyRules.EMPTY_TABLE_INSTANCE).check();
}

/** Tests that {@link PruneEmptyRules#EMPTY_TABLE_INSTANCE} does not prune a
* {@code TableModify}, because it may have side effects even when no rows are
* modified. The input Filter is still pruned to empty Values. */
@Test void testEmptyTableModifyNotPruned() {
final String sql = "insert into sales.dept(deptno, name)\n"
+ "select empno, ename from emp where false";
sql(sql).withRule(PruneEmptyRules.EMPTY_TABLE_INSTANCE).check();
}

@Test void testEmptyAggregate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4192,6 +4192,23 @@ LogicalProject(EXPR$0=[+(+($0, $1), $0)])
<Resource name="planAfter">
<![CDATA[
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
<TestCase name="testEmptyFilterAlwaysFalse">
<Resource name="sql">
<![CDATA[select * from emp where false]]>
</Resource>
<Resource name="planBefore">
<![CDATA[
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[false])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
<Resource name="planAfter">
<![CDATA[
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
Expand Down Expand Up @@ -4451,6 +4468,25 @@ LogicalSort(sort0=[$7], dir0=[ASC], fetch=[0])
<Resource name="planAfter">
<![CDATA[
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
<TestCase name="testEmptySortOffsetGreaterThanMaxRows">
<Resource name="sql">
<![CDATA[select * from (values (1, 2), (3, 4)) as t (a, b)
order by a
offset 5 rows]]>
</Resource>
<Resource name="planBefore">
<![CDATA[
LogicalSort(sort0=[$0], dir0=[ASC], offset=[5])
LogicalProject(A=[$0], B=[$1])
LogicalValues(tuples=[[{ 1, 2 }, { 3, 4 }]])
]]>
</Resource>
<Resource name="planAfter">
<![CDATA[
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
Expand All @@ -4468,6 +4504,26 @@ LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2])
<Resource name="planAfter">
<![CDATA[
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
<TestCase name="testEmptyTableModifyNotPruned">
<Resource name="sql">
<![CDATA[insert into sales.dept(deptno, name)
select empno, ename from emp where false]]>
</Resource>
<Resource name="planBefore">
<![CDATA[
LogicalTableModify(table=[[CATALOG, SALES, DEPT]], operation=[INSERT], flattened=[false])
LogicalProject(DEPTNO=[$0], NAME=[$1])
LogicalFilter(condition=[false])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
<Resource name="planAfter">
<![CDATA[
LogicalTableModify(table=[[CATALOG, SALES, DEPT]], operation=[INSERT], flattened=[false])
LogicalValues(tuples=[[]])
]]>
</Resource>
</TestCase>
Expand Down
Loading