diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java index ebcbdc187ddb..96691863f4aa 100644 --- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java +++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java @@ -27,6 +27,7 @@ import org.apache.calcite.rel.core.Sample; import org.apache.calcite.rel.core.SetOp; import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.core.TableFunctionScan; import org.apache.calcite.rel.core.TableModify; import org.apache.calcite.rel.core.TableScan; import org.apache.calcite.rel.core.Window; @@ -159,11 +160,31 @@ public Set getTableReferences(TableScan rel, RelMetadataQuery mq) { * references. */ public @Nullable Set getTableReferences(SetOp rel, RelMetadataQuery mq) { + return getTableReferences(rel.getInputs(), mq); + } + + /** + * Table references from the relational inputs of a TableFunctionScan. + * + *

Returns an empty set if there are no inputs, and {@code null} if the table + * references of any input cannot be determined. Tables accessed internally + * by the table function are not included. + */ + public @Nullable Set getTableReferences(TableFunctionScan rel, + RelMetadataQuery mq) { + return getTableReferences(rel.getInputs(), mq); + } + + /** Returns the union of the table references of {@code inputs}, assigning + * distinct entity numbers to repeated references to the same table, or {@code null} + * if the references of any input cannot be determined. */ + private static @Nullable Set getTableReferences( + List inputs, RelMetadataQuery mq) { final Set result = new HashSet<>(); // Infer column origin expressions for given references final Multimap, RelTableRef> qualifiedNamesToRefs = HashMultimap.create(); - for (RelNode input : rel.getInputs()) { + for (RelNode input : inputs) { final Map currentTablesMapping = new HashMap<>(); final Set inputTableRefs = mq.getTableReferences(input); if (inputTableRefs == null) { diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java index b6625ab0b361..6123696154ed 100644 --- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java @@ -163,6 +163,7 @@ import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasToString; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -4928,6 +4929,56 @@ public void checkAllPredicatesAndTableSetOp(String sql) { assertNull(tableReferences); } + @Test void testTableReferencesTableFunctionScanSameTableInputs() { + final RelNode rel = fixture() + .withRelFn(builder -> + builder.scan("EMP") + .scan("EMP") + .functionScan(new MockSqlOperatorTable.DedupFunction(), 2, + builder.cursor(2, 0), builder.cursor(2, 1)) + .build()) + .toRel(); + final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + assertThat(mq.getTableReferences(rel), + sortsAs("[[scott, EMP].#0, [scott, EMP].#1]")); + } + + @Test void testTableReferencesTumbleTableFunction() { + final RelNode rel = sql("select * from table(tumble(table emp, " + + "descriptor(hiredate), interval '1' hour))").toRel(); + final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + assertThat(mq.getTableReferences(rel), + sortsAs("[[CATALOG, SALES, EMP].#0]")); + } + + @Test void testTableReferencesTableFunctionScanZeroInputs() { + final RelNode rel = fixture() + .withRelFn(builder -> + builder.functionScan(new MockSqlOperatorTable.RampFunction(), 0, + builder.literal(3)) + .build()) + .toRel(); + final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + assertThat(mq.getTableReferences(rel), empty()); + } + + @Test void testTableReferencesTableFunctionScanUnknownInput() { + final RelNode rel = fixture() + .withRelFn(builder -> { + final RelNode scan = builder.scan("EMP").build(); + final RelNode unknown = + new DummyRelNode(scan.getCluster(), scan.getTraitSet(), scan); + return builder.push(unknown) + .push(scan) + .functionScan(new MockSqlOperatorTable.DedupFunction(), 2, + builder.cursor(2, 0), builder.cursor(2, 1)) + .build(); + }) + .toRel(); + final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + assertNull(mq.getTableReferences(rel)); + } + @Test void testNodeTypeCountEmp() { final String sql = "select * from emp"; sql(sql) diff --git a/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_TableReferencesHandler.java b/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_TableReferencesHandler.java index 6544dc4a6970..85e36d9c7db3 100644 --- a/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_TableReferencesHandler.java +++ b/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_TableReferencesHandler.java @@ -80,6 +80,8 @@ private java.util.Set getTableReferences_( return provider0.getTableReferences((org.apache.calcite.rel.core.SetOp) r, mq); } else if (r instanceof org.apache.calcite.rel.core.Sort) { return provider0.getTableReferences((org.apache.calcite.rel.core.Sort) r, mq); + } else if (r instanceof org.apache.calcite.rel.core.TableFunctionScan) { + return provider0.getTableReferences((org.apache.calcite.rel.core.TableFunctionScan) r, mq); } else if (r instanceof org.apache.calcite.rel.core.TableModify) { return provider0.getTableReferences((org.apache.calcite.rel.core.TableModify) r, mq); } else if (r instanceof org.apache.calcite.rel.core.TableScan) {