You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by GitBox <gi...@apache.org> on 2022/10/10 03:51:26 UTC

[GitHub] [calcite] HanumathRao opened a new pull request, #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

HanumathRao opened a new pull request, #2935:
URL: https://github.com/apache/calcite/pull/2935

   The following changes are made to implement an optimization for pruning out the sub trees when a base-table is empty. The detection of a table being empty is done using MaxRow stat.
   
   I have not used existing stats like rowcount etc for triggering the optimization as they are estimates and can be stale. I also thought about introducing a new stat like exactRowCount but it seems like an overkill (just for base table it is valid and it soon will be an estimate after base table for any other node in the tree). After due consideration, it seems like MaxRow stat fits well for this scenario.
   1) It is not a default stat and it needs to be supplied for a table by overloading the MaxRowHandler.
   2) Default value of the MaxRow is large value (Infinity) and hence the optimization doesn't trigger by default.
   
   EmptyTableOptimizationConfig change adds a new rule to transform the base table to empty values node when maxRowCount is zero. All the existing PruneEmptyRules will do the necessary optimizations to prune out the complex sub-tree once the emptyValues node is created.
   
   Please review the changes.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r999312483


##########
testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java:
##########
@@ -261,6 +261,14 @@ protected MockCatalogReaderSimple(RelDataTypeFactory typeFactory,
     productsTable.addColumn("SUPPLIERID", fixture.intType);
     registerTable(productsTable);
 
+    // Register "EMPTY_PRODUCTS" table.
+    MockTable emptyProductsTable = MockTable.create(this, salesSchema, "EMPTY_PRODUCTS",
+        false, 2000D, 0.0);

Review Comment:
   Is it intentional to have rows > masRows?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on PR #2935:
URL: https://github.com/apache/calcite/pull/2935#issuecomment-1288891782

   I pushed a few small changes to the PR. @HanumathRao let me know if you are OK with those so that I can rebase and merge the PR.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] HanumathRao commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
HanumathRao commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r1002223406


##########
core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml:
##########
@@ -3058,6 +3058,50 @@ LogicalSort(sort0=[$7], dir0=[ASC], fetch=[0])
     <Resource name="planAfter">
       <![CDATA[
 LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTable">
+    <Resource name="sql">
+      <![CDATA[select * from EMPTY_PRODUCTS
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2])
+  LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTableInComplexQuery">
+    <Resource name="sql">
+      <![CDATA[select * from products left join (select * from products as e
+ inner join EMPTY_PRODUCTS as d on e.PRODUCTID = d.PRODUCTID  where e.SUPPLIERID > 10) dt
+ on products.PRODUCTID = dt.PRODUCTID]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+    LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5])
+      LogicalFilter(condition=[>($2, 10)])
+        LogicalJoin(condition=[=($0, $3)], joinType=[inner])
+          LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+          LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])

Review Comment:
   I have tried to use most of the PruneEmptyRules and it didn't prune the Left join. This is because of the way the rules are applied one after the other. There is a dependency of the rules in this particular example PROJECT (EMPTY) needs to be applied before LEFT JOIN(TABLE, EMPTY). PROJECT(EMPTY) depends on the INNER JOIN (TABLE, EMPTY) to be applied. Please let me know if you have any particular rule in mind, then I can try it out. thanks



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] HanumathRao commented on pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
HanumathRao commented on PR #2935:
URL: https://github.com/apache/calcite/pull/2935#issuecomment-1289138925

   Thank you @zabetak for the review and improvements. Please go ahead and merge the PR.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] HanumathRao commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
HanumathRao commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r1002223506


