You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2017/01/13 19:22:39 UTC

[1/4] calcite git commit: Site: Maryann Xue joins PMC

Repository: calcite
Updated Branches:
  refs/heads/master 2c2b88391 -> 5f9c01908


Site: Maryann Xue joins PMC


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/46474d74
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/46474d74
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/46474d74

Branch: refs/heads/master
Commit: 46474d74d3a0851037fd7103ba59adeffbebdab0
Parents: 2c2b883
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jan 9 10:01:35 2017 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Jan 12 10:35:37 2017 -0800

----------------------------------------------------------------------
 site/_data/contributors.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/46474d74/site/_data/contributors.yml
----------------------------------------------------------------------
diff --git a/site/_data/contributors.yml b/site/_data/contributors.yml
index fb3e09b..688f716 100644
--- a/site/_data/contributors.yml
+++ b/site/_data/contributors.yml
@@ -76,7 +76,7 @@
   apacheId: maryannxue
   githubId: maryannxue
   org: Intel
-  role: Committer
+  role: PMC
 - name: Michael Mior
   apacheId: mmior
   githubId: michaelmior


[3/4] calcite git commit: [CALCITE-1549] More helpful error message when schema, table or column not found

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
index 49f109f..c8eb6b0 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
@@ -25,13 +25,16 @@ import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.util.Pair;
 
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Name-resolution scope. Represents any position in a parse tree than an
@@ -60,27 +63,38 @@ public interface SqlValidatorScope {
    * Looks up a node with a given name. Returns null if none is found.
    *
    * @param names       Name of node to find, maybe partially or fully qualified
+   * @param nameMatcher Name matcher
    * @param deep        Whether to look more than one level deep
    * @param resolved    Callback wherein to write the match(es) we find
    */
-  void resolve(List<String> names, boolean deep, Resolved resolved);
+  void resolve(List<String> names, SqlNameMatcher nameMatcher, boolean deep,
+      Resolved resolved);
+
+  /** @deprecated Use
+   * {@link #findQualifyingTableNames(String, SqlNode, SqlNameMatcher)} */
+  @Deprecated // to be removed before 2.0
+  Pair<String, SqlValidatorNamespace> findQualifyingTableName(String columnName,
+      SqlNode ctx);
 
   /**
-   * Finds the table alias which is implicitly qualifying an unqualified
-   * column name. Throws an error if there is not exactly one table.
+   * Finds all table aliases which are implicitly qualifying an unqualified
+   * column name.
    *
    * <p>This method is only implemented in scopes (such as
    * {@link org.apache.calcite.sql.validate.SelectScope}) which can be the
    * context for name-resolution. In scopes such as
    * {@link org.apache.calcite.sql.validate.IdentifierNamespace}, it throws
-   * {@link UnsupportedOperationException}.</p>
+   * {@link UnsupportedOperationException}.
    *
    * @param columnName Column name
    * @param ctx        Validation context, to appear in any error thrown
-   * @return Table alias and namespace
+   * @param nameMatcher Name matcher
+   *
+   * @return Map of applicable table alias and namespaces, never null, empty
+   * if no aliases found
    */
-  Pair<String, SqlValidatorNamespace> findQualifyingTableName(String columnName,
-      SqlNode ctx);
+  Map<String, ScopeChild> findQualifyingTableNames(String columnName,
+      SqlNode ctx, SqlNameMatcher nameMatcher);
 
   /**
    * Collects the {@link SqlMoniker}s of all possible columns in this scope.
@@ -163,34 +177,47 @@ public interface SqlValidatorScope {
    */
   void validateExpr(SqlNode expr);
 
+  /** @deprecated Use
+   * {@link #resolveTable(List, SqlNameMatcher, Path, Resolved)}. */
+  @Deprecated // to be removed before 2.0
+  SqlValidatorNamespace getTableNamespace(List<String> names);
+
   /**
-   * Looks up a table in this scope from its name. If found, returns the
+   * Looks up a table in this scope from its name. If found, calls
+   * {@link Resolved#resolve(List, SqlNameMatcher, boolean, Resolved)}.
    * {@link TableNamespace} that wraps it. If the "table" is defined in a
    * {@code WITH} clause it may be a query, not a table after all.
    *
+   * <p>The name matcher is not null, and one typically uses
+   * {@link SqlValidatorCatalogReader#nameMatcher()}.
+   *
    * @param names Name of table, may be qualified or fully-qualified
-   * @return Namespace of table
+   * @param nameMatcher Name matcher
+   * @param path List of names that we have traversed through so far
    */
-  SqlValidatorNamespace getTableNamespace(List<String> names);
+  void resolveTable(List<String> names, SqlNameMatcher nameMatcher, Path path,
+      Resolved resolved);
 
   /** Converts the type of an expression to nullable, if the context
    * warrants it. */
   RelDataType nullifyType(SqlNode node, RelDataType type);
 
-  /** Callback from
-   * {@link SqlValidatorScope#resolve(List, boolean, Resolved)}. */
+  /** Callback from {@link SqlValidatorScope#resolve}. */
   interface Resolved {
     void found(SqlValidatorNamespace namespace, boolean nullable,
-        SqlValidatorScope scope, Path path);
+        SqlValidatorScope scope, Path path, List<String> remainingNames);
     int count();
-    Path emptyPath();
   }
 
