CalciteSchema.java

/*
 * 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.jdbc;

import org.apache.calcite.linq4j.function.Experimental;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.materialize.Lattice;
import org.apache.calcite.rel.type.RelProtoDataType;
import org.apache.calcite.schema.Function;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.SchemaVersion;
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TableMacro;
import org.apache.calcite.schema.Wrapper;
import org.apache.calcite.schema.impl.MaterializedViewTable;
import org.apache.calcite.schema.impl.StarTable;
import org.apache.calcite.schema.lookup.LikePattern;
import org.apache.calcite.schema.lookup.Lookup;
import org.apache.calcite.schema.lookup.Named;
import org.apache.calcite.util.LazyReference;
import org.apache.calcite.util.NameMap;
import org.apache.calcite.util.NameMultimap;
import org.apache.calcite.util.NameSet;
import org.apache.calcite.util.Pair;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;

import static java.util.Objects.requireNonNull;

/**
 * Schema.
 *
 * <p>Wrapper around user-defined schema used internally.
 */
public abstract class CalciteSchema {

  private final @Nullable CalciteSchema parent;
  public final Schema schema;
  public final String name;
  /** Tables explicitly defined in this schema. Does not include tables in
   * {@link #schema}. */
  protected final NameMap<TableEntry> tableMap;
  private final LazyReference<Lookup<TableEntry>> tables = new LazyReference<>();
  protected final NameMultimap<FunctionEntry> functionMap;
  protected final NameMap<TypeEntry> typeMap;
  protected final NameMap<LatticeEntry> latticeMap;
  protected final NameSet functionNames;
  protected final NameMap<FunctionEntry> nullaryFunctionMap;
  protected final NameMap<CalciteSchema> subSchemaMap;
  private final LazyReference<Lookup<CalciteSchema>> subSchemas = new LazyReference<>();
  private @Nullable List<? extends List<String>> path;

  protected CalciteSchema(@Nullable CalciteSchema parent, Schema schema,
      String name,
      @Nullable NameMap<CalciteSchema> subSchemaMap,
      @Nullable NameMap<TableEntry> tableMap,
      @Nullable NameMap<LatticeEntry> latticeMap,
      @Nullable NameMap<TypeEntry> typeMap,
      @Nullable NameMultimap<FunctionEntry> functionMap,
      @Nullable NameSet functionNames,
      @Nullable NameMap<FunctionEntry> nullaryFunctionMap,
      @Nullable List<? extends List<String>> path) {
    this.parent = parent;
    this.schema = schema;
    this.name = name;
    this.tableMap = tableMap != null ? tableMap : new NameMap<>();
    this.latticeMap = latticeMap != null ? latticeMap : new NameMap<>();
    this.subSchemaMap = subSchemaMap != null ? subSchemaMap : new NameMap<>();
    if (functionMap == null) {
      this.functionMap = new NameMultimap<>();
      this.functionNames = new NameSet();
      this.nullaryFunctionMap = new NameMap<>();
    } else {
      // If you specify functionMap, you must also specify functionNames and
      // nullaryFunctionMap.
      this.functionMap = functionMap;
      this.functionNames = requireNonNull(functionNames, "functionNames");
      this.nullaryFunctionMap =
          requireNonNull(nullaryFunctionMap, "nullaryFunctionMap");
    }
    if (typeMap == null) {
      this.typeMap = new NameMap<>();
    } else {
      this.typeMap = typeMap;
    }
    this.path = path;
  }

  public Lookup<TableEntry> tables() {
    return this.tables.getOrCompute(() ->
        Lookup.concat(
            Lookup.of(this.tableMap),
            enhanceLookup(schema.tables().map((s, n) -> tableEntry(n, s)))));

  }

  public Lookup<CalciteSchema> subSchemas() {
    return subSchemas.getOrCompute(() ->
        Lookup.concat(
            Lookup.of(this.subSchemaMap),
            enhanceLookup(schema.subSchemas().map((s, n) -> createSubSchema(s, n)))));
  }

  /** The derived class is able to enhance the lookup e.g. by introducing a cache. */
  protected <S> Lookup<S> enhanceLookup(Lookup<S> lookup) {
    return lookup;
  }

  /** Creates a sub-schema with a given name that is defined implicitly. */
  protected abstract CalciteSchema createSubSchema(CalciteSchema this,
      Schema schema, String name);

