Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -16,9 +16,12 @@
*/
package org.apache.calcite.rel;

import org.apache.calcite.rel.core.Snapshot;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalAsofJoin;
import org.apache.calcite.rel.logical.LogicalCalc;
import org.apache.calcite.rel.logical.LogicalCorrelate;
import org.apache.calcite.rel.logical.LogicalExchange;
Expand All @@ -38,7 +41,7 @@
* Visits all the relations in a homogeneous way: always redirects calls to
* {@code accept(RelNode)}.
*/
public class RelHomogeneousShuttle extends RelShuttleImpl {

Check warning on line 44 in core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this “Monster Class” into smaller and more specialized ones to reduce its dependencies on other classes from 22 to the maximum authorized 20 or less.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4fuVRFXsppU-LMSwuq&open=AZ4fuVRFXsppU-LMSwuq&pullRequest=4928
@Override public RelNode visit(LogicalAggregate aggregate) {
return visit((RelNode) aggregate);
}
Expand Down Expand Up @@ -71,6 +74,10 @@
return visit((RelNode) join);
}

@Override public RelNode visit(LogicalAsofJoin asofJoin) {
return visit((RelNode) asofJoin);
}

@Override public RelNode visit(LogicalCorrelate correlate) {
return visit((RelNode) correlate);
}
Expand Down Expand Up @@ -106,4 +113,12 @@
@Override public RelNode visit(LogicalRepeatUnion repeatUnion) {
return visit((RelNode) repeatUnion);
}

@Override public RelNode visit(Window window) {
return visit((RelNode) window);
}

@Override public RelNode visit(Snapshot snapshot) {
return visit((RelNode) snapshot);
}
}
6 changes: 6 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/RelShuttle.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/
package org.apache.calcite.rel;

import org.apache.calcite.rel.core.Snapshot;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalAsofJoin;
import org.apache.calcite.rel.logical.LogicalCalc;
Expand Down Expand Up @@ -75,5 +77,9 @@ public interface RelShuttle {

RelNode visit(LogicalRepeatUnion logicalRepeatUnion);

RelNode visit(Window window);

RelNode visit(Snapshot snapshot);

RelNode visit(RelNode other);
}
10 changes: 10 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package org.apache.calcite.rel;

