content: /*
   * 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.plan.RelOptCluster;
  import org.apache.calcite.plan.RelOptCost;
  import org.apache.calcite.plan.RelOptPlanner;
  import org.apache.calcite.plan.RelOptTable;
  import org.apache.calcite.plan.RelOptUtil;
  import org.apache.calcite.plan.volcano.VolcanoPlanner;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rel.core.Project;
  import org.apache.calcite.rel.logical.LogicalCalc;
  import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
  import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
  import org.apache.calcite.rel.metadata.MetadataHandlerProvider;
  import org.apache.calcite.rel.metadata.ProxyingMetadataHandlerProvider;
  import org.apache.calcite.rel.metadata.RelColumnOrigin;
  import org.apache.calcite.rel.metadata.RelMetadataProvider;
  import org.apache.calcite.rel.metadata.RelMetadataQuery;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.rex.RexProgram;
  import org.apache.calcite.runtime.SqlFunctions;
  import org.apache.calcite.sql.SqlExplainLevel;
  import org.apache.calcite.sql.test.SqlTestFactory;
  import org.apache.calcite.sql.test.SqlTester;
  import org.apache.calcite.sql2rel.SqlToRelConverter;
  import org.apache.calcite.tools.RelBuilder;
  import org.apache.calcite.util.ImmutableBitSet;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSortedSet;
  import com.google.common.collect.Iterables;
  import com.google.common.collect.Multimap;
  
  import org.hamcrest.Matcher;
  
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.function.Consumer;
  import java.util.function.Function;
  import java.util.function.Supplier;
  import java.util.function.UnaryOperator;
  
  import static com.google.common.base.Preconditions.checkArgument;
  
  import static org.hamcrest.CoreMatchers.equalTo;
  import static org.hamcrest.CoreMatchers.is;
  import static org.hamcrest.CoreMatchers.not;
  import static org.hamcrest.CoreMatchers.notNullValue;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.Matchers.hasSize;
  import static org.junit.jupiter.api.Assertions.assertNotNull;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  
  /**
   * Parameters for a Metadata test.
   */
  public class RelMetadataFixture {
    /** Default fixture.
     *
     * <p>Use this, or call the {@code withXxx} methods to make one with the
     * properties you need. Fixtures are immutable, so whatever your test does
     * to this fixture, it won't break other tests. */
    public static final RelMetadataFixture DEFAULT =
        new RelMetadataFixture(SqlToRelFixture.TESTER,
            SqlTestFactory.INSTANCE, MetadataConfig.JANINO, RelSupplier.NONE,
            false, r -> r)
            .withFactory(f ->
                f.withValidatorConfig(c -> c.withIdentifierExpansion(true))
                    .withSqlToRelConfig(c ->
                        c.withRelBuilderConfigTransform(b ->
                            b.withAggregateUnique(true)
                                .withPruneInputOfAggregate(false))));
  
    public final SqlTester tester;
    public final SqlTestFactory factory;
    public final MetadataConfig metadataConfig;
    public final RelSupplier relSupplier;
    public final boolean convertAsCalc;
    public final UnaryOperator<RelNode> relTransform;
  
    private RelMetadataFixture(SqlTester tester,
        SqlTestFactory factory, MetadataConfig metadataConfig,
        RelSupplier relSupplier,
        boolean convertAsCalc, UnaryOperator<RelNode> relTransform) {
      this.tester = tester;
      this.factory = factory;
      this.metadataConfig = metadataConfig;
      this.relSupplier = relSupplier;
      this.convertAsCalc = convertAsCalc;
      this.relTransform = relTransform;
    }
  
    //~ 'With' methods ---------------------------------------------------------
    // Each method returns a copy of this fixture, changing the value of one
    // property.
  
    /** Creates a copy of this fixture that uses a given SQL query. */
    public RelMetadataFixture withSql(String sql) {
      final RelSupplier relSupplier = RelSupplier.of(sql);
      if (relSupplier.equals(this.relSupplier)) {
        return this;
      }
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform);
    }
  
    public RelMetadataFixture withConfig(
            UnaryOperator<SqlToRelConverter.Config> transform) {
      return withFactory(f -> f.withSqlToRelConfig(transform));
    }
  
    public RelMetadataFixture withRelBuilderConfig(
            UnaryOperator<RelBuilder.Config> transform) {
      return withConfig(c -> c.addRelBuilderConfigTransform(transform));
    }
  
    /** Creates a copy of this fixture that uses a given function to create a
     * {@link RelNode}. */
    public RelMetadataFixture withRelFn(Function<RelBuilder, RelNode> relFn) {
      final RelSupplier relSupplier =
          RelSupplier.of(builder -> {
            metadataConfig.applyMetadata(builder.getCluster());
            return relFn.apply(builder);
          });
      if (relSupplier.equals(this.relSupplier)) {
        return this;
      }
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform);
    }
  
    public RelMetadataFixture withFactory(
        UnaryOperator<SqlTestFactory> transform) {
      final SqlTestFactory factory = transform.apply(this.factory);
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform);
    }
  
    public RelMetadataFixture withTester(UnaryOperator<SqlTester> transform) {
      final SqlTester tester = transform.apply(this.tester);
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform);
    }
  
    public RelMetadataFixture withMetadataConfig(MetadataConfig metadataConfig) {
      if (metadataConfig.equals(this.metadataConfig)) {
        return this;
      }
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform);
    }
  
    public RelMetadataFixture convertingProjectAsCalc() {
      if (convertAsCalc) {
        return this;
      }
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          true, relTransform);
    }
  
    public RelMetadataFixture withCatalogReaderFactory(
        SqlTestFactory.CatalogReaderFactory catalogReaderFactory) {
      return withFactory(t -> t.withCatalogReader(catalogReaderFactory));
    }
  
    public RelMetadataFixture withCluster(UnaryOperator<RelOptCluster> factory) {
      return withFactory(f -> f.withCluster(factory));
    }
  
    public RelMetadataFixture withRelTransform(UnaryOperator<RelNode> relTransform) {
      final UnaryOperator<RelNode> relTransform1 =
          this.relTransform.andThen(relTransform)::apply;
      return new RelMetadataFixture(tester, factory, metadataConfig, relSupplier,
          convertAsCalc, relTransform1);
    }
  
    //~ Helper methods ---------------------------------------------------------
    // Don't use them too much. Write an assertXxx method if possible.
  
    /** Only for use by RelSupplier. Must be package-private. */
    RelNode sqlToRel(String sql) {
      return tester.convertSqlToRel(factory, sql, false, false).rel;
    }
  
    /** Creates a {@link RelNode} from this fixture's supplier
     * (see {@link #withSql(String)} and {@link #withRelFn(Function)}). */
    public RelNode toRel() {
      final RelNode rel = relSupplier.apply2(this);
      metadataConfig.applyMetadata(rel.getCluster());
      if (convertAsCalc) {
        Project project = (Project) rel;
        checkArgument(project.getVariablesSet().isEmpty(),
            "Calc does not allow variables");
        RexProgram program =
            RexProgram.create(project.getInput().getRowType(),
                project.getProjects(),
                null,
                project.getRowType(),
                project.getCluster().getRexBuilder());
        return LogicalCalc.create(project.getInput(), program);
      }
      return relTransform.apply(rel);
    }
  
    //~ Methods that execute tests ---------------------------------------------
  
    /** Checks the CPU component of
     * {@link RelNode#computeSelfCost(RelOptPlanner, RelMetadataQuery)}. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertCpuCost(Matcher<Double> matcher,
        String reason) {
      RelNode rel = toRel();
      RelOptCost cost = computeRelSelfCost(rel);
      assertThat(reason + "\n"
              + "sql:" + relSupplier + "\n"
              + "plan:" + RelOptUtil.toString(rel, SqlExplainLevel.ALL_ATTRIBUTES),
          cost.getCpu(), matcher);
      return this;
    }
  
    private static RelOptCost computeRelSelfCost(RelNode rel) {
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      RelOptPlanner planner = new VolcanoPlanner();
      return rel.computeSelfCost(planner, mq);
    }
  
    /** Checks {@link RelMetadataQuery#areRowsUnique(RelNode)} for all
     * values of {@code ignoreNulls}. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertRowsUnique(Matcher<Boolean> matcher,
        String reason) {
      return assertRowsUnique(false, matcher, reason)
          .assertRowsUnique(true, matcher, reason);
    }
  
    /** Checks {@link RelMetadataQuery#areRowsUnique(RelNode)}. */
    public RelMetadataFixture assertRowsUnique(boolean ignoreNulls,
        Matcher<Boolean> matcher, String reason) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      Boolean rowsUnique = mq.areRowsUnique(rel, ignoreNulls);
      assertThat(reason + "\n"
              + "sql:" + relSupplier + "\n"
              + "plan:" + RelOptUtil.toString(rel, SqlExplainLevel.ALL_ATTRIBUTES),
          rowsUnique, matcher);
      return this;
    }
  
    /** Checks {@link RelMetadataQuery#getPercentageOriginalRows(RelNode)}. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertPercentageOriginalRows(Matcher<Double> matcher) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      Double result = mq.getPercentageOriginalRows(rel);
      assertNotNull(result);
      assertThat(result, matcher);
      return this;
    }
  
    private RelMetadataFixture checkColumnOrigin(
        Consumer<Set<RelColumnOrigin>> action) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final Set<RelColumnOrigin> columnOrigins = mq.getColumnOrigins(rel, 0);
      action.accept(columnOrigins);
      return this;
    }
  
    /** Checks that {@link RelMetadataQuery#getColumnOrigins(RelNode, int)}
     * for column 0 returns no origins. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertColumnOriginIsEmpty() {
      return checkColumnOrigin(result -> {
        assertNotNull(result);
        assertTrue(result.isEmpty());
      });
    }
  
    private static void checkColumnOrigin(
        RelColumnOrigin rco,
        String expectedTableName,
        String expectedColumnName,
        boolean expectedDerived) {
      RelOptTable actualTable = rco.getOriginTable();
      List<String> actualTableName = actualTable.getQualifiedName();
      assertThat(
          Iterables.getLast(actualTableName),
          equalTo(expectedTableName));
      assertThat(
          actualTable.getRowType()
              .getFieldList()
              .get(rco.getOriginColumnOrdinal())
              .getName(),
          equalTo(expectedColumnName));
      assertThat(rco.isDerived(), equalTo(expectedDerived));
    }
  
    /** Checks that {@link RelMetadataQuery#getColumnOrigins(RelNode, int)}
     * for column 0 returns one origin. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertColumnOriginSingle(String expectedTableName,
        String expectedColumnName, boolean expectedDerived) {
      return checkColumnOrigin(result -> {
        assertNotNull(result);
        assertThat(result, hasSize(1));
        RelColumnOrigin rco = result.iterator().next();
        checkColumnOrigin(rco, expectedTableName, expectedColumnName,
            expectedDerived);
      });
    }
  
    /** Checks that {@link RelMetadataQuery#getColumnOrigins(RelNode, int)}
     * for column 0 returns two origins. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertColumnOriginDouble(
        String expectedTableName1, String expectedColumnName1,
        String expectedTableName2, String expectedColumnName2,
        boolean expectedDerived) {
      assertThat("required so that the test mechanism works", expectedTableName1,
          not(is(expectedTableName2)));
      return checkColumnOrigin(result -> {
        assertNotNull(result);
        assertThat(result, hasSize(2));
        for (RelColumnOrigin rco : result) {
          RelOptTable actualTable = rco.getOriginTable();
          List<String> actualTableName = actualTable.getQualifiedName();
          String actualUnqualifiedName = Iterables.getLast(actualTableName);
          if (actualUnqualifiedName.equals(expectedTableName1)) {
            checkColumnOrigin(rco, expectedTableName1, expectedColumnName1,
                expectedDerived);
          } else {
            checkColumnOrigin(rco, expectedTableName2, expectedColumnName2,
                expectedDerived);
          }
        }
      });
    }
  
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertThatUniqueKeysAre(ImmutableBitSet... expectedUniqueKeys) {
      return assertThatUniqueKeysAre(false, expectedUniqueKeys);
    }
  
    /** Checks result of getting unique keys for SQL. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertThatUniqueKeysAre(boolean ignoreNulls,
        ImmutableBitSet... expectedUniqueKeys) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      Set<ImmutableBitSet> result = mq.getUniqueKeys(rel, ignoreNulls);
      assertThat(result, notNullValue());
      assertThat("unique keys, sql: " + relSupplier
              + ", rel: " + RelOptUtil.toString(rel),
          ImmutableSortedSet.copyOf(result),
          is(ImmutableSortedSet.copyOf(expectedUniqueKeys)));
      checkUniqueConsistent(rel, ignoreNulls);
      return this;
    }
  
    /**
     * Asserts that {@link RelMetadataQuery#getUniqueKeys(RelNode)}
     * and {@link RelMetadataQuery#areColumnsUnique(RelNode, ImmutableBitSet)}
     * return consistent results.
     */
    private static void checkUniqueConsistent(RelNode rel, boolean ignoreNulls) {
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final Set<ImmutableBitSet> uniqueKeys = mq.getUniqueKeys(rel, ignoreNulls);
      assertThat(uniqueKeys, notNullValue());
      for (ImmutableBitSet key : uniqueKeys) {
        Boolean result2 = mq.areColumnsUnique(rel, key, ignoreNulls);
        assertThat("areColumnsUnique. key: " + key
            + ", uniqueKeys: " + uniqueKeys
            + ", rel: " + RelOptUtil.toString(rel),
            SqlFunctions.isTrue(result2), is(isUnique(uniqueKeys, key)));
      }
    }
  
    /**
     * Returns whether {@code key} is unique, that is, whether it or a subset
     * is in {@code uniqueKeys}.
     */
    private static boolean isUnique(Set<ImmutableBitSet> uniqueKeys,
        ImmutableBitSet key) {
      for (ImmutableBitSet uniqueKey : uniqueKeys) {
        if (key.contains(uniqueKey)) {
          return true;
        }
      }
      return false;
    }
  
    /** Checks {@link RelMetadataQuery#getRowCount(RelNode)},
     * {@link RelMetadataQuery#getMaxRowCount(RelNode)},
     * and {@link RelMetadataQuery#getMinRowCount(RelNode)}. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertThatRowCount(Matcher<Number> rowCountMatcher,
        Matcher<Number> minRowCountMatcher, Matcher<Number> maxRowCountMatcher) {
      final RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
  
      final Double rowCount = mq.getRowCount(rel);
      assertThat(rowCount, notNullValue());
      assertThat(rowCount, rowCountMatcher);
  
      final Double min = mq.getMinRowCount(rel);
      assertThat(min, notNullValue());
      assertThat(min, minRowCountMatcher);
  
      final Double max = mq.getMaxRowCount(rel);
      assertThat(max, notNullValue());
      assertThat(max, maxRowCountMatcher);
      return this;
    }
  
    /** Checks {@link RelMetadataQuery#getSelectivity(RelNode, RexNode)}. */
    public RelMetadataFixture assertThatSelectivity(Matcher<Double> matcher) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      Double result = mq.getSelectivity(rel, null);
      assertThat(result, notNullValue());
      assertThat(result, matcher);
      return this;
    }
  
    /** Checks
     * {@link RelMetadataQuery#getDistinctRowCount(RelNode, ImmutableBitSet, RexNode)}
     * with a null predicate. */
    public RelMetadataFixture assertThatDistinctRowCount(ImmutableBitSet groupKey,
        Matcher<Double> matcher) {
      return assertThatDistinctRowCount(r -> groupKey, matcher);
    }
  
    /** Checks
     * {@link RelMetadataQuery#getDistinctRowCount(RelNode, ImmutableBitSet, RexNode)}
     * with a null predicate, deriving the group key from the {@link RelNode}. */
    public RelMetadataFixture assertThatDistinctRowCount(
        Function<RelNode, ImmutableBitSet> groupKeyFn,
        Matcher<Double> matcher) {
      final RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final ImmutableBitSet groupKey = groupKeyFn.apply(rel);
      Double result = mq.getDistinctRowCount(rel, groupKey, null);
      assertThat(result, matcher);
      return this;
    }
  
    /** Checks the {@link RelNode} produced by {@link #toRel}. */
    public RelMetadataFixture assertThatRel(Matcher<RelNode> matcher) {
      final RelNode rel = toRel();
      assertThat(rel, matcher);
      return this;
    }
  
    /** Shorthand for a call to {@link #assertThatNodeTypeCount(Matcher)}
     * with a constant map. */
    @SuppressWarnings({"rawtypes", "unchecked", "UnusedReturnValue"})
    public RelMetadataFixture assertThatNodeTypeCountIs(
        Class<? extends RelNode> k0, Integer v0, Object... rest) {
      final ImmutableMap.Builder<Class<? extends RelNode>, Integer> b =
          ImmutableMap.builder();
      b.put(k0, v0);
      for (int i = 0; i < rest.length;) {
        b.put((Class) rest[i++], (Integer) rest[i++]);
      }
      return assertThatNodeTypeCount(is(b.build()));
    }
  
    /** Checks the number of each sub-class of {@link RelNode},
     * calling {@link RelMetadataQuery#getNodeTypes(RelNode)}. */
    public RelMetadataFixture assertThatNodeTypeCount(
        Matcher<Map<Class<? extends RelNode>, Integer>> matcher) {
      final RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final Multimap<Class<? extends RelNode>, RelNode> result = mq.getNodeTypes(rel);
      assertThat(result, notNullValue());
      final Map<Class<? extends RelNode>, Integer> resultCount = new HashMap<>();
      for (Map.Entry<Class<? extends RelNode>, Collection<RelNode>> e : result.asMap().entrySet()) {
        resultCount.put(e.getKey(), e.getValue().size());
      }
      assertThat(resultCount, matcher);
      return this;
    }
  
    /** Checks {@link RelMetadataQuery#getUniqueKeys(RelNode)}. */
    public RelMetadataFixture assertThatUniqueKeys(
        Matcher<Iterable<ImmutableBitSet>> matcher) {
      final RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final Set<ImmutableBitSet> result = mq.getUniqueKeys(rel);
      assertThat(result, matcher);
      return this;
    }
  
    /** Checks {@link RelMetadataQuery#areColumnsUnique(RelNode, ImmutableBitSet)}. */
    public RelMetadataFixture assertThatAreColumnsUnique(ImmutableBitSet columns,
        Matcher<Boolean> matcher) {
      return assertThatAreColumnsUnique(r -> columns, r -> r, matcher);
    }
  
    /** Checks {@link RelMetadataQuery#areColumnsUnique(RelNode, ImmutableBitSet)},
     * deriving parameters via functions. */
    public RelMetadataFixture assertThatAreColumnsUnique(
        Function<RelNode, ImmutableBitSet> columnsFn,
        UnaryOperator<RelNode> relFn,
        Matcher<Boolean> matcher) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final ImmutableBitSet columns = columnsFn.apply(rel);
      final RelNode rel2 = relFn.apply(rel);
      final Boolean areColumnsUnique = mq.areColumnsUnique(rel2, columns);
      assertThat(areColumnsUnique, matcher);
      return this;
    }
  
    /** Checks {@link RelMetadataQuery#areRowsUnique(RelNode)}. */
    @SuppressWarnings({"UnusedReturnValue"})
    public RelMetadataFixture assertThatAreRowsUnique(Matcher<Boolean> matcher) {
      RelNode rel = toRel();
      final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
      final Boolean areRowsUnique = mq.areRowsUnique(rel);
      assertThat(areRowsUnique, matcher);
      return this;
    }
  
    /**
     * A configuration that describes how metadata should be configured.
     */
    public static class MetadataConfig {
      static final MetadataConfig JANINO =
          new MetadataConfig("Janino",
              JaninoRelMetadataProvider::of,
              RelMetadataQuery.THREAD_PROVIDERS::get,
              true);
  
      static final MetadataConfig PROXYING =
          new MetadataConfig("Proxying",
              ProxyingMetadataHandlerProvider::new,
              () -> DefaultRelMetadataProvider.INSTANCE,
              false);
  
      static final MetadataConfig NOP =
          new MetadataConfig("Nop",
              ProxyingMetadataHandlerProvider::new,
              () -> DefaultRelMetadataProvider.INSTANCE,
              false) {
            @Override void applyMetadata(RelOptCluster cluster,
                RelMetadataProvider provider,
                Function<MetadataHandlerProvider, RelMetadataQuery> supplierFactory) {
              // do nothing
            }
          };
  
      public final String name;
      public final Function<RelMetadataProvider, MetadataHandlerProvider> converter;
      public final Supplier<RelMetadataProvider> defaultProviderSupplier;
      public final boolean isCaching;
  
      public MetadataConfig(String name,
          Function<RelMetadataProvider, MetadataHandlerProvider> converter,
          Supplier<RelMetadataProvider> defaultProviderSupplier,
          boolean isCaching) {
        this.name = name;
        this.converter = converter;
        this.defaultProviderSupplier = defaultProviderSupplier;
        this.isCaching = isCaching;
      }
  
      public MetadataHandlerProvider getDefaultHandlerProvider() {
        return converter.apply(defaultProviderSupplier.get());
      }
  
      void applyMetadata(RelOptCluster cluster) {
        applyMetadata(cluster, defaultProviderSupplier.get());
      }
  
      void applyMetadata(RelOptCluster cluster,
          RelMetadataProvider provider) {
        applyMetadata(cluster, provider, RelMetadataQuery::new);
      }
  
      void applyMetadata(RelOptCluster cluster,
          RelMetadataProvider provider,
          Function<MetadataHandlerProvider, RelMetadataQuery> supplierFactory) {
        cluster.setMetadataProvider(provider);
        cluster.setMetadataQuerySupplier(() ->
            supplierFactory.apply(converter.apply(provider)));
        cluster.invalidateMetadataQuery();
      }
  
      public boolean isCaching() {
        return isCaching;
      }
  
      @Override public String toString() {
        return name;
      }
    }
  }
  