  /** Returns a type with a given name that is defined implicitly
   * (that is, by the underlying {@link Schema} object, not explicitly
   * by a call to {@link #add(String, RelProtoDataType)}), or null. */
  protected abstract @Nullable TypeEntry getImplicitType(String name,
                                                boolean caseSensitive);

  /** Returns table function with a given name and zero arguments that is
   * defined implicitly (that is, by the underlying {@link Schema} object,
   * not explicitly by a call to {@link #add(String, Function)}), or null. */
  protected abstract @Nullable TableEntry getImplicitTableBasedOnNullaryFunction(String tableName,
      boolean caseSensitive);

  /** Adds implicit functions to a builder. */
  protected abstract void addImplicitFunctionsToBuilder(
      ImmutableList.Builder<Function> builder,
      String name, boolean caseSensitive);

  /** Adds implicit function names to a builder. */
  protected abstract void addImplicitFuncNamesToBuilder(
      ImmutableSortedSet.Builder<String> builder);

  /** Adds implicit type names to a builder. */
  protected abstract void addImplicitTypeNamesToBuilder(
      ImmutableSortedSet.Builder<String> builder);

  /** Adds implicit table functions to a builder. */
  protected abstract void addImplicitTablesBasedOnNullaryFunctionsToBuilder(
      ImmutableSortedMap.Builder<String, Table> builder);

  /** Returns a snapshot representation of this CalciteSchema. */
  protected abstract CalciteSchema snapshot(
      @Nullable CalciteSchema parent, SchemaVersion version);

  protected abstract boolean isCacheEnabled();

  public abstract void setCache(boolean cache);

  /** Creates a TableEntryImpl with no SQLs. */
  protected TableEntryImpl tableEntry(String name, Table table) {
    return new TableEntryImpl(this, name, table, ImmutableList.of());
  }

  /** Creates a TableEntryImpl with no SQLs. */
  protected TypeEntryImpl typeEntry(String name, RelProtoDataType relProtoDataType) {
    return new TypeEntryImpl(this, name, relProtoDataType);
  }

  /** Defines a table within this schema. */
  public TableEntry add(String tableName, Table table) {
    return add(tableName, table, ImmutableList.of());
  }

  /** Defines a table within this schema. */
  public TableEntry add(String tableName, Table table,
      ImmutableList<String> sqls) {
    final TableEntryImpl entry =
        new TableEntryImpl(this, tableName, table, sqls);
    tableMap.put(tableName, entry);
    return entry;
  }

  /** Defines a type within this schema. */
  public TypeEntry add(String name, RelProtoDataType type) {
    final TypeEntry entry =
        new TypeEntryImpl(this, name, type);
    typeMap.put(name, entry);
    return entry;
  }

  private FunctionEntry add(String name, Function function) {
    final FunctionEntryImpl entry =
        new FunctionEntryImpl(this, name, function);
    functionMap.put(name, entry);
    functionNames.add(name);
    if (function.getParameters().isEmpty()) {
      nullaryFunctionMap.put(name, entry);
    }
    return entry;
  }

  private LatticeEntry add(String name, Lattice lattice) {
    if (latticeMap.containsKey(name, false)) {
      throw new RuntimeException("Duplicate lattice '" + name + "'");
    }
    final LatticeEntryImpl entry = new LatticeEntryImpl(this, name, lattice);
    latticeMap.put(name, entry);
    return entry;
  }

  public CalciteSchema root() {
    for (CalciteSchema schema = this;;) {
      if (schema.parent == null) {
        return schema;
      }
      schema = schema.parent;
    }
  }

  /** Returns whether this is a root schema. */
  public boolean isRoot() {
    return parent == null;
  }

  /** Returns the path of an object in this schema. */
  public List<String> path(@Nullable String name) {
    final List<String> list = new ArrayList<>();
    if (name != null) {
      list.add(name);
    }
    for (CalciteSchema s = this; s != null; s = s.parent) {
      if (s.parent != null || !s.name.equals("")) {
        // Omit the root schema's name from the path if it's the empty string,
        // which it usually is.
        list.add(s.name);
      }
    }
    return ImmutableList.copyOf(list).reverse();
  }

  public final @Nullable CalciteSchema getSubSchema(String schemaName,
      boolean caseSensitive) {
    return caseSensitive
        ? subSchemas().get(schemaName)
        : Named.entityOrNull(subSchemas().getIgnoreCase(schemaName));
  }

  /** Adds a child schema of this schema. */
  public abstract CalciteSchema add(String name, Schema schema);

