calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject [5/6] calcite git commit: Complete [CALCITE-1208] by giving "resolve" a call-back, so it can make multiple matches
Date Fri, 16 Sep 2016 04:48:19 GMT
Complete [CALCITE-1208] by giving "resolve" a call-back, so it can make multiple matches

Change parser to make 'SELECT t.*.c' illegal.

Address Jinfeng's review comments.

Add a test for converting a query with UNION in FROM clause.


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

Branch: refs/heads/master
Commit: 0938c7b6d767e3242874d87a30d9112512d9243a
Parents: a859f93
Author: Julian Hyde <jhyde@apache.org>
Authored: Fri May 6 11:57:25 2016 -0700
Committer: Julian Hyde <jhyde@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       |  25 +-
 .../calcite/prepare/CalciteCatalogReader.java   |  15 +-
 .../AggregateExpandDistinctAggregatesRule.java  |   2 +-
 .../calcite/rel/type/DynamicRecordTypeImpl.java |  41 ++-
 .../apache/calcite/rel/type/RelDataType.java    |  14 +-
 .../calcite/rel/type/RelDataTypeFactory.java    |  36 ++-
 .../rel/type/RelDataTypeFactoryImpl.java        |  61 +++-
 .../calcite/rel/type/RelDataTypeHolder.java     |  27 +-
 .../calcite/rel/type/RelDataTypeImpl.java       |  58 +---
 .../apache/calcite/rel/type/RelRecordType.java  |  36 ++-
 .../org/apache/calcite/rel/type/StructKind.java |  83 ++++++
 .../apache/calcite/runtime/CalciteResource.java |   3 +
 .../org/apache/calcite/runtime/FlatLists.java   |  41 +++
 .../org/apache/calcite/sql/SqlIdentifier.java   |  16 +
 .../calcite/sql/validate/CatalogScope.java      |  10 -
 .../calcite/sql/validate/DelegatingScope.java   | 265 +++++++++++++----
 .../apache/calcite/sql/validate/EmptyScope.java |   6 +-
 .../apache/calcite/sql/validate/ListScope.java  | 136 +++++----
 .../calcite/sql/validate/ParameterScope.java    |   9 -
 .../calcite/sql/validate/SqlQualified.java      |   5 +-
 .../sql/validate/SqlValidatorCatalogReader.java |   8 -
 .../calcite/sql/validate/SqlValidatorImpl.java  |  74 +++--
 .../calcite/sql/validate/SqlValidatorScope.java | 120 +++++++-
 .../calcite/sql/validate/SqlValidatorUtil.java  |  48 ++-
 .../apache/calcite/sql/validate/WithScope.java  |  15 +-
 .../calcite/sql2rel/SqlToRelConverter.java      |  61 ++--
 .../apache/calcite/util/ImmutableIntList.java   |   6 +-
 .../main/java/org/apache/calcite/util/Pair.java |   5 +
 .../calcite/runtime/CalciteResource.properties  |   1 +
 .../calcite/sql/parser/SqlParserTest.java       |  15 +
 .../apache/calcite/sql/test/SqlAdvisorTest.java |   2 +
 .../apache/calcite/test/MockCatalogReader.java  | 162 ++++++----
 .../calcite/test/SqlToRelConverterTest.java     |  10 +-
 .../apache/calcite/test/SqlValidatorTest.java   | 294 +++++++++++++------
 .../calcite/test/SqlToRelConverterTest.xml      |  31 ++
 .../adapter/splunk/SplunkPushDownRule.java      |  13 +-
 36 files changed, 1165 insertions(+), 589 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 43d6cd5..2d97cf3 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -3686,20 +3686,19 @@ SqlIdentifier CompoundIdentifier() :
     }
     (
         <DOT>
-        (
-            p = Identifier() {
-                star = false;
-                list.add(p);
-                posList.add(getPos());
-            }
-        |
-            <STAR> {
-                star = true;
-                list.add("*");
-                posList.add(getPos());
-            }
-        )
+        p = Identifier() {
+            list.add(p);
+            posList.add(getPos());
+        }
     ) *
