Repository: calcite
Updated Branches:
  refs/heads/master f5b041ff4 -> 09be7e74a


[CALCITE-2045] CREATE TYPE (Shuyi Chen)

Close apache/calcite#638


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

Branch: refs/heads/master
Commit: 570aca3d4fea34edcb04d0a5ec02f0fcf8925b0c
Parents: f5b041f
Author: Shuyi Chen <[email protected]>
Authored: Tue Jan 30 19:37:56 2018 -0800
Committer: Julian Hyde <[email protected]>
Committed: Fri Apr 20 11:11:59 2018 -0700

----------------------------------------------------------------------
 .../adapter/enumerable/RexToLixTranslator.java  |   2 +
 .../apache/calcite/adapter/jdbc/JdbcSchema.java |  13 +++
 .../calcite/jdbc/CachingCalciteSchema.java      |  37 +++++-
 .../org/apache/calcite/jdbc/CalciteSchema.java  |  98 +++++++++++++++-
 .../calcite/jdbc/SimpleCalciteSchema.java       |  23 +++-
 .../org/apache/calcite/model/JsonMapSchema.java |   9 ++
 .../java/org/apache/calcite/model/JsonRoot.java |   1 +
 .../java/org/apache/calcite/model/JsonType.java |  49 ++++++++
 .../apache/calcite/model/JsonTypeAttribute.java |  36 ++++++
 .../org/apache/calcite/model/ModelHandler.java  |  37 +++++-
 .../calcite/prepare/CalciteCatalogReader.java   |   7 +-
 .../apache/calcite/prepare/RelOptTableImpl.java |  13 +++
 .../apache/calcite/runtime/CalciteResource.java |   3 +
 .../java/org/apache/calcite/schema/Schema.java  |  16 +++
 .../org/apache/calcite/schema/SchemaPlus.java   |   4 +
 .../calcite/schema/impl/AbstractSchema.java     |  23 ++++
 .../calcite/schema/impl/DelegatingSchema.java   |   9 ++
 .../org/apache/calcite/sql/SqlDataTypeSpec.java |  59 +++++++---
 .../java/org/apache/calcite/sql/SqlKind.java    |  10 ++
 .../calcite/sql/validate/SqlValidatorImpl.java  |   3 +
 .../calcite/sql/validate/SqlValidatorUtil.java  |  31 +++++
 .../sql2rel/StandardConvertletTable.java        |   3 +
 .../main/java/org/apache/calcite/util/Util.java |   1 -
 .../calcite/runtime/CalciteResource.properties  |   1 +
 .../org/apache/calcite/test/CalciteSuite.java   |   1 +
 .../apache/calcite/test/MockCatalogReader.java  |  15 ++-
 .../java/org/apache/calcite/test/ModelTest.java |  16 +++
 .../apache/calcite/test/SqlValidatorTest.java   |   9 +-
 .../calcite/test/SqlValidatorTestCase.java      |   4 +
 .../java/org/apache/calcite/test/UdtTest.java   |  63 ++++++++++
 server/src/main/codegen/config.fmpp             |   2 +
 .../src/main/codegen/includes/parserImpls.ftl   |  74 ++++++++++++
 .../calcite/sql/ddl/SqlAttributeDefinition.java |  93 +++++++++++++++
 .../apache/calcite/sql/ddl/SqlCreateTable.java  |  10 +-
 .../apache/calcite/sql/ddl/SqlCreateType.java   | 114 +++++++++++++++++++
 .../org/apache/calcite/sql/ddl/SqlDdlNodes.java |  20 ++++
 .../apache/calcite/sql/ddl/SqlDropObject.java   |  11 +-
 .../org/apache/calcite/sql/ddl/SqlDropType.java |  37 ++++++
 .../apache/calcite/test/ServerParserTest.java   |  30 +++++
 .../org/apache/calcite/test/ServerTest.java     |  44 +++++++
 server/src/test/resources/sql/type.iq           |  57 ++++++++++
 site/_docs/model.md                             |  32 +++++-
 site/_docs/reference.md                         |  28 ++++-
 43 files changed, 1102 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 083df7c..805216b 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -780,6 +780,8 @@ public class RexToLixTranslator {
       final BigDecimal bd = literal.getValueAs(BigDecimal.class);
       if (javaClass == float.class) {
         return Expressions.constant(bd, javaClass);
+      } else if (javaClass == double.class) {
+        return Expressions.constant(bd, javaClass);
       }
       assert javaClass == BigDecimal.class;
       return Expressions.new_(BigDecimal.class,

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
index eb91b14..52dd865 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
@@ -410,6 +410,19 @@ public class JdbcSchema implements Schema {
     return getTableMap(!snapshot).keySet();
   }
 
+  protected Map<String, RelProtoDataType> getTypes() {
+    // TODO: populate map from JDBC metadata
+    return ImmutableMap.of();
+  }
+
+  @Override public RelProtoDataType getType(String name) {
+    return getTypes().get(name);
+  }
+
+  @Override public Set<String> getTypeNames() {
+    return getTypes().keySet();
+  }
+
   public Schema getSubSchema(String name) {
     // JDBC does not support sub-schemas.
     return null;

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java 
b/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java
index 96476f1..486f0d2 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CachingCalciteSchema.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.jdbc;
 
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaVersion;
@@ -44,21 +45,22 @@ class CachingCalciteSchema extends CalciteSchema {
   private final Cached<SubSchemaCache> implicitSubSchemaCache;
   private final Cached<NameSet> implicitTableCache;
   private final Cached<NameSet> implicitFunctionCache;
+  private final Cached<NameSet> implicitTypeCache;
 
   private boolean cache = true;
 
   /** Creates a CachingCalciteSchema. */
   CachingCalciteSchema(CalciteSchema parent, Schema schema, String name) {
-    this(parent, schema, name, null, null, null, null, null, null, null);
+    this(parent, schema, name, null, null, null, null, null, null, null, null);
   }
 
   private CachingCalciteSchema(CalciteSchema parent, Schema schema,
       String name, NameMap<CalciteSchema> subSchemaMap,
-      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap,
+      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap, 
NameMap<TypeEntry> typeMap,
       NameMultimap<FunctionEntry> functionMap, NameSet functionNames,
       NameMap<FunctionEntry> nullaryFunctionMap,
       List<? extends List<String>> path) {
-    super(parent, schema, name, subSchemaMap, tableMap, latticeMap,
+    super(parent, schema, name, subSchemaMap, tableMap, latticeMap, typeMap,
         functionMap, functionNames, nullaryFunctionMap, path);
     this.implicitSubSchemaCache =
         new AbstractCached<SubSchemaCache>() {
@@ -81,6 +83,13 @@ class CachingCalciteSchema extends CalciteSchema {
                 CachingCalciteSchema.this.schema.getFunctionNames());
           }
         };
+    this.implicitTypeCache =
+        new AbstractCached<NameSet>() {
+          public NameSet build() {
+            return NameSet.immutableCopyOf(
+                CachingCalciteSchema.this.schema.getTypeNames());
+          }
+        };
   }
 
   public void setCache(boolean cache) {
@@ -133,6 +142,19 @@ class CachingCalciteSchema extends CalciteSchema {
     return null;
   }
 
+  protected TypeEntry getImplicitType(String name, boolean caseSensitive) {
+    final long now = System.currentTimeMillis();
+    final NameSet implicitTypeNames = implicitTypeCache.get(now);
+    for (String typeName
+        : implicitTypeNames.range(name, caseSensitive)) {
+      final RelProtoDataType type = schema.getType(typeName);
+      if (type != null) {
+        return typeEntry(name, type);
+      }
+    }
+    return null;
+  }
+
   protected void addImplicitSubSchemaToBuilder(
       ImmutableSortedMap.Builder<String, CalciteSchema> builder) {
     ImmutableSortedMap<String, CalciteSchema> explicitSubSchemas = 
builder.build();
@@ -177,6 +199,13 @@ class CachingCalciteSchema extends CalciteSchema {
     builder.addAll(set.iterable());
   }
 
+  protected void 
addImplicitTypeNamesToBuilder(ImmutableSortedSet.Builder<String> builder) {
+    // Add implicit types, case-sensitive.
+    final long now = System.currentTimeMillis();
+    final NameSet set = implicitTypeCache.get(now);
+    builder.addAll(set.iterable());
+  }
+
   protected void addImplicitTablesBasedOnNullaryFunctionsToBuilder(
       ImmutableSortedMap.Builder<String, Table> builder) {
     ImmutableSortedMap<String, Table> explicitTables = builder.build();
@@ -217,7 +246,7 @@ class CachingCalciteSchema extends CalciteSchema {
 
   protected CalciteSchema snapshot(CalciteSchema parent, SchemaVersion 
version) {
     CalciteSchema snapshot = new CachingCalciteSchema(parent,
-        schema.snapshot(version), name, null, tableMap, latticeMap,
+        schema.snapshot(version), name, null, tableMap, latticeMap, typeMap,
         functionMap, functionNames, nullaryFunctionMap, getPath());
     for (CalciteSchema subSchema : subSchemaMap.map().values()) {
       CalciteSchema subSchemaSnapshot = subSchema.snapshot(snapshot, version);

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 b189e1b..badf781 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteSchema.java
@@ -19,6 +19,7 @@ package org.apache.calcite.jdbc;
 import org.apache.calcite.linq4j.function.Experimental;
 import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.materialize.Lattice;
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
@@ -62,6 +63,7 @@ public abstract class CalciteSchema {
    *  {@link #schema}. */
   protected final NameMap<TableEntry> tableMap;
   protected final NameMultimap<FunctionEntry> functionMap;
+  protected final NameMap<TypeEntry> typeMap;
   protected final NameMap<LatticeEntry> latticeMap;
   protected final NameSet functionNames;
   protected final NameMap<FunctionEntry> nullaryFunctionMap;
@@ -70,7 +72,7 @@ public abstract class CalciteSchema {
 
   protected CalciteSchema(CalciteSchema parent, Schema schema,
       String name, NameMap<CalciteSchema> subSchemaMap,
-      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap,
+      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap, 
NameMap<TypeEntry> typeMap,
       NameMultimap<FunctionEntry> functionMap, NameSet functionNames,
       NameMap<FunctionEntry> nullaryFunctionMap,
       List<? extends List<String>> path) {
@@ -103,6 +105,11 @@ public abstract class CalciteSchema {
       this.functionNames = Preconditions.checkNotNull(functionNames);
       this.nullaryFunctionMap = Preconditions.checkNotNull(nullaryFunctionMap);
     }
+    if (typeMap == null) {
+      this.typeMap = new NameMap<>();
+    } else {
+      this.typeMap = Preconditions.checkNotNull(typeMap);
+    }
     this.path = path;
   }
 
@@ -118,6 +125,12 @@ public abstract class CalciteSchema {
   protected abstract TableEntry getImplicitTable(String tableName,
       boolean caseSensitive);
 
+  /** Returns a type with a given name that is defined implicitly
+   * (that is, by the underlying {@link Schema} object, not explicitly
+   * by a call to {@link #add(String, RelProtoDataType)}), or null. */
+  protected abstract TypeEntry getImplicitType(String name,
+                                                boolean caseSensitive);
+
   /** Returns table function with a given name and zero arguments that is
    * defined implicitly (that is, by the underlying {@link Schema} object,
    * not explicitly by a call to {@link #add(String, Function)}), or null. */
@@ -141,6 +154,10 @@ public abstract class CalciteSchema {
   protected abstract void addImplicitFuncNamesToBuilder(
       ImmutableSortedSet.Builder<String> builder);
 
+  /** Adds implicit type names to a builder. */
+  protected abstract void addImplicitTypeNamesToBuilder(
+      ImmutableSortedSet.Builder<String> builder);
+
   /** Adds implicit table functions to a builder. */
   protected abstract void addImplicitTablesBasedOnNullaryFunctionsToBuilder(
       ImmutableSortedMap.Builder<String, Table> builder);
@@ -158,6 +175,11 @@ public abstract class CalciteSchema {
     return new TableEntryImpl(this, name, table, ImmutableList.<String>of());
   }
 
+  /** Creates a TableEntryImpl with no SQLs. */
+  protected TypeEntryImpl typeEntry(String name, RelProtoDataType 
relProtoDataType) {
+    return new TypeEntryImpl(this, name, relProtoDataType);
+  }
+
   /** Defines a table within this schema. */
   public TableEntry add(String tableName, Table table) {
     return add(tableName, table, ImmutableList.<String>of());
@@ -172,6 +194,14 @@ public abstract class CalciteSchema {
     return entry;
   }
 
+  /** Defines a type within this schema. */
+  public TypeEntry add(String name, RelProtoDataType type) {
+    final TypeEntry entry =
+        new TypeEntryImpl(this, name, type);
+    typeMap.put(name, entry);
+    return entry;
+  }
+
   private FunctionEntry add(String name, Function function) {
     final FunctionEntryImpl entry =
         new FunctionEntryImpl(this, name, function);
@@ -319,6 +349,27 @@ public abstract class CalciteSchema {
     return Compatible.INSTANCE.navigableSet(builder.build());
   }
 
+  /** Returns the set of all types names. */
+  public final NavigableSet<String> getTypeNames() {
+    final ImmutableSortedSet.Builder<String> builder =
+        new ImmutableSortedSet.Builder<>(NameSet.COMPARATOR);
+    // Add explicit types.
+    builder.addAll(typeMap.map().keySet());
+    // Add implicit types.
+    addImplicitTypeNamesToBuilder(builder);
+    return Compatible.INSTANCE.navigableSet(builder.build());
+  }
+
+  /** Returns a type, explicit and implicit, with a given
+   * name. Never null. */
+  public final TypeEntry getType(String name, boolean caseSensitive) {
+    for (Map.Entry<String, TypeEntry> entry
+        : typeMap.range(name, caseSensitive).entrySet()) {
+      return entry.getValue();
+    }
+    return getImplicitType(name, caseSensitive);
+  }
+
   /** Returns a collection of all functions, explicit and implicit, with a 
given
    * name. Never null. */
   public final Collection<Function> getFunctions(String name, boolean 
caseSensitive) {
@@ -481,6 +532,11 @@ public abstract class CalciteSchema {
     return true;
   }
 
+  @Experimental
+  public boolean removeType(String name) {
+    return typeMap.remove(name) != null;
+  }
+
   /**
    * Entry in a schema, such as a table or sub-schema.
    *
@@ -519,6 +575,15 @@ public abstract class CalciteSchema {
     public abstract Table getTable();
   }
 
+  /** Membership of a type in a schema. */
+  public abstract static class TypeEntry extends Entry {
+    public TypeEntry(CalciteSchema schema, String name) {
+      super(schema, name);
+    }
+
+    public abstract RelProtoDataType getType();
+  }
+
   /** Membership of a function in a schema. */
   public abstract static class FunctionEntry extends Entry {
     public FunctionEntry(CalciteSchema schema, String name) {
@@ -587,6 +652,15 @@ public abstract class CalciteSchema {
       return CalciteSchema.this.getTableNames();
     }
 
+    @Override public RelProtoDataType getType(String name) {
+      final TypeEntry entry = CalciteSchema.this.getType(name, true);
+      return entry == null ? null : entry.getType();
+    }
+
+    @Override public Set<String> getTypeNames() {
+      return CalciteSchema.this.getTypeNames();
+    }
+
     public Collection<Function> getFunctions(String name) {
       return CalciteSchema.this.getFunctions(name, true);
     }
@@ -635,6 +709,10 @@ public abstract class CalciteSchema {
       CalciteSchema.this.add(name, function);
     }
 
+    public void add(String name, RelProtoDataType type) {
+      CalciteSchema.this.add(name, type);
+    }
+
     public void add(String name, Lattice lattice) {
       CalciteSchema.this.add(name, lattice);
     }
@@ -661,6 +739,24 @@ public abstract class CalciteSchema {
   }
 
   /**
+   * Implementation of {@link TypeEntry}
+   * where all properties are held in fields.
+   */
+  public static class TypeEntryImpl extends TypeEntry {
+    private final RelProtoDataType protoDataType;
+
+    /** Creates a TypeEntryImpl. */
+    public TypeEntryImpl(CalciteSchema schema, String name, RelProtoDataType 
protoDataType) {
+      super(schema, name);
+      this.protoDataType = protoDataType;
+    }
+
+    public RelProtoDataType getType() {
+      return protoDataType;
+    }
+  }
+
+  /**
    * Implementation of {@link FunctionEntry}
    * where all properties are held in fields.
    */

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java 
b/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java
index df004c7..15ede8b 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/SimpleCalciteSchema.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.jdbc;
 
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaVersion;
@@ -42,16 +43,16 @@ class SimpleCalciteSchema extends CalciteSchema {
    * <p>Use {@link CalciteSchema#createRootSchema(boolean)}
    * or {@link #add(String, Schema)}. */
   SimpleCalciteSchema(CalciteSchema parent, Schema schema, String name) {
-    this(parent, schema, name, null, null, null, null, null, null, null);
+    this(parent, schema, name, null, null, null, null, null, null, null, null);
   }
 
   private SimpleCalciteSchema(CalciteSchema parent, Schema schema,
       String name, NameMap<CalciteSchema> subSchemaMap,
-      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap,
+      NameMap<TableEntry> tableMap, NameMap<LatticeEntry> latticeMap, 
NameMap<TypeEntry> typeMap,
       NameMultimap<FunctionEntry> functionMap, NameSet functionNames,
       NameMap<FunctionEntry> nullaryFunctionMap,
       List<? extends List<String>> path) {
-    super(parent, schema, name, subSchemaMap, tableMap, latticeMap,
+    super(parent, schema, name, subSchemaMap, tableMap, latticeMap, typeMap,
         functionMap, functionNames, nullaryFunctionMap, path);
   }
 
@@ -86,6 +87,15 @@ class SimpleCalciteSchema extends CalciteSchema {
     return null;
   }
 
+  protected TypeEntry getImplicitType(String name, boolean caseSensitive) {
+    // Check implicit types.
+    RelProtoDataType type = schema.getType(name);
+    if (type != null) {
+      return typeEntry(name, type);
+    }
+    return null;
+  }
+
   protected void addImplicitSubSchemaToBuilder(
       ImmutableSortedMap.Builder<String, CalciteSchema> builder) {
     ImmutableSortedMap<String, CalciteSchema> explicitSubSchemas = 
builder.build();
@@ -119,6 +129,11 @@ class SimpleCalciteSchema extends CalciteSchema {
     builder.addAll(schema.getFunctionNames());
   }
 
+  @Override protected void addImplicitTypeNamesToBuilder(
+      ImmutableSortedSet.Builder<String> builder) {
+    builder.addAll(schema.getTypeNames());
+  }
+
   protected void addImplicitTablesBasedOnNullaryFunctionsToBuilder(
       ImmutableSortedMap.Builder<String, Table> builder) {
     ImmutableSortedMap<String, Table> explicitTables = builder.build();
@@ -155,7 +170,7 @@ class SimpleCalciteSchema extends CalciteSchema {
 
   protected CalciteSchema snapshot(CalciteSchema parent, SchemaVersion 
version) {
     CalciteSchema snapshot = new SimpleCalciteSchema(parent,
-        schema.snapshot(version), name, null, tableMap, latticeMap,
+        schema.snapshot(version), name, null, tableMap, latticeMap, typeMap,
         functionMap, functionNames, nullaryFunctionMap, getPath());
     for (CalciteSchema subSchema : subSchemaMap.map().values()) {
       CalciteSchema subSchemaSnapshot = subSchema.snapshot(snapshot, version);

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/model/JsonMapSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonMapSchema.java 
b/core/src/main/java/org/apache/calcite/model/JsonMapSchema.java
index a7e8783..9e98606 100644
--- a/core/src/main/java/org/apache/calcite/model/JsonMapSchema.java
+++ b/core/src/main/java/org/apache/calcite/model/JsonMapSchema.java
@@ -34,6 +34,12 @@ public class JsonMapSchema extends JsonSchema {
    */
   public final List<JsonTable> tables = new ArrayList<>();
 
+  /** Types in this schema.
+   *
+   * <p>The list may be empty.
+   */
+  public final List<JsonType> types = new ArrayList<>();
+
   /** Functions in this schema.
    *
    * <p>The list may be empty.
@@ -52,6 +58,9 @@ public class JsonMapSchema extends JsonSchema {
     for (JsonFunction jsonFunction : functions) {
       jsonFunction.accept(modelHandler);
     }
+    for (JsonType jsonType : types) {
+      jsonType.accept(modelHandler);
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/model/JsonRoot.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonRoot.java 
b/core/src/main/java/org/apache/calcite/model/JsonRoot.java
index dfc517f..7d45013 100644
--- a/core/src/main/java/org/apache/calcite/model/JsonRoot.java
+++ b/core/src/main/java/org/apache/calcite/model/JsonRoot.java
@@ -31,6 +31,7 @@ import java.util.List;
  * <!-- CHECKSTYLE: OFF -->
  * <pre>{@code Root}
  *   {@link JsonSchema} (in collection {@link JsonRoot#schemas schemas})
+ *     {@link JsonType} (in collection {@link JsonMapSchema#types types})
  *     {@link JsonTable} (in collection {@link JsonMapSchema#tables tables})
  *       {@link JsonColumn} (in collection {@link JsonTable#columns columns})
  *       {@link JsonStream} (in field {@link JsonTable#stream stream})

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/model/JsonType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonType.java 
b/core/src/main/java/org/apache/calcite/model/JsonType.java
new file mode 100644
index 0000000..08db52f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/model/JsonType.java
@@ -0,0 +1,49 @@
+/*
+ * 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.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Type schema element.
+ *
+ * <p>Occurs within {@link JsonMapSchema#tables}.
+ *
+ * @see JsonRoot Description of schema elements
+ */
+public class JsonType {
+  /** Name of this type.
+   *
+   * <p>Required.
+   */
+  public String name;
+
+  /** Type if this is not a struct.
+   */
+  public String type;
+
+  /** Definition of the attributes of this type.
+   */
+  public final List<JsonTypeAttribute> attributes = new ArrayList<>();
+
+  public void accept(ModelHandler handler) {
+    handler.visit(this);
+  }
+}
+
+// End JsonType.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/model/JsonTypeAttribute.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonTypeAttribute.java 
b/core/src/main/java/org/apache/calcite/model/JsonTypeAttribute.java
new file mode 100644
index 0000000..19ca36a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/model/JsonTypeAttribute.java
@@ -0,0 +1,36 @@
+/*
+ * 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.model;
+
+/**
+ * JSON object representing a type attribute.
+ */
+public class JsonTypeAttribute {
+  /** Name of this attribute.
+   *
+   * <p>Required.
+   */
+  public String name;
+
+  /** Type of this attribute.
+   *
+   * <p>Required.
+   */
+  public String type;
+}
+
+// End JsonTypeAttribute.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/model/ModelHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/ModelHandler.java 
b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
index 2f2419d..1bcae90 100644
--- a/core/src/main/java/org/apache/calcite/model/ModelHandler.java
+++ b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
@@ -21,6 +21,8 @@ import org.apache.calcite.avatica.AvaticaUtils;
 import org.apache.calcite.jdbc.CalciteConnection;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.materialize.Lattice;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.AggregateFunction;
 import org.apache.calcite.schema.ScalarFunction;
 import org.apache.calcite.schema.Schema;
@@ -38,6 +40,7 @@ import org.apache.calcite.schema.impl.TableFunctionImpl;
 import org.apache.calcite.schema.impl.TableMacroImpl;
 import org.apache.calcite.schema.impl.ViewTable;
 import org.apache.calcite.sql.SqlDialectFactory;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
@@ -471,6 +474,33 @@ public class ModelHandler {
     return schema;
   }
 
+  public void visit(final JsonType jsonType) {
+    checkRequiredAttributes(jsonType, "name");
+    try {
+      final SchemaPlus schema = currentMutableSchema("type");
+      schema.add(jsonType.name, typeFactory -> {
+        if (jsonType.type != null) {
+          return typeFactory.createSqlType(SqlTypeName.get(jsonType.type));
+        } else {
+          final RelDataTypeFactory.Builder builder = typeFactory.builder();
+          for (JsonTypeAttribute jsonTypeAttribute : jsonType.attributes) {
+            final SqlTypeName typeName =
+                SqlTypeName.get(jsonTypeAttribute.type);
+            RelDataType type = typeFactory.createSqlType(typeName);
+            if (type == null) {
+              type = currentSchema().getType(jsonTypeAttribute.type)
+                  .apply(typeFactory);
+            }
+            builder.add(jsonTypeAttribute.name, type);
+          }
+          return builder.build();
+        }
+      });
+    } catch (Exception e) {
+      throw new RuntimeException("Error instantiating " + jsonType, e);
+    }
+  }
+
   public void visit(JsonFunction jsonFunction) {
     // "name" is not required - a class can have several functions
     checkRequiredAttributes(jsonFunction, "className");
@@ -478,11 +508,8 @@ public class ModelHandler {
       final SchemaPlus schema = currentMutableSchema("function");
       final List<String> path =
           Util.first(jsonFunction.path, currentSchemaPath());
-      create(schema,
-          jsonFunction.name,
-          path,
-          jsonFunction.className,
-          jsonFunction.methodName);
+      addFunctions(schema, jsonFunction.name, path, jsonFunction.className,
+          jsonFunction.methodName, false);
     } catch (Exception e) {
       throw new RuntimeException("Error instantiating " + jsonFunction, e);
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 4dc8be7..c49a9cb 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -173,7 +173,12 @@ public class CalciteCatalogReader implements 
Prepare.CatalogReader {
   }
 
   public RelDataType getNamedType(SqlIdentifier typeName) {
-    return null;
+    CalciteSchema.TypeEntry typeEntry = 
SqlValidatorUtil.getTypeEntry(getRootSchema(), typeName);
+    if (typeEntry != null) {
+      return typeEntry.getType().apply(typeFactory);
+    } else {
+      return null;
+    }
   }
 
   public List<SqlMoniker> getAllSchemaObjectNames(List<String> names) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 8ed16aa..81d84ad 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -33,6 +33,7 @@ import org.apache.calcite.rel.logical.LogicalTableScan;
 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.RelProtoDataType;
 import org.apache.calcite.rel.type.RelRecordType;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.ColumnStrategy;
@@ -468,6 +469,10 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
       throw new UnsupportedOperationException();
     }
 
+    @Override public void add(String name, RelProtoDataType type) {
+      throw new UnsupportedOperationException();
+    }
+
     @Override public void add(String name, Lattice lattice) {
       throw new UnsupportedOperationException();
     }
@@ -500,6 +505,14 @@ public class RelOptTableImpl extends 
Prepare.AbstractPreparingTable {
       return schema.getTableNames();
     }
 
+    @Override public RelProtoDataType getType(String name) {
+      return schema.getType(name);
+    }
+
+    @Override public Set<String> getTypeNames() {
+      return schema.getTypeNames();
+    }
+
     @Override public Collection<org.apache.calcite.schema.Function>
     getFunctions(String name) {
       return schema.getFunctions(name);

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 f122076..48079eb 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -751,6 +751,9 @@ public interface CalciteResource {
 
   @BaseMessage("View ''{0}'' not found")
   ExInst<SqlValidatorException> viewNotFound(String name);
+
+  @BaseMessage("Type ''{0}'' not found")
+  ExInst<SqlValidatorException> typeNotFound(String name);
 }
 
 // End CalciteResource.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/schema/Schema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Schema.java 
b/core/src/main/java/org/apache/calcite/schema/Schema.java
index 312ab58..05843dc 100644
--- a/core/src/main/java/org/apache/calcite/schema/Schema.java
+++ b/core/src/main/java/org/apache/calcite/schema/Schema.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.schema;
 
 import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rel.type.RelProtoDataType;
 
 import java.util.Collection;
 import java.util.Set;
@@ -69,6 +70,21 @@ public interface Schema {
   Set<String> getTableNames();
 
   /**
+   * Returns a type with a given name, or null if not found.
+   *
+   * @param name Table name
+   * @return Table, or null
+   */
+  RelProtoDataType getType(String name);
+
+  /**
+   * Returns the names of the types in this schema.
+   *
+   * @return Names of the tables in this schema
+   */
+  Set<String> getTypeNames();
+
+  /**
    * Returns a list of functions in this schema with the given name, or
    * an empty list if there is no such function.
    *

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/schema/SchemaPlus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/SchemaPlus.java 
b/core/src/main/java/org/apache/calcite/schema/SchemaPlus.java
index 27576ca..a6e0bdd 100644
--- a/core/src/main/java/org/apache/calcite/schema/SchemaPlus.java
+++ b/core/src/main/java/org/apache/calcite/schema/SchemaPlus.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.schema;
 
 import org.apache.calcite.materialize.Lattice;
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.util.Bug;
 
 import com.google.common.collect.ImmutableList;
@@ -73,6 +74,9 @@ public interface SchemaPlus extends Schema {
   /** Adds a function to this schema. */
   void add(String name, Function function);
 
+  /** Adds a type to this schema.  */
+  void add(String name, RelProtoDataType type);
+
   /** Adds a lattice to this schema. */
   void add(String name, Lattice lattice);
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java 
b/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java
index 129d3dd..dc8c25e 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/AbstractSchema.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.schema.impl;
 
 import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaFactory;
@@ -93,6 +94,28 @@ public class AbstractSchema implements Schema {
   }
 
   /**
+   * Returns a map of types in this schema by name.
+   *
+   * <p>The implementations of {@link #getTypeNames()}
+   * and {@link #getType(String)} depend on this map.
+   * The default implementation of this method returns the empty map.
+   * Override this method to change their behavior.</p>
+   *
+   * @return Map of types in this schema by name
+   */
+  protected Map<String, RelProtoDataType> getTypeMap() {
+    return ImmutableMap.of();
+  }
+
+  public RelProtoDataType getType(String name) {
+    return getTypeMap().get(name);
+  }
+
+  public Set<String> getTypeNames() {
+    return getTypeMap().keySet();
+  }
+
+  /**
    * Returns a multi-map of functions in this schema by name.
    * It is a multi-map because functions are overloaded; there may be more than
    * one function in a schema with a given name (as long as they have different

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java 
b/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java
index e222100..7c8bb96 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/DelegatingSchema.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.schema.impl;
 
 import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
@@ -66,6 +67,14 @@ public class DelegatingSchema implements Schema {
     return schema.getTableNames();
   }
 
+  public RelProtoDataType getType(String name) {
+    return schema.getType(name);
+  }
+
+  public Set<String> getTypeNames() {
+    return schema.getTypeNames();
+  }
+
   public Collection<Function> getFunctions(String name) {
     return schema.getFunctions(name);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java 
b/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
index 7c4e2cb..cb921a4 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDataTypeSpec.java
@@ -59,6 +59,7 @@ public class SqlDataTypeSpec extends SqlNode {
 
   private final SqlIdentifier collectionsTypeName;
   private final SqlIdentifier typeName;
+  private final SqlIdentifier baseTypeName;
   private final int scale;
   private final int precision;
   private final String charSetName;
@@ -101,11 +102,28 @@ public class SqlDataTypeSpec extends SqlNode {
   }
 
   /**
+   * Creates a type specification that has no base type.
+   */
+  public SqlDataTypeSpec(
+      SqlIdentifier collectionsTypeName,
+      SqlIdentifier typeName,
+      int precision,
+      int scale,
+      String charSetName,
+      TimeZone timeZone,
+      Boolean nullable,
+      SqlParserPos pos) {
+    this(collectionsTypeName, typeName, typeName, precision, scale, 
charSetName,
+        timeZone, nullable, pos);
+  }
+
+  /**
    * Creates a type specification.
    */
   public SqlDataTypeSpec(
       SqlIdentifier collectionsTypeName,
       SqlIdentifier typeName,
+      SqlIdentifier baseTypeName,
       int precision,
       int scale,
       String charSetName,
@@ -115,6 +133,7 @@ public class SqlDataTypeSpec extends SqlNode {
     super(pos);
     this.collectionsTypeName = collectionsTypeName;
     this.typeName = typeName;
+    this.baseTypeName = baseTypeName;
     this.precision = precision;
     this.scale = scale;
     this.charSetName = charSetName;
@@ -268,27 +287,26 @@ public class SqlDataTypeSpec extends SqlNode {
   }
 
   /**
-   * Throws an error if the type is not built-in.
+   * Throws an error if the type is not found.
    */
   public RelDataType deriveType(SqlValidator validator) {
-    String name = typeName.getSimple();
+    RelDataType type = null;
+    if (typeName.isSimple()) {
+      if (null != collectionsTypeName) {
+        final String collectionName = collectionsTypeName.getSimple();
+        if (SqlTypeName.get(collectionName) == null) {
+          throw validator.newValidationError(this,
+              RESOURCE.unknownDatatypeName(collectionName));
+        }
+      }
 
-    // for now we only support builtin datatypes
-    if (SqlTypeName.get(name) == null) {
-      throw validator.newValidationError(this,
-          RESOURCE.unknownDatatypeName(name));
+      RelDataTypeFactory typeFactory = validator.getTypeFactory();
+      type = deriveType(typeFactory);
     }
-
-    if (null != collectionsTypeName) {
-      final String collectionName = collectionsTypeName.getSimple();
-      if (SqlTypeName.get(collectionName) == null) {
-        throw validator.newValidationError(this,
-            RESOURCE.unknownDatatypeName(collectionName));
-      }
+    if (type == null) {
+      type = validator.getValidatedNodeType(typeName);
     }
-
-    RelDataTypeFactory typeFactory = validator.getTypeFactory();
-    return deriveType(typeFactory);
+    return type;
   }
 
   /**
@@ -308,9 +326,14 @@ public class SqlDataTypeSpec extends SqlNode {
    */
   public RelDataType deriveType(RelDataTypeFactory typeFactory,
       boolean nullable) {
+    if (!typeName.isSimple()) {
+      return null;
+    }
     final String name = typeName.getSimple();
-    final SqlTypeName sqlTypeName =
-        Preconditions.checkNotNull(SqlTypeName.get(name));
+    final SqlTypeName sqlTypeName = SqlTypeName.get(name);
+    if (sqlTypeName == null) {
+      return null;
+    }
 
     // NOTE jvs 15-Jan-2009:  earlier validation is supposed to
     // have caught these, which is why it's OK for them

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/sql/SqlKind.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java 
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 76d7f3c..a2cb6d0 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -941,6 +941,9 @@ public enum SqlKind {
   /** Column declaration. */
   COLUMN_DECL,
 
+  /** Attribute definition. */
+  ATTRIBUTE_DEF,
+
   /** {@code CHECK} constraint. */
   CHECK,
 
@@ -1019,6 +1022,12 @@ public enum SqlKind {
   /** {@code DROP INDEX} DDL statement. */
   DROP_INDEX,
 
+  /** {@code CREATE TYPE} DDL statement. */
+  CREATE_TYPE,
+
+  /** {@code DROP TYPE} DDL statement. */
+  DROP_TYPE,
+
   /** DDL statement not handled above.
    *
    * <p><b>Note to other projects</b>: If you are extending Calcite's SQL 
parser
@@ -1085,6 +1094,7 @@ public enum SqlKind {
           DROP_MATERIALIZED_VIEW,
           CREATE_SEQUENCE, ALTER_SEQUENCE, DROP_SEQUENCE,
           CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
+          CREATE_TYPE, DROP_TYPE,
           SET_OPTION, OTHER_DDL);
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 9c2170e..d80ba61 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
@@ -1545,6 +1545,9 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     if (original != null && original != node) {
       return getValidatedNodeType(original);
     }
+    if (node instanceof SqlIdentifier) {
+      return getCatalogReader().getNamedType((SqlIdentifier) node);
+    }
     return null;
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 394e353..d4c082d 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
@@ -951,6 +951,37 @@ public class SqlValidatorUtil {
   }
 
   /**
+   * Finds a {@link org.apache.calcite.jdbc.CalciteSchema.TypeEntry} in a
+   * given schema whose type has the given name, possibly qualified.
+   *
+   * @param rootSchema root schema
+   * @param typeName name of the type, may be qualified or fully-qualified
+   *
+   * @return TypeEntry with a table with the given name, or null
+   */
+  public static CalciteSchema.TypeEntry getTypeEntry(
+      CalciteSchema rootSchema, SqlIdentifier typeName) {
+    final String name;
+    final List<String> path;
+    if (typeName.isSimple()) {
+      path = ImmutableList.of();
+      name = typeName.getSimple();
+    } else {
+      path = Util.skipLast(typeName.names);
+      name = Util.last(typeName.names);
+    }
+    CalciteSchema schema = rootSchema;
+    for (String p : path) {
+      if (schema == rootSchema
+          && SqlNameMatchers.withCaseSensitive(true).matches(p, 
schema.getName())) {
+        continue;
+      }
+      schema = schema.getSubSchema(p, true);
+    }
+    return schema == null ? null : schema.getType(name, false);
+  }
+
+  /**
    * Finds a {@link org.apache.calcite.jdbc.CalciteSchema.TableEntry} in a
    * given catalog reader whose table has the given name, possibly qualified.
    *

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java 
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index fe92d4b..8c7ffc1 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -572,6 +572,9 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
     }
     RexNode arg = cx.convertExpression(left);
     RelDataType type = dataType.deriveType(typeFactory);
+    if (type == null) {
+      type = cx.getValidator().getValidatedNodeType(dataType.getTypeName());
+    }
     if (arg.getType().isNullable()) {
       type = typeFactory.createTypeWithNullability(type, true);
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 201fe2b..64ab61f 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -158,7 +158,6 @@ public class Util {
               });
 
   //~ Methods ----------------------------------------------------------------
-
   /**
    * Does nothing with its argument. Returns whether it is ensured that
    * the call produces a single value

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 5a0f888..d1ea199 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -244,4 +244,5 @@ CreateTableRequiresColumnTypes=Type required for column 
''{0}'' in CREATE TABLE
 ViewExists=View ''{0}'' already exists and REPLACE not specified
 SchemaNotFound=Schema ''{0}'' not found
 ViewNotFound=View ''{0}'' not found
+TypeNotFound=Type ''{0}'' not found
 # End CalciteResource.properties

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java 
b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
index 585e8ed..a1d87a2 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -147,6 +147,7 @@ import org.junit.runners.Suite;
 
     // slow tests (above 1s)
     UdfTest.class,
+    UdtTest.class,
     TableFunctionTest.class,
     PlannerTest.class,
     RelBuilderTest.class,

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 e179d05..df1bffe 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -177,6 +177,11 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
     final InitializerExpressionFactory countingInitializerExpressionFactory =
         new CountingFactory(ImmutableList.of("DEPTNO"));
 
+    registerType(
+        ImmutableList.of(salesSchema.getCatalogName(), salesSchema.getName(),
+            "customBigInt"),
+        typeFactory -> typeFactory.createSqlType(SqlTypeName.BIGINT));
+
     // Register "EMP" table.
     final MockTable empTable =
         MockTable.create(this, salesSchema, "EMP", false, 14, null,
@@ -621,6 +626,14 @@ public class MockCatalogReader extends 
CalciteCatalogReader {
 
   //~ Methods ----------------------------------------------------------------
 
+  protected void registerType(final List<String> names, final RelProtoDataType 
relProtoDataType) {
+    assert names.get(0).equals(DEFAULT_CATALOG);
+    final List<String> schemaPath = Util.skipLast(names);
+    final CalciteSchema schema = SqlValidatorUtil.getSchema(rootSchema,
+        schemaPath, SqlNameMatchers.withCaseSensitive(true));
+    schema.add(Util.last(names), relProtoDataType);
+  }
+
   protected void registerTable(final MockTable table) {
     table.onRegister(typeFactory);
     final WrapperTable wrapperTable = new WrapperTable(table);
@@ -658,7 +671,7 @@ public class MockCatalogReader extends CalciteCatalogReader 
{
     if (typeName.equalsDeep(addressType.getSqlIdentifier(), Litmus.IGNORE)) {
       return addressType;
     } else {
-      return null;
+      return super.getNamedType(typeName);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/test/java/org/apache/calcite/test/ModelTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/ModelTest.java 
b/core/src/test/java/org/apache/calcite/test/ModelTest.java
index 35e78fb..158527d 100644
--- a/core/src/test/java/org/apache/calcite/test/ModelTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ModelTest.java
@@ -24,6 +24,7 @@ import org.apache.calcite.model.JsonLattice;
 import org.apache.calcite.model.JsonMapSchema;
 import org.apache.calcite.model.JsonRoot;
 import org.apache.calcite.model.JsonTable;
+import org.apache.calcite.model.JsonTypeAttribute;
 import org.apache.calcite.model.JsonView;
 
 import com.fasterxml.jackson.core.JsonParser;
@@ -61,6 +62,17 @@ public class ModelTest {
         + "   schemas: [\n"
         + "     {\n"
         + "       name: 'FoodMart',\n"
+        + "       types: [\n"
+        + "         {\n"
+        + "           name: 'mytype1',\n"
+        + "           attributes: [\n"
+        + "             {\n"
+        + "               name: 'f1',\n"
+        + "               type: 'BIGINT'\n"
+        + "             }\n"
+        + "           ]\n"
+        + "         }\n"
+        + "       ],\n"
         + "       tables: [\n"
         + "         {\n"
         + "           name: 'time_by_day',\n"
@@ -87,6 +99,10 @@ public class ModelTest {
     assertEquals(1, root.schemas.size());
     final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
     assertEquals("FoodMart", schema.name);
+    assertEquals(1, schema.types.size());
+    final List<JsonTypeAttribute> attributes = schema.types.get(0).attributes;
+    assertEquals("f1", attributes.get(0).name);
+    assertEquals("BIGINT", attributes.get(0).type);
     assertEquals(2, schema.tables.size());
     final JsonTable table0 = schema.tables.get(0);
     assertEquals("time_by_day", table0.name);

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/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 32c50e2..7fa1026 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -955,10 +955,17 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
         "INTEGER NOT NULL MULTISET NOT NULL");
   }
 
+  @Test public void testCastRegisteredType() {
+    checkExpFails("cast(123 as customBigInt)",
+        "class org.apache.calcite.sql.SqlIdentifier: CUSTOMBIGINT");
+    checkExpType("cast(123 as sales.customBigInt)", "BIGINT NOT NULL");
+    checkExpType("cast(123 as catalog.sales.customBigInt)", "BIGINT NOT NULL");
+  }
+
   @Test public void testCastFails() {
     checkExpFails(
         "cast('foo' as ^bar^)",
-        "(?s).*Unknown datatype name 'BAR'");
+        "class org.apache.calcite.sql.SqlIdentifier: BAR");
     checkWholeExpFails(
         "cast(multiset[1] as integer)",
         "(?s).*Cast function cannot convert value of type INTEGER MULTISET to 
type INTEGER");

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
index 554c184..77e8753 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
@@ -334,6 +334,10 @@ public class SqlValidatorTestCase {
           if (matcher.matches()) {
             actualLine = Integer.parseInt(matcher.group(1));
             actualColumn = Integer.parseInt(matcher.group(2));
+          } else {
+            if (actualMessage.toString().matches(expectedMsgPattern)) {
+              return;
+            }
           }
         }
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/core/src/test/java/org/apache/calcite/test/UdtTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/UdtTest.java 
b/core/src/test/java/org/apache/calcite/test/UdtTest.java
new file mode 100644
index 0000000..8741844
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/UdtTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.test;
+
+import org.junit.Test;
+
+/**
+ * Tests for user-defined types.
+ */
+public class UdtTest {
+  private CalciteAssert.AssertThat withUdt() {
+    final String model = "{\n"
+        + "  version: '1.0',\n"
+        + "   schemas: [\n"
+        + "     {\n"
+        + "       name: 'adhoc',\n"
+        + "       types: [\n"
+        + "         {\n"
+        + "           name: 'mytype1',\n"
+        + "           type: 'BIGINT'\n"
+        + "         },\n"
+        + "         {\n"
+        + "           name: 'mytype2',\n"
+        + "           attributes: [\n"
+        + "             {\n"
+        + "               name: 'ii',\n"
+        + "               type: 'INTEGER'\n"
+        + "             },\n"
+        + "             {\n"
+        + "               name: 'jj',\n"
+        + "               type: 'INTEGER'\n"
+        + "             }\n"
+        + "           ]\n"
+        + "         }\n"
+        + "       ]\n"
+        + "     }\n"
+        + "   ]\n"
+        + "}";
+    return CalciteAssert.model(model);
+  }
+
+  @Test public void testUdt() {
+    final String sql = "select CAST(\"id\" AS \"adhoc\".mytype1) as ld "
+        + "from (VALUES ROW(1, 'SameName')) AS \"t\" (\"id\", \"desc\")";
+    withUdt().query(sql).returns("LD=1\n");
+  }
+}
+
+// End UdtTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/codegen/config.fmpp
----------------------------------------------------------------------
diff --git a/server/src/main/codegen/config.fmpp 
b/server/src/main/codegen/config.fmpp
index 08b715a..34fe90c 100644
--- a/server/src/main/codegen/config.fmpp
+++ b/server/src/main/codegen/config.fmpp
@@ -68,6 +68,7 @@ data: {
         "SqlCreateMaterializedView"
         "SqlCreateSchema"
         "SqlCreateTable"
+        "SqlCreateType"
         "SqlCreateView"
       ]
 
@@ -77,6 +78,7 @@ data: {
         "SqlDropMaterializedView"
         "SqlDropSchema"
         "SqlDropTable"
+        "SqlDropType"
         "SqlDropView"
       ]
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/codegen/includes/parserImpls.ftl
----------------------------------------------------------------------
diff --git a/server/src/main/codegen/includes/parserImpls.ftl 
b/server/src/main/codegen/includes/parserImpls.ftl
index 9c54540..0fd9b87 100644
--- a/server/src/main/codegen/includes/parserImpls.ftl
+++ b/server/src/main/codegen/includes/parserImpls.ftl
@@ -191,6 +191,69 @@ void TableElement(List<SqlNode> list) :
     )
 }
 
+SqlNodeList AttributeDefList() :
+{
+    final Span s;
+    final List<SqlNode> list = Lists.newArrayList();
+}
+{
+    <LPAREN> { s = span(); }
+    AttributeDef(list)
+    (
+        <COMMA> AttributeDef(list)
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+void AttributeDef(List<SqlNode> list) :
+{
+    final SqlIdentifier id;
+    final SqlDataTypeSpec type;
+    final boolean nullable;
+    SqlNode e = null;
+    final Span s = Span.of();
+}
+{
+    id = SimpleIdentifier()
+    (
+        type = DataType()
+        (
+            <NULL> { nullable = true; }
+        |
+            <NOT> <NULL> { nullable = false; }
+        |
+            { nullable = true; }
+        )
+    )
+    [ <DEFAULT_> e = Expression(ExprContext.ACCEPT_SUB_QUERY) ]
+    {
+        list.add(SqlDdlNodes.attribute(s.add(id).end(this), id,
+            type.withNullable(nullable), e, null));
+    }
+}
+
+SqlCreate SqlCreateType(Span s, boolean replace) :
+{
+    final SqlIdentifier id;
+    SqlNodeList attributeDefList = null;
+    SqlDataTypeSpec type = null;
+}
+{
+    <TYPE>
+    id = CompoundIdentifier()
+    <AS>
+    (
+        attributeDefList = AttributeDefList()
+    |
+        type = DataType()
+    )
+    {
+        return SqlDdlNodes.createType(s.end(this), replace, id, 
attributeDefList, type);
+    }
+}
+
 SqlCreate SqlCreateTable(Span s, boolean replace) :
 {
     final boolean ifNotExists;
@@ -257,6 +320,17 @@ SqlDrop SqlDropSchema(Span s, boolean replace) :
     }
 }
 
+SqlDrop SqlDropType(Span s, boolean replace) :
+{
+    final boolean ifExists;
+    final SqlIdentifier id;
+}
+{
+    <TYPE> ifExists = IfExistsOpt() id = CompoundIdentifier() {
+        return SqlDdlNodes.dropType(s.end(this), ifExists, id);
+    }
+}
+
 SqlDrop SqlDropTable(Span s, boolean replace) :
 {
     final boolean ifExists;

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlAttributeDefinition.java
----------------------------------------------------------------------
diff --git 
a/server/src/main/java/org/apache/calcite/sql/ddl/SqlAttributeDefinition.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlAttributeDefinition.java
new file mode 100644
index 0000000..1e519ec
--- /dev/null
+++ 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlAttributeDefinition.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ddl;
+
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.SqlDataTypeSpec;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Parse tree for SqlAttributeDefinition,
+ * which is part of a {@link SqlCreateType}.
+ */
+public class SqlAttributeDefinition extends SqlCall {
+  private static final SqlSpecialOperator OPERATOR =
+      new SqlSpecialOperator("ATTRIBUTE_DEF", SqlKind.ATTRIBUTE_DEF);
+
+  final SqlIdentifier name;
+  final SqlDataTypeSpec dataType;
+  final SqlNode expression;
+  final SqlCollation collation;
+
+  /** Creates a SqlAttributeDefinition; use {@link SqlDdlNodes#attribute}. */
+  SqlAttributeDefinition(SqlParserPos pos, SqlIdentifier name,
+      SqlDataTypeSpec dataType, SqlNode expression, SqlCollation collation) {
+    super(pos);
+    this.name = name;
+    this.dataType = dataType;
+    this.expression = expression;
+    this.collation = collation;
+  }
+
+  @Override public SqlOperator getOperator() {
+    return OPERATOR;
+  }
+
+  @Override public List<SqlNode> getOperandList() {
+    return ImmutableList.of(name, dataType);
+  }
+
+  @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) 
{
+    name.unparse(writer, 0, 0);
+    dataType.unparse(writer, 0, 0);
+    if (collation != null) {
+      writer.keyword("COLLATE");
+      collation.unparse(writer, 0, 0);
+    }
+    if (dataType.getNullable() != null && !dataType.getNullable()) {
+      writer.keyword("NOT NULL");
+    }
+    if (expression != null) {
+      writer.keyword("DEFAULT");
+      exp(writer);
+    }
+  }
+
+  // TODO: refactor this to a util class to share with SqlColumnDeclaration
+  private void exp(SqlWriter writer) {
+    if (writer.isAlwaysUseParentheses()) {
+      expression.unparse(writer, 0, 0);
+    } else {
+      writer.sep("(");
+      expression.unparse(writer, 0, 0);
+      writer.sep(")");
+    }
+  }
+}
+
+// End SqlAttributeDefinition.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
----------------------------------------------------------------------
diff --git 
a/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
index 22cc2d3..5ae3669 100644
--- a/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
@@ -166,7 +166,15 @@ public class SqlCreateTable extends SqlCreate
     for (Ord<SqlNode> c : Ord.zip(columnList)) {
       if (c.e instanceof SqlColumnDeclaration) {
         final SqlColumnDeclaration d = (SqlColumnDeclaration) c.e;
-        final RelDataType type = d.dataType.deriveType(typeFactory, true);
+        RelDataType type = d.dataType.deriveType(typeFactory, true);
+        final Pair<CalciteSchema, String> pairForType =
+            SqlDdlNodes.schema(context, true, d.dataType.getTypeName());
+        if (type == null) {
+          CalciteSchema.TypeEntry typeEntry = 
pairForType.left.getType(pairForType.right, false);
+          if (typeEntry != null) {
+            type = typeEntry.getType().apply(typeFactory);
+          }
+        }
         builder.add(d.name.getSimple(), type);
         if (d.strategy != ColumnStrategy.VIRTUAL) {
           storedBuilder.add(d.name.getSimple(), type);

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateType.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateType.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateType.java
new file mode 100644
index 0000000..c6d7991
--- /dev/null
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateType.java
@@ -0,0 +1,114 @@
+/*
+ * 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.ddl;
+
+import org.apache.calcite.jdbc.CalcitePrepare;
+import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlCreate;
+import org.apache.calcite.sql.SqlDataTypeSpec;
+import org.apache.calcite.sql.SqlExecutableStatement;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.apache.calcite.util.Pair;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+
+/**
+ * Parse tree for {@code CREATE TYPE} statement.
+ */
+public class SqlCreateType extends SqlCreate
+    implements SqlExecutableStatement {
+  private final SqlIdentifier name;
+  private final SqlNodeList attributeDefs;
+  private final SqlDataTypeSpec dataType;
+
+  private static final SqlOperator OPERATOR =
+      new SqlSpecialOperator("CREATE TYPE", SqlKind.CREATE_TYPE);
+
+  /** Creates a SqlCreateType. */
+  SqlCreateType(SqlParserPos pos, boolean replace, SqlIdentifier name,
+      SqlNodeList attributeDefs, SqlDataTypeSpec dataType) {
+    super(OPERATOR, pos, replace, false);
+    this.name = Preconditions.checkNotNull(name);
+    this.attributeDefs = attributeDefs; // may be null
+    this.dataType = dataType; // may be null
+  }
+
+  @Override public void execute(CalcitePrepare.Context context) {
+    final Pair<CalciteSchema, String> pair =
+        SqlDdlNodes.schema(context, true, name);
+    pair.left.add(pair.right, typeFactory -> {
+      if (dataType != null) {
+        return dataType.deriveType(typeFactory);
+      } else {
+        final RelDataTypeFactory.Builder builder = typeFactory.builder();
+        for (SqlNode def : attributeDefs) {
+          final SqlAttributeDefinition attributeDef =
+              (SqlAttributeDefinition) def;
+          final SqlDataTypeSpec typeSpec = attributeDef.dataType;
+          RelDataType type = typeSpec.deriveType(typeFactory);
+          if (type == null) {
+            Pair<CalciteSchema, String> pair1 =
+                SqlDdlNodes.schema(context, false, typeSpec.getTypeName());
+            type = pair1.left.getType(pair1.right, false).getType()
+                .apply(typeFactory);
+          }
+          builder.add(attributeDef.name.getSimple(), type);
+        }
+        return builder.build();
+      }
+    });
+  }
+
+  @Override public List<SqlNode> getOperandList() {
+    return ImmutableNullableList.of(name, attributeDefs);
+  }
+
+  @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) 
{
+    if (getReplace()) {
+      writer.keyword("CREATE OR REPLACE");
+    } else {
+      writer.keyword("CREATE");
+    }
+    writer.keyword("TYPE");
+    name.unparse(writer, leftPrec, rightPrec);
+    writer.keyword("AS");
+    if (attributeDefs != null) {
+      SqlWriter.Frame frame = writer.startList("(", ")");
+      for (SqlNode a : attributeDefs) {
+        writer.sep(",");
+        a.unparse(writer, 0, 0);
+      }
+      writer.endList(frame);
+    } else if (dataType != null) {
+      dataType.unparse(writer, leftPrec, rightPrec);
+    }
+  }
+}
+
+// End SqlCreateType.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
index 057c2ee..d35e7cc 100644
--- a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
@@ -21,6 +21,7 @@ import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.schema.ColumnStrategy;
 import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlDataTypeSpec;
 import org.apache.calcite.sql.SqlDrop;
 import org.apache.calcite.sql.SqlIdentifier;
@@ -69,6 +70,13 @@ public class SqlDdlNodes {
         library, optionList);
   }
 
+  /** Creates a CREATE TYPE. */
+  public static SqlCreateType createType(SqlParserPos pos, boolean replace,
+      SqlIdentifier name, SqlNodeList attributeList,
+      SqlDataTypeSpec dataTypeSpec) {
+    return new SqlCreateType(pos, replace, name, attributeList, dataTypeSpec);
+  }
+
   /** Creates a CREATE TABLE. */
   public static SqlCreateTable createTable(SqlParserPos pos, boolean replace,
       boolean ifNotExists, SqlIdentifier name, SqlNodeList columnList,
@@ -97,6 +105,12 @@ public class SqlDdlNodes {
     return new SqlDropSchema(pos, foreign, ifExists, name);
   }
 
+  /** Creates a DROP TYPE. */
+  public static SqlDropType dropType(SqlParserPos pos, boolean ifExists,
+      SqlIdentifier name) {
+    return new SqlDropType(pos, ifExists, name);
+  }
+
   /** Creates a DROP TABLE. */
   public static SqlDropTable dropTable(SqlParserPos pos, boolean ifExists,
       SqlIdentifier name) {
@@ -121,6 +135,12 @@ public class SqlDdlNodes {
     return new SqlColumnDeclaration(pos, name, dataType, expression, strategy);
   }
 
+  /** Creates a attribute definition. */
+  public static SqlNode attribute(SqlParserPos pos, SqlIdentifier name,
+      SqlDataTypeSpec dataType, SqlNode expression, SqlCollation collation) {
+    return new SqlAttributeDefinition(pos, name, dataType, expression, 
collation);
+  }
+
   /** Creates a CHECK constraint. */
   public static SqlNode check(SqlParserPos pos, SqlIdentifier name,
       SqlNode expression) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropObject.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropObject.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropObject.java
index 81672f2..523c96e 100644
--- a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropObject.java
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropObject.java
@@ -34,8 +34,8 @@ import java.util.List;
 import static org.apache.calcite.util.Static.RESOURCE;
 
 /**
- * Base class for parse trees of {@code DROP TABLE}, {@code DROP VIEW} and
- * {@code DROP MATERIALIZED VIEW} statements.
+ * Base class for parse trees of {@code DROP TABLE}, {@code DROP VIEW},
+ * {@code DROP MATERIALIZED VIEW} and {@code DROP TYPE} statements.
  */
 abstract class SqlDropObject extends SqlDrop
     implements SqlExecutableStatement {
@@ -84,6 +84,13 @@ abstract class SqlDropObject extends SqlDrop
             RESOURCE.viewNotFound(name.getSimple()));
       }
       break;
+    case DROP_TYPE:
+      existed = schema.removeType(name.getSimple());
+      if (!existed && !ifExists) {
+        throw SqlUtil.newContextException(name.getParserPosition(),
+            RESOURCE.typeNotFound(name.getSimple()));
+      }
+      break;
     default:
       throw new AssertionError(getKind());
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropType.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropType.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropType.java
new file mode 100644
index 0000000..fcad768
--- /dev/null
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDropType.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ddl;
+
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+/**
+ * Parse tree for {@code DROP TYPE} statement.
+ */
+public class SqlDropType extends SqlDropObject {
+  private static final SqlOperator OPERATOR =
+      new SqlSpecialOperator("DROP TYPE", SqlKind.DROP_TYPE);
+
+  SqlDropType(SqlParserPos pos, boolean ifExists, SqlIdentifier name) {
+    super(OPERATOR, pos, ifExists, name);
+  }
+}
+
+// End SqlDropType.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
----------------------------------------------------------------------
diff --git a/server/src/test/java/org/apache/calcite/test/ServerParserTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
index 8cdf336..3bb7c39 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
@@ -92,6 +92,21 @@ public class ServerParserTest extends SqlParserTest {
     sql(sql).ok(expected);
   }
 
+  @Test public void testCreateTypeWithAttributeList() {
+    sql("create type x.mytype1 as (i int not null, j varchar(5) null)")
+        .ok("CREATE TYPE `X`.`MYTYPE1` AS (`I` INTEGER NOT NULL, `J` 
VARCHAR(5))");
+  }
+
+  @Test public void testCreateTypeWithBaseType() {
+    sql("create type mytype1 as varchar(5)")
+        .ok("CREATE TYPE `MYTYPE1` AS VARCHAR(5)");
+  }
+
+  @Test public void testCreateOrReplaceTypeWith() {
+    sql("create or replace type mytype1 as varchar(5)")
+        .ok("CREATE OR REPLACE TYPE `MYTYPE1` AS VARCHAR(5)");
+  }
+
   @Test public void testCreateTable() {
     sql("create table x (i int not null, j varchar(5) null)")
         .ok("CREATE TABLE `X` (`I` INTEGER NOT NULL, `J` VARCHAR(5))");
@@ -210,6 +225,21 @@ public class ServerParserTest extends SqlParserTest {
         .ok("DROP FOREIGN SCHEMA `X`");
   }
 
+  @Test public void testDropType() {
+    sql("drop type X")
+        .ok("DROP TYPE `X`");
+  }
+
+  @Test public void testDropTypeIfExists() {
+    sql("drop type if exists X")
+        .ok("DROP TYPE IF EXISTS `X`");
+  }
+
+  @Test public void testDropTypeTrailingIfExistsFails() {
+    sql("drop type X ^if^ exists")
+        .fails("(?s)Encountered \"if\" at.*");
+  }
+
   @Test public void testDropTable() {
     sql("drop table x")
         .ok("DROP TABLE `X`");

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/test/java/org/apache/calcite/test/ServerTest.java
----------------------------------------------------------------------
diff --git a/server/src/test/java/org/apache/calcite/test/ServerTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerTest.java
index 92885ff..835c19a 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerTest.java
@@ -20,7 +20,9 @@ import org.apache.calcite.config.CalciteConnectionProperty;
 import org.apache.calcite.sql.parser.ddl.SqlDdlParserImpl;
 
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -43,6 +45,9 @@ public class ServerTest {
 
   static final String URL = "jdbc:calcite:";
 
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
   static Connection connect() throws SQLException {
     return DriverManager.getConnection(URL,
         CalciteAssert.propBuilder()
@@ -81,6 +86,45 @@ public class ServerTest {
     }
   }
 
+  @Test public void testCreateType() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      boolean b = s.execute("create type mytype1 as BIGINT");
+      assertThat(b, is(false));
+      b = s.execute("create or replace type mytype2 as (i int not null, jj 
mytype1)");
+      assertThat(b, is(false));
+      b = s.execute("create type mytype3 as (i int not null, jj mytype2)");
+      assertThat(b, is(false));
+      b = s.execute("create or replace type mytype1 as DOUBLE");
+      assertThat(b, is(false));
+      b = s.execute("create table t (c mytype1 NOT NULL)");
+      assertThat(b, is(false));
+      b = s.execute("create type mytype4 as BIGINT");
+      assertThat(b, is(false));
+      int x = s.executeUpdate("insert into t values 12.0");
+      assertThat(x, is(1));
+      x = s.executeUpdate("insert into t values 3.0");
+      assertThat(x, is(1));
+      try (ResultSet r = s.executeQuery("select CAST(c AS mytype4) from t")) {
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt(1), is(12));
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt(1), is(3));
+        assertThat(r.next(), is(false));
+      }
+    }
+  }
+
+  @Test public void testDropType() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      boolean b = s.execute("create type mytype1 as BIGINT");
+      assertThat(b, is(false));
+      b = s.execute("drop type mytype1");
+      assertThat(b, is(false));
+    }
+  }
+
   @Test public void testCreateTable() throws Exception {
     try (Connection c = connect();
          Statement s = c.createStatement()) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/server/src/test/resources/sql/type.iq
----------------------------------------------------------------------
diff --git a/server/src/test/resources/sql/type.iq 
b/server/src/test/resources/sql/type.iq
new file mode 100644
index 0000000..64314e9
--- /dev/null
+++ b/server/src/test/resources/sql/type.iq
@@ -0,0 +1,57 @@
+# type.iq - Type DDL
+#
+# 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.
+#
+!use server
+!set outputformat mysql
+
+create type myint1 as int;
+(0 rows modified)
+
+!update
+
+# Create a basic table
+create table t (i myint1 not null, j int not null);
+(0 rows modified)
+
+!update
+
+select * from t;
+I INTEGER(10) NOT NULL
+J INTEGER(10) NOT NULL
+!type
+
+insert into t values (1, 2);
+(1 row modified)
+
+!update
+
+select * from t;
++---+---+
+| I | J |
++---+---+
+| 1 | 2 |
++---+---+
+(1 row)
+
+!ok
+
+drop table t;
+(0 rows modified)
+
+!update
+
+# End type.iq

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/site/_docs/model.md
----------------------------------------------------------------------
diff --git a/site/_docs/model.md b/site/_docs/model.md
index 2675453..0f646d0 100644
--- a/site/_docs/model.md
+++ b/site/_docs/model.md
@@ -102,7 +102,7 @@ A particular schema implementation can override the
 `Schema.contentsHaveChangedSince` method to tell Calcite
 when it should consider its cache to be out of date.
 
-Tables, functions and sub-schemas explicitly created in a schema are
+Tables, functions, types, and sub-schemas explicitly created in a schema are
 not affected by this caching mechanism. They always appear in the schema
 immediately, and are never flushed.
 
@@ -115,7 +115,8 @@ Like base class <a href="#schema">Schema</a>, occurs within 
`root.schemas`.
   name: 'foodmart',
   type: 'map',
   tables: [ Table... ],
-  functions: [ Function... ]
+  functions: [ Function... ],
+  types: [ Type... ]
 }
 {% endhighlight %}
 
@@ -128,6 +129,8 @@ defines the tables in this schema.
 `functions` (optional list of <a href="#function">Function</a> elements)
 defines the functions in this schema.
 
+`types` defines the types in this schema.
+
 ### Custom Schema
 
 Like base class <a href="#schema">Schema</a>, occurs within `root.schemas`.
@@ -370,6 +373,31 @@ if found, creates an aggregate function.
 
 `path` (optional list of string) is the path for resolving this function.
 
+### Type
+
+Occurs within `root.schemas.types`.
+
+{% highlight json %}
+{
+  name: 'mytype1',
+  type: 'BIGINT',
+  attributes: [
+    {
+      name: 'f1',
+      type: 'BIGINT'
+    }
+  ]
+}
+{% endhighlight %}
+
+`name` (required string) is the name of this type.
+
+`type` (optional) is the SQL type.
+
+`attributes` (optional) is the attribute list of this type.
+If `attributes` and `type` both exist at the same level,
+`type` takes precedence.
+
 ### Lattice
 
 Occurs within `root.schemas.lattices`.

http://git-wip-us.apache.org/repos/asf/calcite/blob/570aca3d/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 9d77fe3..b210854 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2087,11 +2087,13 @@ ddlStatement:
   |   createTableStatement
   |   createViewStatement
   |   createMaterializedViewStatement
+  |   createTypeStatement
   |   dropSchemaStatement
   |   dropForeignSchemaStatement
   |   dropTableStatement
   |   dropViewStatement
   |   dropMaterializedViewStatement
+  |   dropTypeStatement
 
 createSchemaStatement:
       CREATE [ OR REPLACE ] SCHEMA [ IF NOT EXISTS ] name
@@ -2112,6 +2114,19 @@ createTableStatement:
       [ '(' tableElement [, tableElement ]* ')' ]
       [ AS query ]
 
+createTypeStatement:
+      CREATE [ OR REPLACE ] TYPE name AS
+      {
+          baseType
+      |   '(' attributeDef [, attributeDef ]* ')'
+      }
+
+attributeDef:
+      attributeName type
+      [ COLLATE collation ]
+      [ NULL | NOT NULL ]
+      [ DEFAULT expression ]
+
 tableElement:
       columnName type [ columnGenerator ] [ columnConstraint ]
   |   columnName
@@ -2145,19 +2160,22 @@ createMaterializedViewStatement:
       AS query
 
 dropSchemaStatement:
-      DROP SCHEMA name [ IF EXISTS ]
+      DROP SCHEMA [ IF EXISTS ] name
 
 dropForeignSchemaStatement:
-      DROP FOREIGN SCHEMA name [ IF EXISTS ]
+      DROP FOREIGN SCHEMA [ IF EXISTS ] name
 
 dropTableStatement:
-      DROP TABLE name [ IF EXISTS ]
+      DROP TABLE [ IF EXISTS ] name
 
 dropViewStatement:
-      DROP VIEW name [ IF EXISTS ]
+      DROP VIEW [ IF EXISTS ] name
 
 dropMaterializedViewStatement:
-      DROP MATERIALIZED VIEW name [ IF EXISTS ]
+      DROP MATERIALIZED VIEW [ IF EXISTS ] name
+
+dropTypeStatement:
+      DROP TYPE [ IF EXISTS ] name
 {% endhighlight %}
 
 In *createTableStatement*, if you specify *AS query*, you may omit the list of

Reply via email to