  /** Returns a table that materializes the given SQL statement. */
  public final @Nullable TableEntry getTableBySql(String sql) {
    for (TableEntry tableEntry : tableMap.map().values()) {
      if (tableEntry.sqls.contains(sql)) {
        return tableEntry;
      }
    }
    return null;
  }

  /** Returns a table with the given name. Does not look for views. */
  public final @Nullable TableEntry getTable(String tableName, boolean caseSensitive) {
    return Lookup.get(tables(), tableName, caseSensitive);
  }

  public String getName() {
    return name;
  }

  public SchemaPlus plus() {
    return new SchemaPlusImpl();
  }

  public static CalciteSchema from(SchemaPlus plus) {
    return ((SchemaPlusImpl) plus).calciteSchema();
  }

  /** Returns the default path resolving functions from this schema.
   *
   * <p>The path consists is a list of lists of strings.
   * Each list of strings represents the path of a schema from the root schema.
   * For example, [[], [foo], [foo, bar, baz]] represents three schemas: the
   * root schema "/" (level 0), "/foo" (level 1) and "/foo/bar/baz" (level 3).
   *
   * @return Path of this schema; never null, may be empty
   */
  public List<? extends List<String>> getPath() {
    if (path != null) {
      return path;
    }
    // Return a path consisting of just this schema.
    return ImmutableList.of(path(null));
  }

  /** Returns a collection of sub-schemas, both explicit (defined using
   * {@link #add(String, org.apache.calcite.schema.Schema)}) and implicit. */
  public final NavigableMap<String, CalciteSchema> getSubSchemaMap() {
    final ImmutableSortedMap.Builder<String, CalciteSchema> builder =
        new ImmutableSortedMap.Builder<>(NameSet.COMPARATOR);
    final Lookup<CalciteSchema> schemas = subSchemas();
    for (String name : schemas.getNames(LikePattern.any())) {
      builder.put(name, requireNonNull(schemas.get(name)));
    }
    return builder.build();
  }

  /** Returns a collection of lattices.
   *
   * <p>All are explicit (defined using {@link #add(String, Lattice)}). */
  public NavigableMap<String, LatticeEntry> getLatticeMap() {
    return ImmutableSortedMap.copyOf(latticeMap.map());
  }

  /** Returns the set of all table names. Includes implicit and explicit tables
   * and functions with zero parameters. */
  public final Set<String> getTableNames() {
    return getTableNames(LikePattern.any());
  }

  /** Returns the set of table names filtered by the given pattern.
   * Includes implicit and explicit tables and functions with zero parameters. */
  public final Set<String> getTableNames(LikePattern pattern) {
    return tables().getNames(pattern);
  }