import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.rel.core.Snapshot;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalAsofJoin;
import org.apache.calcite.rel.logical.LogicalCalc;
Expand Down Expand Up @@ -47,7 +49,7 @@
* {@link RelNode#copy(org.apache.calcite.plan.RelTraitSet, java.util.List)} if
* any children change.
*/
public class RelShuttleImpl implements RelShuttle {

Check warning on line 52 in core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this “Monster Class” into smaller and more specialized ones to reduce its dependencies on other classes from 22 to the maximum authorized 20 or less.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4fuVT3XsppU-LMSwur&open=AZ4fuVT3XsppU-LMSwur&pullRequest=4928
protected final Deque<RelNode> stack = new ArrayDeque<>();

/**
Expand Down Expand Up @@ -147,6 +149,14 @@
return visitChildren(logicalRepeatUnion);
}

@Override public RelNode visit(Window window) {
return visitChildren(window);
}

@Override public RelNode visit(Snapshot snapshot) {
return visitChildren(snapshot);
}

@Override public RelNode visit(RelNode other) {
return visitChildren(other);
}
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/core/Snapshot.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttle;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.hint.Hintable;
Expand Down Expand Up @@ -119,6 +120,10 @@ protected Snapshot(
return copy(traitSet, getInput(), condition);
}

@Override public RelNode accept(RelShuttle shuttle) {
return shuttle.visit(this);
}

@Override public RelWriter explainTerms(RelWriter pw) {
return super.explainTerms(pw)
.item("period", period);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.calcite.rel.AbstractRelNode;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttle;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.hint.Hintable;
import org.apache.calcite.rel.hint.RelHint;
Expand Down Expand Up @@ -140,6 +141,10 @@ protected TableFunctionScan(RelInput input) {

//~ Methods ----------------------------------------------------------------

@Override public RelNode accept(RelShuttle shuttle) {
return shuttle.visit(this);
}

@Override public final TableFunctionScan copy(RelTraitSet traitSet,
List<RelNode> inputs) {
return copy(traitSet, inputs, rexCall, elementType, getRowType(),
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/core/Window.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttle;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.hint.Hintable;
Expand Down Expand Up @@ -210,6 +211,10 @@ public List<RexLiteral> getConstants() {
return constants;
}

@Override public RelNode accept(RelShuttle shuttle) {
return shuttle.visit(this);
}

@Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
// Cost is proportional to the number of rows and the number of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public ToLogicalConverter(RelBuilder relBuilder) {
return LogicalTableScan.create(scan.getCluster(), scan.getTable(), scan.getHints());
}

@Override public RelNode visit(Window window) {
return visit((RelNode) window);
}

@Override public RelNode visit(RelNode relNode) {
if (relNode instanceof Aggregate) {
final Aggregate agg = (Aggregate) relNode;
Expand Down
140 changes: 140 additions & 0 deletions core/src/test/java/org/apache/calcite/test/RelShuttleCoverageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.test;

import org.apache.calcite.rel.AbstractRelNode;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttle;

import com.google.common.reflect.ClassPath;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertTrue;

/** Guards against introducing new dispatch gaps in {@link RelShuttle}. */
class RelShuttleCoverageTest {

private static final Set<String> SCANNED_PACKAGES =
Set.of("org.apache.calcite.rel.core",
"org.apache.calcite.rel.logical");

/** Pre-existing gaps that predate this safety net. Each should be addressed in its own follow-up
* JIRA before being removed from this list. */
private static final Set<String> KNOWN_UNCOVERED_RELS =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we fixing these too? Is it too disruptive?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I felt it was expanding the scope substantially. On a second thought, I decided to bite the bullet by fixing it for those other UNCOVERED_RELS as well. I will shortly update the PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated it for the other UNCOVERED_RELS as well. Please review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging this let's send a message to the dev list warning people about it, and giving them a chance to comment. Do you want to send it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can send about this breaking change in dev list.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started a discussion in dev mailing list.

Set.of(// core
"org.apache.calcite.rel.core.Collect",
"org.apache.calcite.rel.core.Sample",
"org.apache.calcite.rel.core.Uncollect",
"org.apache.calcite.rel.core.Combine",
// logical
"org.apache.calcite.rel.logical.LogicalConditionalCorrelate",
"org.apache.calcite.rel.logical.LogicalSortExchange",
"org.apache.calcite.rel.logical.LogicalTableSpool");

/** Every concrete RelNode in the scanned packages must be covered by a non-{@code visit(RelNode)}
* overload on {@link RelShuttle}. Without this, a {@code RelShuttleImpl} subclass that customizes
* the type-specific visitor would silently not be called for the rel. */
@Test void everyRelNodeHasMatchingVisitOverload() throws IOException {
final Set<Class<?>> visitParameters = collectVisitParameterTypes();
final Set<Class<? extends RelNode>> relClasses = findConcreteRelNodesInPackages();

final Set<Class<? extends RelNode>> uncovered = relClasses.stream()
.filter(c -> visitParameters.stream().noneMatch(vp -> vp.isAssignableFrom(c)))
.filter(c -> !KNOWN_UNCOVERED_RELS.contains(c.getName()))
.collect(
Collectors.toCollection(() ->
new TreeSet<>(Comparator.comparing(Class::getName))));

assertTrue(uncovered.isEmpty(),
() -> "RelNodes with no RelShuttle.visit(...) overload covering them "
+ "(only the generic visit(RelNode) catch-all matches): " + uncovered);

// Fail-closed: flag any KNOWN_UNCOVERED_RELS entry that is no longer a gap so it gets removed.
final Set<String> obsoleteSkips = relClasses.stream()
.filter(c -> KNOWN_UNCOVERED_RELS.contains(c.getName()))
.filter(c -> visitParameters.stream().anyMatch(vp -> vp.isAssignableFrom(c)))
.map(Class::getName)
.collect(Collectors.toCollection(TreeSet::new));
assertTrue(obsoleteSkips.isEmpty(),
() -> "KNOWN_UNCOVERED_RELS entries that are now covered (remove them): " + obsoleteSkips);
}

/** Every {@link RelShuttle#visit(...)} parameter type (other than {@code RelNode}) must declare
* its own {@code accept(RelShuttle)} so dispatch routes through the type-specific overload. */
@Test void everyVisitParameterTypeDeclaresAccept() {
final Set<Class<?>> missingAccept = collectVisitParameterTypes().stream()
.filter(c -> !declaresAcceptRelShuttle(c))
.collect(
Collectors.toCollection(() ->
new TreeSet<>(Comparator.comparing(Class::getName))));

assertTrue(missingAccept.isEmpty(),
() -> "RelShuttle.visit(X) parameter types whose X does not declare accept(RelShuttle): "
+ missingAccept);
}

/** Visit parameter types other than {@link RelNode} (the catch-all fallback). */
private static Set<Class<?>> collectVisitParameterTypes() {
final Set<Class<?>> params = new HashSet<>();
for (Method method : RelShuttle.class.getMethods()) {
if ("visit".equals(method.getName()) && method.getParameterCount() == 1) {
Class<?> paramType = method.getParameterTypes()[0];
if (paramType != RelNode.class) {
params.add(paramType);
}
}
}
return params;
}

private static boolean declaresAcceptRelShuttle(Class<?> clazz) {
try {
clazz.getDeclaredMethod("accept", RelShuttle.class);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}

private static Set<Class<? extends RelNode>> findConcreteRelNodesInPackages() throws IOException {
final Set<Class<? extends RelNode>> classes = new HashSet<>();
final ClassPath classPath = ClassPath.from(RelShuttleCoverageTest.class.getClassLoader());
for (String packageName : SCANNED_PACKAGES) {
for (ClassPath.ClassInfo info : classPath.getTopLevelClasses(packageName)) {
final Class<?> clazz = info.load();
if (RelNode.class.isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())
&& clazz != AbstractRelNode.class) {
@SuppressWarnings("unchecked")
Class<? extends RelNode> relClass = (Class<? extends RelNode>) clazz;
classes.add(relClass);
}
}
}
return classes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -974,24 +974,25 @@ private static class HintCollector extends RelShuttleImpl {
return super.visit(values);
}

@Override public RelNode visit(RelNode other) {
if (other instanceof Window) {
Window window = (Window) other;
if (!window.getHints().isEmpty()) {
this.hintsCollect.add("Window:" + window.getHints());
}
} else if (other instanceof Snapshot) {
Snapshot snapshot = (Snapshot) other;
if (!snapshot.getHints().isEmpty()) {
this.hintsCollect.add("Snapshot:" + snapshot.getHints());
}
} else if (other instanceof TableFunctionScan) {
TableFunctionScan scan = (TableFunctionScan) other;
if (!scan.getHints().isEmpty()) {
this.hintsCollect.add("TableFunctionScan:" + scan.getHints());
}
@Override public RelNode visit(TableFunctionScan scan) {
if (!scan.getHints().isEmpty()) {
this.hintsCollect.add("TableFunctionScan:" + scan.getHints());
}
return super.visit(scan);
}

@Override public RelNode visit(Window window) {
if (!window.getHints().isEmpty()) {
this.hintsCollect.add("Window:" + window.getHints());
}
return super.visit(window);
}

@Override public RelNode visit(Snapshot snapshot) {
if (!snapshot.getHints().isEmpty()) {
this.hintsCollect.add("Snapshot:" + snapshot.getHints());
}
return super.visit(other);
return super.visit(snapshot);
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions site/_docs/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ The same applies to `SqlBabelCreateTable` and `SqlUnpivot`.
* [<a href="https://issues.apache.org/jira/browse/CALCITE-6942">CALCITE-6942</a>]
Rename the method `decorrelateFetchOneSort` to `decorrelateSortWithRowNumber`.

* [<a href="https://issues.apache.org/jira/browse/CALCITE-7511">CALCITE-7511</a>]
The abstract `TableFunctionScan`, `Window`, and `Snapshot` rel classes now override
`accept(RelShuttle)`, and `RelShuttle` declares new `visit(Window)` and `visit(Snapshot)`
overloads (with default implementations in `RelShuttleImpl` / `RelHomogeneousShuttle`).
Two consumer-facing changes follow:
(1) Callers that implement `RelShuttle` directly must implement the two new methods.
(2) Callers that subclassed `RelShuttleImpl` and routed `Window` / `Snapshot` /
`TableFunctionScan` through `visit(RelNode other)` with `instanceof` checks should
migrate to the type-specific `visit(Window)` / `visit(Snapshot)` /
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this include a pointer to the code that is migrated in this PR?
I guess the commit is not yet final, so you cannot point to a commit. Maybe we have to do this in a subsequent PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that will be better.

`visit(TableFunctionScan)` overrides; those `instanceof` branches in `visit(RelNode)`
will silently stop being called for these types. Note that because the override is on
the abstract parents, **all** subclasses dispatch through the type-specific overload —
not just the `Logical*` variant. For example, `EnumerableTableFunctionScan` now also routes through
`visit(TableFunctionScan)`; the override added earlier in 1.42.0 originally lived on
`LogicalTableFunctionScan` and has been moved up.
Also adds the previously-missing `RelHomogeneousShuttle.visit(LogicalAsofJoin)` forwarding override.
Subclasses of `RelHomogeneousShuttle` that relied on `LogicalAsofJoin` not being routed through their
`visit(RelNode)` override will now see it routed there; this matches the behavior of every other rel
type in the homogeneous shuttle.

#### New features
{: #new-features-1-42-0}

Expand Down
Loading