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.prepare;
  
  import org.apache.calcite.adapter.enumerable.EnumerableRel;
  import org.apache.calcite.config.CalciteSystemProperty;
  import org.apache.calcite.interpreter.BindableConvention;
  import org.apache.calcite.jdbc.CalcitePrepare;
  import org.apache.calcite.jdbc.CalciteSchema;
  import org.apache.calcite.plan.RelOptCluster;
  import org.apache.calcite.plan.RelOptMaterialization;
  import org.apache.calcite.plan.RelOptTable;
  import org.apache.calcite.plan.RelOptUtil;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rel.RelRoot;
  import org.apache.calcite.schema.Schemas;
  import org.apache.calcite.schema.Table;
  import org.apache.calcite.schema.impl.StarTable;
  import org.apache.calcite.sql.SqlNode;
  import org.apache.calcite.sql.parser.SqlParseException;
  import org.apache.calcite.sql.parser.SqlParser;
  import org.apache.calcite.sql2rel.SqlRexConvertletTable;
  import org.apache.calcite.sql2rel.SqlToRelConverter;
  
  import com.google.common.collect.ImmutableList;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Context for populating a {@link Prepare.Materialization}.
   */
  class CalciteMaterializer extends CalcitePrepareImpl.CalcitePreparingStmt {
    CalciteMaterializer(CalcitePrepareImpl prepare,
        CalcitePrepare.Context context,
        CatalogReader catalogReader, CalciteSchema schema,
        RelOptCluster cluster, SqlRexConvertletTable convertletTable) {
      super(prepare, context, catalogReader, catalogReader.getTypeFactory(),
          schema, EnumerableRel.Prefer.ANY, cluster, BindableConvention.INSTANCE,
          convertletTable);
    }
  
    /** Populates a materialization record, converting a table path
     * (essentially a list of strings, like ["hr", "sales"]) into a table object
     * that can be used in the planning process. */
    void populate(Materialization materialization) {
      SqlParser parser = SqlParser.create(materialization.sql);
      SqlNode node;
      try {
        node = parser.parseStmt();
      } catch (SqlParseException e) {
        throw new RuntimeException("parse failed", e);
      }
      final SqlToRelConverter.Config config =
          SqlToRelConverter.config().withTrimUnusedFields(true);
      SqlToRelConverter sqlToRelConverter2 =
          getSqlToRelConverter(getSqlValidator(), catalogReader, config);
  
      RelRoot root = sqlToRelConverter2.convertQuery(node, true, true);
      materialization.queryRel = trimUnusedFields(root).rel;
  
      // Identify and substitute a StarTable in queryRel.
      //
      // It is possible that no StarTables match. That is OK, but the
      // materialization patterns that are recognized will not be as rich.
      //
      // It is possible that more than one StarTable matches. TBD: should we
      // take the best (whatever that means), or all of them?
      useStar(schema, materialization);
  
      List<String> tableName = materialization.materializedTable.path();
      RelOptTable table =
          requireNonNull(this.catalogReader.getTable(tableName),
              () -> "table " + tableName + " is not found");
      materialization.tableRel =
          sqlToRelConverter2.toRel(table, ImmutableList.of());
    }
  
    /** Converts a relational expression to use a
     * {@link StarTable} defined in {@code schema}.
     * Uses the first star table that fits. */
    private void useStar(CalciteSchema schema, Materialization materialization) {
      RelNode queryRel = requireNonNull(materialization.queryRel, "materialization.queryRel");
      for (Callback x : useStar(schema, queryRel)) {
        // Success -- we found a star table that matches.
        materialization.materialize(x.rel, x.starRelOptTable);
        if (CalciteSystemProperty.DEBUG.value()) {
          System.out.println("Materialization "
              + materialization.materializedTable + " matched star table "
              + x.starTable + "; query after re-write: "
              + RelOptUtil.toString(queryRel));
        }
      }
    }
  
    /** Converts a relational expression to use a
     * {@link org.apache.calcite.schema.impl.StarTable} defined in {@code schema}.
     * Uses the first star table that fits. */
    private Iterable<Callback> useStar(CalciteSchema schema, RelNode queryRel) {
      List<CalciteSchema.TableEntry> starTables =
          Schemas.getStarTables(schema.root());
      if (starTables.isEmpty()) {
        // Don't waste effort converting to leaf-join form.
        return ImmutableList.of();
      }
      final List<Callback> list = new ArrayList<>();
      final RelNode rel2 =
          RelOptMaterialization.toLeafJoinForm(queryRel);
      for (CalciteSchema.TableEntry starTable : starTables) {
        final Table table = starTable.getTable();
        assert table instanceof StarTable;
        RelOptTableImpl starRelOptTable =
            RelOptTableImpl.create(catalogReader, table.getRowType(typeFactory),
                starTable, null);
        final RelNode rel3 =
            RelOptMaterialization.tryUseStar(rel2, starRelOptTable);
        if (rel3 != null) {
          list.add(new Callback(rel3, starTable, starRelOptTable));
        }
      }
      return list;
    }
  
    /** Called when we discover a star table that matches. */
    static class Callback {
      public final RelNode rel;
      public final CalciteSchema.TableEntry starTable;
      public final RelOptTableImpl starRelOptTable;
  
      Callback(RelNode rel,
          CalciteSchema.TableEntry starTable,
          RelOptTableImpl starRelOptTable) {
        this.rel = rel;
        this.starTable = starTable;
        this.starRelOptTable = starRelOptTable;
      }
    }
  }
  