  /** Returns the set of all types names. */
  public final NavigableSet<String> getTypeNames() {
    final ImmutableSortedSet.Builder<String> builder =
        new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR);
    // Add explicit types.
    builder.addAll(typeMap.map().keySet());
    // Add implicit types.
    addImplicitTypeNamesToBuilder(builder);
    return builder.build();
  }

  /** Returns a type, explicit and implicit, with a given
   * name. Never null. */
  public final @Nullable TypeEntry getType(String name, boolean caseSensitive) {
    for (Map.Entry<String, TypeEntry> entry
        : typeMap.range(name, caseSensitive).entrySet()) {
      return entry.getValue();
    }
    return getImplicitType(name, caseSensitive);
  }

  /** Returns a collection of all functions, explicit and implicit, with a given
   * name. Never null. */
  public final Collection<Function> getFunctions(String name, boolean caseSensitive) {
    final ImmutableList.Builder<Function> builder = ImmutableList.builder();
    // Add explicit functions.
    for (FunctionEntry functionEntry
        : Pair.right(functionMap.range(name, caseSensitive))) {
      builder.add(functionEntry.getFunction());
    }
    // Add implicit functions.
    addImplicitFunctionsToBuilder(builder, name, caseSensitive);
    return builder.build();
  }

  /** Returns the list of function names in this schema, both implicit and
   * explicit, never null. */
  public final NavigableSet<String> getFunctionNames() {
    final ImmutableSortedSet.Builder<String> builder =
        new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR);
    // Add explicit functions, case-sensitive.
    builder.addAll(functionMap.map().keySet());
    // Add implicit functions, case-sensitive.
    addImplicitFuncNamesToBuilder(builder);
    return builder.build();
  }

  /** Returns tables derived from explicit and implicit functions
   * that take zero parameters. */
  public final NavigableMap<String, Table> getTablesBasedOnNullaryFunctions() {
    ImmutableSortedMap.Builder<String, Table> builder =
        new ImmutableSortedMap.Builder<>(NameSet.COMPARATOR);
    for (Map.Entry<String, FunctionEntry> entry
        : nullaryFunctionMap.map().entrySet()) {
      final Function function = entry.getValue().getFunction();
      if (function instanceof TableMacro) {
        assert function.getParameters().isEmpty();
        final Table table = ((TableMacro) function).apply(ImmutableList.of());
        builder.put(entry.getKey(), table);
      }
    }
    // add tables derived from implicit functions
    addImplicitTablesBasedOnNullaryFunctionsToBuilder(builder);
    return builder.build();
  }

  /** Returns a tables derived from explicit and implicit functions
   * that take zero parameters. */
  public final @Nullable TableEntry getTableBasedOnNullaryFunction(String tableName,
      boolean caseSensitive) {
    for (Map.Entry<String, FunctionEntry> entry
        : nullaryFunctionMap.range(tableName, caseSensitive).entrySet()) {
      final Function function = entry.getValue().getFunction();
      if (function instanceof TableMacro) {
        assert function.getParameters().isEmpty();
        final Table table = ((TableMacro) function).apply(ImmutableList.of());
        return tableEntry(tableName, table);
      }
    }
    return getImplicitTableBasedOnNullaryFunction(tableName, caseSensitive);
  }

  /** Creates a snapshot of this CalciteSchema as of the specified time. All
   * explicit objects in this CalciteSchema will be copied into the snapshot
   * CalciteSchema, while the contents of the snapshot of the underlying schema
   * should not change as specified in {@link Schema#snapshot(SchemaVersion)}.
   * Snapshots of explicit sub schemas will be created and copied recursively.
   *
   * <p>Currently, to accommodate the requirement of creating tables on the fly
   * for materializations, the snapshot will still use the same table map and
   * lattice map as in the original CalciteSchema instead of making copies.
   *
   * @param version The current schema version
   *
   * @return the schema snapshot.
   */
  public CalciteSchema createSnapshot(SchemaVersion version) {
    checkArgument(this.isRoot(), "must be root schema");
    return snapshot(null, version);
  }

  /** Returns a subset of a map whose keys match the given string
   * case-insensitively.
   *
   * @deprecated use NameMap
   */
  @Deprecated // to be removed before 2.0
  protected static <V> NavigableMap<String, V> find(NavigableMap<String, V> map,
      String s) {
    return NameMap.immutableCopyOf(map).range(s, false);
  }

  /** Returns a subset of a set whose values match the given string
   * case-insensitively.
   *
   * @deprecated use NameSet
   */
  @Deprecated // to be removed before 2.0
  protected static Iterable<String> find(NavigableSet<String> set, String name) {
    return NameSet.immutableCopyOf(set).range(name, false);
  }

  /** Creates a root schema.
   *
   * <p>When <code>addMetadataSchema</code> argument is true adds a "metadata"
   * schema containing definitions of tables, columns etc. to root schema.
   * By default, creates a {@link CachingCalciteSchema}.
   */
  public static CalciteSchema createRootSchema(boolean addMetadataSchema) {
    return createRootSchema(addMetadataSchema, true);
  }

  /** Creates a root schema.
   *
   * @param addMetadataSchema Whether to add a "metadata" schema containing
   *              definitions of tables, columns etc.
   * @param cache If true create {@link CachingCalciteSchema};
   *                if false create {@link SimpleCalciteSchema}
   */
  public static CalciteSchema createRootSchema(boolean addMetadataSchema,
      boolean cache) {
    return createRootSchema(addMetadataSchema, cache, "");
  }

  /** Creates a root schema.
   *
   * @param addMetadataSchema Whether to add a "metadata" schema containing
   *              definitions of tables, columns etc.
   * @param cache If true create {@link CachingCalciteSchema};
   *                if false create {@link SimpleCalciteSchema}
   * @param name Schema name
   */
  public static CalciteSchema createRootSchema(boolean addMetadataSchema,
      boolean cache, String name) {
    final Schema rootSchema = new CalciteConnectionImpl.RootSchema();
    return createRootSchema(addMetadataSchema, cache, name, rootSchema);
  }

  @Experimental
  public static CalciteSchema createRootSchema(boolean addMetadataSchema,
      boolean cache, String name, Schema schema) {
    CalciteSchema rootSchema;
    if (cache) {
      rootSchema = new CachingCalciteSchema(null, schema, name);
    } else {
      rootSchema = new SimpleCalciteSchema(null, schema, name);
    }
    if (addMetadataSchema) {
      rootSchema.add("metadata", MetadataSchema.INSTANCE);
    }
    return rootSchema;
  }

  @Experimental
  public boolean removeSubSchema(String name) {
    return subSchemaMap.remove(name) != null;
  }

  @Experimental
  public boolean removeTable(String name) {
    return tableMap.remove(name) != null;
  }

  @Experimental
  public boolean removeFunction(String name) {
    final FunctionEntry remove = nullaryFunctionMap.remove(name);
    if (remove == null) {
      return false;
    }
    functionMap.remove(name, remove);
    return true;
  }

  @Experimental
  public boolean removeType(String name) {
    return typeMap.remove(name) != null;
  }

  /**
   * Entry in a schema, such as a table or sub-schema.
   *
   * <p>Each object's name is a property of its membership in a schema;
   * therefore in principle it could belong to several schemas, or
   * even the same schema several times, with different names. In this
   * respect, it is like an inode in a Unix file system.
   *
   * <p>The members of a schema must have unique names.
   */
  public abstract static class Entry {
    public final CalciteSchema schema;
    public final String name;

    protected Entry(CalciteSchema schema, String name) {
      this.schema = requireNonNull(schema, "schema");
      this.name = requireNonNull(name, "name");
    }

    /** Returns this object's path. For example ["hr", "emps"]. */
    public final List<String> path() {
      return schema.path(name);
    }
  }

  /** Membership of a table in a schema. */
  public abstract static class TableEntry extends Entry {
    public final ImmutableList<String> sqls;

    protected TableEntry(CalciteSchema schema, String name,
        ImmutableList<String> sqls) {
      super(schema, name);
      this.sqls = requireNonNull(sqls, "sqls");
    }

    public abstract Table getTable();
  }

  /** Membership of a type in a schema. */
  public abstract static class TypeEntry extends Entry {
    protected TypeEntry(CalciteSchema schema, String name) {
      super(schema, name);
    }

    public abstract RelProtoDataType getType();
  }

  /** Membership of a function in a schema. */
  public abstract static class FunctionEntry extends Entry {
    protected FunctionEntry(CalciteSchema schema, String name) {
      super(schema, name);
    }

    public abstract Function getFunction();

    /** Whether this represents a materialized view. (At a given point in time,
     * it may or may not be materialized as a table.) */
    public abstract boolean isMaterialization();
  }

  /** Membership of a lattice in a schema. */
  public abstract static class LatticeEntry extends Entry {
    protected LatticeEntry(CalciteSchema schema, String name) {
      super(schema, name);
    }

    public abstract Lattice getLattice();

    public abstract TableEntry getStarTable();
  }

  /** Implementation of {@link SchemaPlus} based on a
   * {@link org.apache.calcite.jdbc.CalciteSchema}. */
  private class SchemaPlusImpl implements SchemaPlus {
    CalciteSchema calciteSchema() {
      return CalciteSchema.this;
    }

    @Override public @Nullable SchemaPlus getParentSchema() {
      return parent == null ? null : parent.plus();
    }

    @Override public String getName() {
      return CalciteSchema.this.getName();
    }

    @Override public boolean isMutable() {
      return schema.isMutable();
    }

    @Override public void setCacheEnabled(boolean cache) {
      CalciteSchema.this.setCache(cache);
    }

    @Override public boolean isCacheEnabled() {
      return CalciteSchema.this.isCacheEnabled();
    }

    @Override public Schema snapshot(SchemaVersion version) {
      throw new UnsupportedOperationException();
    }

    @Override public Expression getExpression(@Nullable SchemaPlus parentSchema, String name) {
      return schema.getExpression(parentSchema, name);
    }

    @Override public Lookup<Table> tables() {
      return CalciteSchema.this.tables().map((table, name) -> table.getTable());
    }

    @Override public Lookup<? extends SchemaPlus> subSchemas() {
      return CalciteSchema.this.subSchemas().map((schema, name) -> schema.plus());
    }

    @Deprecated @Override public @Nullable Table getTable(String name) {
      final TableEntry entry = CalciteSchema.this.getTable(name, true);
      return entry == null ? null : entry.getTable();
    }

    @Deprecated @Override public Set<String> getTableNames() {
      return CalciteSchema.this.getTableNames(LikePattern.any());
    }

    @Override public @Nullable RelProtoDataType getType(String name) {
      final TypeEntry entry = CalciteSchema.this.getType(name, true);
      return entry == null ? null : entry.getType();
    }

    @Override public Set<String> getTypeNames() {
      return CalciteSchema.this.getTypeNames();
    }

    @Override public Collection<Function> getFunctions(String name) {
      return CalciteSchema.this.getFunctions(name, true);
    }

    @Override public NavigableSet<String> getFunctionNames() {
      return CalciteSchema.this.getFunctionNames();
    }

    @Deprecated @Override public @Nullable SchemaPlus getSubSchema(String name) {
      return subSchemas().get(name);
    }

    @Deprecated @Override public Set<String> getSubSchemaNames() {
      return subSchemas().getNames(LikePattern.any());
    }

    @Override public SchemaPlus add(String name, Schema schema) {
      final CalciteSchema calciteSchema = CalciteSchema.this.add(name, schema);
      return calciteSchema.plus();
    }

    @Override public <T extends Object> T unwrap(Class<T> clazz) {
      if (clazz.isInstance(this)) {
        return clazz.cast(this);
      }
      if (clazz.isInstance(CalciteSchema.this)) {
        return clazz.cast(CalciteSchema.this);
      }
      if (clazz.isInstance(CalciteSchema.this.schema)) {
        return clazz.cast(CalciteSchema.this.schema);
      }
      if (schema instanceof Wrapper) {
        return ((Wrapper) schema).unwrapOrThrow(clazz);
      }
      throw new ClassCastException("not a " + clazz);
    }

    @Override public void setPath(ImmutableList<ImmutableList<String>> path) {
      CalciteSchema.this.path = path;
    }

    @Override public void add(String name, Table table) {
      CalciteSchema.this.add(name, table);
    }

    @Override public boolean removeTable(String name) {
      return CalciteSchema.this.removeTable(name);
    }

    @Override public void add(String name, Function function) {
      CalciteSchema.this.add(name, function);
    }

    @Override public void add(String name, RelProtoDataType type) {
      CalciteSchema.this.add(name, type);
    }

    @Override public void add(String name, Lattice lattice) {
      CalciteSchema.this.add(name, lattice);
    }
  }

  /**
   * Implementation of {@link CalciteSchema.TableEntry}
   * where all properties are held in fields.
   */
  public static class TableEntryImpl extends TableEntry {
    private final Table table;

    /** Creates a TableEntryImpl. */
    public TableEntryImpl(CalciteSchema schema, String name, Table table,
        ImmutableList<String> sqls) {
      super(schema, name, sqls);
      this.table = requireNonNull(table, "table");
    }

    @Override public Table getTable() {
      return table;
    }
  }

  /**
   * Implementation of {@link TypeEntry}
   * where all properties are held in fields.
   */
  public static class TypeEntryImpl extends TypeEntry {
    private final RelProtoDataType protoDataType;

    /** Creates a TypeEntryImpl. */
    public TypeEntryImpl(CalciteSchema schema, String name, RelProtoDataType protoDataType) {
      super(schema, name);
      this.protoDataType = protoDataType;
    }

    @Override public RelProtoDataType getType() {
      return protoDataType;
    }
  }

  /**
   * Implementation of {@link FunctionEntry}
   * where all properties are held in fields.
   */
  public static class FunctionEntryImpl extends FunctionEntry {
    private final Function function;

    /** Creates a FunctionEntryImpl. */
    public FunctionEntryImpl(CalciteSchema schema, String name,
        Function function) {
      super(schema, name);
      this.function = function;
    }

    @Override public Function getFunction() {
      return function;
    }

    @Override public boolean isMaterialization() {
      return function
          instanceof MaterializedViewTable.MaterializedViewTableMacro;
    }
  }

  /**
   * Implementation of {@link LatticeEntry}
   * where all properties are held in fields.
   */
  public static class LatticeEntryImpl extends LatticeEntry {
    private final Lattice lattice;
    private final CalciteSchema.TableEntry starTableEntry;

    /** Creates a LatticeEntryImpl. */
    public LatticeEntryImpl(CalciteSchema schema, String name,
        Lattice lattice) {
      super(schema, name);
      this.lattice = lattice;

      // Star table has same name as lattice and is in same schema.
      final StarTable starTable = lattice.createStarTable();
      starTableEntry = schema.add(name, starTable);
    }

    @Override public Lattice getLattice() {
      return lattice;
    }

    @Override public TableEntry getStarTable() {
      return starTableEntry;
    }
  }

}