diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java index 6d5639e341c2..93a4e4ed7d00 100644 --- a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java +++ b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java @@ -26,6 +26,7 @@ import org.apache.calcite.util.ControlFlowException; import org.apache.calcite.util.Util; +import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -33,6 +34,7 @@ import com.google.common.collect.Multimap; import com.google.common.util.concurrent.UncheckedExecutionException; +import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.Nullable; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.CompilerFactoryFactory; @@ -425,4 +427,10 @@ private Key(Class> handlerClass, && ((Key) obj).provider.equals(provider); } } + + @API(status = API.Status.INTERNAL) + @VisibleForTesting + public static void clearStaticCache() { + HANDLERS.invalidateAll(); + } } diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CachingLambdaProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CachingLambdaProvider.java new file mode 100644 index 000000000000..9ab2cd6d0260 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CachingLambdaProvider.java @@ -0,0 +1,61 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.rel.RelNode; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import javax.annotation.concurrent.ThreadSafe; + +/** + * A LambdaProvider that maintains a cache for each RelNode class/Lambda class combination, + * delegating to an underlying LambdaProvider to do actual work. + */ +@ThreadSafe +class CachingLambdaProvider implements LambdaProvider { + + private final LambdaProvider provider; + private final LoadingCache, LoadingCache> cache; + + CachingLambdaProvider(final LambdaProvider provider) { + this.provider = provider; + this.cache = CacheBuilder.newBuilder() + .build(new CacheLoader>() { + @Override public LoadingCache load(final Class relNodeClazz) + throws Exception { + return CacheBuilder.newBuilder().build( + new CacheLoader() { + @Override public Object load(final Class lambdaClazz) throws Exception { + return provider.get((Class) relNodeClazz, lambdaClazz); + } + }); + } + }); + } + + public List get( + Class relNodeClazz, + Class lambdaClazz) throws ExecutionException { + return (List) cache.get(relNodeClazz).get(lambdaClazz); + } + +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CanonicalizingIRMQ.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CanonicalizingIRMQ.java new file mode 100644 index 000000000000..fc59e0abf69f --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/CanonicalizingIRMQ.java @@ -0,0 +1,82 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelDistributions; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelMdUtil; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import static org.apache.calcite.linq4j.Nullness.castNonNull; + +/** + * Canonicalizes methods from null to other standard outputs. + */ +class CanonicalizingIRMQ extends DelegatingIRMQ { + CanonicalizingIRMQ(IRelMetadataQuery delegate) { + super(delegate); + } + + @Override public @Nullable RelDistribution distribution(final RelNode rel) { + RelDistribution distribution = super.distribution(rel); + if (distribution == null) { + return RelDistributions.ANY; + } + return distribution; + } + + public Double getRowCount(RelNode rel) { + return RelMdUtil.validateResult(castNonNull(super.getRowCount(rel))); + } + + public @Nullable Double getPopulationSize(RelNode rel, ImmutableBitSet groupKey) { + return RelMdUtil.validateResult(super.getPopulationSize(rel, groupKey)); + } + + public @Nullable Double getPercentageOriginalRows(RelNode rel) { + Double result = super.getPercentageOriginalRows(rel); + return RelMdUtil.validatePercentage(result); + } + + public @Nullable Double getSelectivity(RelNode rel, @Nullable RexNode predicate) { + return RelMdUtil.validatePercentage(super.getSelectivity(rel, predicate)); + } + + public @Nullable Double getDistinctRowCount( + RelNode rel, + ImmutableBitSet groupKey, + @Nullable RexNode predicate) { + return RelMdUtil.validateResult(super.getDistinctRowCount(rel, groupKey, predicate)); + } + + public RelOptPredicateList getPulledUpPredicates(RelNode rel) { + RelOptPredicateList result = super.getPulledUpPredicates(rel); + return result != null ? result : RelOptPredicateList.EMPTY; + } + + public Boolean isVisibleInExplain(RelNode rel, + SqlExplainLevel explainLevel) { + Boolean b = super.isVisibleInExplain(rel, explainLevel); + return b == null || b; + } +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingIRMQ.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingIRMQ.java new file mode 100644 index 000000000000..f3a4b0ce46cf --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingIRMQ.java @@ -0,0 +1,237 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.volcano.VolcanoPlanner; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelColumnOrigin; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexTableInputRef; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Set; + +/** + * A base class that simply delegates all calls to this to a delegate IRelMetadataQuery. + */ +abstract class DelegatingIRMQ implements IRelMetadataQuery { + private final IRelMetadataQuery delegate; + + DelegatingIRMQ(IRelMetadataQuery delegate) { + this.delegate = delegate; + } + + @Override public @Nullable Multimap, RelNode> getNodeTypes( + final RelNode rel) { + return delegate.getNodeTypes(rel); + } + + @Override public Double getRowCount(final RelNode rel) { + return delegate.getRowCount(rel); + } + + @Override public boolean clearCache(final RelNode rel) { + return delegate.clearCache(rel); + } + + @Override @Nullable public Double getMaxRowCount( + final RelNode rel) { + return delegate.getMaxRowCount(rel); + } + + @Override @Nullable public Double getMinRowCount( + final RelNode rel) { + return delegate.getMinRowCount(rel); + } + + @Override public @Nullable RelOptCost getCumulativeCost( + final RelNode rel) { + return delegate.getCumulativeCost(rel); + } + + @Override public @Nullable RelOptCost getNonCumulativeCost( + final RelNode rel) { + return delegate.getNonCumulativeCost(rel); + } + + @Override @Nullable public Double getPercentageOriginalRows( + final RelNode rel) { + return delegate.getPercentageOriginalRows(rel); + } + + @Override public @Nullable Set getColumnOrigins( + final RelNode rel, final int column) { + return delegate.getColumnOrigins(rel, column); + } + + @Override public @Nullable Set getExpressionLineage( + final RelNode rel, final RexNode expression) { + return delegate.getExpressionLineage(rel, expression); + } + + @Override public @Nullable Set getTableReferences( + final RelNode rel) { + return delegate.getTableReferences(rel); + } + + @Override @Nullable public Double getSelectivity( + final RelNode rel, + final @Nullable RexNode predicate) { + return delegate.getSelectivity(rel, predicate); + } + + @Override public @Nullable Set getUniqueKeys( + final RelNode rel, final boolean ignoreNulls) { + return delegate.getUniqueKeys(rel, ignoreNulls); + } + + @Override @Nullable public Boolean areColumnsUnique( + final RelNode rel, final ImmutableBitSet columns, + final boolean ignoreNulls) { + return delegate.areColumnsUnique(rel, columns, ignoreNulls); + } + + @Override public @Nullable ImmutableList collations( + final RelNode rel) { + return delegate.collations(rel); + } + + @Override public RelDistribution distribution(final RelNode rel) { + return delegate.distribution(rel); + } + + @Override @Nullable public Double getPopulationSize( + final RelNode rel, + final ImmutableBitSet groupKey) { + return delegate.getPopulationSize(rel, groupKey); + } + + @Override @Nullable public Double getAverageRowSize( + final RelNode rel) { + return delegate.getAverageRowSize(rel); + } + + @Override public @Nullable List<@Nullable Double> getAverageColumnSizes( + final RelNode rel) { + return delegate.getAverageColumnSizes(rel); + } + + @Override @Nullable public Boolean isPhaseTransition( + final RelNode rel) { + return delegate.isPhaseTransition(rel); + } + + @Override @Nullable public Integer splitCount( + final RelNode rel) { + return delegate.splitCount(rel); + } + + @Override @Nullable public Double memory( + final RelNode rel) { + return delegate.memory(rel); + } + + @Override @Nullable public Double cumulativeMemoryWithinPhase( + final RelNode rel) { + return delegate.cumulativeMemoryWithinPhase(rel); + } + + @Override @Nullable public Double cumulativeMemoryWithinPhaseSplit( + final RelNode rel) { + return delegate.cumulativeMemoryWithinPhaseSplit(rel); + } + + @Override @Nullable public Double getDistinctRowCount( + final RelNode rel, + final ImmutableBitSet groupKey, + final @Nullable RexNode predicate) { + return delegate.getDistinctRowCount(rel, groupKey, predicate); + } + + @Override public RelOptPredicateList getPulledUpPredicates( + final RelNode rel) { + return delegate.getPulledUpPredicates(rel); + } + + @Override public @Nullable RelOptPredicateList getAllPredicates( + final RelNode rel) { + return delegate.getAllPredicates(rel); + } + + @Override public Boolean isVisibleInExplain(final RelNode rel, + final SqlExplainLevel explainLevel) { + return delegate.isVisibleInExplain(rel, explainLevel); + } + + @Override public @Nullable RelDistribution getDistribution( + final RelNode rel) { + return delegate.getDistribution(rel); + } + + @Override public @Nullable RelOptCost getLowerBoundCost( + final RelNode rel, + final VolcanoPlanner planner) { + return delegate.getLowerBoundCost(rel, planner); + } + + @Override public List<@Nullable Double> getAverageColumnSizesNotNull( + final RelNode rel) { + return delegate.getAverageColumnSizesNotNull(rel); + } + + @Override public @Nullable Set getUniqueKeys( + final RelNode rel) { + return delegate.getUniqueKeys(rel); + } + + @Override @Nullable public Boolean areRowsUnique( + final RelNode rel, final boolean ignoreNulls) { + return delegate.areRowsUnique(rel, ignoreNulls); + } + + @Override @Nullable public Boolean areRowsUnique( + final RelNode rel) { + return delegate.areRowsUnique(rel); + } + + @Override @Nullable public Boolean areColumnsUnique( + final RelNode rel, final ImmutableBitSet columns) { + return delegate.areColumnsUnique(rel, columns); + } + + @Override public @Nullable RelColumnOrigin getColumnOrigin( + final RelNode rel, final int column) { + return delegate.getColumnOrigin(rel, column); + } + + @Override public @Nullable RelOptTable getTableOrigin( + final RelNode rel) { + return delegate.getTableOrigin(rel); + } +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingRelMetadataQuery.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingRelMetadataQuery.java new file mode 100644 index 000000000000..7a7ca1d5fcf0 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/DelegatingRelMetadataQuery.java @@ -0,0 +1,241 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.volcano.VolcanoPlanner; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelColumnOrigin; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexTableInputRef; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + * A version of RelMetadataQuery that overrides all the defaults methods + * and delegates them to a IRelMetadataQuery delegate. + */ +public class DelegatingRelMetadataQuery extends RelMetadataQuery { + + private final IRelMetadataQuery delegate; + + public DelegatingRelMetadataQuery(Function supplier) { + this.delegate = supplier.apply(this); + } + + @Override public @Nullable Multimap, RelNode> getNodeTypes( + final RelNode rel) { + return delegate.getNodeTypes(rel); + } + + @Override public Double getRowCount(final RelNode rel) { + return delegate.getRowCount(rel); + } + + @Override @Nullable public Double getMaxRowCount( + final RelNode rel) { + return delegate.getMaxRowCount(rel); + } + + @Override @Nullable public Double getMinRowCount( + final RelNode rel) { + return delegate.getMinRowCount(rel); + } + + @Override public @Nullable RelOptCost getCumulativeCost( + final RelNode rel) { + return delegate.getCumulativeCost(rel); + } + + @Override public @Nullable RelOptCost getNonCumulativeCost( + final RelNode rel) { + return delegate.getNonCumulativeCost(rel); + } + + @Override @Nullable public Double getPercentageOriginalRows( + final RelNode rel) { + return delegate.getPercentageOriginalRows(rel); + } + + @Override public @Nullable Set getColumnOrigins( + final RelNode rel, final int column) { + return delegate.getColumnOrigins(rel, column); + } + + @Override public @Nullable Set getExpressionLineage( + final RelNode rel, final RexNode expression) { + return delegate.getExpressionLineage(rel, expression); + } + + @Override public @Nullable Set getTableReferences( + final RelNode rel) { + return delegate.getTableReferences(rel); + } + + @Override @Nullable public Double getSelectivity( + final RelNode rel, + final @Nullable RexNode predicate) { + return delegate.getSelectivity(rel, predicate); + } + + @Override public @Nullable Set getUniqueKeys( + final RelNode rel, final boolean ignoreNulls) { + return delegate.getUniqueKeys(rel, ignoreNulls); + } + + @Override @Nullable public Boolean areColumnsUnique( + final RelNode rel, final ImmutableBitSet columns, + final boolean ignoreNulls) { + return delegate.areColumnsUnique(rel, columns, ignoreNulls); + } + + @Override public @Nullable ImmutableList collations( + final RelNode rel) { + return delegate.collations(rel); + } + + @Override public RelDistribution distribution(final RelNode rel) { + return delegate.distribution(rel); + } + + @Override @Nullable public Double getPopulationSize( + final RelNode rel, + final ImmutableBitSet groupKey) { + return delegate.getPopulationSize(rel, groupKey); + } + + @Override @Nullable public Double getAverageRowSize( + final RelNode rel) { + return delegate.getAverageRowSize(rel); + } + + @Override public @Nullable List<@Nullable Double> getAverageColumnSizes( + final RelNode rel) { + return delegate.getAverageColumnSizes(rel); + } + + @Override @Nullable public Boolean isPhaseTransition( + final RelNode rel) { + return delegate.isPhaseTransition(rel); + } + + @Override @Nullable public Integer splitCount( + final RelNode rel) { + return delegate.splitCount(rel); + } + + @Override @Nullable public Double memory( + final RelNode rel) { + return delegate.memory(rel); + } + + @Override @Nullable public Double cumulativeMemoryWithinPhase( + final RelNode rel) { + return delegate.cumulativeMemoryWithinPhase(rel); + } + + @Override @Nullable public Double cumulativeMemoryWithinPhaseSplit( + final RelNode rel) { + return delegate.cumulativeMemoryWithinPhaseSplit(rel); + } + + @Override @Nullable public Double getDistinctRowCount( + final RelNode rel, + final ImmutableBitSet groupKey, + final @Nullable RexNode predicate) { + return delegate.getDistinctRowCount(rel, groupKey, predicate); + } + + @Override public RelOptPredicateList getPulledUpPredicates( + final RelNode rel) { + return delegate.getPulledUpPredicates(rel); + } + + @Override public @Nullable RelOptPredicateList getAllPredicates( + final RelNode rel) { + return delegate.getAllPredicates(rel); + } + + @Override public Boolean isVisibleInExplain(final RelNode rel, + final SqlExplainLevel explainLevel) { + return delegate.isVisibleInExplain(rel, explainLevel); + } + + @Override public @Nullable RelDistribution getDistribution( + final RelNode rel) { + return delegate.getDistribution(rel); + } + + @Override public @Nullable RelOptCost getLowerBoundCost( + final RelNode rel, + final VolcanoPlanner planner) { + return delegate.getLowerBoundCost(rel, planner); + } + + @Override public List<@Nullable Double> getAverageColumnSizesNotNull( + final RelNode rel) { + return delegate.getAverageColumnSizesNotNull(rel); + } + + @Override public @Nullable Set getUniqueKeys( + final RelNode rel) { + return delegate.getUniqueKeys(rel); + } + + @Override @Nullable public Boolean areRowsUnique( + final RelNode rel, final boolean ignoreNulls) { + return delegate.areRowsUnique(rel, ignoreNulls); + } + + @Override @Nullable public Boolean areRowsUnique( + final RelNode rel) { + return delegate.areRowsUnique(rel); + } + + @Override @Nullable public Boolean areColumnsUnique( + final RelNode rel, final ImmutableBitSet columns) { + return delegate.areColumnsUnique(rel, columns); + } + + @Override public @Nullable RelColumnOrigin getColumnOrigin( + final RelNode rel, final int column) { + return delegate.getColumnOrigin(rel, column); + } + + @Override public @Nullable RelOptTable getTableOrigin( + final RelNode rel) { + return delegate.getTableOrigin(rel); + } + + @Override public boolean clearCache(final RelNode rel) { + return delegate.clearCache(rel); + } +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/IRelMetadataQuery.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/IRelMetadataQuery.java new file mode 100644 index 000000000000..0eea772a981b --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/IRelMetadataQuery.java @@ -0,0 +1,137 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.volcano.VolcanoPlanner; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelColumnOrigin; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexTableInputRef; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * The interface that RelMetadataQuery would ideally be. + * + * An interface that abstracts away convenience methods from functional methods so that only unique + * operations must be defined by concrete implementations. + */ +interface IRelMetadataQuery { + + @Nullable Multimap, RelNode> getNodeTypes(RelNode rel); + @Nullable Double getMaxRowCount(RelNode rel); + @Nullable Double getMinRowCount(RelNode rel); + @Nullable RelOptCost getCumulativeCost(RelNode rel); + @Nullable RelOptCost getNonCumulativeCost(RelNode rel); + @Nullable Double getPercentageOriginalRows(RelNode rel); + @Nullable Set getColumnOrigins(RelNode rel, int column); + @Nullable Set getExpressionLineage(RelNode rel, RexNode expression); + @Nullable Set getTableReferences(RelNode rel); + @Nullable Double getSelectivity(RelNode rel, @Nullable RexNode predicate); + @Nullable Set getUniqueKeys(RelNode rel, boolean ignoreNulls); + @Nullable Boolean areColumnsUnique(RelNode rel, ImmutableBitSet columns, boolean ignoreNulls); + @Nullable ImmutableList collations(RelNode rel); + @Nullable Double getPopulationSize(RelNode rel, ImmutableBitSet groupKey); + @Nullable Double getAverageRowSize(RelNode rel); + @Nullable List<@Nullable Double> getAverageColumnSizes(RelNode rel); + @Nullable Boolean isPhaseTransition(RelNode rel); + @Nullable Integer splitCount(RelNode rel); + @Nullable Double memory(RelNode rel); + @Nullable Double cumulativeMemoryWithinPhase(RelNode rel); + @Nullable Double cumulativeMemoryWithinPhaseSplit(RelNode rel); + @Nullable Double getDistinctRowCount(RelNode rel, ImmutableBitSet groupKey, + @Nullable RexNode predicate); + @Nullable RelOptPredicateList getAllPredicates(RelNode rel); + @Nullable RelDistribution getDistribution(RelNode rel); + @Nullable RelOptCost getLowerBoundCost(RelNode rel, VolcanoPlanner planner); + + Double getRowCount(RelNode rel); + RelOptPredicateList getPulledUpPredicates(RelNode rel); + Boolean isVisibleInExplain(RelNode rel, SqlExplainLevel explainLevel); + + boolean clearCache(RelNode rel); + + default List<@Nullable Double> getAverageColumnSizesNotNull(RelNode rel) { + @Nullable List<@Nullable Double> averageColumnSizes = getAverageColumnSizes(rel); + return averageColumnSizes == null + ? Collections.nCopies(rel.getRowType().getFieldCount(), null) + : averageColumnSizes; + } + + default RelDistribution distribution(RelNode rel) { + return getDistribution(rel); + } + + default @Nullable Set getUniqueKeys(RelNode rel) { + return getUniqueKeys(rel, false); + } + default @Nullable Boolean areRowsUnique(RelNode rel, boolean ignoreNulls) { + Double maxRowCount = this.getMaxRowCount(rel); + if (maxRowCount != null && maxRowCount <= 1D) { + return true; + } + ImmutableBitSet columns = + ImmutableBitSet.range(rel.getRowType().getFieldCount()); + return areColumnsUnique(rel, columns, ignoreNulls); + } + + default @Nullable Boolean areRowsUnique(RelNode rel) { + return areRowsUnique(rel, false); + } + + default @Nullable Boolean areColumnsUnique(RelNode rel, ImmutableBitSet columns) { + return areColumnsUnique(rel, columns, false); + } + default @Nullable RelColumnOrigin getColumnOrigin(RelNode rel, int column) { + Set origins = getColumnOrigins(rel, column); + if (origins == null || origins.size() != 1) { + return null; + } + RelColumnOrigin origin = Iterables.getOnlyElement(origins); + return origin; + } + + default @Nullable RelOptTable getTableOrigin(RelNode rel) { + // Determine the simple origin of the first column in the + // RelNode. If it's simple, then that means that the underlying + // table is also simple, even if the column itself is derived. + if (rel.getRowType().getFieldCount() == 0) { + return null; + } + Set colOrigins = getColumnOrigins(rel, 0); + if (colOrigins == null || colOrigins.size() == 0) { + return null; + } + return colOrigins.iterator().next().getOriginTable(); + } + +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaIRMQ.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaIRMQ.java new file mode 100644 index 000000000000..18b3e02d0e95 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaIRMQ.java @@ -0,0 +1,361 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.plan.volcano.VolcanoPlanner; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.CyclicMetadataException; +import org.apache.calcite.rel.metadata.DelegatingMetadataRel; +import org.apache.calcite.rel.metadata.NullSentinel; +import org.apache.calcite.rel.metadata.RelColumnOrigin; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexTableInputRef; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.Table; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +/** + * A metadata retriever that moves through a list of possible handlers until it finds one + * that returns a non-null value (or depletes the list, returning null). + * + *

This caches all RelNode instance/argument tuples so the same work isn't done twice. + *

This monitors for cycles and returns null in those cases. + *

This will automatically identify and recurse on DelegatingMetadataRel nodes. + *

When caching arguments, RexNodes are converted to strings since equals() is shallow. + * This e + */ +class LambdaIRMQ implements IRelMetadataQuery { + private final RelMetadataQuery top; + private final LambdaProvider handlerProvider; + private final Table metadataCache = HashBasedTable.create(); + + LambdaIRMQ(LambdaProvider handlerProvider, RelMetadataQuery top) { + this.top = top; + this.handlerProvider = handlerProvider; + } + + private static RelNode recurseDelegates(RelNode r) { + while (r instanceof DelegatingMetadataRel) { + r = ((DelegatingMetadataRel) r).getMetadataDelegateRel(); + } + return r; + } + + public boolean clearCache(RelNode rel) { + Map row = metadataCache.row(rel); + if (row.isEmpty()) { + return false; + } + + row.clear(); + return true; + } + + private T findLambdas( + Class clazz, + Class methodInterface) { + try { + return (T) handlerProvider.get(clazz, methodInterface); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new RuntimeException(cause); + } + } + + protected final T retrieve( + RelNode r, + Class methodInterface) { + r = recurseDelegates(r); + final Object key = methodInterface; + Object v = check(r, key); + if (v != null) { + return (T) v; + } + metadataCache.put(r, key, NullSentinel.ACTIVE); + try { + final Object x = invoke(r, methodInterface); + metadataCache.put(r, key, NullSentinel.mask(x)); + return (T) x; + } catch (java.lang.Exception e) { + metadataCache.row(r).clear(); + throw e; + } + } + + private T invoke( + RelNode r, + Class methodInterface) { + List> lambdas = + findLambdas(r.getClass(), methodInterface); + if (lambdas.isEmpty()) { + return null; + } + + for (MetadataLambda.Arg0Lambda lambda : lambdas) { + T val = lambda.call(r, top); + if (val != null) { + return val; + } + } + + return null; + } + + private T retrieve( + RelNode r, + Class methodInterface, + Object o0) { + r = recurseDelegates(r); + final List key; + key = org.apache.calcite.runtime.FlatLists.of(methodInterface, keyifyArg(o0)); + Object v = check(r, key); + if (v != null) { + return (T) v; + } + metadataCache.put(r, key, NullSentinel.ACTIVE); + try { + final Object x = invoke(r, methodInterface, o0); + metadataCache.put(r, key, NullSentinel.mask(x)); + return (T) x; + } catch (java.lang.Exception e) { + metadataCache.row(r).clear(); + throw e; + } + } + + private T invoke( + RelNode r, + Class methodInterface, + Object o0) { + List> lambdas = + findLambdas(r.getClass(), methodInterface); + if (lambdas.isEmpty()) { + return null; + } + + for (MetadataLambda.Arg1Lambda lambda : lambdas) { + T val = lambda.call(r, top, o0); + if (val != null) { + return val; + } + } + + return null; + } + + private T retrieve( + RelNode r, + Class methodInterface, + Object o0, + Object o1) { + r = recurseDelegates(r); + final List key; + key = org.apache.calcite.runtime.FlatLists.of(methodInterface, keyifyArg(o0), keyifyArg(o1)); + Object v = check(r, key); + if (v != null) { + return (T) v; + } + metadataCache.put(r, key, NullSentinel.ACTIVE); + try { + final Object x = invoke(r, methodInterface, o0, o1); + metadataCache.put(r, key, NullSentinel.mask(x)); + return (T) x; + } catch (java.lang.Exception e) { + metadataCache.row(r).clear(); + throw e; + } + } + + private T invoke( + RelNode r, + Class methodInterface, + Object o0, + Object o1) { + List> lambdas = + findLambdas(r.getClass(), methodInterface); + if (lambdas.isEmpty()) { + return null; + } + + for (MetadataLambda.Arg2Lambda lambda : lambdas) { + T val = lambda.call(r, top, o0, o1); + if (val != null) { + return val; + } + } + + return null; + } + + private static Object keyifyArg(Object arg) { + if (arg instanceof RexNode) { + // RexNodes need to be converted to strings to support use in a key. + return arg.toString(); + } + return arg; + } + + private Object check(RelNode r, Object key) { + final Object v = metadataCache.get(r, key); + if (v == null) { + return null; + } + if (v == NullSentinel.ACTIVE) { + throw new CyclicMetadataException(); + } + if (v == NullSentinel.INSTANCE) { + return null; + } + return v; + } + + + public @Nullable Multimap, RelNode> getNodeTypes(final RelNode rel) { + return retrieve(rel, MetadataLambda.NodeTypes.class); + } + + public Double getRowCount(final RelNode rel) { + return retrieve(rel, MetadataLambda.RowCount.class); + } + + public @Nullable Double getMaxRowCount(final RelNode rel) { + return retrieve(rel, MetadataLambda.MaxRowCount.class); + } + + public @Nullable Double getMinRowCount(final RelNode rel) { + return retrieve(rel, MetadataLambda.MinRowCount.class); + } + + public @Nullable RelOptCost getCumulativeCost(final RelNode rel) { + return retrieve(rel, MetadataLambda.CumulativeCost.class); + } + + public @Nullable RelOptCost getNonCumulativeCost(final RelNode rel) { + return retrieve(rel, MetadataLambda.NonCumulativeCost.class); + } + + public @Nullable Double getPercentageOriginalRows(final RelNode rel) { + return retrieve(rel, MetadataLambda.PercentageOriginalRows.class); + } + + public @Nullable Set getColumnOrigins(final RelNode rel, final int column) { + return retrieve(rel, MetadataLambda.ColumnOrigins.class, column); + } + + public @Nullable Set getExpressionLineage(final RelNode rel, final RexNode expression) { + return retrieve(rel, MetadataLambda.ExpressionLineage.class, expression); + } + + public @Nullable Set getTableReferences(final RelNode rel) { + return retrieve(rel, MetadataLambda.TableReferences.class); + } + + public @Nullable Double getSelectivity(final RelNode rel, @Nullable final RexNode predicate) { + return retrieve(rel, MetadataLambda.Selectivity.class, predicate); + } + + public @Nullable Set getUniqueKeys( + final RelNode rel, final boolean ignoreNulls) { + return retrieve(rel, MetadataLambda.UniqueKeys.class, ignoreNulls); + } + + public @Nullable Boolean areColumnsUnique( + final RelNode rel, final ImmutableBitSet columns, final boolean ignoreNulls) { + return retrieve(rel, MetadataLambda.ColumnsUnique.class, columns, ignoreNulls); + } + + public @Nullable ImmutableList collations(final RelNode rel) { + return retrieve(rel, MetadataLambda.Collations.class); + } + + public @Nullable Double getPopulationSize(final RelNode rel, final ImmutableBitSet groupKey) { + return retrieve(rel, MetadataLambda.PopulationSize.class, groupKey); + } + + public @Nullable Double getAverageRowSize(final RelNode rel) { + return retrieve(rel, MetadataLambda.AverageRowSize.class); + } + + public @Nullable List<@Nullable Double> getAverageColumnSizes(final RelNode rel) { + return retrieve(rel, MetadataLambda.AverageColumnSizes.class); + } + + public @Nullable Boolean isPhaseTransition(final RelNode rel) { + return retrieve(rel, MetadataLambda.PhaseTransition.class); + } + + public @Nullable Integer splitCount(final RelNode rel) { + return retrieve(rel, MetadataLambda.SplitCount.class); + } + + public @Nullable Double memory(final RelNode rel) { + return retrieve(rel, MetadataLambda.Memory.class); + } + + public @Nullable Double cumulativeMemoryWithinPhase(final RelNode rel) { + return retrieve(rel, MetadataLambda.CumulativeMemoryWithinPhase.class); + } + + public @Nullable Double cumulativeMemoryWithinPhaseSplit(final RelNode rel) { + return retrieve(rel, MetadataLambda.CumulativeMemoryWithinPhaseSplit.class); + } + + public @Nullable Double getDistinctRowCount( + final RelNode rel, final ImmutableBitSet groupKey, @Nullable final RexNode predicate) { + return retrieve(rel, MetadataLambda.DistinctRowCount.class, groupKey, predicate); + } + + public RelOptPredicateList getPulledUpPredicates(final RelNode rel) { + return retrieve(rel, MetadataLambda.PulledUpPredicates.class); + } + + public @Nullable RelOptPredicateList getAllPredicates(final RelNode rel) { + return retrieve(rel, MetadataLambda.AllPredicates.class); + } + + public Boolean isVisibleInExplain(final RelNode rel, final SqlExplainLevel explainLevel) { + return retrieve(rel, MetadataLambda.VisibleInExplain.class, explainLevel); + } + + public @Nullable RelDistribution getDistribution(final RelNode rel) { + return retrieve(rel, MetadataLambda.Distribution.class); + } + + public @Nullable RelOptCost getLowerBoundCost(final RelNode rel, final VolcanoPlanner planner) { + return retrieve(rel, MetadataLambda.LowerBoundCost.class, planner); + } + +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaMetadataSupplier.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaMetadataSupplier.java new file mode 100644 index 000000000000..1453afcf7c63 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaMetadataSupplier.java @@ -0,0 +1,87 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.rel.metadata.RelMetadataQuery; + +import com.google.common.base.Suppliers; + +import org.apiguardian.api.API; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Entry point for creating a Lambda-based RelMetadataQuery supplier. + */ +@API(since = "1.29", status = API.Status.EXPERIMENTAL) +public class LambdaMetadataSupplier implements Supplier { + + // lazy initialize a supplier so we don't maintain multiple caches + // but pay no overhead if people want to use their own supplier + private static final Supplier INSTANCE = + Suppliers.memoize(() -> new LambdaMetadataSupplier()); + + /** + * Get a supplier with a static backing cache. + * + * Create a RelMetadataQuery supplier. Note that this lazily creates a static cache of + * reflection based lambda references so that future calls will avoid recreation + * of the global cache. Thus the first call is expected to take longer since a number + * of Lambdas are generated as part of this invocation. + * + * @return A supplier that can be provided to RelOptCluster.setMetadataQuerySupplier() + */ + public static LambdaMetadataSupplier instance() { + return INSTANCE.get(); + } + + private final LambdaProvider lambdaProvider; + + /** + * Create a supplier with a default ReflectionToLambdaProvider wrapped in a CachingLambdaProvider. + */ + public LambdaMetadataSupplier() { + this(new CachingLambdaProvider(new ReflectionToLambdaProvider())); + } + + /** + * Create a supplier with the provided lambda provider. No additional caching is done. + * + * For performance purposes, users should wrap the provider with a CachingLambdaProvider to avoid + * duplicate lookups. + * + * @param provider The provider that will be used. + */ + public LambdaMetadataSupplier(LambdaProvider provider) { + lambdaProvider = provider; + } + + /** + * Create a new unique query object. + * @return Instance of RelMetadataQuery backed by the classes LambdaProvider. + */ + @Override public RelMetadataQuery get() { + Function supplier = rmq -> { + LambdaIRMQ lambdas = new LambdaIRMQ(lambdaProvider, rmq); + CanonicalizingIRMQ nullCanonicalizingRMQ = new CanonicalizingIRMQ(lambdas); + return nullCanonicalizingRMQ; + }; + return new DelegatingRelMetadataQuery(supplier); + } + +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaProvider.java new file mode 100644 index 000000000000..48605c6f1eb9 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/LambdaProvider.java @@ -0,0 +1,46 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.rel.RelNode; + +import org.apiguardian.api.API; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import javax.annotation.concurrent.ThreadSafe; + +/** + * A method for returning Lambdas associated with a RelNode class for a particular Lambda class. + */ +@API(status = API.Status.EXPERIMENTAL) +@ThreadSafe +@FunctionalInterface +public interface LambdaProvider { + + /** + * Get a list of handlers for the requested RelNode/Lambda class combination. + * @param relNodeClass The class of the RelNode that you want a list of lambdas for. + * @param handlerClass The specific type MetadataLambda you expect. + * @param The class of the RelNode that you want a handler for. + * @param The class of the RelNode that you want a list of lambdas for. + * @return A list of Lambdas + * @throws ExecutionException Thrown if a failure occurs while trying to find or build a lambda. + */ + List get( + Class relNodeClass, Class handlerClass) throws ExecutionException; +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/MetadataLambda.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/MetadataLambda.java new file mode 100644 index 000000000000..5ba3edb5f3f3 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/MetadataLambda.java @@ -0,0 +1,279 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.plan.RelOptCost; +import org.apache.calcite.plan.RelOptPredicateList; +import org.apache.calcite.plan.volcano.VolcanoPlanner; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelColumnOrigin; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexTableInputRef; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; + +import org.apiguardian.api.API; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Set; +import javax.annotation.concurrent.ThreadSafe; + +/** + * + * A marker interface that describes a lambda for Metadata retrieval. + */ +@API(since = "1.29", status = API.Status.EXPERIMENTAL) +@ThreadSafe +public interface MetadataLambda { + /** + * Metadata Lambda for NodeTypes. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface NodeTypes + extends MetadataLambda, Arg0Lambda, RelNode>> { } + + /** + * Metadata Lambda for NodeTypes. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface + interface AverageColumnSizes + extends MetadataLambda, Arg0Lambda> { } + + /** + * Metadata Lambda for RowCount. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface RowCount + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for MaxRowCount. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface MaxRowCount + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for MinRowCount. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface MinRowCount + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for CumulativeCost. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface CumulativeCost + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for NonCumulativeCost. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface NonCumulativeCost + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for PercentageOriginalRows. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface PercentageOriginalRows + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for ColumnOrigins. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface ColumnOrigins + extends MetadataLambda, Arg1Lambda> { } + + /** + * Metadata Lambda for ExpressionLineage. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface ExpressionLineage + extends MetadataLambda, Arg1Lambda> { } + + /** + * Metadata Lambda for TableReferences. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface TableReferences + extends MetadataLambda, Arg0Lambda> { } + + /** + * Metadata Lambda for Selectivity. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface Selectivity + extends MetadataLambda, Arg1Lambda { } + + /** + * Metadata Lambda for UniqueKeys. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface UniqueKeys + extends MetadataLambda, Arg1Lambda> { } + + /** + * Metadata Lambda for ColumnsUnique. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface ColumnsUnique + extends MetadataLambda, Arg2Lambda { } + + /** + * Metadata Lambda for Collations. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface Collations + extends MetadataLambda, Arg0Lambda> { } + + /** + * Metadata Lambda for PopulationSize. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface PopulationSize + extends MetadataLambda, Arg1Lambda { } + + /** + * Metadata Lambda for AverageRowSize. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface AverageRowSize + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for PhaseTransition. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface PhaseTransition + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for SplitCount. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface SplitCount + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for Memory. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface Memory + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for CumulativeMemoryWithinPhase. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface CumulativeMemoryWithinPhase + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for CumulativeMemoryWithinPhaseSplit. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface CumulativeMemoryWithinPhaseSplit + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for DistinctRowCount. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface DistinctRowCount + extends MetadataLambda, Arg2Lambda { } + + /** + * Metadata Lambda for PulledUpPredicates. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface PulledUpPredicates + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for AllPredicates. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface AllPredicates + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for VisibleInExplain. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface VisibleInExplain + extends MetadataLambda, Arg1Lambda { } + + /** + * Metadata Lambda for Distribution. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface Distribution + extends MetadataLambda, Arg0Lambda { } + + /** + * Metadata Lambda for LowerBoundCost. + * @param Specific RelNode subclass this interface captures. + */ + @FunctionalInterface interface LowerBoundCost + extends MetadataLambda, Arg1Lambda { } + + /** + * Base Lambda interface for zero argument metadata call. + * @param Specific RelNode subclass this interface captures. + * @param Return data type. + */ + interface Arg0Lambda { + + /** Call with rel, mq and 0 arguments. */ + R call(T rel, RelMetadataQuery mq); + } + + /** + * Base Lambda interface for one argument metadata call. + * @param Specific RelNode subclass this interface captures. + * @param First argument type. + * @param Return data type. + */ + interface Arg1Lambda { + /** Call with rel, mq and 1 argument. */ + R call(T rel, RelMetadataQuery mq, A0 arg0); + } + + /** + * Base Lambda interface for two argument metadata call. + * @param Specific RelNode subclass this interface captures. + * @param First argument type. + * @param Second argument type. + * @param Return data type. + */ + interface Arg2Lambda { + /** Call with rel, mq and 2 arguments. */ + R call(T rel, RelMetadataQuery mq, A0 arg0, A1 arg1); + } +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/ReflectionToLambdaProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/ReflectionToLambdaProvider.java new file mode 100644 index 000000000000..16c22786c856 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/ReflectionToLambdaProvider.java @@ -0,0 +1,359 @@ +/* + * 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.rel.metadata.lambda; + +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.RelMdAllPredicates; +import org.apache.calcite.rel.metadata.RelMdCollation; +import org.apache.calcite.rel.metadata.RelMdColumnOrigins; +import org.apache.calcite.rel.metadata.RelMdColumnUniqueness; +import org.apache.calcite.rel.metadata.RelMdDistinctRowCount; +import org.apache.calcite.rel.metadata.RelMdDistribution; +import org.apache.calcite.rel.metadata.RelMdExplainVisibility; +import org.apache.calcite.rel.metadata.RelMdExpressionLineage; +import org.apache.calcite.rel.metadata.RelMdLowerBoundCost; +import org.apache.calcite.rel.metadata.RelMdMaxRowCount; +import org.apache.calcite.rel.metadata.RelMdMemory; +import org.apache.calcite.rel.metadata.RelMdMinRowCount; +import org.apache.calcite.rel.metadata.RelMdNodeTypes; +import org.apache.calcite.rel.metadata.RelMdParallelism; +import org.apache.calcite.rel.metadata.RelMdPercentageOriginalRows; +import org.apache.calcite.rel.metadata.RelMdPopulationSize; +import org.apache.calcite.rel.metadata.RelMdPredicates; +import org.apache.calcite.rel.metadata.RelMdRowCount; +import org.apache.calcite.rel.metadata.RelMdSelectivity; +import org.apache.calcite.rel.metadata.RelMdSize; +import org.apache.calcite.rel.metadata.RelMdTableReferences; +import org.apache.calcite.rel.metadata.RelMdUniqueKeys; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.util.BuiltInMethod; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Table; +import com.google.common.primitives.Primitives; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Create a set of lambda handlers via reflection patterns. + * + * This does reflection to lambda conversion based on a set of singleton objects + * with appropriate signatures, similar to how ReflectiveRelMetadataProvider works. + * Any class that can be discovered using ReflectiveRelMetadataProvider should also be consumable + * using this class. Ultimately, the goal may be to move to direct lambda registration as opposed to + * the old system of partial reflection discovery. + */ +@ThreadSafe +public class ReflectionToLambdaProvider implements LambdaProvider { + + public static final ImmutableList DEFAULT_SOURCES = ImmutableList.builder() + .add( + s(RelMdColumnOrigins.class, BuiltInMethod.COLUMN_ORIGIN, + MetadataLambda.ColumnOrigins.class)) + .add( + s(RelMdPercentageOriginalRows.class, BuiltInMethod.PERCENTAGE_ORIGINAL_ROWS, + MetadataLambda.PercentageOriginalRows.class)) + .add( + s(RelMdExpressionLineage.class, BuiltInMethod.EXPRESSION_LINEAGE, + MetadataLambda.ExpressionLineage.class)) + .add( + s(RelMdTableReferences.class, BuiltInMethod.TABLE_REFERENCES, + MetadataLambda.TableReferences.class)) + .add( + s(RelMdNodeTypes.class, BuiltInMethod.NODE_TYPES, + MetadataLambda.NodeTypes.class)) + .add( + s(RelMdRowCount.class, BuiltInMethod.ROW_COUNT, + MetadataLambda.RowCount.class)) + .add( + s(RelMdMaxRowCount.class, BuiltInMethod.MAX_ROW_COUNT, + MetadataLambda.MaxRowCount.class)) + .add( + s(RelMdMinRowCount.class, BuiltInMethod.MIN_ROW_COUNT, + MetadataLambda.MinRowCount.class)) + .add( + s(RelMdUniqueKeys.class, BuiltInMethod.UNIQUE_KEYS, + MetadataLambda.UniqueKeys.class)) + .add( + s(RelMdColumnUniqueness.class, BuiltInMethod.COLUMN_UNIQUENESS, + MetadataLambda.ColumnsUnique.class)) + .add( + s(RelMdPopulationSize.class, BuiltInMethod.POPULATION_SIZE, + MetadataLambda.PopulationSize.class)) + .add( + s(RelMdSize.class, BuiltInMethod.AVERAGE_ROW_SIZE, + MetadataLambda.AverageRowSize.class)) + .add( + s(RelMdSize.class, BuiltInMethod.AVERAGE_COLUMN_SIZES, + MetadataLambda.AverageColumnSizes.class)) + .add( + s(RelMdParallelism.class, BuiltInMethod.IS_PHASE_TRANSITION, + MetadataLambda.PhaseTransition.class)) + .add( + s(RelMdParallelism.class, BuiltInMethod.SPLIT_COUNT, + MetadataLambda.SplitCount.class)) + .add( + s(RelMdDistribution.class, BuiltInMethod.DISTRIBUTION, + MetadataLambda.Distribution.class)) + .add( + s(RelMdLowerBoundCost.class, BuiltInMethod.LOWER_BOUND_COST, + MetadataLambda.LowerBoundCost.class)) + .add( + s(RelMdMemory.class, BuiltInMethod.MEMORY, + MetadataLambda.Memory.class)) + .add( + s(RelMdDistinctRowCount.class, BuiltInMethod.DISTINCT_ROW_COUNT, + MetadataLambda.DistinctRowCount.class)) + .add( + s(RelMdSelectivity.class, BuiltInMethod.SELECTIVITY, + MetadataLambda.Selectivity.class)) + .add( + s(RelMdExplainVisibility.class, BuiltInMethod.EXPLAIN_VISIBILITY, + MetadataLambda.VisibleInExplain.class)) + .add( + s(RelMdPredicates.class, BuiltInMethod.PREDICATES, + MetadataLambda.PulledUpPredicates.class)) + .add( + s(RelMdAllPredicates.class, BuiltInMethod.ALL_PREDICATES, + MetadataLambda.AllPredicates.class)) + .add( + s(RelMdCollation.class, BuiltInMethod.COLLATIONS, + MetadataLambda.Collations.class)) + .add( + s(RelMdPercentageOriginalRows.class, BuiltInMethod.CUMULATIVE_COST, + MetadataLambda.CumulativeCost.class)) + .add( + s(RelMdPercentageOriginalRows.class, BuiltInMethod.NON_CUMULATIVE_COST, + MetadataLambda.NonCumulativeCost.class)) + .build(); + + // Maintains a list of lambdas associated with each RelNode + MetadataInterface pair. + private final Table, Class, List> items; + + // Maintains an ordered list of relnode interfaces for a particular relnode. This is done so we + // don't have to do the enumeration for each separate MetadataLambda. + private final LoadingCache, List>> + classImplementations = CacheBuilder.newBuilder().build( + new CacheLoader, List>>() { + @Override public List> load(final Class key) + throws Exception { + return getImplements((Class) key); + } + }); + + public ReflectionToLambdaProvider(Source... sources) { + this(Arrays.asList(sources)); + } + + public ReflectionToLambdaProvider() { + this(DEFAULT_SOURCES); + } + + public ReflectionToLambdaProvider(Iterable sources) { + + // build a list of lambdas for a given class. + HashBasedTable, Class, List> table = HashBasedTable.create(); + + for (Source source : sources) { + Map, Object> lambdas = findMethodsAndCreateLambdas(source); + for (Map.Entry, Object> e : lambdas.entrySet()) { + List objects = table.get(e.getKey(), source.lambdaClass); + if (objects == null) { + objects = new ArrayList<>(); + table.put(e.getKey(), source.lambdaClass, objects); + } + objects.add(e.getValue()); + } + } + + this.items = table; + } + + /** + * For a given source, generate a map of specific RelNode to MetadataLambda mappings. + */ + private Map, Object> findMethodsAndCreateLambdas(Source source) { + Class clazz = source.singleton.getClass(); + String name = source.sourceMethod; + Class arg = source.lambdaClass; + try { + final Object singleton = source.singleton; + + List methods = Arrays.stream(clazz.getMethods()) + .filter(f -> f.getName().equals(name)) + .collect(Collectors.toList()); + Map, Object> output = new HashMap<>(); + for (Method reflectionMethod : methods) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType callSiteType = MethodType.methodType(arg, clazz); + MethodType functionalMethod; + MethodType delegateMethod; + + // generate methods based on number of arguments. + if (MetadataLambda.Arg0Lambda.class.isAssignableFrom(arg)) { + functionalMethod = MethodType.methodType(Object.class, RelNode.class, + reflectionMethod.getParameterTypes()[1]); + delegateMethod = MethodType.methodType(reflectionMethod.getReturnType(), + reflectionMethod.getParameterTypes()[0], RelMetadataQuery.class); + } else if (MetadataLambda.Arg1Lambda.class.isAssignableFrom(arg)) { + functionalMethod = MethodType.methodType(Object.class, RelNode.class, + reflectionMethod.getParameterTypes()[1], Object.class); + delegateMethod = MethodType.methodType(reflectionMethod.getReturnType(), + reflectionMethod.getParameterTypes()[0], RelMetadataQuery.class, + reflectionMethod.getParameterTypes()[2]); + if (reflectionMethod.getParameterTypes()[2].isPrimitive()) { + delegateMethod = delegateMethod.changeParameterType(2, + Primitives.wrap(reflectionMethod.getParameterTypes()[2])); + } + } else if (MetadataLambda.Arg2Lambda.class.isAssignableFrom(arg)) { + functionalMethod = MethodType.methodType(Object.class, RelNode.class, + reflectionMethod.getParameterTypes()[1], Object.class, Object.class); + delegateMethod = MethodType.methodType(reflectionMethod.getReturnType(), + reflectionMethod.getParameterTypes()[0], RelMetadataQuery.class, + reflectionMethod.getParameterTypes()[2], reflectionMethod.getParameterTypes()[3]); + if (reflectionMethod.getParameterTypes()[2].isPrimitive()) { + delegateMethod = delegateMethod.changeParameterType(2, + Primitives.wrap(reflectionMethod.getParameterTypes()[2])); + } + if (reflectionMethod.getParameterTypes()[3].isPrimitive()) { + delegateMethod = delegateMethod.changeParameterType(3, + Primitives.wrap(reflectionMethod.getParameterTypes()[3])); + } + } else { + throw new IllegalStateException(); + } + MethodHandle delegate = lookup.unreflect(reflectionMethod); + CallSite callSite = LambdaMetafactory.metafactory(lookup, "call", + callSiteType, functionalMethod, delegate, delegateMethod); + Object val = callSite.getTarget().bindTo(singleton).invoke(); + output.put(reflectionMethod.getParameterTypes()[0], val); + } + if (output.isEmpty()) { + throw new UnsupportedOperationException( + String.format(Locale.ROOT, "Provided source has no methods found. Method Name: %s, " + + "Singleton Type: %s, Lambda Class %s.", source.singleton.getClass().getName(), + source.sourceMethod, source.lambdaClass.getSimpleName())); + } + return output; + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + /** Describes a source of metadata methods. **/ + public static class Source { + private final Object singleton; + private final String sourceMethod; + private final Class lambdaClass; + + private Source( + final Class privateSingletonClass, + final String sourceMethod, + final Class lambdaClass) { + this(privateSingleton(privateSingletonClass), sourceMethod, lambdaClass); + } + + private Source(final Object singleton, final String sourceMethod, final Class lambdaClass) { + this.singleton = singleton; + this.sourceMethod = sourceMethod; + this.lambdaClass = lambdaClass; + } + + private static Object privateSingleton(Class clazz) { + try { + final Constructor[] constructors = clazz.getDeclaredConstructors(); + Constructor noArg = Arrays.stream(constructors) + .filter(c -> c.getParameterTypes().length == 0).findFirst().get(); + noArg.setAccessible(true); + return noArg.newInstance(); + } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { + throw new RuntimeException(e); + } + } + + public static Source of(Class sourceClass, String sourceMethod, Class lambdaClass) { + return new Source(sourceClass, sourceMethod, lambdaClass); + } + + public static Source of(Object instance, String sourceMethod, Class lambdaClass) { + return new Source(instance, sourceMethod, lambdaClass); + } + } + + @Override public List get( + Class relnodeClass, + Class handlerClass) throws ExecutionException { + List> classes = classImplementations.get(relnodeClass); + ImmutableList.Builder handlers = ImmutableList.builder(); + for (Class clazz : classes) { + List partialHandlers = items.get(clazz, handlerClass); + if (partialHandlers == null) { + continue; + } + + for (Object o : partialHandlers) { + handlers.add((T) o); + } + } + return handlers.build(); + } + + private static Source s(Class sourceClass, BuiltInMethod method, Class lambdaClass) { + return Source.of(sourceClass, method.getMethodName(), lambdaClass); + } + + /** + * Generate a list of interfaces/classes that this node implements, from nearest to furthest. + */ + private static List> getImplements(Class base) { + ImmutableList.Builder> builder = ImmutableList.builder(); + addImplements(base, builder); + return builder.build(); + } + + private static void addImplements( + Class base, + ImmutableList.Builder> builder) { + if (base == null || !RelNode.class.isAssignableFrom(base)) { + return; + } + builder.add((Class) base); + Arrays.stream(base.getInterfaces()).forEach(c -> addImplements(c, builder)); + addImplements(base.getSuperclass(), builder); + } + +} diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/lambda/package-info.java b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/package-info.java new file mode 100644 index 000000000000..6b95542d2b40 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/metadata/lambda/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Defines an implementation of RelMetadataQuery that is based on Lambdas. + * + * @see org.apache.calcite.rel.metadata.lambda.LambdaMetadataSupplier + */ +package org.apache.calcite.rel.metadata.lambda; diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java index 251555b39980..9bc8e442dc25 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -1826,29 +1826,31 @@ private void checkResultSetMetaData(Connection connection, String sql) + " \"time_by_day\".\"the_year\",\n" + " \"product_class\".\"product_family\"") .explainContains("" - + "EnumerableAggregateRel(group=[{0, 1, 2}], m0=[COUNT($3)])\n" - + " EnumerableCalcRel(expr#0..61=[{inputs}], c0=[$t19], c1=[$t4], c2=[$t46], product_id=[$t34])\n" - + " EnumerableJoinRel(condition=[=($35, $0)], joinType=[inner])\n" - + " EnumerableCalcRel(expr#0..9=[{inputs}], expr#10=[CAST($t4):INTEGER], expr#11=[1997], expr#12=[=($t10, $t11)], proj#0..9=[{exprs}], $condition=[$t12])\n" - + " EnumerableTableScan(table=[[foodmart2, time_by_day]])\n" - + " EnumerableCalcRel(expr#0..51=[{inputs}], proj#0..23=[{exprs}], product_id=[$t44], time_id=[$t45], customer_id=[$t46], promotion_id=[$t47], store_id0=[$t48], store_sales=[$t49], store_cost=[$t50], unit_sales=[$t51], product_class_id=[$t24], product_subcategory=[$t25], product_category=[$t26], product_department=[$t27], product_family=[$t28], product_class_id0=[$t29], product_id0=[$t30], brand_name=[$t31], product_name=[$t32], SKU=[$t33], SRP=[$t34], gross_weight=[$t35], net_weight=[$t36], recyclable_package=[$t37], low_fat=[$t38], units_per_case=[$t39], cases_per_pallet=[$t40], shelf_width=[$t41], shelf_height=[$t42], shelf_depth=[$t43])\n" - + " EnumerableJoinRel(condition=[=($48, $0)], joinType=[inner])\n" - + " EnumerableCalcRel(expr#0..23=[{inputs}], expr#24=['USA'], expr#25=[=($t9, $t24)], proj#0..23=[{exprs}], $condition=[$t25])\n" - + " EnumerableTableScan(table=[[foodmart2, store]])\n" - + " EnumerableCalcRel(expr#0..27=[{inputs}], proj#0..4=[{exprs}], product_class_id0=[$t13], product_id=[$t14], brand_name=[$t15], product_name=[$t16], SKU=[$t17], SRP=[$t18], gross_weight=[$t19], net_weight=[$t20], recyclable_package=[$t21], low_fat=[$t22], units_per_case=[$t23], cases_per_pallet=[$t24], shelf_width=[$t25], shelf_height=[$t26], shelf_depth=[$t27], product_id0=[$t5], time_id=[$t6], customer_id=[$t7], promotion_id=[$t8], store_id=[$t9], store_sales=[$t10], store_cost=[$t11], unit_sales=[$t12])\n" - + " EnumerableJoinRel(condition=[=($13, $0)], joinType=[inner])\n" - + " EnumerableTableScan(table=[[foodmart2, product_class]])\n" - + " EnumerableJoinRel(condition=[=($0, $9)], joinType=[inner])\n" - + " EnumerableTableScan(table=[[foodmart2, sales_fact_1997]])\n" - + " EnumerableTableScan(table=[[foodmart2, product]])\n" - + "\n" - + "]>") - .returns("+-------+---------------------+-----+------+------------+\n" - + "| c0 | c1 | c2 | c3 | c4 |\n" - + "+-------+---------------------+-----+------+------------+\n" - + "| Drink | Alcoholic Beverages | USA | WA | Bellingham |\n" - + "| Drink | Dairy | USA | WA | Bellingham |\n" - + "+-------+---------------------+-----+------+------------+"); + + "EnumerableAggregate(group=[{1, 6, 10}], m0=[COUNT()])\n" + + " EnumerableMergeJoin(condition=[=($2, $8)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$2], dir0=[ASC])\n" + + " EnumerableMergeJoin(condition=[=($3, $5)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$3], dir0=[ASC])\n" + + " EnumerableHashJoin(condition=[=($0, $4)], joinType=[inner])\n" + + " EnumerableCalc(expr#0..23=[{inputs}], expr#24=['USA':VARCHAR(30)], " + + "expr#25=[=($t9, $t24)], store_id=[$t0], store_country=[$t9], $condition=[$t25])\n" + + " EnumerableTableScan(table=[[foodmart2, store]])\n" + + " EnumerableCalc(expr#0..7=[{inputs}], proj#0..1=[{exprs}], " + + "store_id=[$t4])\n" + + " EnumerableTableScan(table=[[foodmart2, sales_fact_1997]])\n" + + " EnumerableCalc(expr#0..9=[{inputs}], expr#10=[CAST($t4):INTEGER], " + + "expr#11=[1997], expr#12=[=($t10, $t11)], time_id=[$t0], the_year=[$t4], " + + "$condition=[$t12])\n" + + " EnumerableTableScan(table=[[foodmart2, time_by_day]])\n" + + " EnumerableHashJoin(condition=[=($0, $2)], joinType=[inner])\n" + + " EnumerableCalc(expr#0..14=[{inputs}], proj#0..1=[{exprs}])\n" + + " EnumerableTableScan(table=[[foodmart2, product]])\n" + + " EnumerableCalc(expr#0..4=[{inputs}], product_class_id=[$t0], " + + "product_family=[$t4])\n" + + " EnumerableTableScan(table=[[foodmart2, product_class]])") + .returns("c0=USA; c1=1997; c2=Non-Consumable; m0=16414\n" + + "c0=USA; c1=1997; c2=Drink; m0=7978\n" + + "c0=USA; c1=1997; c2=Food; m0=62445\n"); } /** Tests a simple (primary key to primary key) N-way join, with arbitrary diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataJaninoTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataJaninoTest.java new file mode 100644 index 000000000000..e111e3de8197 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/RelMetadataJaninoTest.java @@ -0,0 +1,345 @@ +/* + * 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.linq4j.tree.Types; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelRoot; +import org.apache.calcite.rel.core.Aggregate; +import org.apache.calcite.rel.logical.LogicalAggregate; +import org.apache.calcite.rel.logical.LogicalFilter; +import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider; +import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; +import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider; +import org.apache.calcite.rel.metadata.Metadata; +import org.apache.calcite.rel.metadata.MetadataDef; +import org.apache.calcite.rel.metadata.MetadataHandler; +import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider; +import org.apache.calcite.rel.metadata.RelMetadataProvider; +import org.apache.calcite.rel.metadata.RelMetadataQuery; + +import com.google.common.collect.ImmutableList; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Unit test for {@link DefaultRelMetadataProvider}. + */ +public class RelMetadataJaninoTest extends RelMetadataTestBase { + @Override Supplier getSupplier() { + return RelMetadataQuery::instance; + } + + @Test void testBrokenCustomProviderWithMetadataFactory() { + final List buf = new ArrayList<>(); + ColTypeImpl.THREAD_LIST.set(buf); + + final String sql = "select deptno, count(*) from emp where deptno > 10 " + + "group by deptno having count(*) = 0"; + final RelRoot root = tester + .withClusterFactory(cluster -> { + cluster.setMetadataProvider( + ChainedRelMetadataProvider.of( + ImmutableList.of(BrokenColTypeImpl.SOURCE, + cluster.getMetadataProvider()))); + return cluster; + }) + .convertSqlToRel(sql); + + final RelNode rel = root.rel; + assertThat(rel, instanceOf(LogicalFilter.class)); + final MyRelMetadataQuery mq = new MyRelMetadataQuery(); + + try { + assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); + fail("expected error"); + } catch (IllegalArgumentException e) { + final String value = "No handler for method [public abstract " + + "java.lang.String org.apache.calcite.test.RelMetadataJaninoTest$ColType$Handler.getColType(" + + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)] " + + "applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter]; " + + "we recommend you create a catch-all (RelNode) handler"; + assertThat(e.getMessage(), is(value)); + } + } + + @Test void testBrokenCustomProviderWithMetadataQuery() { + final List buf = new ArrayList<>(); + ColTypeImpl.THREAD_LIST.set(buf); + + final String sql = "select deptno, count(*) from emp where deptno > 10 " + + "group by deptno having count(*) = 0"; + final RelRoot root = tester + .withClusterFactory(cluster -> { + cluster.setMetadataProvider( + ChainedRelMetadataProvider.of( + ImmutableList.of(BrokenColTypeImpl.SOURCE, + cluster.getMetadataProvider()))); + cluster.setMetadataQuerySupplier(MyRelMetadataQuery::new); + return cluster; + }) + .convertSqlToRel(sql); + + final RelNode rel = root.rel; + assertThat(rel, instanceOf(LogicalFilter.class)); + assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); + final MyRelMetadataQuery mq = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); + + try { + assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); + fail("expected error"); + } catch (IllegalArgumentException e) { + final String value = "No handler for method [public abstract java.lang.String " + + "org.apache.calcite.test.RelMetadataJaninoTest$ColType$Handler.getColType(" + + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)]" + + " applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter];" + + " we recommend you create a catch-all (RelNode) handler"; + assertThat(e.getMessage(), is(value)); + } + } + + @Deprecated // to be removed before 2.0 + public String colType(RelMetadataQuery mq, RelNode rel, int column) { + return rel.metadata(ColType.class, mq).getColType(column); + } + + public String colType(MyRelMetadataQuery myRelMetadataQuery, RelNode rel, int column) { + return myRelMetadataQuery.colType(rel, column); + } + + @Deprecated // to be removed before 2.0 + @Test void testCustomProviderWithRelMetadataFactory() { + final List buf = new ArrayList<>(); + ColTypeImpl.THREAD_LIST.set(buf); + + final String sql = "select deptno, count(*) from emp where deptno > 10 " + + "group by deptno having count(*) = 0"; + final RelRoot root = tester + .withClusterFactory(cluster -> { + // Create a custom provider that includes ColType. + // Include the same provider twice just to be devious. + final ImmutableList list = + ImmutableList.of(ColTypeImpl.SOURCE, ColTypeImpl.SOURCE, + cluster.getMetadataProvider()); + cluster.setMetadataProvider( + ChainedRelMetadataProvider.of(list)); + return cluster; + }) + .convertSqlToRel(sql); + final RelNode rel = root.rel; + + // Top node is a filter. Its metadata uses getColType(RelNode, int). + assertThat(rel, instanceOf(LogicalFilter.class)); + final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); + assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); + assertThat(colType(mq, rel, 1), equalTo("EXPR$1-rel")); + + // Next node is an aggregate. Its metadata uses + // getColType(LogicalAggregate, int). + final RelNode input = rel.getInput(0); + assertThat(input, instanceOf(LogicalAggregate.class)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + + // There is no caching. Another request causes another call to the provider. + assertThat(buf.toString(), equalTo("[DEPTNO-rel, EXPR$1-rel, DEPTNO-agg]")); + assertThat(buf.size(), equalTo(3)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(4)); + + // Now add a cache. Only the first request for each piece of metadata + // generates a new call to the provider. + final RelOptPlanner planner = rel.getCluster().getPlanner(); + rel.getCluster().setMetadataProvider( + new org.apache.calcite.rel.metadata.CachingRelMetadataProvider( + rel.getCluster().getMetadataProvider(), planner)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(5)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(5)); + assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); + assertThat(buf.size(), equalTo(6)); + assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); + assertThat(buf.size(), equalTo(6)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(6)); + + // With a different timestamp, a metadata item is re-computed on first call. + long timestamp = planner.getRelMetadataTimestamp(rel); + assertThat(timestamp, equalTo(0L)); + ((MockRelOptPlanner) planner).setRelMetadataTimestamp(timestamp + 1); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(7)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(7)); + } + + @Test void testCustomProviderWithRelMetadataQuery() { + final List buf = new ArrayList<>(); + ColTypeImpl.THREAD_LIST.set(buf); + + final String sql = "select deptno, count(*) from emp where deptno > 10 " + + "group by deptno having count(*) = 0"; + final RelRoot root = tester + .withClusterFactory(cluster -> { + // Create a custom provider that includes ColType. + // Include the same provider twice just to be devious. + final ImmutableList list = + ImmutableList.of(ColTypeImpl.SOURCE, ColTypeImpl.SOURCE, + cluster.getMetadataProvider()); + cluster.setMetadataProvider( + ChainedRelMetadataProvider.of(list)); + cluster.setMetadataQuerySupplier(MyRelMetadataQuery::new); + return cluster; + }) + .convertSqlToRel(sql); + final RelNode rel = root.rel; + + // Top node is a filter. Its metadata uses getColType(RelNode, int). + assertThat(rel, instanceOf(LogicalFilter.class)); + assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); + final MyRelMetadataQuery mq = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); + assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); + assertThat(colType(mq, rel, 1), equalTo("EXPR$1-rel")); + + // Next node is an aggregate. Its metadata uses + // getColType(LogicalAggregate, int). + final RelNode input = rel.getInput(0); + assertThat(input, instanceOf(LogicalAggregate.class)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + + // The metadata query is caching, only the first request for each piece of metadata + // generates a new call to the provider. + assertThat(buf.toString(), equalTo("[DEPTNO-rel, EXPR$1-rel, DEPTNO-agg]")); + assertThat(buf.size(), equalTo(3)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(3)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(3)); + assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); + assertThat(buf.size(), equalTo(4)); + assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); + assertThat(buf.size(), equalTo(4)); + assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(4)); + + // Invalidate the metadata query triggers clearing of all the metadata. + rel.getCluster().invalidateMetadataQuery(); + assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); + final MyRelMetadataQuery mq1 = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); + assertThat(colType(mq1, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(5)); + assertThat(colType(mq1, input, 0), equalTo("DEPTNO-agg")); + assertThat(buf.size(), equalTo(5)); + // Resets the RelMetadataQuery to default. + rel.getCluster().setMetadataQuerySupplier(RelMetadataQuery::instance); + } + + /** Custom metadata interface. */ + public interface ColType extends Metadata { + Method METHOD = Types.lookupMethod(ColType.class, "getColType", int.class); + + MetadataDef DEF = + MetadataDef.of(ColType.class, ColType.Handler.class, METHOD); + + String getColType(int column); + + /** Handler API. */ + interface Handler extends MetadataHandler { + String getColType(RelNode r, RelMetadataQuery mq, int column); + } + } + + /** A provider for {@link RelMetadataTestBase.ColType} via + * reflection. */ + public abstract static class PartialColTypeImpl + implements MetadataHandler { + static final ThreadLocal> THREAD_LIST = new ThreadLocal<>(); + + @Deprecated + public MetadataDef getDef() { + return ColType.DEF; + } + + /** Implementation of {@link ColType#getColType(int)} for + * {@link org.apache.calcite.rel.logical.LogicalAggregate}, called via + * reflection. */ + @SuppressWarnings("UnusedDeclaration") + public String getColType(Aggregate rel, RelMetadataQuery mq, int column) { + final String name = + rel.getRowType().getFieldList().get(column).getName() + "-agg"; + THREAD_LIST.get().add(name); + return name; + } + } + + /** A provider for {@link RelMetadataTestBase.ColType} via + * reflection. */ + public static class ColTypeImpl extends PartialColTypeImpl { + public static final RelMetadataProvider SOURCE = + ReflectiveRelMetadataProvider.reflectiveSource(new ColTypeImpl(), ColType.Handler.class); + + /** Implementation of {@link ColType#getColType(int)} for + * {@link RelNode}, called via reflection. */ + @SuppressWarnings("UnusedDeclaration") + public String getColType(RelNode rel, RelMetadataQuery mq, int column) { + final String name = + rel.getRowType().getFieldList().get(column).getName() + "-rel"; + THREAD_LIST.get().add(name); + return name; + } + } + + /** Implementation of {@link ColType} that has no fall-back for {@link RelNode}. */ + public static class BrokenColTypeImpl extends PartialColTypeImpl { + public static final RelMetadataProvider SOURCE = + ReflectiveRelMetadataProvider.reflectiveSource( + new BrokenColTypeImpl(), ColType.Handler.class); + } + + /** Extension to {@link RelMetadataQuery} to support {@link ColType}. + * + *

Illustrates how you would package up a user-defined metadata type. */ + private static class MyRelMetadataQuery extends RelMetadataQuery { + private ColType.Handler colTypeHandler; + + MyRelMetadataQuery() { + colTypeHandler = initialHandler(ColType.Handler.class); + } + + public String colType(RelNode rel, int column) { + for (;;) { + try { + return colTypeHandler.getColType(rel, this, column); + } catch (JaninoRelMetadataProvider.NoHandler e) { + colTypeHandler = revise(ColType.Handler.class); + } + } + } + } +} diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataLambdaTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataLambdaTest.java new file mode 100644 index 000000000000..ac43d2d41882 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/RelMetadataLambdaTest.java @@ -0,0 +1,31 @@ +/* + * 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.metadata.RelMetadataQuery; +import org.apache.calcite.rel.metadata.lambda.LambdaMetadataSupplier; + +import java.util.function.Supplier; + +/** + * Unit test for {@link LambdaMetadataSupplier}. + */ +public class RelMetadataLambdaTest extends RelMetadataTestBase { + @Override Supplier getSupplier() { + return LambdaMetadataSupplier.instance(); + } +} diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTestBase.java similarity index 90% rename from core/src/test/java/org/apache/calcite/test/RelMetadataTest.java rename to core/src/test/java/org/apache/calcite/test/RelMetadataTestBase.java index d83b48ced736..2e6af72963a6 100644 --- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTestBase.java @@ -18,7 +18,6 @@ import org.apache.calcite.adapter.enumerable.EnumerableMergeJoin; import org.apache.calcite.config.CalciteSystemProperty; -import org.apache.calcite.linq4j.tree.Types; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.plan.RelOptPlanner; @@ -66,13 +65,10 @@ import org.apache.calcite.rel.logical.LogicalUnion; import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.rel.metadata.BuiltInMetadata; -import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider; -import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider; import org.apache.calcite.rel.metadata.Metadata; import org.apache.calcite.rel.metadata.MetadataDef; import org.apache.calcite.rel.metadata.MetadataHandler; -import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider; import org.apache.calcite.rel.metadata.RelColumnOrigin; import org.apache.calcite.rel.metadata.RelMdCollation; import org.apache.calcite.rel.metadata.RelMdColumnUniqueness; @@ -141,6 +137,7 @@ import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; +import java.util.function.Supplier; import static org.apache.calcite.test.Matchers.within; @@ -158,17 +155,16 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; /** - * Unit test for {@link DefaultRelMetadataProvider}. See + * Base test for testing RelMetadataQuery providers. See * {@link SqlToRelTestBase} class comments for details on the schema used. Note * that no optimizer rules are fired on the translation of the SQL into * relational algebra (e.g. join conditions in the WHERE clause will look like * filters), so it's necessary to phrase the SQL carefully. */ -public class RelMetadataTest extends SqlToRelTestBase { +public abstract class RelMetadataTestBase extends SqlToRelTestBase { //~ Static fields/initializers --------------------------------------------- private static final double EPSILON = 1.0e-5; @@ -194,6 +190,8 @@ public class RelMetadataTest extends SqlToRelTestBase { * time. */ private static final ReentrantLock LOCK = new ReentrantLock(); + abstract Supplier getSupplier(); + //~ Methods ---------------------------------------------------------------- /** Creates a tester. */ @@ -209,9 +207,9 @@ private RelNode convertSql(String sql) { return convertSql(tester, sql); } - private static RelNode convertSql(Tester tester, String sql) { + private RelNode convertSql(Tester tester, String sql) { final RelRoot root = tester.convertSqlToRel(sql); - root.rel.getCluster().setMetadataProvider(DefaultRelMetadataProvider.INSTANCE); + root.rel.getCluster().setMetadataQuerySupplier(getSupplier()); return root.rel; } @@ -220,6 +218,13 @@ private RelNode convertSql(String sql, boolean typeCoercion) { return convertSql(tester, sql); } + private RelBuilder builder() { + final FrameworkConfig config = RelBuilderTest.config().build(); + final RelBuilder builder = RelBuilder.create(config); + builder.getCluster().setMetadataQuerySupplier(getSupplier()); + return builder; + } + private void checkPercentageOriginalRows(String sql, double expected) { checkPercentageOriginalRows(sql, expected, EPSILON); } @@ -1323,8 +1328,7 @@ private void checkColumnUniquenessForFilterWithConstantColumns(String sql) { } @Test void testColumnUniquenessForExchangeWithConstantColumns() { - final FrameworkConfig config = RelBuilderTest.config().build(); - final RelBuilder builder = RelBuilder.create(config); + final RelBuilder builder = builder(); RelNode exchange = builder.scan("EMP") .project(builder.field("DEPTNO"), builder.field("SAL")) .distinct() @@ -1336,8 +1340,7 @@ private void checkColumnUniquenessForFilterWithConstantColumns(String sql) { } @Test void testColumnUniquenessForCorrelateWithConstantColumns() { - final FrameworkConfig config = RelBuilderTest.config().build(); - final RelBuilder builder = RelBuilder.create(config); + final RelBuilder builder = builder(); RelNode rel0 = builder.scan("EMP") .project(builder.field("DEPTNO"), builder.field("SAL")) .distinct() @@ -1527,210 +1530,6 @@ private RelNode convertProjectAsCalc(String s) { .assertRowsUnique(is(true), "set query is always unique"); } - @Test void testBrokenCustomProviderWithMetadataFactory() { - final List buf = new ArrayList<>(); - ColTypeImpl.THREAD_LIST.set(buf); - - final String sql = "select deptno, count(*) from emp where deptno > 10 " - + "group by deptno having count(*) = 0"; - final RelRoot root = tester - .withClusterFactory(cluster -> { - cluster.setMetadataProvider( - ChainedRelMetadataProvider.of( - ImmutableList.of(BrokenColTypeImpl.SOURCE, - cluster.getMetadataProvider()))); - return cluster; - }) - .convertSqlToRel(sql); - - final RelNode rel = root.rel; - assertThat(rel, instanceOf(LogicalFilter.class)); - final MyRelMetadataQuery mq = new MyRelMetadataQuery(); - - try { - assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); - fail("expected error"); - } catch (IllegalArgumentException e) { - final String value = "No handler for method [public abstract " - + "java.lang.String org.apache.calcite.test.RelMetadataTest$ColType$Handler.getColType(" - + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)] " - + "applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter]; " - + "we recommend you create a catch-all (RelNode) handler"; - assertThat(e.getMessage(), is(value)); - } - } - - @Test void testBrokenCustomProviderWithMetadataQuery() { - final List buf = new ArrayList<>(); - ColTypeImpl.THREAD_LIST.set(buf); - - final String sql = "select deptno, count(*) from emp where deptno > 10 " - + "group by deptno having count(*) = 0"; - final RelRoot root = tester - .withClusterFactory(cluster -> { - cluster.setMetadataProvider( - ChainedRelMetadataProvider.of( - ImmutableList.of(BrokenColTypeImpl.SOURCE, - cluster.getMetadataProvider()))); - cluster.setMetadataQuerySupplier(MyRelMetadataQuery::new); - return cluster; - }) - .convertSqlToRel(sql); - - final RelNode rel = root.rel; - assertThat(rel, instanceOf(LogicalFilter.class)); - assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); - final MyRelMetadataQuery mq = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); - - try { - assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); - fail("expected error"); - } catch (IllegalArgumentException e) { - final String value = "No handler for method [public abstract java.lang.String " - + "org.apache.calcite.test.RelMetadataTest$ColType$Handler.getColType(" - + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)]" - + " applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter];" - + " we recommend you create a catch-all (RelNode) handler"; - assertThat(e.getMessage(), is(value)); - } - } - - @Deprecated // to be removed before 2.0 - public String colType(RelMetadataQuery mq, RelNode rel, int column) { - return rel.metadata(ColType.class, mq).getColType(column); - } - - public String colType(MyRelMetadataQuery myRelMetadataQuery, RelNode rel, int column) { - return myRelMetadataQuery.colType(rel, column); - } - - @Deprecated // to be removed before 2.0 - @Test void testCustomProviderWithRelMetadataFactory() { - final List buf = new ArrayList<>(); - ColTypeImpl.THREAD_LIST.set(buf); - - final String sql = "select deptno, count(*) from emp where deptno > 10 " - + "group by deptno having count(*) = 0"; - final RelRoot root = tester - .withClusterFactory(cluster -> { - // Create a custom provider that includes ColType. - // Include the same provider twice just to be devious. - final ImmutableList list = - ImmutableList.of(ColTypeImpl.SOURCE, ColTypeImpl.SOURCE, - cluster.getMetadataProvider()); - cluster.setMetadataProvider( - ChainedRelMetadataProvider.of(list)); - return cluster; - }) - .convertSqlToRel(sql); - final RelNode rel = root.rel; - - // Top node is a filter. Its metadata uses getColType(RelNode, int). - assertThat(rel, instanceOf(LogicalFilter.class)); - final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); - assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); - assertThat(colType(mq, rel, 1), equalTo("EXPR$1-rel")); - - // Next node is an aggregate. Its metadata uses - // getColType(LogicalAggregate, int). - final RelNode input = rel.getInput(0); - assertThat(input, instanceOf(LogicalAggregate.class)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - - // There is no caching. Another request causes another call to the provider. - assertThat(buf.toString(), equalTo("[DEPTNO-rel, EXPR$1-rel, DEPTNO-agg]")); - assertThat(buf.size(), equalTo(3)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(4)); - - // Now add a cache. Only the first request for each piece of metadata - // generates a new call to the provider. - final RelOptPlanner planner = rel.getCluster().getPlanner(); - rel.getCluster().setMetadataProvider( - new org.apache.calcite.rel.metadata.CachingRelMetadataProvider( - rel.getCluster().getMetadataProvider(), planner)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(5)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(5)); - assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); - assertThat(buf.size(), equalTo(6)); - assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); - assertThat(buf.size(), equalTo(6)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(6)); - - // With a different timestamp, a metadata item is re-computed on first call. - long timestamp = planner.getRelMetadataTimestamp(rel); - assertThat(timestamp, equalTo(0L)); - ((MockRelOptPlanner) planner).setRelMetadataTimestamp(timestamp + 1); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(7)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(7)); - } - - @Test void testCustomProviderWithRelMetadataQuery() { - final List buf = new ArrayList<>(); - ColTypeImpl.THREAD_LIST.set(buf); - - final String sql = "select deptno, count(*) from emp where deptno > 10 " - + "group by deptno having count(*) = 0"; - final RelRoot root = tester - .withClusterFactory(cluster -> { - // Create a custom provider that includes ColType. - // Include the same provider twice just to be devious. - final ImmutableList list = - ImmutableList.of(ColTypeImpl.SOURCE, ColTypeImpl.SOURCE, - cluster.getMetadataProvider()); - cluster.setMetadataProvider( - ChainedRelMetadataProvider.of(list)); - cluster.setMetadataQuerySupplier(MyRelMetadataQuery::new); - return cluster; - }) - .convertSqlToRel(sql); - final RelNode rel = root.rel; - - // Top node is a filter. Its metadata uses getColType(RelNode, int). - assertThat(rel, instanceOf(LogicalFilter.class)); - assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); - final MyRelMetadataQuery mq = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); - assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel")); - assertThat(colType(mq, rel, 1), equalTo("EXPR$1-rel")); - - // Next node is an aggregate. Its metadata uses - // getColType(LogicalAggregate, int). - final RelNode input = rel.getInput(0); - assertThat(input, instanceOf(LogicalAggregate.class)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - - // The metadata query is caching, only the first request for each piece of metadata - // generates a new call to the provider. - assertThat(buf.toString(), equalTo("[DEPTNO-rel, EXPR$1-rel, DEPTNO-agg]")); - assertThat(buf.size(), equalTo(3)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(3)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(3)); - assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); - assertThat(buf.size(), equalTo(4)); - assertThat(colType(mq, input, 1), equalTo("EXPR$1-agg")); - assertThat(buf.size(), equalTo(4)); - assertThat(colType(mq, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(4)); - - // Invalidate the metadata query triggers clearing of all the metadata. - rel.getCluster().invalidateMetadataQuery(); - assertThat(rel.getCluster().getMetadataQuery(), instanceOf(MyRelMetadataQuery.class)); - final MyRelMetadataQuery mq1 = (MyRelMetadataQuery) rel.getCluster().getMetadataQuery(); - assertThat(colType(mq1, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(5)); - assertThat(colType(mq1, input, 0), equalTo("DEPTNO-agg")); - assertThat(buf.size(), equalTo(5)); - // Resets the RelMetadataQuery to default. - rel.getCluster().setMetadataQuerySupplier(RelMetadataQuery::instance); - } - /** Unit test for * {@link org.apache.calcite.rel.metadata.RelMdCollation#project} * and other helper functions for deducing collations. */ @@ -2088,6 +1887,7 @@ private void checkAverageRowSize(RelOptCluster cluster, RelOptTable empTable, private void checkPredicates(RelOptCluster cluster, RelOptTable empTable, RelOptTable deptTable) { final RelBuilder relBuilder = RelBuilder.proto().create(cluster, null); + relBuilder.getCluster().setMetadataQuerySupplier(getSupplier()); final RelMetadataQuery mq = cluster.getMetadataQuery(); final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable, @@ -3359,8 +3159,7 @@ private void checkNodeTypeCount(String sql, Map, Intege /** Tests calling {@link RelMetadataQuery#getTableOrigin} for * an aggregate with no columns. Previously threw. */ @Test void testEmptyAggregateTableOrigin() { - final FrameworkConfig config = RelBuilderTest.config().build(); - final RelBuilder builder = RelBuilder.create(config); + final RelBuilder builder = builder(); RelMetadataQuery mq = builder.getCluster().getMetadataQuery(); RelNode agg = builder .scan("EMP") @@ -3371,8 +3170,7 @@ private void checkNodeTypeCount(String sql, Map, Intege } @Test void testGetPredicatesForJoin() throws Exception { - final FrameworkConfig config = RelBuilderTest.config().build(); - final RelBuilder builder = RelBuilder.create(config); + final RelBuilder builder = builder(); RelNode join = builder .scan("EMP") .scan("DEPT") @@ -3397,8 +3195,7 @@ private void checkNodeTypeCount(String sql, Map, Intege } @Test void testGetPredicatesForFilter() throws Exception { - final FrameworkConfig config = RelBuilderTest.config().build(); - final RelBuilder builder = RelBuilder.create(config); + final RelBuilder builder = builder(); RelNode filter = builder .scan("EMP") .filter(builder.call(NONDETERMINISTIC_OP)) @@ -3475,88 +3272,7 @@ public static Matcher> sortsAs(final String value) { //~ Inner classes and interfaces ------------------------------------------- - /** Custom metadata interface. */ - public interface ColType extends Metadata { - Method METHOD = Types.lookupMethod(ColType.class, "getColType", int.class); - - MetadataDef DEF = - MetadataDef.of(ColType.class, ColType.Handler.class, METHOD); - - String getColType(int column); - - /** Handler API. */ - interface Handler extends MetadataHandler { - String getColType(RelNode r, RelMetadataQuery mq, int column); - } - } - - /** A provider for {@link org.apache.calcite.test.RelMetadataTest.ColType} via - * reflection. */ - public abstract static class PartialColTypeImpl - implements MetadataHandler { - static final ThreadLocal> THREAD_LIST = new ThreadLocal<>(); - @Deprecated - public MetadataDef getDef() { - return ColType.DEF; - } - - /** Implementation of {@link ColType#getColType(int)} for - * {@link org.apache.calcite.rel.logical.LogicalAggregate}, called via - * reflection. */ - @SuppressWarnings("UnusedDeclaration") - public String getColType(Aggregate rel, RelMetadataQuery mq, int column) { - final String name = - rel.getRowType().getFieldList().get(column).getName() + "-agg"; - THREAD_LIST.get().add(name); - return name; - } - } - - /** A provider for {@link org.apache.calcite.test.RelMetadataTest.ColType} via - * reflection. */ - public static class ColTypeImpl extends PartialColTypeImpl { - public static final RelMetadataProvider SOURCE = - ReflectiveRelMetadataProvider.reflectiveSource(new ColTypeImpl(), ColType.Handler.class); - - /** Implementation of {@link ColType#getColType(int)} for - * {@link RelNode}, called via reflection. */ - @SuppressWarnings("UnusedDeclaration") - public String getColType(RelNode rel, RelMetadataQuery mq, int column) { - final String name = - rel.getRowType().getFieldList().get(column).getName() + "-rel"; - THREAD_LIST.get().add(name); - return name; - } - } - - /** Implementation of {@link ColType} that has no fall-back for {@link RelNode}. */ - public static class BrokenColTypeImpl extends PartialColTypeImpl { - public static final RelMetadataProvider SOURCE = - ReflectiveRelMetadataProvider.reflectiveSource( - new BrokenColTypeImpl(), ColType.Handler.class); - } - - /** Extension to {@link RelMetadataQuery} to support {@link ColType}. - * - *

Illustrates how you would package up a user-defined metadata type. */ - private static class MyRelMetadataQuery extends RelMetadataQuery { - private ColType.Handler colTypeHandler; - - MyRelMetadataQuery() { - colTypeHandler = initialHandler(ColType.Handler.class); - } - - public String colType(RelNode rel, int column) { - for (;;) { - try { - return colTypeHandler.getColType(rel, this, column); - } catch (JaninoRelMetadataProvider.NoHandler e) { - colTypeHandler = revise(ColType.Handler.class); - } - } - } - } /** * Dummy rel node used for testing. @@ -3595,7 +3311,7 @@ private static class CompositeKeysCatalogReader } /** Parameters for a test. */ - private static class Sql { + private class Sql { private final Tester tester; private final String sql; @@ -3614,7 +3330,7 @@ Sql assertCpuCost(Matcher matcher, String reason) { return this; } - private static RelOptCost computeRelSelfCost(RelNode rel) { + private RelOptCost computeRelSelfCost(RelNode rel) { final RelMetadataQuery mq = rel.getCluster().getMetadataQuery(); RelOptPlanner planner = new VolcanoPlanner(); return rel.computeSelfCost(planner, mq); @@ -3644,4 +3360,5 @@ Sql assertRowsUnique(boolean[] ignoreNulls, Matcher matcher, } } + } diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java index 9ace78b35146..2db003441d25 100644 --- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java +++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java @@ -97,7 +97,7 @@ import java.util.ArrayList; import java.util.List; -import static org.apache.calcite.test.RelMetadataTest.sortsAs; +import static org.apache.calcite.test.RelMetadataTestBase.sortsAs; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/gradle.properties b/gradle.properties index 5d0052739969..2d365ac8d326 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ com.google.protobuf.version=0.8.10 de.thetaphi.forbiddenapis.version=3.1 kotlin.version=1.5.31 net.ltgt.errorprone.version=1.3.0 -me.champeau.gradle.jmh.version=0.5.0 +me.champeau.gradle.jmh.version=0.5.3 org.jetbrains.gradle.plugin.idea-ext.version=0.5 org.nosphere.apache.rat.version=0.7.0 org.owasp.dependencycheck.version=6.1.6 diff --git a/ubenchmark/build.gradle.kts b/ubenchmark/build.gradle.kts index ffaa8cb05439..f314d2524de7 100644 --- a/ubenchmark/build.gradle.kts +++ b/ubenchmark/build.gradle.kts @@ -26,6 +26,8 @@ dependencies { jmhImplementation("org.codehaus.janino:commons-compiler") jmhImplementation("org.openjdk.jmh:jmh-core") jmhImplementation("org.openjdk.jmh:jmh-generator-annprocess") + jmhImplementation(project(":testkit")) + jmhImplementation("org.hsqldb:hsqldb") } // See https://github.com/melix/jmh-gradle-plugin diff --git a/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/MetadataBenchmark.java b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/MetadataBenchmark.java new file mode 100644 index 000000000000..0f310361fe9e --- /dev/null +++ b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/MetadataBenchmark.java @@ -0,0 +1,139 @@ +/* + * 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.benchmarks; + +import org.apache.calcite.jdbc.Driver; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider; +import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.metadata.lambda.LambdaMetadataSupplier; +import org.apache.calcite.runtime.Hook; +import org.apache.calcite.test.CalciteAssert; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * A benchmark to compare metadata retrieval time for a complex query. + * + * Compares Janino and Lambda-based metadata retrieval systems. + * + */ +@Fork(value = 1, jvmArgsPrepend = "-Xmx2048m") +@State(Scope.Benchmark) +@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS) +@Warmup(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS) +@Threads(1) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +public class MetadataBenchmark { + + @Setup + public void setup() throws SQLException { + DriverManager.registerDriver(new Driver()); + } + + private void test(final Supplier supplier) { + CalciteAssert.that() + .with(CalciteAssert.Config.FOODMART_CLONE) + .query("select \"store\".\"store_country\" as \"c0\",\n" + + " \"time_by_day\".\"the_year\" as \"c1\",\n" + + " \"product_class\".\"product_family\" as \"c2\",\n" + + " count(\"sales_fact_1997\".\"product_id\") as \"m0\"\n" + + "from \"store\" as \"store\",\n" + + " \"sales_fact_1997\" as \"sales_fact_1997\",\n" + + " \"time_by_day\" as \"time_by_day\",\n" + + " \"product_class\" as \"product_class\",\n" + + " \"product\" as \"product\"\n" + + "where \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\"\n" + + "and \"store\".\"store_country\" = 'USA'\n" + + "and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\"\n" + + "and \"time_by_day\".\"the_year\" = 1997\n" + + "and \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\"\n" + + "and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\"\n" + + "group by \"store\".\"store_country\",\n" + + " \"time_by_day\".\"the_year\",\n" + + " \"product_class\".\"product_family\"") + .withHook(Hook.CONVERTED, (Consumer) rel -> { + rel.getCluster().setMetadataQuerySupplier(supplier); + rel.getCluster().invalidateMetadataQuery(); + }) + .explainContains("" + + "EnumerableAggregate(group=[{1, 6, 10}], m0=[COUNT()])\n" + + " EnumerableMergeJoin(condition=[=($2, $8)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$2], dir0=[ASC])\n" + + " EnumerableMergeJoin(condition=[=($3, $5)], joinType=[inner])\n" + + " EnumerableSort(sort0=[$3], dir0=[ASC])\n" + + " EnumerableHashJoin(condition=[=($0, $4)], joinType=[inner])\n" + + " EnumerableCalc(expr#0..23=[{inputs}], expr#24=['USA':VARCHAR(30)], " + + "expr#25=[=($t9, $t24)], store_id=[$t0], store_country=[$t9], $condition=[$t25])\n" + + " EnumerableTableScan(table=[[foodmart2, store]])\n" + + " EnumerableCalc(expr#0..7=[{inputs}], proj#0..1=[{exprs}], " + + "store_id=[$t4])\n" + + " EnumerableTableScan(table=[[foodmart2, sales_fact_1997]])\n" + + " EnumerableCalc(expr#0..9=[{inputs}], expr#10=[CAST($t4):INTEGER], " + + "expr#11=[1997], expr#12=[=($t10, $t11)], time_id=[$t0], the_year=[$t4], " + + "$condition=[$t12])\n" + + " EnumerableTableScan(table=[[foodmart2, time_by_day]])\n" + + " EnumerableHashJoin(condition=[=($0, $2)], joinType=[inner])\n" + + " EnumerableCalc(expr#0..14=[{inputs}], proj#0..1=[{exprs}])\n" + + " EnumerableTableScan(table=[[foodmart2, product]])\n" + + " EnumerableCalc(expr#0..4=[{inputs}], product_class_id=[$t0], " + + "product_family=[$t4])\n" + + " EnumerableTableScan(table=[[foodmart2, product_class]])") + .returns("c0=USA; c1=1997; c2=Non-Consumable; m0=16414\n" + + "c0=USA; c1=1997; c2=Drink; m0=7978\n" + + "c0=USA; c1=1997; c2=Food; m0=62445\n"); + } + + @Benchmark + public void janino() { + test(RelMetadataQuery::instance); + } + + @Benchmark + public void janinoWithCompile() { + JaninoRelMetadataProvider.clearStaticCache(); + test(RelMetadataQuery::instance); + } + + @Benchmark + public void lambda() { + test(LambdaMetadataSupplier.instance()); + } + + @Benchmark + public void lambdaWithConstruction() { + test(new LambdaMetadataSupplier()); + } + +}