##########
testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReaderSimple.java:
##########
@@ -261,6 +261,14 @@ protected MockCatalogReaderSimple(RelDataTypeFactory typeFactory,
     productsTable.addColumn("SUPPLIERID", fixture.intType);
     registerTable(productsTable);
 
+    // Register "EMPTY_PRODUCTS" table.
+    MockTable emptyProductsTable = MockTable.create(this, salesSchema, "EMPTY_PRODUCTS",
+        false, 2000D, 0.0);

Review Comment:
   It was intentional but fixed it anyway in the latest commit.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r1003201432


##########
core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml:
##########
@@ -3058,6 +3058,50 @@ LogicalSort(sort0=[$7], dir0=[ASC], fetch=[0])
     <Resource name="planAfter">
       <![CDATA[
 LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTable">
+    <Resource name="sql">
+      <![CDATA[select * from EMPTY_PRODUCTS
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2])
+  LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTableInComplexQuery">
+    <Resource name="sql">
+      <![CDATA[select * from products left join (select * from products as e
+ inner join EMPTY_PRODUCTS as d on e.PRODUCTID = d.PRODUCTID  where e.SUPPLIERID > 10) dt
+ on products.PRODUCTID = dt.PRODUCTID]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+    LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5])
+      LogicalFilter(condition=[>($2, 10)])
+        LogicalJoin(condition=[=($0, $3)], joinType=[inner])
+          LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+          LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])

Review Comment:
   What I had in mind is something like https://github.com/apache/calcite/pull/2935/commits/f4d0777db3f2f8618c8d796024e9bd3db4f733f8.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r999314056


##########
core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml:
##########
@@ -3058,6 +3058,50 @@ LogicalSort(sort0=[$7], dir0=[ASC], fetch=[0])
     <Resource name="planAfter">
       <![CDATA[
 LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTable">
+    <Resource name="sql">
+      <![CDATA[select * from EMPTY_PRODUCTS
+]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2])
+  LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testEmptyTableInComplexQuery">
+    <Resource name="sql">
+      <![CDATA[select * from products left join (select * from products as e
+ inner join EMPTY_PRODUCTS as d on e.PRODUCTID = d.PRODUCTID  where e.SUPPLIERID > 10) dt
+ on products.PRODUCTID = dt.PRODUCTID]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+    LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5])
+      LogicalFilter(condition=[>($2, 10)])
+        LogicalJoin(condition=[=($0, $3)], joinType=[inner])
+          LogicalTableScan(table=[[CATALOG, SALES, PRODUCTS]])
+          LogicalTableScan(table=[[CATALOG, SALES, EMPTY_PRODUCTS]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(PRODUCTID=[$0], NAME=[$1], SUPPLIERID=[$2], PRODUCTID0=[$3], NAME0=[$4], SUPPLIERID0=[$5], PRODUCTID00=[$6], NAME00=[$7], SUPPLIERID00=[$8])
+  LogicalJoin(condition=[=($0, $3)], joinType=[left])

Review Comment:
   I think that by adding the appropriate rules the left join can also be removed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] HanumathRao commented on pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
HanumathRao commented on PR #2935:
URL: https://github.com/apache/calcite/pull/2935#issuecomment-1287499498

   > > I am using the call context to get the RelMetadataQuery, I think this is not fully formed during the matching of the rule
   > 
   > @HanumathRao can you elaborate a bit what you mean when you say that the metadata query is not formed during `matches`? Both `matches` and `onMatch` take a `RelOptRuleCall` as parameter and in both cases `call.getMetadataQuery()` is available.
   
   Thanks @zabetak, I think I misunderstood your earlier comment. I thought that you are asking me to introduce a predicate in operand supplier itself to rule out the application of the rule. I fixed the code in the latest commit to return false for not applicable cases in matches method.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] HanumathRao commented on pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
HanumathRao commented on PR #2935:
URL: https://github.com/apache/calcite/pull/2935#issuecomment-1279557853

   Thanks @zabetak for the review. I have addressed all the comments except the following. 
   
   _It is better to put the row count check in the matches method. When the condition is not satisfied the rule can be eliminated much earlier._
   
   I am using the call context to get the RelMetadataQuery, I think this is not fully formed during the matching of the rule. I thought about using the MetadataQuery singleton INSTANCE, but it can get complex during the matching. Please let me know if you have an easier way in mind.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak closed pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak closed pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata
URL: https://github.com/apache/calcite/pull/2935


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on PR #2935:
URL: https://github.com/apache/calcite/pull/2935#issuecomment-1283786273

   > I am using the call context to get the RelMetadataQuery, I think this is not fully formed during the matching of the rule
   
   @HanumathRao can you elaborate a bit what you mean when you say that the metadata query is not formed during `matches`? Both `matches` and `onMatch` take a `RelOptRuleCall` as parameter and in both cases `call.getMetadataQuery()` is available.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [calcite] zabetak commented on a diff in pull request #2935: [CALCITE-5314] Prune empty parts of a query by exploiting stats/metadata

Posted by GitBox <gi...@apache.org>.
zabetak commented on code in PR #2935:
URL: https://github.com/apache/calcite/pull/2935#discussion_r992121760


##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })
+          .withDescription("ConvertEmptyTableToValues")

Review Comment:
   nit: To keep things uniform maybe rename to `"PruneZeroRowsTable"`



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })
+          .withDescription("ConvertEmptyTableToValues")
+          .toRule();
+
+  /** Configuration for rule that transforms an empty table into an empty values node.
+   * MaxRowCount is used as the stat to transform, hence Table implementer needs
+   * to supply this metadata if this optimization needs to be applied.*/
+  @Value.Immutable
+  public interface EmptyTableOptimizationConfig extends PruneEmptyRule.Config {
+
+    @Override default PruneEmptyRule toRule() {
+      return new PruneEmptyRule(this) {
+        @Override public void onMatch(RelOptRuleCall call) {
+          TableScan tableScan = call.rel(0);
+          Double maxRowCount = call.getMetadataQuery().getMaxRowCount(tableScan);
+          if (maxRowCount != null && maxRowCount == 0.0) {
+            call.transformTo(call.builder().push(tableScan).empty().build());

Review Comment:
   The effect of this rule is very similar to what `RemoveEmptySingleRule` is doing. The latter is also trying to conserve the traits so maybe it is a good idea to attemp to conserve the traits as well here.



##########
core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java:
##########
@@ -3413,6 +3413,54 @@ RelOptFixture checkDynamicFunctions(boolean treatDynamicCallsAsConstant) {
         .check();
   }
 
+  @Test void testEmptyTableProject() {
+    // table is transformed to empty values and extra project will be removed.
+    final String sql = "select * from EMPTY_PRODUCTS\n";
+    sql(sql)
+        .withRule(
+            PruneEmptyRules.EMPTY_TABLE,
+            PruneEmptyRules.PROJECT_INSTANCE)
+        .check();
+  }
+
+  @Test void testEmptyTableJoinLeft() {
+    // inner join is eliminated after the left table is transformed to empty values.
+    final String sql = "select * from EMPTY_PRODUCTS as e\n"
+        + ",products as d where e.PRODUCTID = d.PRODUCTID\n";
+    sql(sql)
+        .withRule(
+            PruneEmptyRules.EMPTY_TABLE,
+            PruneEmptyRules.JOIN_LEFT_INSTANCE)
+        .check();
+  }
+
+  @Test void testEmptyTableJoinRight() {
+    // inner join is eliminated after the right table is transformed to empty values.
+    final String sql = "select * from products as e\n"
+        + ",EMPTY_PRODUCTS as d where e.PRODUCTID = d.PRODUCTID\n";
+    sql(sql)
+        .withRule(
+            PruneEmptyRules.EMPTY_TABLE,
+            PruneEmptyRules.JOIN_RIGHT_INSTANCE)
+        .check();
+  }

Review Comment:
   I don't think need a test case for every possible combination of rules of `EMPTY_TABLE` with the others. I think we can keep just two:
   * `testEmptyTable` which just tests the new rule.
   * `testEmptyTableInComplexQuery` which exploits multiple pruning rules including `EMPTY_TABLE` to simplify a plan completely leaving only an empty `LogicalValues` at the end.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =

Review Comment:
   I think it would be a good idea to add the new rule to `org.apache.calcite.plan.RelOptRules#ABSTRACT_RULES` list along with the other pruning rules.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })
+          .withDescription("ConvertEmptyTableToValues")
+          .toRule();
+
+  /** Configuration for rule that transforms an empty table into an empty values node.
+   * MaxRowCount is used as the stat to transform, hence Table implementer needs
+   * to supply this metadata if this optimization needs to be applied.*/
+  @Value.Immutable
+  public interface EmptyTableOptimizationConfig extends PruneEmptyRule.Config {
+
+    @Override default PruneEmptyRule toRule() {
+      return new PruneEmptyRule(this) {
+        @Override public void onMatch(RelOptRuleCall call) {
+          TableScan tableScan = call.rel(0);
+          Double maxRowCount = call.getMetadataQuery().getMaxRowCount(tableScan);
+          if (maxRowCount != null && maxRowCount == 0.0) {

Review Comment:
   It is better to put the row count check in the `matches` method. When the condition is not satisfied the rule can be eliminated much earlier.



##########
testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReader.java:
##########
@@ -618,6 +621,57 @@ public StructKind getKind() {
     }
   }
 
+  /**
+   * Mock implementation of
+   * {@link org.apache.calcite.prepare.Prepare.PreparingTable} which supplies MaxRow stat.
+   */
+  public static class MaxRowMockTable
+      extends MockTable implements BuiltInMetadata.MaxRowCount.Handler {

Review Comment:
   Maybe you can avoid introducing a new sub-class and make `MockTable` implement the `MaxRowCount.Handler` assuming that rowCount == maxRowCount at this point. If we need to differentiate in the future we can add or modify the constructors/factories in `MockTable`.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -469,7 +470,6 @@ public interface SortFetchZeroRuleConfig extends PruneEmptyRule.Config {
             call.transformTo(emptyValues);
           }
         }
-

Review Comment:
   nit: Please avoid unnecessary formatting changes.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })
+          .withDescription("ConvertEmptyTableToValues")
+          .toRule();
+
+  /** Configuration for rule that transforms an empty table into an empty values node.
+   * MaxRowCount is used as the stat to transform, hence Table implementer needs
+   * to supply this metadata if this optimization needs to be applied.*/
+  @Value.Immutable
+  public interface EmptyTableOptimizationConfig extends PruneEmptyRule.Config {

Review Comment:
   A possibly better name would be `ZeroMaxRowsRuleConfig`.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })

Review Comment:
   I think it can be simplified to `.withOperandSupplier(b0 -> b0.operand(TableScan.class).noInputs());`



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =
+      ImmutableEmptyTableOptimizationConfig.of()
+          .withOperandSupplier(b0 -> {
+            return b0.operand(TableScan.class).noInputs();
+          })
+          .withDescription("ConvertEmptyTableToValues")
+          .toRule();
+
+  /** Configuration for rule that transforms an empty table into an empty values node.
+   * MaxRowCount is used as the stat to transform, hence Table implementer needs
+   * to supply this metadata if this optimization needs to be applied.*/
+  @Value.Immutable
+  public interface EmptyTableOptimizationConfig extends PruneEmptyRule.Config {
+
+    @Override default PruneEmptyRule toRule() {
+      return new PruneEmptyRule(this) {
+        @Override public void onMatch(RelOptRuleCall call) {
+          TableScan tableScan = call.rel(0);

Review Comment:
   It is better to be more general here (use `RelNode` here instead of `TableScan`); the rule will still work correctly and if people want to reduce or enlarge the scope they can do it by changing the matching operands of the configuration.



##########
core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java:
##########
@@ -540,4 +540,32 @@ public interface JoinRightEmptyRuleConfig extends PruneEmptyRule.Config {
       };
     }
   }
+
+
+  public static final RelOptRule EMPTY_TABLE =

Review Comment:
   All other rules in this class are suffixed with `INSTANCE`. To keep things uniform I would suggest one of the following:
   * `EMPTY_TABLE_INSTANCE`
   * `TABLE_ZERO_ROWS_INSTANCE`
   * `ZERO_ROWS_TABLE_INSTANCE`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@calcite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org