+    (
+        <DOT>
+        <STAR> {
+            star = true;
+            list.add("");
+            posList.add(getPos());
+        }
+    )?
     {
         SqlParserPos pos = SqlParserPos.sum(posList);
         if (star) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 9d8f3ef..0e14c83 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -74,7 +74,6 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
   final CalciteSchema rootSchema;
   final JavaTypeFactory typeFactory;
   private final List<String> defaultSchema;
-  private final boolean elideRecord = true;
   private final boolean caseSensitive;
 
   public CalciteCatalogReader(
@@ -203,27 +202,17 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
   }
 
   public RelDataTypeField field(RelDataType rowType, String alias) {
-    return SqlValidatorUtil.lookupField(caseSensitive, elideRecord, rowType,
-        alias);
-  }
-
-  public int fieldOrdinal(RelDataType rowType, String alias) {
-    RelDataTypeField field = field(rowType, alias);
-    return field != null ? field.getIndex() : -1;
+    return SqlValidatorUtil.lookupField(caseSensitive, rowType, alias);
   }
 
   public boolean matches(String string, String name) {
     return Util.matches(caseSensitive, string, name);
   }
 
-  public int match(List<String> strings, String name) {
-    return Util.findMatch(strings, name, caseSensitive);
-  }
-
   public RelDataType createTypeFromProjection(final RelDataType type,
       final List<String> columnNameList) {
     return SqlValidatorUtil.createTypeFromProjection(type, columnNameList,
-        typeFactory, caseSensitive, elideRecord);
+        typeFactory, caseSensitive);
   }
 
   public void lookupOperatorOverloads(final SqlIdentifier opName,

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
index a5f27f7..de12e42 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
@@ -571,7 +571,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       List<Integer> argList) {
     ImmutableIntList list = ImmutableIntList.of();
     for (int arg : argList) {
-      list = list.add(remap(groupSet, arg));
+      list = list.append(remap(groupSet, arg));
     }
     return list;
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java b/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
index 21206ac..82b6714 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
@@ -24,35 +24,34 @@ import java.util.Collections;
 import java.util.List;
 
 /**
- * Implementation of RelDataType for dynamic table. It's used in
- * Sql validation phase, where field list is mutable for getField() call.
+ * Implementation of {@link RelDataType} for a dynamic table.
  *
- * <p>After Sql validation, a normal RelDataTypeImpl with immutable field list
- * would take place of DynamicRecordTypeImpl instance for dynamic table. </p>
+ * <p>It's used during SQL validation, where the field list is mutable for
+ * the getField() call. After SQL validation, a normal {@link RelDataTypeImpl}
+ * with an immutable field list takes the place of the DynamicRecordTypeImpl
+ * instance.
  */
 public class DynamicRecordTypeImpl extends DynamicRecordType {
-
-  private final RelDataTypeFactory typeFactory;
   private final RelDataTypeHolder holder;
 
+  /** Creates a DynamicRecordTypeImpl. */
   public DynamicRecordTypeImpl(RelDataTypeFactory typeFactory) {
-    this.typeFactory = typeFactory;
-    this.holder = new RelDataTypeHolder();
-    this.holder.setRelDataTypeFactory(typeFactory);
+    this.holder = new RelDataTypeHolder(typeFactory);
     computeDigest();
   }
 
-  public List<RelDataTypeField> getFieldList() {
-    return holder.getFieldList(typeFactory);
+  @Override public List<RelDataTypeField> getFieldList() {
+    return holder.getFieldList();
   }
 
-  public int getFieldCount() {
+  @Override public int getFieldCount() {
     return holder.getFieldCount();
   }
 
-  public RelDataTypeField getField(String fieldName, boolean caseSensitive, boolean elideRecord) {
-    Pair<RelDataTypeField, Boolean> pair = holder.getFieldOrInsert(typeFactory, fieldName,
-        caseSensitive);
+  @Override public RelDataTypeField getField(String fieldName,
+      boolean caseSensitive, boolean elideRecord) {
+    final Pair<RelDataTypeField, Boolean> pair =
+        holder.getFieldOrInsert(fieldName, caseSensitive);
     // If a new field is added, we should re-compute the digest.
     if (pair.right) {
       computeDigest();
@@ -61,27 +60,27 @@ public class DynamicRecordTypeImpl extends DynamicRecordType {
     return pair.left;
   }
 
-  public List<String> getFieldNames() {
+  @Override public List<String> getFieldNames() {
     return holder.getFieldNames();
   }
 
-  public SqlTypeName getSqlTypeName() {
+  @Override public SqlTypeName getSqlTypeName() {
     return SqlTypeName.ROW;
   }
 
-  public RelDataTypePrecedenceList getPrecedenceList() {
+  @Override public RelDataTypePrecedenceList getPrecedenceList() {
     return new SqlTypeExplicitPrecedenceList(Collections.<SqlTypeName>emptyList());
   }
 
   protected void generateTypeString(StringBuilder sb, boolean withDetail) {
-    sb.append("(DynamicRecordRow" + getFieldNames() + ")");
+    sb.append("(DynamicRecordRow").append(getFieldNames()).append(")");
   }
 
-  public boolean isStruct() {
+  @Override public boolean isStruct() {
     return true;
   }
 
-  public RelDataTypeFamily getFamily() {
+  @Override public RelDataTypeFamily getFamily() {
     return getSqlTypeName().getFamily();
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index 442538e..7b18d67 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -36,12 +36,6 @@ public interface RelDataType /*extends Type*/ {
   int SCALE_NOT_SPECIFIED = Integer.MIN_VALUE;
   int PRECISION_NOT_SPECIFIED = -1;
 
-  enum StructKind {
-    FULLY_QUALIFIED,
-    PEEK_FIELDS,
-    PEEK_FIELDS_DEFAULT,
-  }
-
   //~ Methods ----------------------------------------------------------------
 
   /**
@@ -83,9 +77,10 @@ public interface RelDataType /*extends Type*/ {
   int getFieldCount();
 
   /**
-   * Gets the StructKind of a structured type.
+   * Returns the rule for resolving the fields of a structured type,
+   * or {@link StructKind#NONE} if this is not a structured type.
    *
-   * @return the StructKind that determines how its fields are resolved.
+   * @return the StructKind that determines how this type's fields are resolved
    */
   StructKind getStructKind();
 
@@ -242,9 +237,10 @@ public interface RelDataType /*extends Type*/ {
   RelDataTypeComparability getComparability();
 
   /**
-   *@return whether it has dynamic structure (for "schema-on-read" table)
+   * @return whether it has dynamic structure (for "schema-on-read" table)
    */
   boolean isDynamicStruct();
+
 }
 
 // End RelDataType.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
index 7c3e88c..1b3a1be 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
@@ -50,7 +50,7 @@ public interface RelDataTypeFactory {
   RelDataTypeSystem getTypeSystem();
 
   /**
-   * Creates a type which corresponds to a Java class.
+   * Creates a type that corresponds to a Java class.
    *
    * @param clazz the Java class used to define the type
    * @return canonical Java type descriptor
@@ -66,28 +66,37 @@ public interface RelDataTypeFactory {
   RelDataType createJoinType(RelDataType... types);
 
   /**
-   * Creates a type which represents a structured collection of fields, given
+   * Creates a type that represents a structured collection of fields, given
    * lists of the names and types of the fields.
    *
+   * @param  kind         Name resolution policy
    * @param typeList      types of the fields
    * @param fieldNameList names of the fields
    * @return canonical struct type descriptor
    */
+  RelDataType createStructType(StructKind kind,
+      List<RelDataType> typeList,
+      List<String> fieldNameList);
+
+  /** Creates a type that represents a structured collection of fields.
+   * Shorthand for <code>createStructType(StructKind.FULLY_QUALIFIED, typeList,
+   * fieldNameList)</code>. */
   RelDataType createStructType(
       List<RelDataType> typeList,
       List<String> fieldNameList);
 
   /**
-   * Creates a type which represents a structured collection of fields,
+   * Creates a type that represents a structured collection of fields,
    * obtaining the field information via a callback.
    *
    * @param fieldInfo callback for field information
    * @return canonical struct type descriptor
    */
+  @Deprecated // to be removed before 2.0
   RelDataType createStructType(FieldInfo fieldInfo);
 
   /**
-   * Creates a type which represents a structured collection of fieldList,
+   * Creates a type that represents a structured collection of fieldList,
    * obtaining the field information from a list of (name, type) pairs.
    *
    * @param fieldList List of (name, type) pairs
@@ -140,7 +149,7 @@ public interface RelDataTypeFactory {
   RelDataType copyType(RelDataType type);
 
   /**
-   * Creates a type which is the same as another type but with possibly
+   * Creates a type that is the same as another type but with possibly
    * different nullability. The output type may be identical to the input
    * type. For type systems without a concept of nullability, the return value
    * is always the same as the input.
@@ -156,7 +165,7 @@ public interface RelDataTypeFactory {
       boolean nullable);
 
   /**
-   * Creates a Type which is the same as another type but with possibly
+   * Creates a type that is the same as another type but with possibly
    * different charset or collation. For types without a concept of charset or
    * collation this function must throw an error.
    *
@@ -179,7 +188,7 @@ public interface RelDataTypeFactory {
   /**
    * Returns the most general of a set of types (that is, one type to which
    * they can all be cast), or null if conversion is not possible. The result
-   * may be a new type which is less restrictive than any of the input types,
+   * may be a new type that is less restrictive than any of the input types,
    * e.g. <code>leastRestrictive(INT, NUMERIC(3, 2))</code> could be
    * {@code NUMERIC(12, 2)}.
    *
@@ -288,8 +297,9 @@ public interface RelDataTypeFactory {
   //~ Inner Interfaces -------------------------------------------------------
 
   /**
-   * Callback which provides enough information to create fields.
+   * Callback that provides enough information to create fields.
    */
+  @Deprecated // to be removed before 2.0
   interface FieldInfo {
     /**
      * Returns the number of fields.
@@ -319,10 +329,11 @@ public interface RelDataTypeFactory {
    * Implementation of {@link FieldInfo} that provides a fluid API to build
    * a list of fields.
    */
+  @SuppressWarnings("deprecation")
   class FieldInfoBuilder implements FieldInfo {
     private final List<String> names = new ArrayList<>();
     private final List<RelDataType> types = new ArrayList<>();
-
+    private StructKind kind = StructKind.FULLY_QUALIFIED;
     private final RelDataTypeFactory typeFactory;
 
     /**
@@ -418,6 +429,11 @@ public interface RelDataTypeFactory {
       return this;
     }
 
+    public FieldInfoBuilder kind(StructKind kind) {
+      this.kind = kind;
+      return this;
+    }
+
     /**
      * Makes sure that field names are unique.
      */
@@ -435,7 +451,7 @@ public interface RelDataTypeFactory {
      * Creates a struct type with the current contents of this builder.
      */
     public RelDataType build() {
-      return typeFactory.createStructType(types, names);
+      return typeFactory.createStructType(kind, types, names);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
index eaff896..774a728 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
@@ -22,7 +22,6 @@ import org.apache.calcite.sql.type.JavaToSqlTypeConversionRules;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.base.Preconditions;
@@ -41,6 +40,8 @@ import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nonnull;
 
 /**
  * Abstract base for implementations of {@link RelDataTypeFactory}.
@@ -56,21 +57,20 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
           .softValues()
           .build(
               new CacheLoader<Object, RelDataType>() {
-                @Override public RelDataType load(Object key) {
-                  if (key instanceof RelDataType) {
-                    return (RelDataType) key;
+                @Override public RelDataType load(@Nonnull Object k) {
+                  if (k instanceof RelDataType) {
+                    return (RelDataType) k;
                   }
                   @SuppressWarnings("unchecked")
-                  final Pair<List<String>, List<RelDataType>> pair =
-                      (Pair<List<String>, List<RelDataType>>) key;
+                  final Key key = (Key) k;
                   final ImmutableList.Builder<RelDataTypeField> list =
                       ImmutableList.builder();
-                  for (int i = 0; i < pair.left.size(); i++) {
+                  for (int i = 0; i < key.names.size(); i++) {
                     list.add(
                         new RelDataTypeFieldImpl(
-                            pair.left.get(i), i, pair.right.get(i)));
+                            key.names.get(i), i, key.types.get(i)));
                   }
-                  return new RelRecordType(list.build());
+                  return new RelRecordType(key.kind, list.build());
                 }
               });
 
@@ -132,18 +132,24 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
         new RelCrossType(flattenedTypes, getFieldList(flattenedTypes)));
   }
 
-  // implement RelDataTypeFactory
   public RelDataType createStructType(
       final List<RelDataType> typeList,
       final List<String> fieldNameList) {
+    return createStructType(StructKind.FULLY_QUALIFIED, typeList,
+        fieldNameList);
+  }
+
+  public RelDataType createStructType(StructKind kind,
+      final List<RelDataType> typeList,
+      final List<String> fieldNameList) {
     assert typeList.size() == fieldNameList.size();
-    return canonize(fieldNameList, typeList);
+    return canonize(kind, fieldNameList, typeList);
   }
 
   // implement RelDataTypeFactory
   public RelDataType createStructType(
       final RelDataTypeFactory.FieldInfo fieldInfo) {
-    return canonize(
+    return canonize(StructKind.FULLY_QUALIFIED,
         new AbstractList<String>() {
           @Override public String get(int index) {
             return fieldInfo.getFieldName(index);
@@ -346,16 +352,16 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
    * key is more expensive, because it must be immutable and not hold
    * references into other data structures.</p>
    */
-  protected RelDataType canonize(
+  protected RelDataType canonize(final StructKind kind,
       final List<String> names,
       final List<RelDataType> types) {
-    final RelDataType type = CACHE.getIfPresent(Pair.of(names, types));
+    final RelDataType type = CACHE.getIfPresent(new Key(kind, names, types));
     if (type != null) {
       return type;
     }
     final ImmutableList<String> names2 = ImmutableList.copyOf(names);
     final ImmutableList<RelDataType> types2 = ImmutableList.copyOf(types);
-    return CACHE.getUnchecked(Pair.of(names2, types2));
+    return CACHE.getUnchecked(new Key(kind, names2, types2));
   }
 
   /**
@@ -646,6 +652,31 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
       return typeName;
     }
   }
+
+  /** Key to the data type cache. */
+  private static class Key {
+    private final StructKind kind;
+    private final List<String> names;
+    private final List<RelDataType> types;
+
+    Key(StructKind kind, List<String> names, List<RelDataType> types) {
+      this.kind = kind;
+      this.names = names;
+      this.types = types;
+    }
+
+    @Override public int hashCode() {
+      return Objects.hash(kind, names, types);
+    }
+
+    @Override public boolean equals(Object obj) {
+      return obj == this
+          || obj instanceof Key
+          && kind == ((Key) obj).kind
+          && names.equals(((Key) obj).names)
+          && types.equals(((Key) obj).types);
+    }
+  }
 }
 
 // End RelDataTypeFactoryImpl.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
index 414045d..1a777ff 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
@@ -26,12 +26,15 @@ import java.util.List;
 /**
  * Holding the expandable list of fields for dynamic table.
  */
-public class RelDataTypeHolder {
-  List<RelDataTypeField> fields = new ArrayList<>();
+class RelDataTypeHolder {
+  private final List<RelDataTypeField> fields = new ArrayList<>();
+  private final RelDataTypeFactory typeFactory;
 
-  private RelDataTypeFactory typeFactory;
+  RelDataTypeHolder(RelDataTypeFactory typeFactory) {
+    this.typeFactory = typeFactory;
+  }
 
-  public List<RelDataTypeField> getFieldList(RelDataTypeFactory typeFactory) {
+  public List<RelDataTypeField> getFieldList() {
     return fields;
   }
 
@@ -43,15 +46,12 @@ public class RelDataTypeHolder {
    * Get field if exists, otherwise inserts a new field. The new field by default will have "any"
    * type, except for the dynamic star field.
    *
-   * @param typeFactory RelDataTypeFactory
    * @param fieldName Request field name
    * @param caseSensitive Case Sensitive
    * @return A pair of RelDataTypeField and Boolean. Boolean indicates whether a new field is added
    * to this holder.
    */
-  public Pair<RelDataTypeField, Boolean> getFieldOrInsert(RelDataTypeFactory typeFactory,
-      String fieldName, boolean caseSensitive) {
-
+  Pair<RelDataTypeField, Boolean> getFieldOrInsert(String fieldName, boolean caseSensitive) {
     // First check if this field name exists in our field list
     for (RelDataTypeField f : fields) {
       if (Util.matches(caseSensitive, f.getName(), fieldName)) {
@@ -75,16 +75,7 @@ public class RelDataTypeHolder {
   }
 
   public List<String> getFieldNames() {
-    List<String> fieldNames = new ArrayList<>();
-    for (RelDataTypeField f : fields) {
-      fieldNames.add(f.getName());
-    }
-
-    return fieldNames;
-  }
-
-  public void setRelDataTypeFactory(RelDataTypeFactory typeFactory) {
-    this.typeFactory = typeFactory;
+    return Pair.left(fields);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
index 0fc14f3..a559686 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
@@ -84,36 +84,6 @@ public abstract class RelDataTypeImpl
         return field;
       }
     }
-    for (RelDataTypeField field : fieldList) {
-      RelDataType dataType = field.getType();
-      if (dataType.isStruct()
-          && dataType.getStructKind() == StructKind.PEEK_FIELDS_DEFAULT) {
-        for (RelDataTypeField subField : dataType.getFieldList()) {
-          if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
-            return subField;
-          }
-        }
-      }
-    }
-    RelDataTypeField matched = null;
-    for (RelDataTypeField field : fieldList) {
-      RelDataType dataType = field.getType();
-      if (dataType.isStruct()
-          && dataType.getStructKind() == StructKind.PEEK_FIELDS) {
-        for (RelDataTypeField subField : dataType.getFieldList()) {
-          if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
-            if (matched == null) {
-              matched = subField;
-            } else {
-              // TODO throw columnAmbiguous
-            }
-          }
-        }
-      }
-    }
-    if (matched != null) {
-      return matched;
-    }
     if (elideRecord) {
       final List<Slot> slots = Lists.newArrayList();
       getFieldRecurse(slots, this, 0, fieldName, caseSensitive);
@@ -172,7 +142,6 @@ public abstract class RelDataTypeImpl
     }
   }
 
-  // implement RelDataType
   public List<RelDataTypeField> getFieldList() {
     assert isStruct();
     return fieldList;
@@ -182,18 +151,15 @@ public abstract class RelDataTypeImpl
     return Pair.left(fieldList);
   }
 
-  // implement RelDataType
   public int getFieldCount() {
     assert isStruct() : this;
     return fieldList.size();
   }
 
   public StructKind getStructKind() {
-    assert isStruct() : this;
-    return StructKind.FULLY_QUALIFIED;
+    return isStruct() ? StructKind.FULLY_QUALIFIED : StructKind.NONE;
   }
 
-  // implement RelDataType
   public RelDataType getComponentType() {
     // this is not a collection type
     return null;
@@ -209,13 +175,11 @@ public abstract class RelDataTypeImpl
     return null;
   }
 
-  // implement RelDataType
   public boolean isStruct() {
     return fieldList != null;
   }
 
-  // implement RelDataType
-  public boolean equals(Object obj) {
+  @Override public boolean equals(Object obj) {
     if (obj instanceof RelDataTypeImpl) {
       final RelDataTypeImpl that = (RelDataTypeImpl) obj;
       return this.digest.equals(that.digest);
@@ -223,52 +187,42 @@ public abstract class RelDataTypeImpl
     return false;
   }
 
-  // implement RelDataType
-  public int hashCode() {
+  @Override public int hashCode() {
     return digest.hashCode();
   }
 
-  // implement RelDataType
   public String getFullTypeString() {
     return digest;
   }
 
-  // implement RelDataType
   public boolean isNullable() {
     return false;
   }
 
-  // implement RelDataType
   public Charset getCharset() {
     return null;
   }
 
-  // implement RelDataType
   public SqlCollation getCollation() {
     return null;
   }
 
-  // implement RelDataType
   public SqlIntervalQualifier getIntervalQualifier() {
     return null;
   }
 
-  // implement RelDataType
   public int getPrecision() {
     return PRECISION_NOT_SPECIFIED;
   }
 
-  // implement RelDataType
   public int getScale() {
     return SCALE_NOT_SPECIFIED;
   }
 
-  // implement RelDataType
   public SqlTypeName getSqlTypeName() {
     return null;
   }
 
-  // implement RelDataType
   public SqlIdentifier getSqlIdentifier() {
     SqlTypeName typeName = getSqlTypeName();
     if (typeName == null) {
@@ -279,7 +233,6 @@ public abstract class RelDataTypeImpl
         SqlParserPos.ZERO);
   }
 
-  // implement RelDataType
   public RelDataTypeFamily getFamily() {
     // by default, put each type into its own family
     return this;
@@ -310,14 +263,12 @@ public abstract class RelDataTypeImpl
     digest = sb.toString();
   }
 
-  // implement RelDataType
-  public String toString() {
+  @Override public String toString() {
     StringBuilder sb = new StringBuilder();
     generateTypeString(sb, false);
     return sb.toString();
   }
 
-  // implement RelDataType
   public RelDataTypePrecedenceList getPrecedenceList() {
     // by default, make each type have a precedence list containing
     // only other types in the same family
@@ -336,7 +287,6 @@ public abstract class RelDataTypeImpl
     };
   }
 
-  // implement RelDataType
   public RelDataTypeComparability getComparability() {
     return RelDataTypeComparability.ALL;
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
index 8d09cf4..7ae4dda 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
@@ -19,6 +19,8 @@ package org.apache.calcite.rel.type;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.sql.type.SqlTypeName;
 
+import com.google.common.base.Preconditions;
+
 import java.io.Serializable;
 import java.util.List;
 
@@ -26,37 +28,55 @@ import java.util.List;
  * RelRecordType represents a structured type having named fields.
  */
 public class RelRecordType extends RelDataTypeImpl implements Serializable {
+  /** Name resolution policy; usually {@link StructKind#FULLY_QUALIFIED}. */
+  private final StructKind kind;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
    * Creates a <code>RecordType</code>. This should only be called from a
    * factory method.
    */
-  public RelRecordType(List<RelDataTypeField> fields) {
+  public RelRecordType(StructKind kind, List<RelDataTypeField> fields) {
     super(fields);
+    this.kind = Preconditions.checkNotNull(kind);
     computeDigest();
   }
 
+  public RelRecordType(List<RelDataTypeField> fields) {
+    this(StructKind.FULLY_QUALIFIED, fields);
+  }
+
   //~ Methods ----------------------------------------------------------------
 
-  // implement RelDataType
-  public SqlTypeName getSqlTypeName() {
+  @Override public SqlTypeName getSqlTypeName() {
     return SqlTypeName.ROW;
   }
 
-  // implement RelDataType
-  public boolean isNullable() {
+  @Override public boolean isNullable() {
     return false;
   }
 
-  // implement RelDataType
-  public int getPrecision() {
+  @Override public int getPrecision() {
     // REVIEW: angel 18-Aug-2005 Put in fake implementation for precision
     return 0;
   }
 
+  @Override public StructKind getStructKind() {
+    return kind;
+  }
+
   protected void generateTypeString(StringBuilder sb, boolean withDetail) {
-    sb.append("RecordType(");
+    sb.append("RecordType");
+    switch (kind) {
+    case PEEK_FIELDS:
+      sb.append(":peek");
+      break;
+    case PEEK_FIELDS_DEFAULT:
+      sb.append(":peek_default");
+      break;
+    }
+    sb.append("(");
     for (Ord<RelDataTypeField> ord : Ord.zip(fieldList)) {
       if (ord.i > 0) {
         sb.append(", ");

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/StructKind.java b/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
new file mode 100644
index 0000000..b10f816
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.type;
+
+/**
+ * Describes a policy for resolving fields in record types.
+ *
+ * <p>The usual value is {@link #FULLY_QUALIFIED}.
+ *
+ * <p>A field whose record type is labeled {@link #PEEK_FIELDS} can be omitted.
+ * In Phoenix, column families are represented by fields like this.
+ * {@link #PEEK_FIELDS_DEFAULT} is similar, but represents the default column
+ * family, so it will win in the event of a tie.
+ *
+ * <p>SQL usually disallows a record type. For instance,
+ *
+ * <blockquote>SELECT address.zip FROM Emp AS e</blockquote>
+ *
+ * is disallowed because {@code address} "looks like" a table alias. You'd have
+ * to write
+ *
+ * <blockquote>SELECT e.address.zip FROM Emp AS e</blockquote>
+ *
+ * <p>But if a table has one or more columns that are record-typed and are
+ * labeled {@link #PEEK_FIELDS} or {@link #PEEK_FIELDS_DEFAULT} we suspend that
+ * rule and would allow {@code address.zip}.
+ *
+ * <p>If there are multiple matches, we choose the one that is:
+ * <ol>
+ *   <li>Shorter. If you write {@code zipcode}, {@code address.zipcode} will
+ *   be preferred over {@code product.supplier.zipcode}.
+ *   <li>Uses as little skipping as possible. A match that is fully-qualified
+ *   will beat one that uses {@code PEEK_FIELDS_DEFAULT} at some point, which
+ *   will beat one that uses {@code PEEK_FIELDS} at some point.
+ * </ol>
+ */
+public enum StructKind {
+  /** This is not a structured type. */
+  NONE,
+
+  /** This is a traditional structured type, where each field must be
+   * referenced explicitly.
+   *
+   * <p>Also, when referencing a struct column, you
+   * need to qualify it with the table alias, per standard SQL. For instance,
+   * {@code SELECT c.address.zipcode FROM customer AS c}
+   * is valid but
+   * {@code SELECT address.zipcode FROM customer}
+   * it not valid.
+   */
+  FULLY_QUALIFIED,
+
+  /** As {@link #PEEK_FIELDS}, but takes priority if another struct-typed
+   * field also has a field of the name being sought.
+   *
+   * <p>In Phoenix, only one of a table's columns is labeled
+   * {@code PEEK_FIELDS_DEFAULT} - the default column family - but in principle
+   * there could be more than one. */
+  PEEK_FIELDS_DEFAULT,
+
+  /** If a field has this type, you can see its fields without qualifying them
+   * with the name of this field.
+   *
+   * <p>For example, if {@code address} is labeled {@code PEEK_FIELDS}, you
+   * could write {@code zipcode} as shorthand for {@code address.zipcode}. */
+  PEEK_FIELDS,
+}
+
+// End StructKind.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 b936859..f261659 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -607,6 +607,9 @@ public interface CalciteResource {
   @BaseMessage("Table ''{0}'' not found")
   ExInst<CalciteException> tableNotFound(String tableName);
 
+  @BaseMessage("Not a record type. The ''*'' operator requires a record")
+  ExInst<SqlValidatorException> starRequiresRecordType();
+
   @BaseMessage("FILTER expression must be of type BOOLEAN")
   ExInst<CalciteException> filterMustBeBoolean();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
index eba51b7..4ab4ef2 100644
--- a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
+++ b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
@@ -18,7 +18,10 @@ package org.apache.calcite.runtime;
 
 import org.apache.calcite.util.ImmutableNullableList;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.AbstractList;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
@@ -238,6 +241,17 @@ public class FlatLists {
     }
   }
 
+  /** Returns a list that consists of a given list plus an element. */
+  public static <E> List<E> append(List<E> list, E e) {
+    if (list instanceof AbstractFlatList) {
+      //noinspection unchecked
+      return ((AbstractFlatList) list).append(e);
+    }
+    final List<E> newList = new ArrayList<>(list);
+    newList.add(e);
+    return FlatLists.of(newList);
+  }
+
   /** Base class for flat lists. */
   public abstract static class AbstractFlatList<T>
       extends AbstractImmutableList<T> implements RandomAccess {
@@ -246,6 +260,9 @@ public class FlatLists {
       return Arrays.asList((T[]) toArray());
     }
 
+    /** Returns a list that consists of a this list's elements plus a given
+     * element. */
+    public abstract List<T> append(T e);
   }
 
   /**
@@ -348,6 +365,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return new Flat2List<T>(t0, e);
+    }
   }
 
   /**
@@ -469,6 +490,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return new Flat3List<T>(t0, t1, e);
+    }
   }
 
   /**
@@ -609,6 +634,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return new Flat4List<T>(t0, t1, t2, e);
+    }
   }
 
   /**
@@ -768,6 +797,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return new Flat5List<T>(t0, t1, t2, t3, e);
+    }
   }
 
   /**
@@ -946,6 +979,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return new Flat6List<T>(t0, t1, t2, t3, t4, e);
+    }
   }
 
   /**
@@ -1144,6 +1181,10 @@ public class FlatLists {
     public int compareTo(List o) {
       return ComparableListImpl.compare((List) this, o);
     }
+
+    public List<T> append(T e) {
+      return ImmutableList.of(t0, t1, t2, t3, t5, e);
+    }
   }
 
   /** Empty list that implements the {@link Comparable} interface. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 d9b44ba..d070150 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
@@ -30,6 +30,7 @@ import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -173,6 +174,21 @@ public class SqlIdentifier extends SqlNode {
     }
   }
 
+  /** Returns an identifier that is the same as this except with a component
+   * added at a given position. Does not modify this identifier. */
+  public SqlIdentifier add(int i, String name, SqlParserPos pos) {
+    final List<String> names2 = new ArrayList<>(names);
+    names2.add(i, name);
+    final List<SqlParserPos> pos2;
+    if (componentPositions == null) {
+      pos2 = null;
+    } else {
+      pos2 = new ArrayList<>(componentPositions);
+      pos2.add(i, pos);
+    }
+    return new SqlIdentifier(names2, collation, pos, pos2);
+  }
+
   /**
    * Returns the position of the <code>i</code>th component of a compound
    * identifier, or the position of the whole identifier if that information

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java b/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
index c2ef43e..1420123 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
@@ -73,16 +73,6 @@ class CatalogScope extends DelegatingScope {
     throw new UnsupportedOperationException();
   }
 
-  public SqlValidatorNamespace resolve(List<String> names,
-      SqlValidatorScope[] ancestorOut, int[] offsetOut) {
-    final ImmutableList<String> nameList =
-        ImmutableList.<String>builder().addAll(this.names).addAll(names)
-            .build();
-    if (schemaNames.contains(nameList)) {
-      return new SchemaNamespace(validator, nameList);
-    }
-    return null;
-  }
 }
 
 // End CatalogScope.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 d48bb50..491ea1d 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
@@ -19,6 +19,7 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.rel.type.DynamicRecordType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.StructKind;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlNode;
@@ -29,9 +30,13 @@ import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -74,11 +79,42 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     throw new UnsupportedOperationException();
   }
 
-  public SqlValidatorNamespace resolve(
-      List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut) {
-    return parent.resolve(names, ancestorOut, offsetOut);
+  public void resolve(List<String> names, boolean deep, Resolved resolved) {
+    parent.resolve(names, deep, resolved);
+  }
+
+  /** If a record type allows implicit references to fields, recursively looks
+   * into the fields. Otherwise returns immediately. */
+  void resolveInNamespace(SqlValidatorNamespace ns, List<String> names,
+      Path path, Resolved resolved) {
+    if (names.isEmpty()) {
+      resolved.found(ns, this, path);
+      return;
+    }
+    final RelDataType rowType = ns.getRowType();
+    if (rowType.isStruct()) {
+      final String name = names.get(0);
+      final RelDataTypeField field0 =
+          validator.catalogReader.field(rowType, name);
+      if (field0 != null) {
+        final SqlValidatorNamespace ns2 = ns.lookupChild(field0.getName());
+        final Step path2 = path.add(rowType, field0.getIndex(),
+            StructKind.FULLY_QUALIFIED);
+        resolveInNamespace(ns2, names.subList(1, names.size()), 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 SqlValidatorNamespace ns2 = ns.lookupChild(field.getName());
+            resolveInNamespace(ns2, names, path2, resolved);
+          }
+        }
+      }
+    }
   }
 
   protected void addColumnNames(
@@ -113,6 +149,11 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     return parent.findQualifyingTableName(columnName, ctx);
   }
 
+  protected Map<String, SqlValidatorNamespace>
+  findQualifyingTables(String columnName) {
+    return ImmutableMap.of();
+  }
+
   public RelDataType resolveColumn(String name, SqlNode ctx) {
     return parent.resolveColumn(name, ctx);
   }
@@ -148,6 +189,7 @@ public abstract class DelegatingScope implements SqlValidatorScope {
       return SqlQualified.create(this, 1, null, identifier);
     }
 
+    final SqlIdentifier previous = identifier;
     String columnName;
     switch (identifier.names.size()) {
     case 1: {
@@ -159,63 +201,167 @@ public abstract class DelegatingScope implements SqlValidatorScope {
 
       final RelDataTypeField field =
           validator.catalogReader.field(namespace.getRowType(), columnName);
+      if (field != null) {
+        if (hasAmbiguousUnresolvedStar(namespace.getRowType(), field,
+            columnName)) {
+          throw validator.newValidationError(identifier,
+              RESOURCE.columnAmbiguous(columnName));
+        }
 
-      checkAmbiguousUnresolvedStar(namespace.getRowType(), field, identifier, columnName);
-
+        columnName = field.getName(); // use resolved field name
+      }
       // todo: do implicit collation here
       final SqlParserPos pos = identifier.getParserPosition();
-      SqlIdentifier expanded =
-          new SqlIdentifier(
-              ImmutableList.of(tableName, field.getName()),  // use resolved field name
-              null,
-              pos,
-              ImmutableList.of(SqlParserPos.ZERO, pos));
-      validator.setOriginal(expanded, identifier);
-      return SqlQualified.create(this, 1, namespace, expanded);
+      identifier =
+          new SqlIdentifier(ImmutableList.of(tableName, columnName), null,
+              pos, ImmutableList.of(SqlParserPos.ZERO, pos));
     }
-
+    // fall through
     default: {
       SqlValidatorNamespace fromNs = null;
+      Path fromPath = null;
+      final ResolvedImpl resolved = new ResolvedImpl();
       final int size = identifier.names.size();
       int i = size - 1;
       for (; i > 0; i--) {
         final SqlIdentifier prefix = identifier.getComponent(0, i);
-        fromNs = resolve(prefix.names, null, null);
-        if (fromNs != null) {
-          if (fromNs.getEnclosingNode() != null) {
-            String alias =
-                SqlValidatorUtil.getAlias(fromNs.getEnclosingNode(), -1);
-            if (alias != null
-                && !alias.equals(identifier.names.get(i - 1))) {
-              identifier = identifier.setName(i - 1, alias);
-            }
-          }
+        resolved.clear();
+        resolve(prefix.names, false, resolved);
+        if (resolved.count() == 1) {
+          final Resolve resolve = resolved.only();
+          fromNs = resolve.namespace;
+          fromPath = resolve.path;
           break;
         }
       }
       if (fromNs == null || fromNs instanceof SchemaNamespace) {
-        final SqlIdentifier prefix1 = identifier.skipLast(1);
-        throw validator.newValidationError(prefix1,
-            RESOURCE.tableNameNotFound(prefix1.toString()));
+        // Look for a column not qualified by a table alias.
+        columnName = identifier.names.get(0);
+        final Map<String, SqlValidatorNamespace> map =
+            findQualifyingTables(columnName);
+        switch (map.size()) {
+        default:
+          final SqlIdentifier prefix1 = identifier.skipLast(1);
+          throw validator.newValidationError(prefix1,
+              RESOURCE.tableNameNotFound(prefix1.toString()));
+        case 1:
+          fromPath = resolved.emptyPath();
+          fromNs = map.entrySet().iterator().next().getValue();
+        }
+
+        // Throw an error if the table was not found.
+        // If one or more of the child namespaces allows peeking
+        // (e.g. if they are Phoenix column families) then we relax the SQL
+        // standard requirement that record fields are qualified by table alias.
+        if (!hasLiberalChild()) {
+          final SqlIdentifier prefix1 = identifier.skipLast(1);
+          throw validator.newValidationError(prefix1,
+              RESOURCE.tableNameNotFound(prefix1.toString()));
+        }
+      }
+
+      // If a table alias is part of the identifier, make sure that the table
+      // alias uses the same case as it was defined. For example, in
+      //
+      //    SELECT e.empno FROM Emp as E
+      //
+      // change "e.empno" to "E.empno".
+      if (fromNs.getEnclosingNode() != null) {
+        String alias =
+            SqlValidatorUtil.getAlias(fromNs.getEnclosingNode(), -1);
+        if (alias != null
+            && i > 0
+            && !alias.equals(identifier.names.get(i - 1))) {
+          identifier = identifier.setName(i - 1, alias);
+        }
       }
       RelDataType fromRowType = fromNs.getRowType();
-      for (int j = i; j < size; j++) {
-        final SqlIdentifier last = identifier.getComponent(j);
-        columnName = last.getSimple();
-        final RelDataTypeField field =
-            validator.catalogReader.field(fromRowType, columnName);
-        if (field == null) {
-          throw validator.newValidationError(last,
-              RESOURCE.columnNotFoundInTable(columnName,
-                  identifier.getComponent(0, j).toString()));
+      if (fromPath.stepCount() > 1) {
+        for (Step p : fromPath.steps()) {
+          fromRowType = fromRowType.getFieldList().get(p.i).getType();
         }
+        ++i;
+      }
+      final SqlIdentifier suffix = identifier.getComponent(i, size);
+      resolved.clear();
+      resolveInNamespace(fromNs, suffix.names, resolved.emptyPath(), resolved);
+      final Path path;
+      switch (resolved.count()) {
+      case 0:
+        // 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".
+        int k = size - 1;
+        for (; k > i; --k) {
+          SqlIdentifier suffix2 = identifier.getComponent(i, k);
+          resolved.clear();
+          resolveInNamespace(fromNs, suffix2.names, resolved.emptyPath(),
+              resolved);
+          if (resolved.count() > 0) {
+            break;
+          }
+        }
+        final SqlIdentifier prefix = identifier.getComponent(0, i);
+        final SqlIdentifier suffix3 = identifier.getComponent(i, k + 1);
+        throw validator.newValidationError(suffix3,
+            RESOURCE.columnNotFoundInTable(suffix3.toString(), prefix.toString()));
+      case 1:
+        path = resolved.only().path;
+        break;
+      default:
+        final Comparator<Resolve> c =
+            new Comparator<Resolve>() {
+              public int compare(Resolve o1, Resolve o2) {
+                // Name resolution that uses fewer implicit steps wins.
+                int c = Integer.compare(worstKind(o1.path), worstKind(o2.path));
+                if (c != 0) {
+                  return c;
+                }
+                // Shorter path wins
+                return Integer.compare(o1.path.stepCount(), o2.path.stepCount());
+              }
+
+              private int worstKind(Path path) {
+                int kind = -1;
+                for (Step step : path.steps()) {
+                  kind = Math.max(kind, step.kind.ordinal());
+                }
+                return kind;
+              }
+            };
+        Collections.sort(resolved.resolves, c);
+        if (c.compare(resolved.resolves.get(0), resolved.resolves.get(1)) == 0) {
+          throw validator.newValidationError(suffix,
+              RESOURCE.columnAmbiguous(suffix.toString()));
+        }
+        path = resolved.resolves.get(0).path;
+      }
 
-        checkAmbiguousUnresolvedStar(fromRowType, field, identifier, columnName);
-
-        // normalize case to match definition, in a copy of the identifier
-        identifier = identifier.setName(j, field.getName());
-        fromRowType = field.getType();
+      // Normalize case to match definition, make elided fields explicit,
+      // and check that references to dynamic stars ("**") are unambiguous.
+      int k = i;
+      for (Step step : path.steps()) {
+        final RelDataTypeField field0 =
+            step.rowType.getFieldList().get(step.i);
+        final String fieldName = field0.getName();
+        switch (step.kind) {
+        case PEEK_FIELDS:
+        case PEEK_FIELDS_DEFAULT:
+          identifier = identifier.add(k, fieldName, SqlParserPos.ZERO);
+          break;
+        default:
+          final String name = identifier.names.get(k);
+          if (!fieldName.equals(name)) {
+            identifier = identifier.setName(k, fieldName);
+          }
+          if (hasAmbiguousUnresolvedStar(step.rowType, field0, name)) {
+            throw validator.newValidationError(identifier,
+                RESOURCE.columnAmbiguous(name));
+          }
+        }
+        ++k;
       }
+
       if (i > 1) {
         // Simplify overqualified identifiers.
         // For example, schema.emp.deptno becomes emp.deptno.
@@ -226,11 +372,19 @@ public abstract class DelegatingScope implements SqlValidatorScope {
         //   SELECT schema.emp.deptno FROM schema.emp AS e
         identifier = identifier.getComponent(i - 1, identifier.names.size());
       }
+
+      if (!previous.equals(identifier)) {
+        validator.setOriginal(identifier, previous);
+      }
       return SqlQualified.create(this, i, fromNs, identifier);
     }
     }
   }
 
+  protected boolean hasLiberalChild() {
+    return false;
+  }
+
   public void validateExpr(SqlNode expr) {
     // Do not delegate to parent. An expression valid in this scope may not
     // be valid in the parent scope.
@@ -248,27 +402,22 @@ public abstract class DelegatingScope implements SqlValidatorScope {
     return parent.getOrderList();
   }
 
-  private void checkAmbiguousUnresolvedStar(RelDataType fromRowType, RelDataTypeField field,
-      SqlIdentifier identifier, String columnName) {
-
-    if (field != null
-        && field.isDynamicStar()
+  /** Returns whether {@code rowType} contains more than one star column.
+   * Having more than one star columns implies ambiguous column. */
+  private boolean hasAmbiguousUnresolvedStar(RelDataType rowType,
+      RelDataTypeField field, String columnName) {
+    if (field.isDynamicStar()
         && !DynamicRecordType.isDynamicStarColName(columnName)) {
-      // Make sure fromRowType only contains one star column.
-      // Having more than one star columns implies ambiguous column.
       int count = 0;
-      for (RelDataTypeField possibleStar : fromRowType.getFieldList()) {
+      for (RelDataTypeField possibleStar : rowType.getFieldList()) {
         if (possibleStar.isDynamicStar()) {
-          count++;
+          if (++count > 1) {
+            return true;
+          }
         }
       }
-
-      if (count > 1) {
-        throw validator.newValidationError(identifier,
-            RESOURCE.columnAmbiguous(columnName));
-      }
     }
-
+    return false;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 9da3bff..e7f0ff8 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
@@ -64,11 +64,7 @@ class EmptyScope implements SqlValidatorScope {
     throw new UnsupportedOperationException();
   }
 
-  public SqlValidatorNamespace resolve(
-      List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut) {
-    return null;
+  public void resolve(List<String> names, boolean deep, Resolved resolved) {
   }
 
   public SqlValidatorNamespace getTableNamespace(List<String> names) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 8567cc5..c4d292f 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
@@ -16,15 +16,21 @@
  */
 package org.apache.calcite.sql.validate;
 
+import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.StructKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -39,7 +45,7 @@ public abstract class ListScope extends DelegatingScope {
    * List of child {@link SqlValidatorNamespace} objects and their names.
    */
   protected final List<Pair<String, SqlValidatorNamespace>> children =
-      new ArrayList<Pair<String, SqlValidatorNamespace>>();
+      new ArrayList<>();
 
   //~ Constructors -----------------------------------------------------------
 
@@ -63,57 +69,30 @@ public abstract class ListScope extends DelegatingScope {
     return Pair.right(children);
   }
 
-  protected SqlValidatorNamespace getChild(String alias) {
-    if (alias == null) {
-      if (children.size() != 1) {
-        throw Util.newInternal(
-            "no alias specified, but more than one table in from list");
-      }
-      return children.get(0).right;
-    } else {
-      final int i = validator.catalogReader.match(Pair.left(children), alias);
-      if (i >= 0) {
-        return children.get(i).right;
-      }
-      return null;
-    }
-  }
-
-  /** Returns a child namespace that matches a fully-qualified list of names.
-   * This will be a schema-qualified table, for example
-   *
-   * <blockquote><pre>SELECT sales.emp.empno FROM sales.emp</pre></blockquote>
-   */
-  protected SqlValidatorNamespace getChild(List<String> names) {
-    int i = findChild(names);
-    return i < 0 ? null : children.get(i).right;
-  }
-
-  protected int findChild(List<String> names) {
-    for (int i = 0; i < children.size(); i++) {
-      Pair<String, SqlValidatorNamespace> child = children.get(i);
-      if (child.left != null) {
-        if (validator.catalogReader.matches(child.left, Util.last(names))) {
-          if (names.size() == 1) {
-            return i;
-          }
-        } else {
+  private int findChild(List<String> names) {
+    for (Ord<Pair<String, SqlValidatorNamespace>> child : Ord.zip(children)) {
+      String lastName = Util.last(names);
+      if (child.e.left != null) {
+        if (!validator.catalogReader.matches(child.e.left, 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.i;
+        }
       }
 
       // Look up the 2 tables independently, in case one is qualified with
       // catalog & schema and the other is not.
-      final SqlValidatorTable table = child.right.getTable();
+      final SqlValidatorTable table = child.e.right.getTable();
       if (table != null) {
         final SqlValidatorTable table2 =
             validator.catalogReader.getTable(names);
         if (table2 != null
             && table.getQualifiedName().equals(table2.getQualifiedName())) {
-          return i;
+          return child.i;
         }
       }
     }
@@ -134,47 +113,84 @@ public abstract class ListScope extends DelegatingScope {
     parent.findAliases(result);
   }
 
-  public Pair<String, SqlValidatorNamespace>
+  @Override public Pair<String, SqlValidatorNamespace>
   findQualifyingTableName(final String columnName, SqlNode ctx) {
-    int count = 0;
-    Pair<String, SqlValidatorNamespace> tableName = null;
-    for (Pair<String, SqlValidatorNamespace> child : children) {
-      final RelDataType rowType = child.right.getRowType();
-      if (validator.catalogReader.field(rowType, columnName) != null) {
-        tableName = child;
-        count++;
-      }
-    }
-    switch (count) {
+    final Map<String, SqlValidatorNamespace> map =
+        findQualifyingTables(columnName);
+    switch (map.size()) {
     case 0:
       return parent.findQualifyingTableName(columnName, ctx);
     case 1:
-      return tableName;
+      return Pair.of(map.entrySet().iterator().next());
     default:
       throw validator.newValidationError(ctx,
           RESOURCE.columnAmbiguous(columnName));
     }
   }
 
-  public SqlValidatorNamespace resolve(
-      List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut) {
+  @Override public Map<String, SqlValidatorNamespace>
+  findQualifyingTables(String columnName) {
+    final Map<String, SqlValidatorNamespace> map = new HashMap<>();
+    for (Pair<String, SqlValidatorNamespace> child : children) {
+      final ResolvedImpl resolved = new ResolvedImpl();
+      resolve(ImmutableList.of(child.left, columnName), true, resolved);
+      if (resolved.count() > 0) {
+        map.put(child.getKey(), child.getValue());
+      }
+    }
+    return map;
+  }
+
+  @Override protected boolean hasLiberalChild() {
+    for (Pair<String, SqlValidatorNamespace> child : children) {
+      final RelDataType rowType = child.right.getRowType();
+      switch (rowType.getStructKind()) {
+      case PEEK_FIELDS:
+      case PEEK_FIELDS_DEFAULT:
+        return true;
+      }
+      for (RelDataTypeField field : rowType.getFieldList()) {
+        switch (field.getType().getStructKind()) {
+        case PEEK_FIELDS:
+        case PEEK_FIELDS_DEFAULT:
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override public void resolve(List<String> names, boolean deep,
+      Resolved resolved) {
     // First resolve by looking through the child namespaces.
     final int i = findChild(names);
     if (i >= 0) {
-      if (ancestorOut != null) {
-        ancestorOut[0] = this;
+      final Step path =
+          resolved.emptyPath().add(null, i, StructKind.FULLY_QUALIFIED);
+      resolved.found(children.get(i).right, this, path);
+      return;
+    }
+
+    // Recursively look deeper into the record-valued fields of the namespace,
+    // if it allows skipping fields.
+    if (deep) {
+      for (Ord<Pair<String, SqlValidatorNamespace>> child : Ord.zip(children)) {
+        // If identifier starts with table alias, remove the alias.
+        final List<String> names2 =
+            validator.catalogReader.matches(child.e.left, names.get(0))
+                ? names.subList(1, names.size())
+                : names;
+        resolveInNamespace(child.e.right, names2, resolved.emptyPath(),
+            resolved);
       }
-      if (offsetOut != null) {
-        offsetOut[0] = i;
+      if (resolved.count() > 0) {
+        return;
       }
-      return children.get(i).right;
     }
 
     // Then call the base class method, which will delegate to the
     // parent scope.
-    return parent.resolve(names, ancestorOut, offsetOut);
+    super.resolve(names, deep, resolved);
   }
 
   public RelDataType resolveColumn(String columnName, SqlNode ctx) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java b/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
index f1a4e91..c9a0484 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
@@ -20,7 +20,6 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -57,14 +56,6 @@ public class ParameterScope extends EmptyScope {
     return this;
   }
 
-  public SqlValidatorNamespace resolve(
-      List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut) {
-    assert names.size() == 1;
-    final RelDataType type = nameToTypeMap.get(names.get(0));
-    return new ParameterNamespace(validator, type);
-  }
 }
 
 // End ParameterScope.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 067c324..55c1d01 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
@@ -61,8 +61,11 @@ public class SqlQualified {
       return identifier.names;
     }
     final ImmutableList.Builder<String> builder = ImmutableList.builder();
+    final SqlValidatorScope.ResolvedImpl resolved =
+        new SqlValidatorScope.ResolvedImpl();
+    scope.resolve(Util.skipLast(identifier.names), false, resolved);
     SqlValidatorNamespace namespace =
-        scope.resolve(Util.skipLast(identifier.names), null, null);
+        resolved.count() == 1 ? resolved.only().namespace : null;
     builder.add(identifier.names.get(0));
     for (String name : Util.skip(identifier.names)) {
       if (namespace != null) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 b157c71..b43f85c 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
@@ -80,16 +80,8 @@ public interface SqlValidatorCatalogReader {
    */
   RelDataTypeField field(RelDataType rowType, String alias);
 
-  /**
-   * Finds the ordinal of a field with a given name, using the case-sensitivity
-   * of the current session.
-   */
-  int fieldOrdinal(RelDataType rowType, String alias);
-
   boolean matches(String string, String name);
 
-  int match(List<String> strings, String name);
-
   RelDataType createTypeFromProjection(RelDataType type,
       List<String> columnNameList);
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 44377d1..f4bbb79 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
@@ -445,7 +445,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     switch (identifier.names.size()) {
     case 1:
       for (Pair<String, SqlValidatorNamespace> p : scope.children) {
-
         if (p.right.getRowType().isDynamicStruct()) {
           // don't expand star if the underneath table is dynamic.
           // Treat this star as a special field in validation/conversion and
@@ -482,33 +481,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 includeSystemVars);
           }
         }
-
       }
       return true;
+
     default:
       final SqlIdentifier prefixId = identifier.skipLast(1);
-      final SqlValidatorNamespace fromNs;
-      if (prefixId.names.size() == 1) {
-        String tableName = prefixId.names.get(0);
-        final SqlValidatorNamespace childNs = scope.getChild(tableName);
-        if (childNs == null) {
-          // e.g. "select r.* from e"
-          throw newValidationError(identifier.getComponent(0),
-              RESOURCE.unknownIdentifier(tableName));
-        }
-        final SqlNode from = childNs.getNode();
-        fromNs = getNamespace(from);
-      } else {
-        fromNs = scope.getChild(prefixId.names);
-        if (fromNs == null) {
-          // e.g. "select s.t.* from e"
-          throw newValidationError(prefixId,
-              RESOURCE.unknownIdentifier(prefixId.toString()));
-        }
-      }
-      assert fromNs != null;
+      final SqlValidatorScope.ResolvedImpl resolved =
+          new SqlValidatorScope.ResolvedImpl();
+      scope.resolve(prefixId.names, true, resolved);
+      if (resolved.count() == 0) {
+        // e.g. "select s.t.* from e"
+        // or "select r.* from e"
+        throw newValidationError(prefixId,
+            RESOURCE.unknownIdentifier(prefixId.toString()));
+      }
+      final SqlValidatorNamespace fromNs = resolved.only().namespace;
       final RelDataType rowType = fromNs.getRowType();
-
       if (rowType.isDynamicStruct()) {
         // don't expand star if the underneath table is dynamic.
         addToSelectList(
@@ -518,7 +506,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             prefixId.plus(DynamicRecordType.DYNAMIC_STAR_PREFIX, startPosition),
             scope,
             includeSystemVars);
-      } else {
+      } else if (rowType.isStruct()) {
         for (RelDataTypeField field : rowType.getFieldList()) {
           String columnName = field.getName();
 
@@ -531,8 +519,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
               scope,
               includeSystemVars);
         }
+      } else {
+        throw newValidationError(prefixId, RESOURCE.starRequiresRecordType());
       }
-
       return true;
     }
   }
@@ -701,7 +690,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       SqlValidatorNamespace ns = null;
       for (String name : subNames) {
         if (ns == null) {
-          ns = scope.resolve(ImmutableList.of(name), null, null);
+          final SqlValidatorScope.ResolvedImpl resolved =
+              new SqlValidatorScope.ResolvedImpl();
+          scope.resolve(ImmutableList.of(name), false, resolved);
+          if (resolved.count() == 1) {
+            ns = resolved.only().namespace;
+          }
         } else {
           ns = ns.lookupChild(name);
         }
@@ -932,10 +926,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       final SqlValidatorScope parentScope =
           ((DelegatingScope) scope).getParent();
       if (id.isSimple()) {
-        SqlValidatorNamespace ns =
-            parentScope.resolve(id.names, null, null);
-        if (ns != null) {
-          return ns;
+        final SqlValidatorScope.ResolvedImpl resolved =
+            new SqlValidatorScope.ResolvedImpl();
+        parentScope.resolve(id.names, false, resolved);
+        if (resolved.count() == 1) {
+          return resolved.only().namespace;
         }
       }
     }
@@ -4439,7 +4434,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       }
 
       // Resolve the longest prefix of id that we can
-      SqlValidatorNamespace resolvedNs;
       int i;
       for (i = id.names.size() - 1; i > 0; i--) {
         // REVIEW jvs 9-June-2005: The name resolution rules used
@@ -4450,10 +4444,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // we could do a better job if they were looked up via
         // resolveColumn.
 
-        resolvedNs = scope.resolve(id.names.subList(0, i), null, null);
-        if (resolvedNs != null) {
+        final SqlValidatorScope.ResolvedImpl resolved =
+            new SqlValidatorScope.ResolvedImpl();
+        scope.resolve(id.names.subList(0, i), false, resolved);
+        if (resolved.count() == 1) {
           // There's a namespace with the name we seek.
-          type = resolvedNs.getRowType();
+          final SqlValidatorScope.Resolve resolve = resolved.only();
+          type = resolve.namespace.getRowType();
+          for (SqlValidatorScope.Step p : Util.skip(resolve.path.steps())) {
+            type = type.getFieldList().get(p.i).getType();
+          }
           break;
         }
       }
@@ -4525,9 +4525,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
   private static class Expander extends SqlScopedShuttle {
     private final SqlValidatorImpl validator;
 
-    public Expander(
-        SqlValidatorImpl validator,
-        SqlValidatorScope scope) {
+    Expander(SqlValidatorImpl validator, SqlValidatorScope scope) {
       super(scope);
       this.validator = validator;
     }
@@ -4549,7 +4547,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
       // SqlIdentifier "col_name" would be resolved to a dynamic star field in dynTable's rowType.
       // Expand such SqlIdentifier to ITEM operator.
       if (DynamicRecordType.isDynamicStarColName(Util.last(fqId.names))
-        && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) {
+          && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) {
         SqlNode[] inputs = new SqlNode[2];
         inputs[0] = fqId;
         inputs[1] = SqlLiteral.createCharString(

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 ee72ffb..c30b9ff 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
@@ -17,6 +17,7 @@
 package org.apache.calcite.sql.validate;
 
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.StructKind;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlNode;
@@ -24,6 +25,11 @@ import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.util.Pair;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -53,14 +59,11 @@ public interface SqlValidatorScope {
   /**
    * Looks up a node with a given name. Returns null if none is found.
    *
-   * @param names       Name of node to find
-   * @param ancestorOut If not null, writes the ancestor scope here
-   * @param offsetOut   If not null, writes the offset within the ancestor here
+   * @param names       Name of node to find, maybe partially or fully qualified
+   * @param deep        Whether to look more than one level deep
+   * @param resolved    Callback wherein to write the match(es) we find
    */
-  SqlValidatorNamespace resolve(
-      List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut);
+  void resolve(List<String> names, boolean deep, Resolved resolved);
 
   /**
    * Finds the table alias which is implicitly qualifying an unqualified
@@ -172,6 +175,109 @@ public interface SqlValidatorScope {
   /** 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)}. */
+  interface Resolved {
+    void found(SqlValidatorNamespace namespace, SqlValidatorScope scope,
+        Path path);
+    int count();
+    Path emptyPath();
+  }
+
+  /** A sequence of steps by which an identifier was resolved. */
+  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);
+    }
+
+    /** Number of steps in this path. */
+    public int stepCount() {
+      return 0;
+    }
+
+    /** Returns the steps in this path. */
+    public List<Step> steps() {
+      ImmutableList.Builder<Step> paths = new ImmutableList.Builder<>();
+      build(paths);
+      return paths.build();
+    }
+
+    protected void build(ImmutableList.Builder<Step> paths) {
+    }
+  }
+
+  /** A path that has no steps. */
+  class EmptyPath extends Path {
+  }
+
+  /** A step in resolving an identifier. */
+  class Step extends Path {
+    final Path parent;
+    final RelDataType rowType;
+    public final int i;
+    final StructKind kind;
+
+    Step(Path parent, RelDataType rowType, int i, StructKind kind) {
+      this.parent = Preconditions.checkNotNull(parent);
+      this.rowType = rowType; // may be null
+      this.i = i;
+      this.kind = Preconditions.checkNotNull(kind);
+    }
+
+    @Override public int stepCount() {
+      return 1 + parent.stepCount();
+    }
+
+    protected void build(ImmutableList.Builder<Step> paths) {
+      parent.build(paths);
+      paths.add(this);
+    }
+  }
+
+  /** Default implementation of
+   * {@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, SqlValidatorScope scope, Path path) {
+      resolves.add(new Resolve(namespace, scope, path));
+    }
+
+    public int count() {
+      return resolves.size();
+    }
+
+    public Path emptyPath() {
+      return emptyPath;
+    }
+
+    public Resolve only() {
+      return Iterables.getOnlyElement(resolves);
+    }
+
+    /** Resets all state. */
+    public void clear() {
+      resolves.clear();
+    }
+
+  }
+
+  /** A match found when looking up a name. */
+  class Resolve {
+    public final SqlValidatorNamespace namespace;
+    public final SqlValidatorScope scope;
+    public final Path path;
+
+    Resolve(SqlValidatorNamespace namespace, SqlValidatorScope scope,
+        Path path) {
+      this.namespace = Preconditions.checkNotNull(namespace);
+      this.scope = scope;
+      this.path = path;
+    }
+  }
 }
 
 // End SqlValidatorScope.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 249f678..249f5f5 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
@@ -109,14 +109,13 @@ public class SqlValidatorUtil {
    * Looks up a field with a given name, returning null if not found.
    *
    * @param caseSensitive Whether match is case-sensitive
-   * @param elideRecord Whether to find fields nested within records
    * @param rowType    Row type
    * @param columnName Field name
    * @return Field, or null if not found
    */
   public static RelDataTypeField lookupField(boolean caseSensitive,
-      boolean elideRecord, final RelDataType rowType, String columnName) {
-    return rowType.getField(columnName, caseSensitive, elideRecord);
+      final RelDataType rowType, String columnName) {
+    return rowType.getField(columnName, caseSensitive, false);
   }
 
   public static void checkCharsetAndCollateConsistentIfCharType(
@@ -445,16 +444,15 @@ public class SqlValidatorUtil {
       SqlValidatorScope scope,
       List<String> names) {
     assert names.size() > 0;
-    SqlValidatorNamespace namespace = null;
-    for (int i = 0; i < names.size(); i++) {
-      String name = names.get(i);
-      if (i == 0) {
-        namespace = scope.resolve(ImmutableList.of(name), null, null);
-      } else {
-        namespace = namespace.lookupChild(name);
-      }
+    final SqlValidatorScope.ResolvedImpl resolved =
+        new SqlValidatorScope.ResolvedImpl();
+    scope.resolve(ImmutableList.of(names.get(0)), false, resolved);
+    assert resolved.count() == 1;
+    SqlValidatorNamespace namespace = resolved.only().namespace;
+    for (String name : Util.skip(names)) {
+      namespace = namespace.lookupChild(name);
+      assert namespace != null;
     }
-    assert namespace != null;
     return namespace;
   }
 
@@ -524,14 +522,14 @@ public class SqlValidatorUtil {
 
   public static RelDataType createTypeFromProjection(RelDataType type,
       List<String> columnNameList, RelDataTypeFactory typeFactory,
-      boolean caseSensitive, boolean elideRecord) {
+      boolean caseSensitive) {
     // If the names in columnNameList and type have case-sensitive differences,
     // the resulting type will use those from type. These are presumably more
     // canonical.
     final List<RelDataTypeField> fields =
         new ArrayList<>(columnNameList.size());
     for (String name : columnNameList) {
-      RelDataTypeField field = type.getField(name, caseSensitive, elideRecord);
+      RelDataTypeField field = type.getField(name, caseSensitive, false);
       fields.add(type.getFieldList().get(field.getIndex()));
     }
     return typeFactory.createStructType(fields);
@@ -657,26 +655,24 @@ public class SqlValidatorUtil {
       String originalRelName = expr.names.get(0);
       String originalFieldName = expr.names.get(1);
 
-      int[] nsIndexes = {-1};
-      final SqlValidatorScope[] ancestorScopes = {null};
-      SqlValidatorNamespace foundNs =
-          scope.resolve(
-              ImmutableList.of(originalRelName),
-              ancestorScopes,
-              nsIndexes);
+      final SqlValidatorScope.ResolvedImpl resolved =
+          new SqlValidatorScope.ResolvedImpl();
+      scope.resolve(ImmutableList.of(originalRelName), false, resolved);
 
-      assert foundNs != null;
-      assert nsIndexes.length == 1;
-      int childNamespaceIndex = nsIndexes[0];
+      assert resolved.count() == 1;
+      final SqlValidatorScope.Resolve resolve = resolved.only();
+      final SqlValidatorNamespace foundNs = resolve.namespace;
+      final int childNamespaceIndex = resolve.path.steps().get(0).i;
 
       int namespaceOffset = 0;
 
       if (childNamespaceIndex > 0) {
         // If not the first child, need to figure out the width of
         // output types from all the preceding namespaces
-        assert ancestorScopes[0] instanceof ListScope;
+        final SqlValidatorScope ancestorScope = resolve.scope;
+        assert ancestorScope instanceof ListScope;
         List<SqlValidatorNamespace> children =
-            ((ListScope) ancestorScopes[0]).getChildren();
+            ((ListScope) ancestorScope).getChildren();
 
         for (int j = 0; j < childNamespaceIndex; j++) {
           namespaceOffset +=

http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/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 9141f83..42c8b99 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
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.sql.validate;
 
+import org.apache.calcite.rel.type.StructKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlWithItem;
 
@@ -53,14 +54,16 @@ class WithScope extends ListScope {
     return super.getTableNamespace(names);
   }
 
-  @Override public SqlValidatorNamespace resolve(List<String> names,
-      SqlValidatorScope[] ancestorOut,
-      int[] offsetOut) {
+  @Override public void resolve(List<String> names, boolean deep, Resolved resolved) {
     if (names.size() == 1
-        && names.get(0).equals(withItem.name.getSimple())) {
-      return validator.getNamespace(withItem);
+        && 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, null, path);
+      return;
     }
-    return super.resolve(names, ancestorOut, offsetOut);
+    super.resolve(names, deep, resolved);
   }
 }
 


Mime
View raw message