-  /** A sequence of steps by which an identifier was resolved. */
+  /** A sequence of steps by which an identifier was resolved. Immutable. */
   abstract class Path {
-    /** Creates a path which consists of this path plus one additional step. */
-    Step add(RelDataType rowType, int i, StructKind kind) {
-      return new Step(this, rowType, i, kind);
+    /** The empty path. */
+    @SuppressWarnings("StaticInitializerReferencesSubClass")
+    public static final EmptyPath EMPTY = new EmptyPath();
+
+    /** Creates a path that consists of this path plus one additional step. */
+    public Step plus(RelDataType rowType, int i, String name, StructKind kind) {
+      return new Step(this, rowType, i, name, kind);
     }
 
     /** Number of steps in this path. */
@@ -205,6 +232,16 @@ public interface SqlValidatorScope {
       return paths.build();
     }
 
+    /** Returns a list ["step1", "step2"]. */
+    List<String> stepNames() {
+      return Lists.transform(steps(),
+          new Function<Step, String>() {
+            public String apply(Step input) {
+              return input.name;
+            }
+          });
+    }
+
     protected void build(ImmutableList.Builder<Step> paths) {
     }
   }
@@ -218,12 +255,15 @@ public interface SqlValidatorScope {
     final Path parent;
     final RelDataType rowType;
     public final int i;
+    public final String name;
     final StructKind kind;
 
-    Step(Path parent, RelDataType rowType, int i, StructKind kind) {
+    Step(Path parent, RelDataType rowType, int i, String name,
+        StructKind kind) {
       this.parent = Preconditions.checkNotNull(parent);
       this.rowType = rowType; // may be null
       this.i = i;
+      this.name = name;
       this.kind = Preconditions.checkNotNull(kind);
     }
 
@@ -241,21 +281,17 @@ public interface SqlValidatorScope {
    * {@link org.apache.calcite.sql.validate.SqlValidatorScope.Resolved}. */
   class ResolvedImpl implements Resolved {
     final List<Resolve> resolves = new ArrayList<>();
-    private final EmptyPath emptyPath = new EmptyPath();
 
     public void found(SqlValidatorNamespace namespace, boolean nullable,
-        SqlValidatorScope scope, Path path) {
-      resolves.add(new Resolve(namespace, nullable, scope, path));
+        SqlValidatorScope scope, Path path, List<String> remainingNames) {
+      resolves.add(
+          new Resolve(namespace, nullable, scope, path, remainingNames));
     }
 
     public int count() {
       return resolves.size();
     }
 
-    public Path emptyPath() {
-      return emptyPath;
-    }
-
     public Resolve only() {
       return Iterables.getOnlyElement(resolves);
     }
@@ -273,13 +309,17 @@ public interface SqlValidatorScope {
     private final boolean nullable;
     public final SqlValidatorScope scope; // may be null
     public final Path path;
+    /** Names not matched; empty if it was a full match. */
+    final List<String> remainingNames;
 
     Resolve(SqlValidatorNamespace namespace, boolean nullable,
-        SqlValidatorScope scope, Path path) {
+        SqlValidatorScope scope, Path path, List<String> remainingNames) {
       this.namespace = Preconditions.checkNotNull(namespace);
       this.nullable = nullable;
       this.scope = scope;
       this.path = Preconditions.checkNotNull(path);
+      this.remainingNames = remainingNames == null ? ImmutableList.<String>of()
+          : ImmutableList.copyOf(remainingNames);
     }
 
     /** The row type of the found namespace, nullable if the lookup has

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index c4b07f1..34149f9 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -108,14 +108,7 @@ public class SqlValidatorUtil {
     return table;
   }
 
-  /**
-   * Looks up a field with a given name, returning null if not found.
-   *
-   * @param caseSensitive Whether match is case-sensitive
-   * @param rowType    Row type
-   * @param columnName Field name
-   * @return Field, or null if not found
-   */
+  @Deprecated // to be removed before 2.0
   public static RelDataTypeField lookupField(boolean caseSensitive,
       final RelDataType rowType, String columnName) {
     return rowType.getField(columnName, caseSensitive, false);
@@ -452,7 +445,8 @@ public class SqlValidatorUtil {
       RelOptTable table) {
     final Table t = table == null ? null : table.unwrap(Table.class);
     if (!(t instanceof CustomColumnResolvingTable)) {
-      return catalogReader.field(rowType, id.getSimple());
+      final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+      return nameMatcher.field(rowType, id.getSimple());
     }
 
     final List<Pair<RelDataTypeField, List<String>>> entries =
@@ -478,9 +472,11 @@ public class SqlValidatorUtil {
       SqlValidatorScope scope,
       List<String> names) {
     assert names.size() > 0;
+    final SqlNameMatcher nameMatcher =
+        scope.getValidator().getCatalogReader().nameMatcher();
     final SqlValidatorScope.ResolvedImpl resolved =
         new SqlValidatorScope.ResolvedImpl();
-    scope.resolve(ImmutableList.of(names.get(0)), false, resolved);
+    scope.resolve(ImmutableList.of(names.get(0)), nameMatcher, false, resolved);
     assert resolved.count() == 1;
     SqlValidatorNamespace namespace = resolved.only().namespace;
     for (String name : Util.skip(names)) {
@@ -498,15 +494,10 @@ public class SqlValidatorUtil {
     List<String> subNames = Util.skipLast(names);
 
     // Try successively with catalog.schema, catalog and no prefix
-    List<String> x = catalogReader.getSchemaName();
-    for (;;) {
+    for (List<String> x : catalogReader.getSchemaPaths()) {
       final List<String> names2 =
           ImmutableList.<String>builder().addAll(x).addAll(subNames).build();
       hints.addAll(catalogReader.getAllSchemaObjectNames(names2));
-      if (x.isEmpty()) {
-        break;
-      }
-      x = Util.skipLast(x);
     }
   }
 
@@ -689,9 +680,12 @@ public class SqlValidatorUtil {
       String originalRelName = expr.names.get(0);
       String originalFieldName = expr.names.get(1);
 
+      final SqlNameMatcher nameMatcher =
+          scope.getValidator().getCatalogReader().nameMatcher();
       final SqlValidatorScope.ResolvedImpl resolved =
           new SqlValidatorScope.ResolvedImpl();
-      scope.resolve(ImmutableList.of(originalRelName), false, resolved);
+      scope.resolve(ImmutableList.of(originalRelName), nameMatcher, false,
+          resolved);
 
       assert resolved.count() == 1;
       final SqlValidatorScope.Resolve resolve = resolved.only();
@@ -714,9 +708,7 @@ public class SqlValidatorUtil {
         }
       }
 
-      RelDataTypeField field =
-          scope.getValidator().getCatalogReader().field(rowType,
-              originalFieldName);
+      RelDataTypeField field = nameMatcher.field(rowType, originalFieldName);
       int origPos = namespaceOffset + field.getIndex();
 
       groupExprProjection.put(origPos, ref);
@@ -835,6 +827,7 @@ public class SqlValidatorUtil {
 
     /** Copies a list of nodes. */
     public static SqlNodeList copy(SqlValidatorScope scope, SqlNodeList list) {
+      //noinspection deprecation
       return (SqlNodeList) list.accept(new DeepCopier(scope));
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
index 4d7bd65..8a6f29d 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
@@ -54,17 +54,30 @@ class WithScope extends ListScope {
     return super.getTableNamespace(names);
   }
 
-  @Override public void resolve(List<String> names, boolean deep,
-      Resolved resolved) {
+  @Override public void resolveTable(List<String> names,
+      SqlNameMatcher nameMatcher, Path path, Resolved resolved) {
     if (names.size() == 1
         && names.equals(withItem.name.names)) {
       final SqlValidatorNamespace ns = validator.getNamespace(withItem);
-      final Step path = resolved.emptyPath()
-          .add(ns.getRowType(), 0, StructKind.FULLY_QUALIFIED);
-      resolved.found(ns, false, null, path);
+      final Step path2 = path
+          .plus(ns.getRowType(), 0, names.get(0), StructKind.FULLY_QUALIFIED);
+      resolved.found(ns, false, null, path2, null);
       return;
     }
-    super.resolve(names, deep, resolved);
+    super.resolveTable(names, nameMatcher, path, resolved);
+  }
+
+  @Override public void resolve(List<String> names, SqlNameMatcher nameMatcher,
+      boolean deep, Resolved resolved) {
+    if (names.size() == 1
+        && names.equals(withItem.name.names)) {
+      final SqlValidatorNamespace ns = validator.getNamespace(withItem);
+      final Step path = Path.EMPTY.plus(ns.getRowType(), 0, names.get(0),
+          StructKind.FULLY_QUALIFIED);
+      resolved.found(ns, false, null, path, null);
+      return;
+    }
+    super.resolve(names, nameMatcher, deep, resolved);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index fb27449..857eb7c 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -142,6 +142,7 @@ import org.apache.calcite.sql.validate.ListScope;
 import org.apache.calcite.sql.validate.ParameterScope;
 import org.apache.calcite.sql.validate.SelectScope;
 import org.apache.calcite.sql.validate.SqlMonotonicity;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
 import org.apache.calcite.sql.validate.SqlQualified;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro;
@@ -434,7 +435,7 @@ public class SqlToRelConverter {
         validator.getTypeFactory().createStructType(
             Pair.right(validatedFields),
             SqlValidatorUtil.uniquify(Pair.left(validatedFields),
-                catalogReader.isCaseSensitive()));
+                catalogReader.nameMatcher().isCaseSensitive()));
 
     final List<RelDataTypeField> convertedFields =
         result.getRowType().getFieldList().subList(0, validatedFields.size());
@@ -2202,10 +2203,12 @@ public class SqlToRelConverter {
       String originalRelName = lookup.getOriginalRelName();
       String originalFieldName = fieldAccess.getField().getName();
 
-      SqlValidatorScope.ResolvedImpl resolved =
+      final SqlNameMatcher nameMatcher =
+          lookup.bb.scope.getValidator().getCatalogReader().nameMatcher();
+      final SqlValidatorScope.ResolvedImpl resolved =
           new SqlValidatorScope.ResolvedImpl();
-      lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false,
-          resolved);
+      lookup.bb.scope.resolve(ImmutableList.of(originalRelName),
+          nameMatcher, false, resolved);
       assert resolved.count() == 1;
       final SqlValidatorScope.Resolve resolve = resolved.only();
       final SqlValidatorNamespace foundNs = resolve.namespace;
@@ -2308,10 +2311,12 @@ public class SqlToRelConverter {
       DeferredLookup lookup = mapCorrelToDeferred.get(correlName);
       String originalRelName = lookup.getOriginalRelName();
 
+      final SqlNameMatcher nameMatcher =
+          lookup.bb.scope.getValidator().getCatalogReader().nameMatcher();
       final SqlValidatorScope.ResolvedImpl resolved =
           new SqlValidatorScope.ResolvedImpl();
-      lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false,
-          resolved);
+      lookup.bb.scope.resolve(ImmutableList.of(originalRelName), nameMatcher,
+          false, resolved);
 
       SqlValidatorScope ancestorScope = resolved.only().scope;
 
@@ -2385,6 +2390,7 @@ public class SqlToRelConverter {
   private RexNode convertUsing(SqlValidatorNamespace leftNamespace,
       SqlValidatorNamespace rightNamespace,
       List<String> nameList) {
+    final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
     final List<RexNode> list = Lists.newArrayList();
     for (String name : nameList) {
       List<RexNode> operands = new ArrayList<>();
@@ -2392,7 +2398,7 @@ public class SqlToRelConverter {
       for (SqlValidatorNamespace n : ImmutableList.of(leftNamespace,
           rightNamespace)) {
         final RelDataType rowType = n.getRowType();
-        final RelDataTypeField field = catalogReader.field(rowType, name);
+        final RelDataTypeField field = nameMatcher.field(rowType, name);
         operands.add(
             rexBuilder.makeInputRef(field.getType(),
                 offset + field.getIndex()));
@@ -3064,8 +3070,9 @@ public class SqlToRelConverter {
     // expression list according to the ordinal value returned from
     // the table construct, leaving nulls in the list for columns
     // that are not referenced.
+    final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
     for (Pair<String, RexNode> p : Pair.zip(targetColumnNames, columnExprs)) {
-      RelDataTypeField field = catalogReader.field(targetRowType, p.left);
+      RelDataTypeField field = nameMatcher.field(targetRowType, p.left);
       assert field != null : "column " + p.left + " not found";
       sourceExps.set(field.getIndex(), p.right);
     }
@@ -3505,7 +3512,8 @@ public class SqlToRelConverter {
       fieldNames.add(deriveAlias(expr, aliases, i));
     }
 
-    fieldNames = SqlValidatorUtil.uniquify(fieldNames, catalogReader.isCaseSensitive());
+    fieldNames = SqlValidatorUtil.uniquify(fieldNames,
+        catalogReader.nameMatcher().isCaseSensitive());
 
     bb.setRoot(
         RelOptUtil.createProject(bb.root, exprs, fieldNames),
@@ -3902,9 +3910,11 @@ public class SqlToRelConverter {
         }
         return Pair.of(node, null);
       }
+      final SqlNameMatcher nameMatcher =
+          scope.getValidator().getCatalogReader().nameMatcher();
       final SqlValidatorScope.ResolvedImpl resolved =
           new SqlValidatorScope.ResolvedImpl();
-      scope.resolve(qualified.prefix(), false, resolved);
+      scope.resolve(qualified.prefix(), nameMatcher, false, resolved);
       if (!(resolved.count() == 1)) {
         return null;
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java b/core/src/main/java/org/apache/calcite/util/Util.java
index 4117a8d..fee522d 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -2358,6 +2358,29 @@ public class Util {
     return "".equals(v) || "true".equalsIgnoreCase(v);
   }
 
+  /** Returns a copy of a list of lists, making the component lists immutable if
+   * they are not already. */
+  public static <E> List<List<E>>
+  immutableCopy(Iterable<? extends Iterable<E>> lists) {
+    int n = 0;
+    for (Iterable<E> list : lists) {
+      if (!(list instanceof ImmutableList)) {
+        ++n;
+      }
+    }
+    if (n == 0) {
+      // Lists are already immutable. Furthermore, if the outer list is
+      // immutable we will just return "lists" unchanged.
+      return ImmutableList.copyOf((Iterable) lists);
+    }
+    final ImmutableList.Builder<List<E>> builder =
+        ImmutableList.builder();
+    for (Iterable<E> list : lists) {
+      builder.add(ImmutableList.copyOf(list));
+    }
+    return builder.build();
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 5a278b6..7be1266 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -57,9 +57,17 @@ DuplicateTargetColumn=Target column ''{0}'' is assigned more than once
 UnmatchInsertColumn=Number of INSERT target columns ({0,number}) does not equal number of source items ({1,number})
 TypeNotAssignable=Cannot assign to target field ''{0}'' of type {1} from source field ''{2}'' of type {3}
 TableNameNotFound=Table ''{0}'' not found
+TableNotFound=Table ''{0}'' not found
+TableNameNotFoundDidYouMean=Table ''{0}'' not found; did you mean ''{1}''?
+ObjectNotFound=Object ''{0}'' not found
+ObjectNotFoundWithin=Object ''{0}'' not found within ''{1}''
+ObjectNotFoundDidYouMean=Object ''{0}'' not found; did you mean ''{1}''?
+ObjectNotFoundWithinDidYouMean=Object ''{0}'' not found within ''{1}''; did you mean ''{2}''?
 NotASequence=Table ''{0}'' is not a sequence
 ColumnNotFound=Column ''{0}'' not found in any table
+ColumnNotFoundDidYouMean=Column ''{0}'' not found in any table; did you mean ''{1}''?
 ColumnNotFoundInTable=Column ''{0}'' not found in table ''{1}''
+ColumnNotFoundInTableDidYouMean=Column ''{0}'' not found in table ''{1}''; did you mean ''{2}''?
 ColumnAmbiguous=Column ''{0}'' is ambiguous
 NeedQueryOp=Operand {0} must be a query
 NeedSameTypeParameter=Parameters must be of the same type
@@ -199,7 +207,6 @@ CannotStreamValues=Cannot stream VALUES
 ModifiableViewMustBeBasedOnSingleTable=Modifiable view must be based on a single table
 MoreThanOneMappedColumn=View is not modifiable. More than one expression maps to column ''{0}'' of base table ''{1}''
 NoValueSuppliedForViewColumn=View is not modifiable. No value is supplied for NOT NULL column ''{0}'' of base table ''{1}''
-TableNotFound=Table ''{0}'' not found
 StarRequiresRecordType=Not a record type. The ''*'' operator requires a record
 FilterMustBeBoolean=FILTER expression must be of type BOOLEAN
 CannotStreamResultsForNonStreamingInputs=Cannot stream results of a query with no streaming inputs: ''{0}''. At least one input should be convertible to a stream

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java b/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
index e760cd0..ddc997d 100644
--- a/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
+++ b/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
@@ -32,6 +32,7 @@ import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
 import org.apache.calcite.util.Smalls;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
 import org.junit.Test;
@@ -141,8 +142,8 @@ public class LookupOperatorOverloadsTest {
           statement.createPrepareContext();
       final JavaTypeFactory typeFactory = prepareContext.getTypeFactory();
       CalciteCatalogReader reader =
-          new CalciteCatalogReader(prepareContext.getRootSchema(), false, null,
-              typeFactory);
+          new CalciteCatalogReader(prepareContext.getRootSchema(), false,
+              ImmutableList.<String>of(), typeFactory);
 
       final List<SqlOperator> operatorList = new ArrayList<>();
       SqlIdentifier myFuncIdentifier =

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/sql/test/DefaultSqlTestFactory.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/DefaultSqlTestFactory.java b/core/src/test/java/org/apache/calcite/sql/test/DefaultSqlTestFactory.java
index be651ff..63c1cdd 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/DefaultSqlTestFactory.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/DefaultSqlTestFactory.java
@@ -16,15 +16,15 @@
  */
 package org.apache.calcite.sql.test;
 
+import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.avatica.util.Casing;
 import org.apache.calcite.avatica.util.Quoting;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.advise.SqlAdvisor;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParser;
-import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.sql.validate.SqlValidator;
@@ -90,8 +90,8 @@ public class DefaultSqlTestFactory implements SqlTestFactory {
     final boolean caseSensitive = (Boolean) factory.get("caseSensitive");
     final SqlConformance conformance =
         (SqlConformance) factory.get("conformance");
-    final RelDataTypeFactory typeFactory =
-        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final JavaTypeFactory typeFactory =
+        new JavaTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
     return SqlValidatorUtil.newValidator(operatorTable,
         new MockCatalogReader(typeFactory, caseSensitive).init(),
         typeFactory, conformance);

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 326a460..24a4626 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -37,10 +37,12 @@ import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeSet;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -65,6 +67,7 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
 
   protected static final List<String> SALES_TABLES =
       Arrays.asList(
+          "SCHEMA(CATALOG.SALES)",
           "TABLE(CATALOG.SALES.EMP)",
           "TABLE(CATALOG.SALES.EMP_B)",
           "TABLE(CATALOG.SALES.EMP_20)",
@@ -359,8 +362,8 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
       String sql,
       List<String>... expectedLists) throws Exception {
     List<String> expectedList = plus(expectedLists);
-    Collections.sort(expectedList);
-    assertHint(sql, toString(expectedList));
+    final String expected = toString(new TreeSet<>(expectedList));
+    assertHint(sql, expected);
   }
 
   /**
@@ -410,8 +413,7 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
       String sql,
       List<String>... expectedResults) {
     List<String> expectedList = plus(expectedResults);
-    Collections.sort(expectedList);
-    String expected = toString(expectedList);
+    String expected = toString(new TreeSet<>(expectedList));
     assertComplete(sql, expected, null);
   }
 
@@ -480,7 +482,7 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
    * @param list List
    * @return String with one item of the list per line
    */
-  private static <T> String toString(List<T> list) {
+  private static <T> String toString(Collection<T> list) {
     StringBuilder buf = new StringBuilder();
     for (T t : list) {
       buf.append(t).append("\n");

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/CollectionTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CollectionTypeTest.java b/core/src/test/java/org/apache/calcite/test/CollectionTypeTest.java
index 2a2c52b..23d51db 100644
--- a/core/src/test/java/org/apache/calcite/test/CollectionTypeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/CollectionTypeTest.java
@@ -387,6 +387,7 @@ public class CollectionTypeTest {
           .build();
     }
 
+
     public Statistic getStatistic() {
       return Statistics.UNKNOWN;
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java b/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java
index 59a3bfa..a2ec482 100644
--- a/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java
@@ -141,7 +141,7 @@ public class ExceptionMessageTest {
       fail("Query should fail");
     } catch (SQLException e) {
       assertThat(e.getMessage(),
-          containsString("Table 'nonexistentTable' not found"));
+          containsString("Object 'nonexistentTable' not found"));
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
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 126df14..25d3dcf 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -5192,7 +5192,7 @@ public class JdbcTest {
             + "empid=150; deptno=10; name=Sebastian; salary=7000.0; commission=null\n"
             + "empid=110; deptno=10; name=Theodore; salary=11500.0; commission=250\n");
     that.query("select * from \"adhoc\".EMPLOYEES")
-        .throws_("Table 'adhoc.EMPLOYEES' not found");
+        .throws_("Object 'EMPLOYEES' not found within 'adhoc'");
   }
 
   /** Test case for
@@ -6113,7 +6113,15 @@ public class JdbcTest {
     final CalciteAssert.AssertThat with2 =
         CalciteAssert.that().with(Lex.JAVA);
     with2.query("select COUNT(*) as c from `metaData`.`tAbles`")
-        .throws_("Table 'metaData.tAbles' not found");
+        .throws_("Object 'metaData' not found; did you mean 'metadata'?");
+    with2.query("select COUNT(*) as c from `metaData`.`TABLES`")
+        .throws_("Object 'metaData' not found; did you mean 'metadata'?");
+    with2.query("select COUNT(*) as c from `metaData`.`tables`")
+        .throws_("Object 'metaData' not found; did you mean 'metadata'?");
+    with2.query("select COUNT(*) as c from `metaData`.`nonExistent`")
+        .throws_("Object 'metaData' not found; did you mean 'metadata'?");
+    with2.query("select COUNT(*) as c from `metadata`.`tAbles`")
+        .throws_("Object 'tAbles' not found within 'metadata'; did you mean 'TABLES'?");
   }
 
   /** Test case for
@@ -6126,7 +6134,7 @@ public class JdbcTest {
     // With [CALCITE-1563], the following query succeeded; it queried
     // metadata.tables.
     with.query("select COUNT(*) as c from `metaData`.`zoo`")
-        .throws_("Table 'metaData.zoo' not found");
+        .throws_("Object 'zoo' not found within 'metadata'");
     with.query("select COUNT(*) as c from `metaData`.`tAbLes`")
         .returns("c=2\n");
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/LatticeTest.java b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
index be87b1c..54f4ea7 100644
--- a/core/src/test/java/org/apache/calcite/test/LatticeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
@@ -172,7 +172,7 @@ public class LatticeTest {
   @Test public void testLatticeInvalidSqlFails() {
     modelWithLattice("star", "select foo from nonexistent")
         .connectThrows("Error instantiating JsonLattice(name=star, ")
-        .connectThrows("Table 'NONEXISTENT' not found");
+        .connectThrows("Object 'NONEXISTENT' not found");
   }
 
   /** Tests a lattice whose SQL is invalid because it contains a GROUP BY. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index a21218b..18ce756 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -16,13 +16,14 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.QueryProvider;
 import org.apache.calcite.linq4j.Queryable;
 import org.apache.calcite.linq4j.tree.Expression;
-import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.prepare.CalciteCatalogReader;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollations;
@@ -54,25 +55,24 @@ import org.apache.calcite.schema.Path;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.Schemas;
+import org.apache.calcite.schema.Statistic;
+import org.apache.calcite.schema.StreamableTable;
 import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.Wrapper;
+import org.apache.calcite.schema.impl.AbstractSchema;
 import org.apache.calcite.sql.SqlAccessType;
 import org.apache.calcite.sql.SqlCollation;
-import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlIntervalQualifier;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.ObjectSqlType;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlModality;
-import org.apache.calcite.sql.validate.SqlMoniker;
-import org.apache.calcite.sql.validate.SqlMonikerImpl;
-import org.apache.calcite.sql.validate.SqlMonikerType;
 import org.apache.calcite.sql.validate.SqlMonotonicity;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
 import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
-import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Litmus;
@@ -82,7 +82,6 @@ import org.apache.calcite.util.Util;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
 
 import java.lang.reflect.Type;
@@ -105,22 +104,15 @@ import java.util.Set;
  * Also two streams "ORDERS", "SHIPMENTS";
  * and a view "EMP_20".
  */
-public class MockCatalogReader implements Prepare.CatalogReader {
+public class MockCatalogReader extends CalciteCatalogReader {
   //~ Static fields/initializers ---------------------------------------------
 
-  protected static final String DEFAULT_CATALOG = "CATALOG";
-  protected static final String DEFAULT_SCHEMA = "SALES";
-
-  public static final Ordering<Iterable<String>>
-  CASE_INSENSITIVE_LIST_COMPARATOR =
-      Ordering.from(String.CASE_INSENSITIVE_ORDER).lexicographical();
+  static final String DEFAULT_CATALOG = "CATALOG";
+  static final String DEFAULT_SCHEMA = "SALES";
+  static final List<String> PREFIX = ImmutableList.of(DEFAULT_SCHEMA);
 
   //~ Instance fields --------------------------------------------------------
 
-  protected final RelDataTypeFactory typeFactory;
-  private final boolean caseSensitive;
-  private final Map<List<String>, MockTable> tables;
-  protected final Map<String, MockSchema> schemas;
   private RelDataType addressType;
 
   //~ Constructors -----------------------------------------------------------
@@ -134,19 +126,18 @@ public class MockCatalogReader implements Prepare.CatalogReader {
    */
   public MockCatalogReader(RelDataTypeFactory typeFactory,
       boolean caseSensitive) {
-    this.typeFactory = typeFactory;
-    this.caseSensitive = caseSensitive;
-    if (caseSensitive) {
-      tables = Maps.newHashMap();
-      schemas = Maps.newHashMap();
-    } else {
-      tables = Maps.newTreeMap(CASE_INSENSITIVE_LIST_COMPARATOR);
-      schemas = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
-    }
+    super(CalciteSchema.createRootSchema(false, true, DEFAULT_CATALOG),
+        SqlNameMatchers.withCaseSensitive(caseSensitive),
+        ImmutableList.of(PREFIX, ImmutableList.<String>of()),
+        typeFactory);
   }
 
   @Override public boolean isCaseSensitive() {
-    return caseSensitive;
+    return nameMatcher.isCaseSensitive();
+  }
+
+  public SqlNameMatcher nameMatcher() {
+    return nameMatcher;
   }
 
   /**
@@ -449,55 +440,26 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
   //~ Methods ----------------------------------------------------------------
 
-  public void lookupOperatorOverloads(SqlIdentifier opName,
-      SqlFunctionCategory category, SqlSyntax syntax,
-      List<SqlOperator> operatorList) {
-  }
-
-  public List<SqlOperator> getOperatorList() {
-    return ImmutableList.of();
-  }
-
-  public Prepare.CatalogReader withSchemaPath(List<String> schemaPath) {
-    return this;
-  }
-
-  public Prepare.PreparingTable getTableForMember(List<String> names) {
-    return getTable(names);
-  }
-
-  public RelDataTypeFactory getTypeFactory() {
-    return typeFactory;
-  }
-
-  public void registerRules(RelOptPlanner planner) {
-  }
-
-  protected void registerTable(MockTable table) {
+  protected void registerTable(final MockTable table) {
     table.onRegister(typeFactory);
-    tables.put(table.getQualifiedName(), table);
+    assert table.names.get(0).equals(DEFAULT_CATALOG);
+    final CalciteSchema schema =
+        rootSchema.getSubSchema(table.names.get(1), true);
+    final WrapperTable wrapperTable = new WrapperTable(table);
+    if (table.stream) {
+      schema.add(table.names.get(2),
+          new StreamableWrapperTable(table) {
+            public Table stream() {
+              return wrapperTable;
+            }
+          });
+    } else {
+      schema.add(table.names.get(2), wrapperTable);
+    }
   }
 
   protected void registerSchema(MockSchema schema) {
-    schemas.put(schema.name, schema);
-  }
-
-  public Prepare.PreparingTable getTable(final List<String> names) {
-    switch (names.size()) {
-    case 1:
-      // assume table in SALES schema (the original default)
-      // if it's not supplied, because SqlValidatorTest is effectively
-      // using SALES as its default schema.
-      return tables.get(
-          ImmutableList.of(DEFAULT_CATALOG, DEFAULT_SCHEMA, names.get(0)));
-    case 2:
-      return tables.get(
-          ImmutableList.of(DEFAULT_CATALOG, names.get(0), names.get(1)));
-    case 3:
-      return tables.get(names);
-    default:
-      return null;
-    }
+    rootSchema.add(schema.name, new AbstractSchema());
   }
 
   public RelDataType getNamedType(SqlIdentifier typeName) {
@@ -508,65 +470,6 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     }
   }
 
-  public List<SqlMoniker> getAllSchemaObjectNames(List<String> names) {
-    List<SqlMoniker> result;
-    switch (names.size()) {
-    case 0:
-      // looking for catalog and schema names
-      return ImmutableList.<SqlMoniker>builder()
-          .add(new SqlMonikerImpl(DEFAULT_CATALOG, SqlMonikerType.CATALOG))
-          .addAll(getAllSchemaObjectNames(ImmutableList.of(DEFAULT_CATALOG)))
-          .build();
-    case 1:
-      // looking for schema names
-      result = Lists.newArrayList();
-      for (MockSchema schema : schemas.values()) {
-        final String catalogName = names.get(0);
-        if (schema.getCatalogName().equals(catalogName)) {
-          final ImmutableList<String> names1 =
-              ImmutableList.of(catalogName, schema.name);
-          result.add(new SqlMonikerImpl(names1, SqlMonikerType.SCHEMA));
-        }
-      }
-      return result;
-    case 2:
-      // looking for table names in the given schema
-      MockSchema schema = schemas.get(names.get(1));
-      if (schema == null) {
-        return Collections.emptyList();
-      }
-      result = Lists.newArrayList();
-      for (String tableName : schema.tableNames) {
-        result.add(
-            new SqlMonikerImpl(
-                ImmutableList.of(schema.getCatalogName(), schema.name,
-                    tableName),
-                SqlMonikerType.TABLE));
-      }
-      return result;
-    default:
-      return Collections.emptyList();
-    }
-  }
-
-  public List<String> getSchemaName() {
-    return ImmutableList.of(DEFAULT_CATALOG, DEFAULT_SCHEMA);
-  }
-
-  public RelDataTypeField field(RelDataType rowType, String alias) {
-    return SqlValidatorUtil.lookupField(caseSensitive, rowType, alias);
-  }
-
-  public boolean matches(String string, String name) {
-    return Util.matches(caseSensitive, string, name);
-  }
-
-  public RelDataType createTypeFromProjection(final RelDataType type,
-      final List<String> columnNameList) {
-    return SqlValidatorUtil.createTypeFromProjection(type, columnNameList,
-        typeFactory, caseSensitive);
-  }
-
   private static List<RelCollation> deduceMonotonicity(
       Prepare.PreparingTable table) {
     final List<RelCollation> collationList = Lists.newArrayList();
@@ -1197,6 +1100,63 @@ public class MockCatalogReader implements Prepare.CatalogReader {
           });
     }
   }
+
+  /** Wrapper around a {@link MockTable}, giving it a {@link Table} interface.
+   * You can get the {@code MockTable} by calling {@link #unwrap(Class)}. */
+  private static class WrapperTable implements Table, Wrapper {
+    private final MockTable table;
+
+    WrapperTable(MockTable table) {
+      this.table = table;
+    }
+
+    public <C> C unwrap(Class<C> aClass) {
+      return aClass.isInstance(this) ? aClass.cast(this)
+          : aClass.isInstance(table) ? aClass.cast(table)
+          : null;
+    }
+
+    public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return table.getRowType();
+    }
+
+    public Statistic getStatistic() {
+      return new Statistic() {
+        public Double getRowCount() {
+          return table.rowCount;
+        }
+
+        public boolean isKey(ImmutableBitSet columns) {
+          return table.isKey(columns);
+        }
+
+        public List<RelCollation> getCollations() {
+          return table.collationList;
+        }
+
+        public RelDistribution getDistribution() {
+          return table.getDistribution();
+        }
+      };
+    }
+
+    public Schema.TableType getJdbcTableType() {
+      return table.stream ? Schema.TableType.STREAM : Schema.TableType.TABLE;
+    }
+  }
+
+  /** Wrapper around a {@link MockTable}, giving it a {@link StreamableTable}
+   * interface. */
+  private static class StreamableWrapperTable extends WrapperTable
+      implements StreamableTable {
+    StreamableWrapperTable(MockTable table) {
+      super(table);
+    }
+
+    public Table stream() {
+      return this;
+    }
+  }
 }
 
 // End MockCatalogReader.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java b/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
index bf2ad40..80ee5b1 100644
--- a/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
@@ -196,7 +196,7 @@ public class MultiJdbcSchemaJoinTest {
       fail("expected error, got " + rs);
     } catch (SQLException e) {
       assertThat(e.getCause().getCause().getMessage(),
-          equalTo("Table 'DB.TABLE2' not found"));
+          equalTo("Object 'TABLE2' not found within 'DB'"));
     }
 
     stmt1.execute("create table table2(id varchar(10) not null primary key, "
@@ -209,7 +209,7 @@ public class MultiJdbcSchemaJoinTest {
       fail("expected error, got " + rs);
     } catch (SQLException e) {
       assertThat(e.getCause().getCause().getMessage(),
-          equalTo("Table 'DB.TABLE2' not found"));
+          equalTo("Object 'TABLE2' not found within 'DB'"));
     }
 
     // disable caching and table becomes visible

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java b/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
index da6a0f5..606f1d6 100644
--- a/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
@@ -621,7 +621,7 @@ public class ReflectiveSchemaTest {
     // BitSet is not a valid relation type. It's as if "bitSet" field does
     // not exist.
     with.query("select * from \"s\".\"bitSet\"")
-        .throws_("Table 's.bitSet' not found");
+        .throws_("Object 'bitSet' not found within 's'");
     // Enumerable field returns 3 records with 0 fields
     with.query("select * from \"s\".\"enumerable\"")
         .returns("\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
index 3cfbf35..7520b4a 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -254,7 +254,8 @@ public abstract class SqlToRelTestBase {
     }
 
     public RelOptTable getTableForMember(List<String> names) {
-      final SqlValidatorTable table = catalogReader.getTable(names);
+      final SqlValidatorTable table =
+          catalogReader.getTable(names);
       final RelDataType rowType = table.getRowType();
       final List<RelCollation> collationList = deduceMonotonicity(table);
       if (names.size() < 3) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 02d242b..898ce80 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -4661,7 +4661,11 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
    */
   @Test public void testStarInFromFails() {
     sql("select emp.empno AS x from ^sales.*^")
-        .fails("Table 'SALES.\\*' not found");
+        .fails("Object '\\*' not found within 'SALES'");
+    sql("select * from ^emp.*^")
+        .fails("Object '\\*' not found within 'SALES.EMP'");
+    sql("select emp.empno AS x from ^emp.*^")
+        .fails("Object '\\*' not found within 'SALES.EMP'");
     sql("select emp.empno from emp where emp.^*^ is not null")
         .fails("Unknown field '\\*'");
   }
@@ -5552,14 +5556,14 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "with emp3 as (select * from ^emp2^),\n"
             + " emp2 as (select * from emp)\n"
             + "select * from emp3",
-        "Table 'EMP2' not found");
+        "Object 'EMP2' not found");
 
     // forward reference in with-item not used; should still fail
     checkFails(
         "with emp3 as (select * from ^emp2^),\n"
             + " emp2 as (select * from emp)\n"
             + "select * from emp2",
-        "Table 'EMP2' not found");
+        "Object 'EMP2' not found");
 
     // table not used is ok
     checkResultType(
@@ -5572,11 +5576,11 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     checkFails("with emp2 as (select * from emp),\n"
             + " emp3 as (select * from ^emp3^)\n"
             + "values (1)",
-        "Table 'EMP3' not found");
+        "Object 'EMP3' not found");
 
     // self-reference not ok
     checkFails("with emp2 as (select * from ^emp2^)\n"
-        + "select * from emp2 where false", "Table 'EMP2' not found");
+        + "select * from emp2 where false", "Object 'EMP2' not found");
 
     // refer to 2 previous tables, not just immediately preceding
     checkResultType("with emp2 as (select * from emp),\n"
@@ -7097,7 +7101,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
             + " BOOLEAN NOT NULL SLACKER) NOT NULL";
     checkResultType("select * from (table emp)", empRecordType);
     checkResultType("table emp", empRecordType);
-    checkFails("table ^nonexistent^", "Table 'NONEXISTENT' not found");
+    checkFails("table ^nonexistent^", "Object 'NONEXISTENT' not found");
+    checkFails("table ^sales.nonexistent^",
+        "Object 'NONEXISTENT' not found within 'SALES'");
+    checkFails("table ^nonexistent.foo^", "Object 'NONEXISTENT' not found");
   }
 
   @Test public void testCollectionTable() {
@@ -7162,7 +7169,7 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
     checkFails(
         "select * from table(dedup(cursor(select * from ^bloop^),'ename'))",
-        "Table 'BLOOP' not found");
+        "Object 'BLOOP' not found");
   }
 
   @Test public void testScalarSubQuery() {
@@ -7483,11 +7490,16 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
 
     tester1.checkQueryFails(
         "select ^e^.EMPNO from [EMP] as [e]",
-        "Table 'E' not found");
+        "Table 'E' not found; did you mean 'e'\\?");
 
     tester1.checkQueryFails(
         "select ^x^ from (\n"
             + "  select [e].EMPNO as [x] from [EMP] as [e])",
+        "Column 'X' not found in any table; did you mean 'x'\\?");
+
+    tester1.checkQueryFails(
+        "select ^x^ from (\n"
+            + "  select [e].EMPNO as [x ] from [EMP] as [e])",
         "Column 'X' not found in any table");
 
     tester1.checkQueryFails("select EMP.^\"x\"^ from EMP",
@@ -7506,15 +7518,20 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
 
     tester1.checkQueryFails(
         "select ^e^.EMPNO from EMP as E",
-        "Table 'e' not found");
+        "Table 'e' not found; did you mean 'E'\\?");
 
     tester1.checkQueryFails(
         "select ^E^.EMPNO from EMP as e",
-        "Table 'E' not found");
+        "Table 'E' not found; did you mean 'e'\\?");
 
     tester1.checkQueryFails(
         "select ^x^ from (\n"
             + "  select e.EMPNO as X from EMP as e)",
+        "Column 'x' not found in any table; did you mean 'X'\\?");
+
+    tester1.checkQueryFails(
+        "select ^x^ from (\n"
+            + "  select e.EMPNO as Xx from EMP as e)",
         "Column 'x' not found in any table");
 
     // double-quotes are not valid in this lexical convention
@@ -7546,15 +7563,15 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "RecordType(INTEGER NOT NULL path, INTEGER NOT NULL x) NOT NULL");
     tester1.checkFails(
         "select ^PATH^ from (select 1 as path from (values (true)))",
-        "Column 'PATH' not found in any table",
+        "Column 'PATH' not found in any table; did you mean 'path'\\?",
         false);
     tester1.checkFails(
         "select t.^PATH^ from (select 1 as path from (values (true))) as t",
-        "Column 'PATH' not found in table 't'",
+        "Column 'PATH' not found in table 't'; did you mean 'path'\\?",
         false);
     tester1.checkQueryFails(
         "select t.x, t.^PATH^ from (values (true, 1)) as t(path, x)",
-        "Column 'PATH' not found in table 't'");
+        "Column 'PATH' not found in table 't'; did you mean 'path'\\?");
 
     // Built-in functions can be written in any case, even those with no args,
     // and regardless of spaces between function name and open parenthesis.
@@ -7598,7 +7615,7 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     tester2.checkQueryFails(
         "select * from emp as [e] where exists (\n"
             + "select 1 from dept where dept.deptno = ^[E]^.deptno)",
-        "(?s).*Table 'E' not found");
+        "(?s).*Table 'E' not found; did you mean 'e'\\?");
 
     checkFails("select count(1), ^empno^ from emp",
         "Expression 'EMPNO' is not being grouped");
@@ -7643,6 +7660,91 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     tester1.checkQuery("select deptno, count(*) from EMP group by DEPTNO");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1549">[CALCITE-1549]
+   * Improve error message when table or column not found</a>. */
+  @Test public void testTableNotFoundDidYouMean() {
+    // No table in default schema
+    tester.checkQueryFails("select * from ^unknownTable^",
+        "Object 'UNKNOWNTABLE' not found");
+
+    // Similar table exists in default schema
+    tester.checkQueryFails("select * from ^\"Emp\"^",
+        "Object 'Emp' not found within 'SALES'; did you mean 'EMP'\\?");
+
+    // Schema correct, but no table in specified schema
+    tester.checkQueryFails("select * from ^sales.unknownTable^",
+        "Object 'UNKNOWNTABLE' not found within 'SALES'");
+    // Similar table exists in specified schema
+    tester.checkQueryFails("select * from ^sales.\"Emp\"^",
+        "Object 'Emp' not found within 'SALES'; did you mean 'EMP'\\?");
+
+    // No schema found
+    tester.checkQueryFails("select * from ^unknownSchema.unknownTable^",
+        "Object 'UNKNOWNSCHEMA' not found");
+    // Similar schema found
+    tester.checkQueryFails("select * from ^\"sales\".emp^",
+        "Object 'sales' not found; did you mean 'SALES'\\?");
+    tester.checkQueryFails("select * from ^\"saLes\".\"eMp\"^",
+        "Object 'saLes' not found; did you mean 'SALES'\\?");
+
+    // Spurious after table
+    tester.checkQueryFails("select * from ^emp.foo^",
+        "Object 'FOO' not found within 'SALES\\.EMP'");
+    tester.checkQueryFails("select * from ^sales.emp.foo^",
+        "Object 'FOO' not found within 'SALES\\.EMP'");
+
+    // Alias not found
+    tester.checkQueryFails("select ^aliAs^.\"name\"\n"
+            + "from sales.emp as \"Alias\"",
+        "Table 'ALIAS' not found; did you mean 'Alias'\\?");
+    // Alias not found, fully-qualified
+    tester.checkQueryFails("select ^sales.\"emp\"^.\"name\" from sales.emp",
+        "Table 'SALES\\.emp' not found; did you mean 'EMP'\\?");
+  }
+
+  @Test public void testColumnNotFoundDidYouMean() {
+    // Column not found
+    tester.checkQueryFails("select ^\"unknownColumn\"^ from emp",
+        "Column 'unknownColumn' not found in any table");
+    // Similar column in table, unqualified table name
+    tester.checkQueryFails("select ^\"empNo\"^ from emp",
+        "Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
+    // Similar column in table, table name qualified with schema
+    tester.checkQueryFails("select ^\"empNo\"^ from sales.emp",
+        "Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
+    // Similar column in table, table name qualified with catalog and schema
+    tester.checkQueryFails("select ^\"empNo\"^ from catalog.sales.emp",
+        "Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
+    // With table alias
+    tester.checkQueryFails("select e.^\"empNo\"^ from catalog.sales.emp as e",
+        "Column 'empNo' not found in table 'E'; did you mean 'EMPNO'\\?");
+    // With fully-qualified table alias
+    tester.checkQueryFails("select catalog.sales.emp.^\"empNo\"^\n"
+            + "from catalog.sales.emp",
+        "Column 'empNo' not found in table 'CATALOG\\.SALES\\.EMP'; "
+            + "did you mean 'EMPNO'\\?");
+    // Similar column in table; multiple tables
+    tester.checkQueryFails("select ^\"name\"^ from emp, dept",
+        "Column 'name' not found in any table; did you mean 'NAME'\\?");
+    // Similar column in table; table and a query
+    tester.checkQueryFails("select ^\"name\"^ from emp,\n"
+            + "  (select * from dept) as d",
+        "Column 'name' not found in any table; did you mean 'NAME'\\?");
+    // Similar column in table; table and an un-aliased query
+    tester.checkQueryFails("select ^\"name\"^ from emp, (select * from dept)",
+        "Column 'name' not found in any table; did you mean 'NAME'\\?");
+    // Similar column in table, multiple tables
+    tester.checkQueryFails("select ^\"deptno\"^ from emp,\n"
+            + "  (select deptno as \"deptNo\" from dept)",
+        "Column 'deptno' not found in any table; "
+            + "did you mean 'DEPTNO', 'deptNo'\\?");
+    tester.checkQueryFails("select ^\"deptno\"^ from emp,\n"
+            + "  (select * from dept) as t(\"deptNo\", name)",
+        "Column 'deptno' not found in any table; "
+            + "did you mean 'DEPTNO', 'deptNo'\\?");
+  }
+
   /** Tests matching of built-in operator names. */
   @Test public void testUnquotedBuiltInFunctionNames() {
     final SqlTester mysql = tester

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 4b970ce..42f06c6 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -1285,7 +1285,8 @@ public class UtilTest {
     assertFalse(Util.isDistinct(Arrays.asList("a", null, "b", null)));
   }
 
-  /** Unit test for {@link Util#intersects(Collection, Collection)}. */
+  /** Unit test for
+   * {@link Util#intersects(java.util.Collection, java.util.Collection)}. */
   @Test public void testIntersects() {
     final List<String> empty = Collections.emptyList();
     final List<String> listA = Collections.singletonList("a");
@@ -1698,6 +1699,42 @@ public class UtilTest {
 
   }
 
+  /** Tests {@link Util#immutableCopy(Iterable)}. */
+  @Test public void testImmutableCopy() {
+    final List<Integer> list3 = Arrays.asList(1, 2, 3);
+    final List<Integer> immutableList3 = ImmutableList.copyOf(list3);
+    final List<Integer> list0 = Arrays.asList();
+    final List<Integer> immutableList0 = ImmutableList.copyOf(list0);
+    final List<Integer> list1 = Arrays.asList(1);
+    final List<Integer> immutableList1 = ImmutableList.copyOf(list1);
+
+    final List<List<Integer>> list301 = Arrays.asList(list3, list0, list1);
+    final List<List<Integer>> immutableList301 = Util.immutableCopy(list301);
+    assertThat(immutableList301.size(), is(3));
+    assertThat(immutableList301, is(list301));
+    assertThat(immutableList301, not(sameInstance(list301)));
+    for (List<Integer> list : immutableList301) {
+      assertThat(list, isA((Class) ImmutableList.class));
+    }
+
+    // if you copy the copy, you get the same instance
+    final List<List<Integer>> immutableList301b =
+        Util.immutableCopy(immutableList301);
+    assertThat(immutableList301b, sameInstance(immutableList301));
+    assertThat(immutableList301b, not(sameInstance(list301)));
+
+    // if the elements of the list are immutable lists, they are not copied
+    final List<List<Integer>> list301c =
+        Arrays.asList(immutableList3, immutableList0, immutableList1);
+    final List<List<Integer>> list301d = Util.immutableCopy(list301c);
+    assertThat(list301d.size(), is(3));
+    assertThat(list301d, is(list301));
+    assertThat(list301d, not(sameInstance(list301)));
+    assertThat(list301d.get(0), sameInstance(immutableList3));
+    assertThat(list301d.get(1), sameInstance(immutableList0));
+    assertThat(list301d.get(2), sameInstance(immutableList1));
+  }
+
   @Test public void testAsIndexView() {
     final List<String> values  = Lists.newArrayList("abCde", "X", "y");
     final Map<String, String> map = Util.asIndexMap(values,

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/test/resources/sql/misc.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.iq b/core/src/test/resources/sql/misc.iq
index 36a2f93..cc3a4af 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -64,6 +64,71 @@ group by "hr"."emps"."empid";
 
 !ok
 
+# Case-sensitive errors
+select empid from "hr"."emps";
+Column 'EMPID' not found in any table; did you mean 'empid'?
+!error
+
+select empid from "hr".emps;
+Object 'EMPS' not found within 'hr'; did you mean 'emps'?
+!error
+
+select empid from hr.emps;
+Object 'HR' not found; did you mean 'hr'?
+!error
+
+select empid from bad_schema.bad_table;
+Object 'BAD_SCHEMA' not found
+!error
+
+select empid from bad_cat.bad_schema.bad_table;
+Object 'BAD_CAT' not found
+!error
+
+select empid from "catalog".bad_schema.bad_table;
+Object 'catalog' not found
+!error
+
+select empid from catalog.bad_schema.bad_table;
+Object 'CATALOG' not found
+!error
+
+select empid from catalog.HR.bad_table;
+Object 'CATALOG' not found
+!error
+
+select empid from catalog."hr".bad_table;
+Object 'CATALOG' not found
+!error
+
+select empid from catalog."hr".emp;
+Object 'CATALOG' not found
+!error
+
+select empid from HR.bad_table;
+Object 'HR' not found; did you mean 'hr'?
+!error
+
+select empid from "HR".bad_table;
+Object 'HR' not found; did you mean 'hr'?
+!error
+
+select empid from HR."emps";
+Object 'HR' not found; did you mean 'hr'?
+!error
+
+select empid from "hr".bad_table;
+Object 'BAD_TABLE' not found within 'hr'
+!error
+
+select empid from "hr".emps;
+Object 'EMPS' not found within 'hr'; did you mean 'emps'?
+!error
+
+select empid from "hr";
+Object 'hr' not found
+!error
+
 # [CALCITE-307] CAST(timestamp AS DATE) gives ClassCastException
 # Based on [DRILL-1051]
 with data(c_row, c_timestamp) as (select * from (values


[2/4] calcite git commit: [CALCITE-1574] Memory leak in maven

Posted by jh...@apache.org.
[CALCITE-1574] Memory leak in maven

Upgrade maven-jar-plugin and maven-source-plugin to versions that
have fixed [MSOURCES-94].

Disable UdfTest.testUserDefinedFunction; logged
  [CALCITE-1561] Intermittent test failures
to remind us to re-enable.

Skip javadoc on Travis; with it, builds exceed Travis time limit.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/28ae333f
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/28ae333f
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/28ae333f

Branch: refs/heads/master
Commit: 28ae333f0c7174b758487dbb5ddef91dacacd213
Parents: 46474d7
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Jan 12 10:36:25 2017 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Jan 13 09:11:39 2017 -0800

----------------------------------------------------------------------
 .travis.yml                                      |  4 ++--
 .../java/org/apache/calcite/test/UdfTest.java    |  2 ++
 pom.xml                                          | 19 +++++++++++++++++++
 3 files changed, 23 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/28ae333f/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index b63f468..c8fdab8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,8 +31,8 @@ install:
   - cd avatica && mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && cd ..
   - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
 script:
-  - cd avatica && mvn -Dsurefire.useFile=false javadoc:javadoc test && cd ..
-  - mvn -Dsurefire.useFile=false javadoc:javadoc test
+  - cd avatica && mvn -Dsurefire.useFile=false test && cd ..
+  - mvn -Dsurefire.useFile=false test
 git:
   depth: 10000
 sudo: false

http://git-wip-us.apache.org/repos/asf/calcite/blob/28ae333f/core/src/test/java/org/apache/calcite/test/UdfTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/UdfTest.java b/core/src/test/java/org/apache/calcite/test/UdfTest.java
index 3372102..774edad 100644
--- a/core/src/test/java/org/apache/calcite/test/UdfTest.java
+++ b/core/src/test/java/org/apache/calcite/test/UdfTest.java
@@ -26,6 +26,7 @@ import org.apache.calcite.util.Smalls;
 
 import com.google.common.collect.ImmutableList;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.sql.Connection;
@@ -149,6 +150,7 @@ public class UdfTest {
 
   /** Tests a user-defined function that is defined in terms of a class with
    * non-static methods. */
+  @Ignore("[CALCITE-1561] Intermittent test failures")
   @Test public void testUserDefinedFunction() throws Exception {
     final String sql = "select \"adhoc\".my_plus(\"deptno\", 100) as p\n"
         + "from \"adhoc\".EMPLOYEES";

http://git-wip-us.apache.org/repos/asf/calcite/blob/28ae333f/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f728112..f196350 100644
--- a/pom.xml
+++ b/pom.xml
@@ -676,6 +676,20 @@ limitations under the License.
           <artifactId>maven-javadoc-plugin</artifactId>
           <version>${maven-javadoc-plugin.version}</version>
         </plugin>
+        <!-- Override base, which uses version 3.0.0. -->
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+          <configuration>
+            <archive>
+              <manifest>
+                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+              </manifest>
+            </archive>
+          </configuration>
+        </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
@@ -683,6 +697,11 @@ limitations under the License.
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-source-plugin</artifactId>
+          <version>3.0.1</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
           <configuration>
             <threadCount>1</threadCount>


[4/4] calcite git commit: [CALCITE-1549] More helpful error message when schema, table or column not found

Posted by jh...@apache.org.
[CALCITE-1549] More helpful error message when schema, table or column not found


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/5f9c0190
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/5f9c0190
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/5f9c0190

Branch: refs/heads/master
Commit: 5f9c019080c7231acaf3df80732d915351051d93
Parents: 28ae333
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Jan 3 14:45:27 2017 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Jan 13 09:12:17 2017 -0800

----------------------------------------------------------------------
 .../calcite/adapter/enumerable/RexImpTable.java |   5 +-
 .../org/apache/calcite/jdbc/CalciteSchema.java  |  17 +-
 .../calcite/jdbc/JavaTypeFactoryImpl.java       |  15 +-
 .../calcite/prepare/CalciteCatalogReader.java   | 164 ++++++++-----
 .../org/apache/calcite/prepare/Prepare.java     |   2 +-
 .../apache/calcite/prepare/RelOptTableImpl.java |   4 +-
 .../apache/calcite/runtime/CalciteResource.java |  32 ++-
 .../java/org/apache/calcite/schema/Table.java   |   4 +
 .../java/org/apache/calcite/schema/Wrapper.java |  27 +++
 .../org/apache/calcite/sql/SqlIdentifier.java   |  16 +-
 .../calcite/sql/validate/AbstractNamespace.java |   2 +-
 .../calcite/sql/validate/DelegatingScope.java   | 144 +++++++++---
 .../DelegatingSqlValidatorCatalogReader.java    |   4 +-
 .../apache/calcite/sql/validate/EmptyScope.java | 103 ++++++++-
 .../sql/validate/IdentifierNamespace.java       |  78 ++++++-
 .../apache/calcite/sql/validate/ListScope.java  |  70 ++++--
 .../calcite/sql/validate/OrderByScope.java      |   6 +-
 .../calcite/sql/validate/SqlNameMatcher.java    |  62 +++++
 .../calcite/sql/validate/SqlNameMatchers.java   | 154 +++++++++++++
 .../calcite/sql/validate/SqlQualified.java      |   5 +-
 .../sql/validate/SqlValidatorCatalogReader.java |  41 +++-
 .../calcite/sql/validate/SqlValidatorImpl.java  |  67 +++---
 .../calcite/sql/validate/SqlValidatorScope.java |  94 +++++---
 .../calcite/sql/validate/SqlValidatorUtil.java  |  33 ++-
 .../apache/calcite/sql/validate/WithScope.java  |  25 +-
 .../calcite/sql2rel/SqlToRelConverter.java      |  30 ++-
 .../main/java/org/apache/calcite/util/Util.java |  23 ++
 .../calcite/runtime/CalciteResource.properties  |   9 +-
 .../prepare/LookupOperatorOverloadsTest.java    |   5 +-
 .../calcite/sql/test/DefaultSqlTestFactory.java |   8 +-
 .../apache/calcite/sql/test/SqlAdvisorTest.java |  12 +-
 .../apache/calcite/test/CollectionTypeTest.java |   1 +
 .../calcite/test/ExceptionMessageTest.java      |   2 +-
 .../java/org/apache/calcite/test/JdbcTest.java  |  14 +-
 .../org/apache/calcite/test/LatticeTest.java    |   2 +-
 .../apache/calcite/test/MockCatalogReader.java  | 228 ++++++++-----------
 .../calcite/test/MultiJdbcSchemaJoinTest.java   |   4 +-
 .../calcite/test/ReflectiveSchemaTest.java      |   2 +-
 .../apache/calcite/test/SqlToRelTestBase.java   |   3 +-
 .../apache/calcite/test/SqlValidatorTest.java   | 130 +++++++++--
 .../java/org/apache/calcite/util/UtilTest.java  |  39 +++-
 core/src/test/resources/sql/misc.iq             |  65 ++++++
 42 files changed, 1335 insertions(+), 416 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index f06680b..3d87041 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -1661,8 +1661,9 @@ public class RexImpTable {
       assert translatedOperands.size() == 1;
       ConstantExpression x = (ConstantExpression) translatedOperands.get(0);
       List<String> names = Util.stringToList((String) x.value);
-      RelOptTable table =
-          Prepare.CatalogReader.THREAD_LOCAL.get().getTable(names);
+      final Prepare.CatalogReader catalogReader =
+          Prepare.CatalogReader.THREAD_LOCAL.get();
+      RelOptTable table = catalogReader.getTable(names);
       System.out.println("Now, do something with table " + table);
       return super.implement(translator, call, translatedOperands);
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
index 89e377d..cb61627 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
@@ -376,12 +376,25 @@ public abstract class CalciteSchema {
    */
   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) {
     CalciteSchema rootSchema;
     final Schema schema = new CalciteConnectionImpl.RootSchema();
     if (cache) {
-      rootSchema = new CachingCalciteSchema(null, schema, "");
+      rootSchema = new CachingCalciteSchema(null, schema, name);
     } else {
-      rootSchema = new SimpleCalciteSchema(null, schema, "");
+      rootSchema = new SimpleCalciteSchema(null, schema, name);
     }
     if (addMetadataSchema) {
       rootSchema.add("metadata", MetadataSchema.INSTANCE);

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
index a1f6e3f..c600d6f 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
@@ -22,6 +22,7 @@ import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.linq4j.tree.Types;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
@@ -233,19 +234,25 @@ public class JavaTypeFactoryImpl
   }
 
   public RelDataType toSql(RelDataType type) {
+    return toSql(this, type);
+  }
+
+  /** Converts a type in Java format to a SQL-oriented type. */
+  public static RelDataType toSql(final RelDataTypeFactory typeFactory,
+      RelDataType type) {
     if (type instanceof RelRecordType) {
-      return createStructType(
+      return typeFactory.createStructType(
           Lists.transform(type.getFieldList(),
               new Function<RelDataTypeField, RelDataType>() {
                 public RelDataType apply(RelDataTypeField a0) {
-                  return toSql(a0.getType());
+                  return toSql(typeFactory, a0.getType());
                 }
               }),
           type.getFieldNames());
     }
     if (type instanceof JavaType) {
-      return createTypeWithNullability(
-          createSqlType(type.getSqlTypeName()),
+      return typeFactory.createTypeWithNullability(
+          typeFactory.createSqlType(type.getSqlTypeName()),
           type.isNullable());
     }
     return type;

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
index 0e14c83..7f0823c 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -16,8 +16,8 @@
  */
 package org.apache.calcite.prepare;
 
-import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -30,6 +30,7 @@ import org.apache.calcite.schema.ScalarFunction;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.schema.TableFunction;
 import org.apache.calcite.schema.TableMacro;
+import org.apache.calcite.schema.Wrapper;
 import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlOperator;
@@ -45,6 +46,8 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlMoniker;
 import org.apache.calcite.sql.validate.SqlMonikerImpl;
 import org.apache.calcite.sql.validate.SqlMonikerType;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
 import org.apache.calcite.sql.validate.SqlUserDefinedAggFunction;
 import org.apache.calcite.sql.validate.SqlUserDefinedFunction;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
@@ -52,6 +55,7 @@ import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Util;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
@@ -61,6 +65,7 @@ import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
@@ -71,56 +76,74 @@ import java.util.NavigableSet;
  * functions defined schemas.
  */
 public class CalciteCatalogReader implements Prepare.CatalogReader {
-  final CalciteSchema rootSchema;
-  final JavaTypeFactory typeFactory;
-  private final List<String> defaultSchema;
-  private final boolean caseSensitive;
-
-  public CalciteCatalogReader(
-      CalciteSchema rootSchema,
-      boolean caseSensitive,
-      List<String> defaultSchema,
-      JavaTypeFactory typeFactory) {
-    super();
-    assert rootSchema != defaultSchema;
-    this.rootSchema = rootSchema;
-    this.caseSensitive = caseSensitive;
-    this.defaultSchema = defaultSchema;
+  protected final CalciteSchema rootSchema;
+  protected final RelDataTypeFactory typeFactory;
+  private final List<List<String>> schemaPaths;
+  protected final SqlNameMatcher nameMatcher;
+
+  public CalciteCatalogReader(CalciteSchema rootSchema, boolean caseSensitive,
+      List<String> defaultSchema, RelDataTypeFactory typeFactory) {
+    this(rootSchema, SqlNameMatchers.withCaseSensitive(caseSensitive),
+        ImmutableList.of(Preconditions.checkNotNull(defaultSchema),
+            ImmutableList.<String>of()),
+        typeFactory);
+  }
+
+  protected CalciteCatalogReader(CalciteSchema rootSchema,
+      SqlNameMatcher nameMatcher, List<List<String>> schemaPaths,
+      RelDataTypeFactory typeFactory) {
+    this.rootSchema = Preconditions.checkNotNull(rootSchema);
+    this.nameMatcher = nameMatcher;
+    this.schemaPaths =
+        Util.immutableCopy(Util.isDistinct(schemaPaths)
+            ? schemaPaths
+            : new LinkedHashSet<>(schemaPaths));
     this.typeFactory = typeFactory;
   }
 
   public CalciteCatalogReader withSchemaPath(List<String> schemaPath) {
-    return new CalciteCatalogReader(rootSchema, caseSensitive, schemaPath,
-        typeFactory);
+    return new CalciteCatalogReader(rootSchema, nameMatcher,
+        ImmutableList.of(schemaPath, ImmutableList.<String>of()), typeFactory);
   }
 
-  public RelOptTableImpl getTable(final List<String> names) {
+  public Prepare.PreparingTable getTable(final List<String> names) {
     // First look in the default schema, if any.
-    if (defaultSchema != null) {
-      RelOptTableImpl table = getTableFrom(names, defaultSchema);
+    // If not found, look in the root schema.
+    for (List<String> schemaPath : schemaPaths) {
+      Prepare.PreparingTable table =
+          getTableFrom(names, schemaPath, nameMatcher);
       if (table != null) {
         return table;
       }
     }
-    // If not found, look in the root schema
-    return getTableFrom(names, ImmutableList.<String>of());
+    return null;
   }
 
-  private RelOptTableImpl getTableFrom(List<String> names,
-      List<String> schemaNames) {
+  private Prepare.PreparingTable getTableFrom(List<String> names,
+      List<String> schemaNames, SqlNameMatcher nameMatcher) {
     CalciteSchema schema =
-        getSchema(Iterables.concat(schemaNames, Util.skipLast(names)));
+        getSchema(Iterables.concat(schemaNames, Util.skipLast(names)),
+            nameMatcher);
     if (schema == null) {
       return null;
     }
     final String name = Util.last(names);
-    CalciteSchema.TableEntry entry = schema.getTable(name, caseSensitive);
+    CalciteSchema.TableEntry entry =
+        schema.getTable(name, nameMatcher.isCaseSensitive());
     if (entry == null) {
-      entry = schema.getTableBasedOnNullaryFunction(name, caseSensitive);
+      entry = schema.getTableBasedOnNullaryFunction(name,
+          nameMatcher.isCaseSensitive());
     }
     if (entry != null) {
       final Table table = entry.getTable();
       final String name2 = entry.name;
+      if (table instanceof Wrapper) {
+        final Prepare.PreparingTable relOptTable =
+            ((Wrapper) table).unwrap(Prepare.PreparingTable.class);
+        if (relOptTable != null) {
+          return relOptTable;
+        }
+      }
       return RelOptTableImpl.create(this, table.getRowType(typeFactory),
           schema.add(name2, table), null);
     }
@@ -129,21 +152,27 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
 
   private Collection<Function> getFunctionsFrom(List<String> names) {
     final List<Function> functions2 = Lists.newArrayList();
-    final List<? extends List<String>> schemaNameList;
+    final List<List<String>> schemaNameList = new ArrayList<>();
     if (names.size() > 1) {
-      // If name is qualified, ignore path.
-      schemaNameList = ImmutableList.of(ImmutableList.<String>of());
-    } else {
-      CalciteSchema schema = getSchema(defaultSchema);
-      if (schema == null) {
-        schemaNameList = ImmutableList.of();
+      // Name qualified: ignore path. But we do look in "/catalog" and "/",
+      // the last 2 items in the path.
+      if (schemaPaths.size() > 1) {
+        schemaNameList.addAll(Util.skip(schemaPaths));
       } else {
-        schemaNameList = schema.getPath();
+        schemaNameList.addAll(schemaPaths);
+      }
+    } else {
+      for (List<String> schemaPath : schemaPaths) {
+        CalciteSchema schema = getSchema(schemaPath, nameMatcher);
+        if (schema != null) {
+          schemaNameList.addAll(schema.getPath());
+        }
       }
     }
     for (List<String> schemaNames : schemaNameList) {
       CalciteSchema schema =
-          getSchema(Iterables.concat(schemaNames, Util.skipLast(names)));
+          getSchema(Iterables.concat(schemaNames, Util.skipLast(names)),
+              nameMatcher);
       if (schema != null) {
         final String name = Util.last(names);
         functions2.addAll(schema.getFunctions(name, true));
@@ -152,10 +181,15 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
     return functions2;
   }
 
-  private CalciteSchema getSchema(Iterable<String> schemaNames) {
+  private CalciteSchema getSchema(Iterable<String> schemaNames,
+      SqlNameMatcher nameMatcher) {
     CalciteSchema schema = rootSchema;
     for (String schemaName : schemaNames) {
-      schema = schema.getSubSchema(schemaName, caseSensitive);
+      if (schema == rootSchema
+          && nameMatcher.matches(schemaName, schema.getName())) {
+        continue;
+      }
+      schema = schema.getSubSchema(schemaName, nameMatcher.isCaseSensitive());
       if (schema == null) {
         return null;
       }
@@ -168,51 +202,65 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
   }
 
   public List<SqlMoniker> getAllSchemaObjectNames(List<String> names) {
-    final CalciteSchema schema = getSchema(names);
+    final CalciteSchema schema = getSchema(names, nameMatcher);
     if (schema == null) {
       return ImmutableList.of();
     }
     final List<SqlMoniker> result = new ArrayList<>();
+
+    // Add root schema if not anonymous
+    if (!schema.name.equals("")) {
+      result.add(moniker(schema, null, SqlMonikerType.SCHEMA));
+    }
+
     final Map<String, CalciteSchema> schemaMap = schema.getSubSchemaMap();
 
     for (String subSchema : schemaMap.keySet()) {
-      result.add(
-          new SqlMonikerImpl(schema.path(subSchema), SqlMonikerType.SCHEMA));
+      result.add(moniker(schema, subSchema, SqlMonikerType.SCHEMA));
     }
 
     for (String table : schema.getTableNames()) {
-      result.add(
-          new SqlMonikerImpl(schema.path(table), SqlMonikerType.TABLE));
+      result.add(moniker(schema, table, SqlMonikerType.TABLE));
     }
 
     final NavigableSet<String> functions = schema.getFunctionNames();
     for (String function : functions) { // views are here as well
-      result.add(
-          new SqlMonikerImpl(schema.path(function), SqlMonikerType.FUNCTION));
+      result.add(moniker(schema, function, SqlMonikerType.FUNCTION));
     }
     return result;
   }
 
-  public List<String> getSchemaName() {
-    return defaultSchema;
+  private SqlMonikerImpl moniker(CalciteSchema schema, String name,
+      SqlMonikerType type) {
+    final List<String> path = schema.path(name);
+    if (path.size() == 1
+        && !schema.root().name.equals("")
+        && type == SqlMonikerType.SCHEMA) {
+      type = SqlMonikerType.CATALOG;
+    }
+    return new SqlMonikerImpl(path, type);
   }
 
-  public RelOptTableImpl getTableForMember(List<String> names) {
+  public List<List<String>> getSchemaPaths() {
+    return schemaPaths;
+  }
+
+  public Prepare.PreparingTable getTableForMember(List<String> names) {
     return getTable(names);
   }
 
   public RelDataTypeField field(RelDataType rowType, String alias) {
-    return SqlValidatorUtil.lookupField(caseSensitive, rowType, alias);
+    return nameMatcher.field(rowType, alias);
   }
 
   public boolean matches(String string, String name) {
-    return Util.matches(caseSensitive, string, name);
+    return nameMatcher.matches(string, name);
   }
 
   public RelDataType createTypeFromProjection(final RelDataType type,
       final List<String> columnNameList) {
     return SqlValidatorUtil.createTypeFromProjection(type, columnNameList,
-        typeFactory, caseSensitive);
+        typeFactory, nameMatcher.isCaseSensitive());
   }
 
   public void lookupOperatorOverloads(final SqlIdentifier opName,
@@ -327,13 +375,17 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
       return typeFactory.createTypeWithNullability(
           typeFactory.createSqlType(SqlTypeName.ANY), true);
     }
-    return typeFactory.toSql(type);
+    return JavaTypeFactoryImpl.toSql(typeFactory, type);
   }
 
   public List<SqlOperator> getOperatorList() {
     return null;
   }
 
+  public CalciteSchema getRootSchema() {
+    return rootSchema;
+  }
+
   public RelDataTypeFactory getTypeFactory() {
     return typeFactory;
   }
@@ -342,10 +394,12 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
   }
 
   @Override public boolean isCaseSensitive() {
-    return caseSensitive;
+    return nameMatcher.isCaseSensitive();
   }
 
-
+  public SqlNameMatcher nameMatcher() {
+    return nameMatcher;
+  }
 }
 
 // End CalciteCatalogReader.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
index b5ceec8..5de9b35 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -388,7 +388,7 @@ public abstract class Prepare {
      * different schema path. */
     CatalogReader withSchemaPath(List<String> schemaPath);
 
-    PreparingTable getTable(List<String> names);
+    @Override PreparingTable getTable(List<String> names);
 
     ThreadLocal<CatalogReader> THREAD_LOCAL = new ThreadLocal<>();
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
index 6d7ffac..a9c381e 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -326,11 +326,11 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
     return SqlAccessType.ALL;
   }
 
-  /** Im0plementation of {@link SchemaPlus} that wraps a regular schema and knows
+  /** Implementation of {@link SchemaPlus} that wraps a regular schema and knows
    * its name and parent.
    *
    * <p>It is read-only, and functionality is limited in other ways, it but
-   * allows table expressions to be genenerated. */
+   * allows table expressions to be generated. */
   private static class MySchemaPlus implements SchemaPlus {
     private final SchemaPlus parent;
     private final String name;

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 77a6bf9..9ada4f2 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -158,15 +158,44 @@ public interface CalciteResource {
   @BaseMessage("Table ''{0}'' not found")
   ExInst<SqlValidatorException> tableNameNotFound(String a0);
 
+  @BaseMessage("Table ''{0}'' not found; did you mean ''{1}''?")
+  ExInst<SqlValidatorException> tableNameNotFoundDidYouMean(String a0,
+      String a1);
+
+  /** Same message as {@link #tableNameNotFound(String)} but a different kind
+   * of exception, so it can be used in {@code RelBuilder}. */
+  @BaseMessage("Table ''{0}'' not found")
+  ExInst<CalciteException> tableNotFound(String tableName);
+
+  @BaseMessage("Object ''{0}'' not found")
+  ExInst<SqlValidatorException> objectNotFound(String a0);
+
+  @BaseMessage("Object ''{0}'' not found within ''{1}''")
+  ExInst<SqlValidatorException> objectNotFoundWithin(String a0, String a1);
+
+  @BaseMessage("Object ''{0}'' not found; did you mean ''{1}''?")
+  ExInst<SqlValidatorException> objectNotFoundDidYouMean(String a0, String a1);
+
+  @BaseMessage("Object ''{0}'' not found within ''{1}''; did you mean ''{2}''?")
+  ExInst<SqlValidatorException> objectNotFoundWithinDidYouMean(String a0,
+      String a1, String a2);
+
   @BaseMessage("Table ''{0}'' is not a sequence")
   ExInst<SqlValidatorException> notASequence(String a0);
 
   @BaseMessage("Column ''{0}'' not found in any table")
   ExInst<SqlValidatorException> columnNotFound(String a0);
 
+  @BaseMessage("Column ''{0}'' not found in any table; did you mean ''{1}''?")
+  ExInst<SqlValidatorException> columnNotFoundDidYouMean(String a0, String a1);
+
   @BaseMessage("Column ''{0}'' not found in table ''{1}''")
   ExInst<SqlValidatorException> columnNotFoundInTable(String a0, String a1);
 
+  @BaseMessage("Column ''{0}'' not found in table ''{1}''; did you mean ''{2}''?")
+  ExInst<SqlValidatorException> columnNotFoundInTableDidYouMean(String a0,
+      String a1, String a2);
+
   @BaseMessage("Column ''{0}'' is ambiguous")
   ExInst<SqlValidatorException> columnAmbiguous(String a0);
 
@@ -610,9 +639,6 @@ public interface CalciteResource {
   @BaseMessage("View is not modifiable. No value is supplied for NOT NULL column ''{0}'' of base table ''{1}''")
   ExInst<SqlValidatorException> noValueSuppliedForViewColumn(String columnName, String tableName);
 
-  @BaseMessage("Table ''{0}'' not found")
-  ExInst<CalciteException> tableNotFound(String tableName);
-
   @BaseMessage("Not a record type. The ''*'' operator requires a record")
   ExInst<SqlValidatorException> starRequiresRecordType();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/schema/Table.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Table.java b/core/src/main/java/org/apache/calcite/schema/Table.java
index b25745e..8062565 100644
--- a/core/src/main/java/org/apache/calcite/schema/Table.java
+++ b/core/src/main/java/org/apache/calcite/schema/Table.java
@@ -34,6 +34,9 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
  * <a href="http://en.wikipedia.org/wiki/Inode">i-node</a> concept in the UNIX
  * filesystem.)</p>
  *
+ * <p>A particular table instance may also implement {@link Wrapper},
+ * to give access to sub-objects.
+ *
  * @see TableMacro
  */
 public interface Table {
@@ -56,6 +59,7 @@ public interface Table {
 
   /** Type of table. */
   Schema.TableType getJdbcTableType();
+
 }
 
 // End Table.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/schema/Wrapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Wrapper.java b/core/src/main/java/org/apache/calcite/schema/Wrapper.java
new file mode 100644
index 0000000..c14439d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/Wrapper.java
@@ -0,0 +1,27 @@
+/*
+ * 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.schema;
+
+/**
+ * Mix-in interface that allows you to find sub-objects.
+ */
+public interface Wrapper {
+  /** Returns an instance of a class, or null. */
+  <C> C unwrap(Class<C> aClass);
+}
+
+// End Wrapper.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
index 7e2294c..4839805 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
@@ -141,12 +141,22 @@ public class SqlIdentifier extends SqlNode {
     return SqlKind.IDENTIFIER;
   }
 
-  public SqlNode clone(SqlParserPos pos) {
+  @Override public SqlNode clone(SqlParserPos pos) {
     return new SqlIdentifier(names, collation, pos, componentPositions);
   }
 
-  public String toString() {
-    return Util.sepList(Lists.transform(names, EMPTY_TO_STAR), ".");
+  @Override public String toString() {
+    return getString(names);
+  }
+
+  /** Converts a list of strings to a qualified identifier. */
+  public static String getString(List<String> names) {
+    return Util.sepList(toStar(names), ".");
+  }
+
+  /** Converts empty strings in a list of names to stars. */
+  public static List<String> toStar(List<String> names) {
+    return Lists.transform(names, EMPTY_TO_STAR);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
index d5f3c18..b6bd058 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java
@@ -149,7 +149,7 @@ abstract class AbstractNamespace implements SqlValidatorNamespace {
 
   public boolean fieldExists(String name) {
     final RelDataType rowType = getRowType();
-    return validator.catalogReader.field(rowType, name) != null;
+    return validator.catalogReader.nameMatcher().field(rowType, name) != null;
   }
 
   public List<Pair<SqlNode, SqlMonotonicity>> getMonotonicExprs() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
index 52983d1..8b004cd 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
@@ -31,10 +31,12 @@ import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -83,16 +85,18 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     throw new UnsupportedOperationException();
   }
 
-  public void resolve(List<String> names, boolean deep, Resolved resolved) {
-    parent.resolve(names, deep, resolved);
+  public void resolve(List<String> names, SqlNameMatcher nameMatcher,
+      boolean deep, Resolved resolved) {
+    parent.resolve(names, nameMatcher, deep, resolved);
   }
 
   /** If a record type allows implicit references to fields, recursively looks
    * into the fields. Otherwise returns immediately. */
   void resolveInNamespace(SqlValidatorNamespace ns, boolean nullable,
-      List<String> names, Path path, Resolved resolved) {
+      List<String> names, SqlNameMatcher nameMatcher, Path path,
+      Resolved resolved) {
     if (names.isEmpty()) {
-      resolved.found(ns, nullable, this, path);
+      resolved.found(ns, nullable, this, path, null);
       return;
     }
     final RelDataType rowType = ns.getRowType();
@@ -109,32 +113,33 @@ public abstract class DelegatingScope implements SqlValidatorScope {
             final List<String> remainder = entry.getValue();
             final SqlValidatorNamespace ns2 =
                 new FieldNamespace(validator, field.getType());
-            final Step path2 = path.add(rowType, field.getIndex(),
-                StructKind.FULLY_QUALIFIED);
-            resolveInNamespace(ns2, nullable, remainder, path2, resolved);
+            final Step path2 = path.plus(rowType, field.getIndex(),
+                field.getName(), StructKind.FULLY_QUALIFIED);
+            resolveInNamespace(ns2, nullable, remainder, nameMatcher, path2,
+                resolved);
           }
           return;
         }
       }
 
       final String name = names.get(0);
-      final RelDataTypeField field0 =
-          validator.catalogReader.field(rowType, name);
+      final RelDataTypeField field0 = nameMatcher.field(rowType, name);
       if (field0 != null) {
         final SqlValidatorNamespace ns2 = ns.lookupChild(field0.getName());
-        final Step path2 = path.add(rowType, field0.getIndex(),
-            StructKind.FULLY_QUALIFIED);
+        final Step path2 = path.plus(rowType, field0.getIndex(),
+            field0.getName(), StructKind.FULLY_QUALIFIED);
         resolveInNamespace(ns2, nullable, names.subList(1, names.size()),
-            path2, resolved);
+            nameMatcher, path2, resolved);
       } else {
         for (RelDataTypeField field : rowType.getFieldList()) {
           switch (field.getType().getStructKind()) {
           case PEEK_FIELDS:
           case PEEK_FIELDS_DEFAULT:
-            final Step path2 = path.add(rowType, field.getIndex(),
-                field.getType().getStructKind());
+            final Step path2 = path.plus(rowType, field.getIndex(),
+                field.getName(), field.getType().getStructKind());
             final SqlValidatorNamespace ns2 = ns.lookupChild(field.getName());
-            resolveInNamespace(ns2, nullable, names, path2, resolved);
+            resolveInNamespace(ns2, nullable, names, nameMatcher, path2,
+                resolved);
           }
         }
       }
@@ -170,13 +175,20 @@ public abstract class DelegatingScope implements SqlValidatorScope {
 
   public Pair<String, SqlValidatorNamespace>
   findQualifyingTableName(String columnName, SqlNode ctx) {
+    //noinspection deprecation
     return parent.findQualifyingTableName(columnName, ctx);
   }
 
-  protected Map<String, ScopeChild> findQualifyingTables(String columnName) {
+  protected Map<String, ScopeChild> findQualifyingTables(String columnName,
+      SqlNameMatcher nameMatcher) {
     return ImmutableMap.of();
   }
 
+  public Map<String, ScopeChild> findQualifyingTableNames(String columnName,
+      SqlNode ctx, SqlNameMatcher nameMatcher) {
+    return parent.findQualifyingTableNames(columnName, ctx, nameMatcher);
+  }
+
   public RelDataType resolveColumn(String name, SqlNode ctx) {
     return parent.resolveColumn(name, ctx);
   }
@@ -189,6 +201,11 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     return parent.getTableNamespace(names);
   }
 
+  public void resolveTable(List<String> names, SqlNameMatcher nameMatcher,
+      Path path, Resolved resolved) {
+    parent.resolveTable(names, nameMatcher, path, resolved);
+  }
+
   public SqlValidatorScope getOperandScope(SqlCall call) {
     if (call instanceof SqlSelect) {
       return validator.getSelectScope((SqlSelect) call);
@@ -213,20 +230,51 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     }
 
     final SqlIdentifier previous = identifier;
+    final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
     String columnName;
+    final String tableName;
+    final SqlValidatorNamespace namespace;
     switch (identifier.names.size()) {
     case 1: {
       columnName = identifier.names.get(0);
-      final Pair<String, SqlValidatorNamespace> pair =
-          findQualifyingTableName(columnName, identifier);
-      final String tableName = pair.left;
-      final SqlValidatorNamespace namespace = pair.right;
+      final Map<String, ScopeChild> map =
+          findQualifyingTableNames(columnName, identifier, nameMatcher);
+      switch (map.size()) {
+      case 0:
+        if (nameMatcher.isCaseSensitive()) {
+          final SqlNameMatcher liberalMatcher = SqlNameMatchers.liberal();
+          final Map<String, ScopeChild> map2 =
+              findQualifyingTableNames(columnName, identifier, liberalMatcher);
+          if (!map2.isEmpty()) {
+            final List<String> list = new ArrayList<>();
+            for (ScopeChild entry : map2.values()) {
+              final RelDataTypeField field =
+                  liberalMatcher.field(entry.namespace.getRowType(),
+                      columnName);
+              list.add(field.getName());
+            }
+            Collections.sort(list);
+            throw validator.newValidationError(identifier,
+                RESOURCE.columnNotFoundDidYouMean(columnName,
+                    Util.sepList(list, "', '")));
+          }
+        }
+        throw validator.newValidationError(identifier,
+            RESOURCE.columnNotFound(columnName));
+      case 1:
+        tableName = map.keySet().iterator().next();
+        namespace = map.get(tableName).namespace;
+        break;
+      default:
+        throw validator.newValidationError(identifier,
+            RESOURCE.columnAmbiguous(columnName));
+      }
 
       final ResolvedImpl resolved = new ResolvedImpl();
-      resolveInNamespace(namespace, false, identifier.names,
-          resolved.emptyPath(), resolved);
+      resolveInNamespace(namespace, false, identifier.names, nameMatcher,
+          Path.EMPTY, resolved);
       final RelDataTypeField field =
-          validator.catalogReader.field(namespace.getRowType(), columnName);
+          nameMatcher.field(namespace.getRowType(), columnName);
       if (field != null) {
         if (hasAmbiguousUnresolvedStar(namespace.getRowType(), field,
             columnName)) {
@@ -253,7 +301,7 @@ public abstract class DelegatingScope implements SqlValidatorScope {
       for (; i > 0; i--) {
         final SqlIdentifier prefix = identifier.getComponent(0, i);
         resolved.clear();
-        resolve(prefix.names, false, resolved);
+        resolve(prefix.names, nameMatcher, false, resolved);
         if (resolved.count() == 1) {
           final Resolve resolve = resolved.only();
           fromNs = resolve.namespace;
@@ -261,11 +309,24 @@ public abstract class DelegatingScope implements SqlValidatorScope {
           fromRowType = resolve.rowType();
           break;
         }
+        // Look for a table alias that is the wrong case.
+        if (nameMatcher.isCaseSensitive()) {
+          final SqlNameMatcher liberalMatcher = SqlNameMatchers.liberal();
+          resolved.clear();
+          resolve(prefix.names, liberalMatcher, false, resolved);
+          if (resolved.count() == 1) {
+            final Step lastStep = Util.last(resolved.only().path.steps());
+            throw validator.newValidationError(prefix,
+                RESOURCE.tableNameNotFoundDidYouMean(prefix.toString(),
+                    lastStep.name));
+          }
+        }
       }
       if (fromNs == null || fromNs instanceof SchemaNamespace) {
         // Look for a column not qualified by a table alias.
         columnName = identifier.names.get(0);
-        final Map<String, ScopeChild> map = findQualifyingTables(columnName);
+        final Map<String, ScopeChild> map =
+            findQualifyingTables(columnName, nameMatcher);
         switch (map.size()) {
         default:
           final SqlIdentifier prefix1 = identifier.skipLast(1);
@@ -274,21 +335,22 @@ public abstract class DelegatingScope implements SqlValidatorScope {
         case 1: {
           final Map.Entry<String, ScopeChild> entry =
               map.entrySet().iterator().next();
-          final String tableName = entry.getKey();
+          final String tableName2 = map.keySet().iterator().next();
           fromNs = entry.getValue().namespace;
-          fromPath = resolved.emptyPath();
+          fromPath = Path.EMPTY;
 
           // Adding table name is for RecordType column with StructKind.PEEK_FIELDS or
           // StructKind.PEEK_FIELDS only. Access to a field in a RecordType column of
           // other StructKind should always be qualified with table name.
           final RelDataTypeField field =
-              validator.catalogReader.field(fromNs.getRowType(), columnName);
+              nameMatcher.field(fromNs.getRowType(), columnName);
           if (field != null) {
             switch (field.getType().getStructKind()) {
             case PEEK_FIELDS:
             case PEEK_FIELDS_DEFAULT:
               columnName = field.getName(); // use resolved field name
-              resolve(ImmutableList.of(tableName), false, resolved);
+              resolve(ImmutableList.of(tableName2), nameMatcher, false,
+                  resolved);
               if (resolved.count() == 1) {
                 final Resolve resolve = resolved.only();
                 fromNs = resolve.namespace;
@@ -296,7 +358,7 @@ public abstract class DelegatingScope implements SqlValidatorScope {
                 fromRowType = resolve.rowType();
                 identifier = identifier
                     .setName(0, columnName)
-                    .add(0, tableName, SqlParserPos.ZERO);
+                    .add(0, tableName2, SqlParserPos.ZERO);
                 ++i;
                 ++size;
               }
@@ -339,11 +401,27 @@ public abstract class DelegatingScope implements SqlValidatorScope {
       }
       final SqlIdentifier suffix = identifier.getComponent(i, size);
       resolved.clear();
-      resolveInNamespace(fromNs, false, suffix.names, resolved.emptyPath(),
+      resolveInNamespace(fromNs, false, suffix.names, nameMatcher, Path.EMPTY,
           resolved);
       final Path path;
       switch (resolved.count()) {
       case 0:
+        // Maybe the last component was correct, just wrong case
+        if (nameMatcher.isCaseSensitive()) {
+          SqlNameMatcher liberalMatcher = SqlNameMatchers.liberal();
+          resolved.clear();
+          resolveInNamespace(fromNs, false, suffix.names, liberalMatcher,
+              Path.EMPTY, resolved);
+          if (resolved.count() > 0) {
+            int k = size - 1;
+            final SqlIdentifier prefix = identifier.getComponent(0, i);
+            final SqlIdentifier suffix3 = identifier.getComponent(i, k + 1);
+            final Step step = Util.last(resolved.resolves.get(0).path.steps());
+            throw validator.newValidationError(suffix3,
+                RESOURCE.columnNotFoundInTableDidYouMean(suffix3.toString(),
+                    prefix.toString(), step.name));
+          }
+        }
         // Find the shortest suffix that also fails. Suppose we cannot resolve
         // "a.b.c"; we find we cannot resolve "a.b" but can resolve "a". So,
         // the error will be "Column 'a.b' not found".
@@ -351,8 +429,8 @@ public abstract class DelegatingScope implements SqlValidatorScope {
         for (; k > i; --k) {
           SqlIdentifier suffix2 = identifier.getComponent(i, k);
           resolved.clear();
-          resolveInNamespace(fromNs, false, suffix2.names,
-              resolved.emptyPath(), resolved);
+          resolveInNamespace(fromNs, false, suffix2.names, nameMatcher,
+              Path.EMPTY, resolved);
           if (resolved.count() > 0) {
             break;
           }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
index b1d9c39..244afb5 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java
@@ -52,8 +52,8 @@ public abstract class DelegatingSqlValidatorCatalogReader
     return catalogReader.getAllSchemaObjectNames(names);
   }
 
-  public List<String> getSchemaName() {
-    return catalogReader.getSchemaName();
+  public List<List<String>> getSchemaPaths() {
+    return catalogReader.getSchemaPaths();
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
index 3e3ce3f..b9a38cf 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
@@ -16,7 +16,13 @@
  */
 package org.apache.calcite.sql.validate;
 
+import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.prepare.Prepare;
+import org.apache.calcite.prepare.RelOptTableImpl;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.StructKind;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.Wrapper;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDataTypeSpec;
 import org.apache.calcite.sql.SqlDynamicParam;
@@ -26,9 +32,15 @@ import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -64,7 +76,8 @@ class EmptyScope implements SqlValidatorScope {
     throw new UnsupportedOperationException();
   }
 
-  public void resolve(List<String> names, boolean deep, Resolved resolved) {
+  public void resolve(List<String> names, SqlNameMatcher nameMatcher,
+      boolean deep, Resolved resolved) {
   }
 
   public SqlValidatorNamespace getTableNamespace(List<String> names) {
@@ -74,6 +87,89 @@ class EmptyScope implements SqlValidatorScope {
         : null;
   }
 
+  public void resolveTable(List<String> names, SqlNameMatcher nameMatcher,
+      Path path, Resolved resolved) {
+    final List<Resolve> imperfectResolves = new ArrayList<>();
+    final List<Resolve> resolves = ((ResolvedImpl) resolved).resolves;
+
+    // Look in the default schema, then default catalog, then root schema.
+    for (List<String> schemaPath : validator.catalogReader.getSchemaPaths()) {
+      resolve_(validator.catalogReader.getRootSchema(), names, schemaPath,
+          nameMatcher, path, resolved);
+      for (Resolve resolve : resolves) {
+        if (resolve.remainingNames.isEmpty()) {
+          // There is a full match. Return it as the only match.
+          ((ResolvedImpl) resolved).clear();
+          resolves.add(resolve);
+          return;
+        }
+      }
+      imperfectResolves.addAll(resolves);
+    }
+    // If there were no matches in the last round, restore those found in
+    // previous rounds
+    if (resolves.isEmpty()) {
+      resolves.addAll(imperfectResolves);
+    }
+  }
+
+  private void resolve_(final CalciteSchema rootSchema, List<String> names,
+      List<String> schemaNames, SqlNameMatcher nameMatcher, Path path,
+      Resolved resolved) {
+    final List<String> concat = ImmutableList.<String>builder()
+        .addAll(schemaNames).addAll(names).build();
+    CalciteSchema schema = rootSchema;
+    SqlValidatorNamespace namespace = null;
+    List<String> remainingNames = concat;
+    for (String schemaName : concat) {
+      if (schema == rootSchema
+          && nameMatcher.matches(schemaName, schema.name)) {
+        remainingNames = Util.skip(remainingNames);
+        continue;
+      }
+      final CalciteSchema subSchema =
+          schema.getSubSchema(schemaName, nameMatcher.isCaseSensitive());
+      if (subSchema != null) {
+        path = path.plus(null, -1, subSchema.name, StructKind.NONE);
+        remainingNames = Util.skip(remainingNames);
+        schema = subSchema;
+        namespace = new SchemaNamespace(validator,
+            ImmutableList.copyOf(path.stepNames()));
+        continue;
+      }
+      CalciteSchema.TableEntry entry =
+          schema.getTable(schemaName, nameMatcher.isCaseSensitive());
+      if (entry == null) {
+        entry = schema.getTableBasedOnNullaryFunction(schemaName,
+            nameMatcher.isCaseSensitive());
+      }
+      if (entry != null) {
+        path = path.plus(null, -1, entry.name, StructKind.NONE);
+        remainingNames = Util.skip(remainingNames);
+        final Table table = entry.getTable();
+        final String name2 = entry.name;
+        SqlValidatorTable table2 = null;
+        if (table instanceof Wrapper) {
+          table2 = ((Wrapper) table).unwrap(Prepare.PreparingTable.class);
+        }
+        if (table2 == null) {
+          table2 = RelOptTableImpl.create(null,
+              table.getRowType(validator.typeFactory), schema.add(name2, table),
+              null);
+        }
+        namespace = new TableNamespace(validator, table2);
+        resolved.found(namespace, false, this, path, remainingNames);
+        return;
+      }
+      // neither sub-schema nor table
+      if (namespace != null
+          && !remainingNames.equals(names)) {
+        resolved.found(namespace, false, this, path, remainingNames);
+      }
+      return;
+    }
+  }
+
   public RelDataType nullifyType(SqlNode node, RelDataType type) {
     return type;
   }
@@ -105,6 +201,11 @@ class EmptyScope implements SqlValidatorScope {
         RESOURCE.columnNotFound(columnName));
   }
 
+  public Map<String, ScopeChild> findQualifyingTableNames(String columnName,
+      SqlNode ctx, SqlNameMatcher nameMatcher) {
+    return ImmutableMap.of();
+  }
+
   public void addChild(SqlValidatorNamespace ns, String alias,
       boolean nullable) {
     // cannot add to the empty scope

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
index 5282ddb..f198389 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
@@ -27,6 +27,7 @@ import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Pair;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
@@ -77,8 +78,7 @@ public class IdentifierNamespace extends AbstractNamespace {
     super(validator, enclosingNode);
     this.id = id;
     this.extendList = extendList;
-    this.parentScope = parentScope;
-    assert parentScope != null;
+    this.parentScope = Preconditions.checkNotNull(parentScope);
   }
 
   IdentifierNamespace(SqlValidatorImpl validator, SqlNode node,
@@ -100,13 +100,75 @@ public class IdentifierNamespace extends AbstractNamespace {
     }
   }
 
-  public RelDataType validateImpl(RelDataType targetRowType) {
-    resolvedNamespace = parentScope.getTableNamespace(id.names);
-    if (resolvedNamespace == null) {
-      throw validator.newValidationError(id,
-          RESOURCE.tableNameNotFound(id.toString()));
+  private SqlValidatorNamespace resolveImpl(SqlIdentifier id) {
+    final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
+    final SqlValidatorScope.ResolvedImpl resolved =
+        new SqlValidatorScope.ResolvedImpl();
+    final List<String> names = SqlIdentifier.toStar(id.names);
+    parentScope.resolveTable(names, nameMatcher,
+        SqlValidatorScope.Path.EMPTY, resolved);
+    SqlValidatorScope.Resolve previousResolve = null;
+    if (resolved.count() == 1) {
+      final SqlValidatorScope.Resolve resolve =
+          previousResolve = resolved.only();
+      if (resolve.remainingNames.isEmpty()) {
+        return resolve.namespace;
+      }
+      // If we're not case sensitive, give an error.
+      // If we're case sensitive, we'll shortly try again and give an error
+      // then.
+      if (!nameMatcher.isCaseSensitive()) {
+        throw validator.newValidationError(id,
+            RESOURCE.objectNotFoundWithin(resolve.remainingNames.get(0),
+                SqlIdentifier.getString(resolve.path.stepNames())));
+      }
+    }
+
+    // Failed to match.  If we're matching case-sensitively, try a more
+    // lenient match. If we find something we can offer a helpful hint.
+    if (nameMatcher.isCaseSensitive()) {
+      final SqlNameMatcher liberalMatcher = SqlNameMatchers.liberal();
+      resolved.clear(); // TODO: remove?
+      parentScope.resolveTable(names, liberalMatcher,
+          SqlValidatorScope.Path.EMPTY, resolved);
+      if (resolved.count() == 1) {
+        final SqlValidatorScope.Resolve resolve = resolved.only();
+        if (resolve.remainingNames.isEmpty()
+            || previousResolve == null) {
+          // We didn't match it case-sensitive, so they must have had the
+          // right identifier, wrong case.
+          //
+          // If previousResolve is null, we matched nothing case-sensitive and
+          // everything case-insensitive, so the mismatch must have been at
+          // position 0.
+          final int i = previousResolve == null ? 0
+              : previousResolve.path.stepCount();
+          final int offset = resolve.path.stepCount()
+              + resolve.remainingNames.size() - names.size();
+          final List<String> prefix =
+              resolve.path.stepNames().subList(0, offset + i);
+          final String next = resolve.path.stepNames().get(i + offset);
+          if (prefix.isEmpty()) {
+            throw validator.newValidationError(id,
+                RESOURCE.objectNotFoundDidYouMean(names.get(i), next));
+          } else {
+            throw validator.newValidationError(id,
+                RESOURCE.objectNotFoundWithinDidYouMean(names.get(i),
+                    SqlIdentifier.getString(prefix), next));
+          }
+        } else {
+          throw validator.newValidationError(id,
+              RESOURCE.objectNotFoundWithin(resolve.remainingNames.get(0),
+                  SqlIdentifier.getString(resolve.path.stepNames())));
+        }
+      }
     }
+    throw validator.newValidationError(id,
+        RESOURCE.objectNotFound(id.getComponent(0).toString()));
+  }
 
+  public RelDataType validateImpl(RelDataType targetRowType) {
+    resolvedNamespace = Preconditions.checkNotNull(resolveImpl(id));
     if (resolvedNamespace instanceof TableNamespace) {
       SqlValidatorTable table = resolvedNamespace.getTable();
       if (validator.shouldExpandIdentifiers()) {
@@ -117,7 +179,7 @@ public class IdentifierNamespace extends AbstractNamespace {
           // identifier, as best we can. We assume that qualification
           // adds names to the front, e.g. FOO.BAR becomes BAZ.FOO.BAR.
           List<SqlParserPos> poses =
-              new ArrayList<SqlParserPos>(
+              new ArrayList<>(
                   Collections.nCopies(
                       qualifiedNames.size(), id.getParserPosition()));
           int offset = qualifiedNames.size() - id.names.size();

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java b/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
index 7c869c5..7228f63 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
@@ -79,18 +79,19 @@ public abstract class ListScope extends DelegatingScope {
     return Lists.transform(children, ScopeChild.NAME_FN);
   }
 
-  private int findChild(List<String> names) {
+  private ScopeChild findChild(List<String> names,
+      SqlNameMatcher nameMatcher) {
     for (ScopeChild child : children) {
       String lastName = Util.last(names);
       if (child.name != null) {
-        if (!validator.catalogReader.matches(child.name, lastName)) {
+        if (!nameMatcher.matches(child.name, lastName)) {
           // Alias does not match last segment. Don't consider the
           // fully-qualified name. E.g.
           //    SELECT sales.emp.name FROM sales.emp AS otherAlias
           continue;
         }
         if (names.size() == 1) {
-          return child.ordinal;
+          return child;
         }
       }
 
@@ -98,15 +99,18 @@ public abstract class ListScope extends DelegatingScope {
       // catalog & schema and the other is not.
       final SqlValidatorTable table = child.namespace.getTable();
       if (table != null) {
-        final SqlValidatorTable table2 =
-            validator.catalogReader.getTable(names);
-        if (table2 != null
-            && table.getQualifiedName().equals(table2.getQualifiedName())) {
-          return child.ordinal;
+        final ResolvedImpl resolved = new ResolvedImpl();
+        resolveTable(names, nameMatcher, Path.EMPTY, resolved);
+        if (resolved.count() == 1
+            && resolved.only().remainingNames.isEmpty()
+            && resolved.only().namespace instanceof TableNamespace
+            && resolved.only().namespace.getTable().getQualifiedName().equals(
+                table.getQualifiedName())) {
+          return child;
         }
       }
     }
-    return -1;
+    return null;
   }
 
   public void findAllColumnNames(List<SqlMoniker> result) {
@@ -125,9 +129,12 @@ public abstract class ListScope extends DelegatingScope {
 
   @Override public Pair<String, SqlValidatorNamespace>
   findQualifyingTableName(final String columnName, SqlNode ctx) {
-    final Map<String, ScopeChild> map = findQualifyingTables(columnName);
+    final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
+    final Map<String, ScopeChild> map =
+        findQualifyingTables(columnName, nameMatcher);
     switch (map.size()) {
     case 0:
+      //noinspection deprecation
       return parent.findQualifyingTableName(columnName, ctx);
     case 1:
       final Map.Entry<String, ScopeChild> entry =
@@ -140,11 +147,25 @@ public abstract class ListScope extends DelegatingScope {
   }
 
   @Override public Map<String, ScopeChild>
-  findQualifyingTables(String columnName) {
+  findQualifyingTableNames(String columnName, SqlNode ctx,
+      SqlNameMatcher nameMatcher) {
+    final Map<String, ScopeChild> map =
+        findQualifyingTables(columnName, nameMatcher);
+    switch (map.size()) {
+    case 0:
+      return parent.findQualifyingTableNames(columnName, ctx, nameMatcher);
+    default:
+      return map;
+    }
+  }
+
+  @Override public Map<String, ScopeChild>
+  findQualifyingTables(String columnName, SqlNameMatcher nameMatcher) {
     final Map<String, ScopeChild> map = new HashMap<>();
     for (ScopeChild child : children) {
       final ResolvedImpl resolved = new ResolvedImpl();
-      resolve(ImmutableList.of(child.name, columnName), true, resolved);
+      resolve(ImmutableList.of(child.name, columnName), nameMatcher, true,
+          resolved);
       if (resolved.count() > 0) {
         map.put(child.name, child);
       }
@@ -152,15 +173,15 @@ public abstract class ListScope extends DelegatingScope {
     return map;
   }
 
-  @Override public void resolve(List<String> names, boolean deep,
-      Resolved resolved) {
+  @Override public void resolve(List<String> names, SqlNameMatcher nameMatcher,
+      boolean deep, Resolved resolved) {
     // First resolve by looking through the child namespaces.
-    final int i = findChild(names);
-    if (i >= 0) {
+    final ScopeChild child0 = findChild(names, nameMatcher);
+    if (child0 != null) {
       final Step path =
-          resolved.emptyPath().add(null, i, StructKind.FULLY_QUALIFIED);
-      final ScopeChild child = children.get(i);
-      resolved.found(child.namespace, child.nullable, this, path);
+          Path.EMPTY.plus(child0.namespace.getRowType(), child0.ordinal,
+              child0.name, StructKind.FULLY_QUALIFIED);
+      resolved.found(child0.namespace, child0.nullable, this, path, null);
       return;
     }
 
@@ -170,11 +191,11 @@ public abstract class ListScope extends DelegatingScope {
       for (ScopeChild child : children) {
         // If identifier starts with table alias, remove the alias.
         final List<String> names2 =
-            validator.catalogReader.matches(child.name, names.get(0))
+            nameMatcher.matches(child.name, names.get(0))
                 ? names.subList(1, names.size())
                 : names;
-        resolveInNamespace(child.namespace, child.nullable, names2,
-            resolved.emptyPath(), resolved);
+        resolveInNamespace(child.namespace, child.nullable, names2, nameMatcher,
+            Path.EMPTY, resolved);
       }
       if (resolved.count() > 0) {
         return;
@@ -183,17 +204,18 @@ public abstract class ListScope extends DelegatingScope {
 
     // Then call the base class method, which will delegate to the
     // parent scope.
-    super.resolve(names, deep, resolved);
+    super.resolve(names, nameMatcher, deep, resolved);
   }
 
   public RelDataType resolveColumn(String columnName, SqlNode ctx) {
+    final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
     int found = 0;
     RelDataType type = null;
     for (ScopeChild child : children) {
       SqlValidatorNamespace childNs = child.namespace;
       final RelDataType childRowType = childNs.getRowType();
       final RelDataTypeField field =
-          validator.catalogReader.field(childRowType, columnName);
+          nameMatcher.field(childRowType, columnName);
       if (field != null) {
         found++;
         type = field.getType();

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java b/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java
index 65958fd..117390f 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java
@@ -74,7 +74,8 @@ public class OrderByScope extends DelegatingScope {
           validator.getNamespace(select);
       final RelDataType rowType = selectNs.getRowType();
 
-      final RelDataTypeField field = validator.catalogReader.field(rowType, name);
+      final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
+      final RelDataTypeField field = nameMatcher.field(rowType, name);
       if (field != null && !field.isDynamicStar()) {
         // if identifier is resolved to a dynamic star, use super.fullyQualify() for such case.
         return SqlQualified.create(this, 1, selectNs, identifier);
@@ -86,7 +87,8 @@ public class OrderByScope extends DelegatingScope {
   public RelDataType resolveColumn(String name, SqlNode ctx) {
     final SqlValidatorNamespace selectNs = validator.getNamespace(select);
     final RelDataType rowType = selectNs.getRowType();
-    final RelDataTypeField field = validator.catalogReader.field(rowType, name);
+    final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
+    final RelDataTypeField field = nameMatcher.field(rowType, name);
     if (field != null) {
       return field.getType();
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
new file mode 100644
index 0000000..104f3d2
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Checks whether two names are the same according to a case-sensitivity policy.
+ *
+ * @see SqlNameMatchers
+ */
+public interface SqlNameMatcher {
+  /** Returns whether name matching is case-sensitive. */
+  boolean isCaseSensitive();
+
+  /** Returns a name matches another.
+   *
+   * @param string Name written in code
+   * @param name Name of object we are trying to match
+   * @return Whether matches
+   */
+  boolean matches(String string, String name);
+
+  /** Looks up an item in a map. */
+  <K extends List<String>, V> V get(Map<K, V> map, List<String> prefixNames,
+      List<String> names);
+
+  /** Returns the most recent match.
+   *
+   * <p>In the default implementation,
+   * throws {@link UnsupportedOperationException}. */
+  String bestString();
+
+  /** Finds a field with a given name, using the currenct case-sensitivity,
+   * returning null if not found.
+   *
+   * @param rowType    Row type
+   * @param fieldName Field name
+   * @return Field, or null if not found
+   */
+  RelDataTypeField field(RelDataType rowType, String fieldName);
+}
+
+// End SqlNameMatcher.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatchers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatchers.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatchers.java
new file mode 100644
index 0000000..0ccc09f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatchers.java
@@ -0,0 +1,154 @@
+/*
+ * 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.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.util.Util;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helpers for {@link SqlNameMatcher}.
+ */
+public class SqlNameMatchers {
+
+  private static final BaseMatcher CASE_SENSITIVE = new BaseMatcher(true);
+  private static final BaseMatcher CASE_INSENSITIVE = new BaseMatcher(false);
+
+  private SqlNameMatchers() {}
+
+  /** Returns a name matcher with the given case sensitivity. */
+  public static SqlNameMatcher withCaseSensitive(final boolean caseSensitive) {
+    return caseSensitive ? CASE_SENSITIVE : CASE_INSENSITIVE;
+  }
+
+  /** Creates a name matcher that can suggest corrections to what the user
+   * typed. It matches liberally (case-insensitively) and also records the last
+   * match. */
+  public static SqlNameMatcher liberal() {
+    return new LiberalNameMatcher();
+  }
+
+  /** Partial implementation of {@link SqlNameMatcher}. */
+  private static class BaseMatcher implements SqlNameMatcher {
+    private final boolean caseSensitive;
+
+    BaseMatcher(boolean caseSensitive) {
+      this.caseSensitive = caseSensitive;
+    }
+
+    public boolean isCaseSensitive() {
+      return caseSensitive;
+    }
+
+    public boolean matches(String string, String name) {
+      return caseSensitive ? string.equals(name)
+          : string.equalsIgnoreCase(name);
+    }
+
+    protected boolean listMatches(List<String> list0, List<String> list1) {
+      if (list0.size() != list1.size()) {
+        return false;
+      }
+      for (int i = 0; i < list0.size(); i++) {
+        String s0 = list0.get(i);
+        String s1 = list1.get(i);
+        if (!matches(s0, s1)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    public <K extends List<String>, V> V get(Map<K, V> map,
+        List<String> prefixNames, List<String> names) {
+      final List<String> key = concat(prefixNames, names);
+      if (caseSensitive) {
+        //noinspection SuspiciousMethodCalls
+        return map.get(key);
+      }
+      for (Map.Entry<K, V> entry : map.entrySet()) {
+        if (listMatches(key, entry.getKey())) {
+          matched(prefixNames, entry.getKey());
+          return entry.getValue();
+        }
+      }
+      return null;
+    }
+
+    private List<String> concat(List<String> prefixNames, List<String> names) {
+      if (prefixNames.isEmpty()) {
+        return names;
+      } else {
+        return ImmutableList.<String>builder().addAll(prefixNames).addAll(names)
+            .build();
+      }
+    }
+
+    protected void matched(List<String> prefixNames, List<String> names) {
+    }
+
+    protected List<String> bestMatch() {
+      throw new UnsupportedOperationException();
+    }
+
+    public String bestString() {
+      return SqlIdentifier.getString(bestMatch());
+    }
+
+    public RelDataTypeField field(RelDataType rowType, String fieldName) {
+      return rowType.getField(fieldName, caseSensitive, false);
+    }
+  }
+
+  /** Matcher that remembers the requests that were made of it. */
+  private static class LiberalNameMatcher extends BaseMatcher {
+    List<String> matchedNames;
+
+    LiberalNameMatcher() {
+      super(false);
+    }
+
+    @Override protected boolean listMatches(List<String> list0,
+        List<String> list1) {
+      final boolean b = super.listMatches(list0, list1);
+      if (b) {
+        matchedNames = ImmutableList.copyOf(list1);
+      }
+      return b;
+    }
+
+    @Override protected void matched(List<String> prefixNames,
+        List<String> names) {
+      matchedNames = ImmutableList.copyOf(
+          Util.startsWith(names, prefixNames)
+              ? Util.skip(names, prefixNames.size())
+              : names);
+    }
+
+    @Override public List<String> bestMatch() {
+      return matchedNames;
+    }
+  }
+}
+
+// End SqlNameMatchers.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
index 55c1d01..52d4e1b 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
@@ -60,10 +60,13 @@ public class SqlQualified {
     if (scope == null) {
       return identifier.names;
     }
+    final SqlNameMatcher nameMatcher =
+        scope.getValidator().getCatalogReader().nameMatcher();
     final ImmutableList.Builder<String> builder = ImmutableList.builder();
     final SqlValidatorScope.ResolvedImpl resolved =
         new SqlValidatorScope.ResolvedImpl();
-    scope.resolve(Util.skipLast(identifier.names), false, resolved);
+    final List<String> prefix = Util.skipLast(identifier.names);
+    scope.resolve(prefix, nameMatcher, false, resolved);
     SqlValidatorNamespace namespace =
         resolved.count() == 1 ? resolved.only().namespace : null;
     builder.add(identifier.names.get(0));

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
index b43f85c..a71f8f4 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.sql.validate;
 
+import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.sql.SqlIdentifier;
@@ -35,10 +36,17 @@ public interface SqlValidatorCatalogReader {
   //~ Methods ----------------------------------------------------------------
 
   /**
-   * Finds a table with the given name, possibly qualified.
+   * Finds a table or schema with the given name, possibly qualified.
    *
-   * @param names Qualified name of table
-   * @return named table, or null if not found
+   * <p>Uses the case-sensitivity policy of the catalog reader.
+   *
+   * <p>If not found, returns null. If you want a more descriptive error
+   * message or to override the case-sensitivity of the match, use
+   * {@link SqlValidatorScope#resolveTable}.
+   *
+   * @param names Name of table, may be qualified or fully-qualified
+   *
+   * @return Table with the given name, or null
    */
   SqlValidatorTable getTable(List<String> names);
 
@@ -68,24 +76,37 @@ public interface SqlValidatorCatalogReader {
   List<SqlMoniker> getAllSchemaObjectNames(List<String> names);
 
   /**
-   * Returns the name of the current schema.
+   * Returns the paths of all schemas to look in for tables.
    *
-   * @return name of the current schema
+   * @return paths of current schema and root schema
    */
-  List<String> getSchemaName();
+  List<List<String>> getSchemaPaths();
 
-  /**
-   * Finds a field with a given name, using the case-sensitivity of the current
-   * session.
-   */
+  /** @deprecated Use
+   * {@link #nameMatcher()}.{@link SqlNameMatcher#field(RelDataType, String)} */
+  @Deprecated // to be removed before 2.0
   RelDataTypeField field(RelDataType rowType, String alias);
 
+  /** Returns an implementation of
+   * {@link org.apache.calcite.sql.validate.SqlNameMatcher}
+   * that matches the case-sensitivity policy. */
+  SqlNameMatcher nameMatcher();
+
+  /** @deprecated Use
+   * {@link #nameMatcher()}.{@link SqlNameMatcher#matches(String, String)} */
+  @Deprecated // to be removed before 2.0
   boolean matches(String string, String name);
 
   RelDataType createTypeFromProjection(RelDataType type,
       List<String> columnNameList);
 
+  /** @deprecated Use
+   * {@link #nameMatcher()}.{@link SqlNameMatcher#isCaseSensitive()} */
+  @Deprecated // to be removed before 2.0
   boolean isCaseSensitive();
+
+  /** Returns the root namespace for name resolution. */
+  CalciteSchema getRootSchema();
 }
 
 // End SqlValidatorCatalogReader.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5f9c0190/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 7b2645c..3766258 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -320,9 +320,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
           select,
           unknownType,
           list,
-          catalogReader.isCaseSensitive()
-          ? new LinkedHashSet<String>()
-              : new TreeSet<String>(String.CASE_INSENSITIVE_ORDER),
+          catalogReader.nameMatcher().isCaseSensitive()
+              ? new LinkedHashSet<String>()
+              : new TreeSet<>(String.CASE_INSENSITIVE_ORDER),
           types,
           includeSystemVars);
     }
@@ -504,7 +504,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       final SqlIdentifier prefixId = identifier.skipLast(1);
       final SqlValidatorScope.ResolvedImpl resolved =
           new SqlValidatorScope.ResolvedImpl();
-      scope.resolve(prefixId.names, true, resolved);
+      final SqlNameMatcher nameMatcher =
+          scope.validator.catalogReader.nameMatcher();
+      scope.resolve(prefixId.names, nameMatcher, true, resolved);
       if (resolved.count() == 0) {
         // e.g. "select s.t.* from e"
         // or "select r.* from e"
@@ -737,7 +739,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         if (ns == null) {
           final SqlValidatorScope.ResolvedImpl resolved =
               new SqlValidatorScope.ResolvedImpl();
-          scope.resolve(ImmutableList.of(name), false, resolved);
+          final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+          scope.resolve(ImmutableList.of(name), nameMatcher, false, resolved);
           if (resolved.count() == 1) {
             ns = resolved.only().namespace;
           }
@@ -971,9 +974,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       final SqlValidatorScope parentScope =
           ((DelegatingScope) scope).getParent();
       if (id.isSimple()) {
+        final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
         final SqlValidatorScope.ResolvedImpl resolved =
             new SqlValidatorScope.ResolvedImpl();
-        parentScope.resolve(id.names, false, resolved);
+        parentScope.resolve(id.names, nameMatcher, false, resolved);
         if (resolved.count() == 1) {
           return resolved.only().namespace;
         }
@@ -2929,11 +2933,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               rightRowType);
 
       // Check compatibility of the chosen columns.
+      final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
       for (String name : naturalColumnNames) {
         final RelDataType leftColType =
-            catalogReader.field(leftRowType, name).getType();
+            nameMatcher.field(leftRowType, name).getType();
         final RelDataType rightColType =
-            catalogReader.field(rightRowType, name).getType();
+            nameMatcher.field(rightRowType, name).getType();
         if (!SqlTypeUtil.isComparable(leftColType, rightColType)) {
           throw newValidationError(join,
               RESOURCE.naturalOrUsingColumnNotCompatible(name,
@@ -2994,7 +2999,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       String name = id.names.get(0);
       final SqlValidatorNamespace namespace = getNamespace(leftOrRight);
       final RelDataType rowType = namespace.getRowType();
-      final RelDataTypeField field = catalogReader.field(rowType, name);
+      final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+      final RelDataTypeField field = nameMatcher.field(rowType, name);
       if (field != null) {
         if (Collections.frequency(rowType.getFieldNames(), name) > 1) {
           throw newValidationError(id,
@@ -3053,7 +3059,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     // Make sure that items in FROM clause have distinct aliases.
     final SelectScope fromScope = (SelectScope) getFromScope(select);
     List<String> names = fromScope.getChildNames();
-    if (!catalogReader.isCaseSensitive()) {
+    if (!catalogReader.nameMatcher().isCaseSensitive()) {
       names = Lists.transform(names,
           new Function<String, String>() {
             public String apply(String s) {
@@ -3325,24 +3331,24 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
   public void validateSequenceValue(SqlValidatorScope scope, SqlIdentifier id) {
     // Resolve identifier as a table.
-    final SqlValidatorNamespace ns = scope.getTableNamespace(id.names);
-    if (ns == null) {
+    final SqlValidatorScope.ResolvedImpl resolved =
+        new SqlValidatorScope.ResolvedImpl();
+    scope.resolveTable(id.names, catalogReader.nameMatcher(),
+        SqlValidatorScope.Path.EMPTY, resolved);
+    if (resolved.count() != 1) {
       throw newValidationError(id, RESOURCE.tableNameNotFound(id.toString()));
     }
-
     // We've found a table. But is it a sequence?
-    if (!(ns instanceof TableNamespace)) {
-      throw newValidationError(id, RESOURCE.notASequence(id.toString()));
-    }
-    final SqlValidatorTable table = ns.getTable();
-    final Table table1 = ((RelOptTable) table).unwrap(Table.class);
-    switch (table1.getJdbcTableType()) {
-    case SEQUENCE:
-    case TEMPORARY_SEQUENCE:
-      break;
-    default:
-      throw newValidationError(id, RESOURCE.notASequence(id.toString()));
+    final SqlValidatorNamespace ns = resolved.only().namespace;
+    if (ns instanceof TableNamespace) {
+      final Table table = ((RelOptTable) ns.getTable()).unwrap(Table.class);
+      switch (table.getJdbcTableType()) {
+      case SEQUENCE:
+      case TEMPORARY_SEQUENCE:
+        return;
+      }
     }
+    throw newValidationError(id, RESOURCE.notASequence(id.toString()));
   }
 
   public SqlValidatorScope getWithScope(SqlNode withItem) {
@@ -4139,7 +4145,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
   }
 
   SqlValidatorNamespace lookupFieldNamespace(RelDataType rowType, String name) {
-    final RelDataTypeField field = catalogReader.field(rowType, name);
+    final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+    final RelDataTypeField field = nameMatcher.field(rowType, name);
     return new FieldNamespace(this, field.getType());
   }
 
@@ -4480,9 +4487,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // we could do a better job if they were looked up via
         // resolveColumn.
 
+        final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
         final SqlValidatorScope.ResolvedImpl resolved =
             new SqlValidatorScope.ResolvedImpl();
-        scope.resolve(id.names.subList(0, i), false, resolved);
+        scope.resolve(id.names.subList(0, i), nameMatcher, false, resolved);
         if (resolved.count() == 1) {
           // There's a namespace with the name we seek.
           final SqlValidatorScope.Resolve resolve = resolved.only();
@@ -4522,7 +4530,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
           name = "*";
           field = null;
         } else {
-          field = catalogReader.field(type, name);
+          final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+          field = nameMatcher.field(type, name);
         }
         if (field == null) {
           throw newValidationError(id.getComponent(i),
@@ -4696,8 +4705,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         final SqlValidatorNamespace selectNs = getNamespace(select);
         final RelDataType rowType =
             selectNs.getRowTypeSansSystemColumns();
-        RelDataTypeField field =
-            catalogReader.field(rowType, alias);
+        final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+        RelDataTypeField field = nameMatcher.field(rowType, alias);
         if (field != null) {
           return nthSelectItem(
               field.getIndex(),