You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by mm...@apache.org on 2017/09/05 14:36:53 UTC

[10/16] calcite git commit: [CALCITE-1968] OpenGIS Simple Feature Access SQL 1.2.1: add GEOMETRY data type and first 35 functions

[CALCITE-1968] OpenGIS Simple Feature Access SQL 1.2.1: add GEOMETRY data type and first 35 functions

Add Spatial page, document GIS functions in SQL reference (indicating
which ones are implemented), and add "countries" data set for testing.

Functions: ST_AsText, ST_AsWKT, ST_Boundary, ST_Buffer, ST_Contains,
ST_ContainsProperly, ST_Crosses, ST_Disjoint, ST_Distance, ST_DWithin,
ST_Envelope, ST_EnvelopesIntersect, ST_Equals, ST_GeometryType,
ST_GeometryTypeCode, ST_GeomFromText, ST_Intersects, ST_Is3D,
ST_LineFromText, ST_MakeLine, ST_MakePoint, ST_MLineFromText,
ST_MPointFromText, ST_MPolyFromText, ST_Overlaps, ST_Point,
ST_PointFromText, ST_PolyFromText, ST_SetSRID, ST_Touches,
ST_Transform, ST_Union, ST_Within, ST_Z.


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

Branch: refs/heads/branch-1.14
Commit: cc20ca13db4d506d9d4d1b861dd1c7ac3944e56e
Parents: b2bf1ca
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Aug 24 03:27:35 2017 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue Aug 29 13:24:42 2017 -0700

----------------------------------------------------------------------
 core/pom.xml                                    |    4 +
 core/src/main/codegen/templates/Parser.jj       |   11 +-
 .../calcite/jdbc/JavaTypeFactoryImpl.java       |    3 +
 .../org/apache/calcite/model/ModelHandler.java  |   39 +-
 .../calcite/prepare/CalcitePrepareImpl.java     |    6 +-
 .../apache/calcite/runtime/CalciteResource.java |    3 +
 .../apache/calcite/runtime/GeoFunctions.java    |  651 ++++++++++
 .../apache/calcite/sql/type/ExtraSqlTypes.java  |    3 +
 .../sql/type/JavaToSqlTypeConversionRules.java  |    3 +
 .../sql/type/SqlTypeAssignmentRules.java        |    3 +
 .../apache/calcite/sql/type/SqlTypeFamily.java  |    5 +-
 .../apache/calcite/sql/type/SqlTypeName.java    |    3 +-
 .../sql/validate/SqlAbstractConformance.java    |    4 +
 .../calcite/sql/validate/SqlConformance.java    |   12 +-
 .../sql/validate/SqlConformanceEnum.java        |   11 +
 .../calcite/runtime/CalciteResource.properties  |    1 +
 .../calcite/jdbc/CalciteRemoteDriverTest.java   |    2 +-
 .../calcite/sql/parser/SqlParserTest.java       |    7 +
 .../org/apache/calcite/test/CalciteAssert.java  |   23 +
 .../calcite/test/CountriesTableFunction.java    |  327 +++++
 .../org/apache/calcite/test/QuidemTest.java     |    4 +
 .../apache/calcite/test/SqlValidatorTest.java   |   18 +
 core/src/test/resources/sql/spatial.iq          | 1114 ++++++++++++++++++
 file/src/test/resources/geo/countries.csv       |  246 ++++
 pom.xml                                         |    6 +
 site/_data/docs.yml                             |    1 +
 site/_docs/reference.md                         |  342 +++++-
 site/_docs/spatial.md                           |   63 +
 src/main/config/checkstyle/suppressions.xml     |    3 +
 29 files changed, 2908 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index 6e8428a..de6e24c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -60,6 +60,10 @@ limitations under the License.
       <artifactId>commons-lang3</artifactId>
     </dependency>
     <dependency>
+      <groupId>com.esri.geometry</groupId>
+      <artifactId>esri-geometry-api</artifactId>
+    </dependency>
+    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-core</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/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 fae4fd1..0aef85b 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -4363,7 +4363,7 @@ SqlIdentifier TypeName() :
     }
 }
 
-// Types used for for JDBC and ODBC scalar conversion function
+// Types used for JDBC and ODBC scalar conversion function
 SqlTypeName SqlTypeName(Span s) :
 {
 }
@@ -4383,6 +4383,13 @@ SqlTypeName SqlTypeName(Span s) :
 |
     <TIMESTAMP> { return SqlTypeName.TIMESTAMP; }
 |
+    <GEOMETRY> {
+        if (!this.conformance.allowGeometry()) {
+            throw new ParseException(RESOURCE.geometryDisabled().str());
+        }
+        return SqlTypeName.GEOMETRY;
+    }
+|
     (<DECIMAL> | <DEC> | <NUMERIC>) { return SqlTypeName.DECIMAL; }
 |
     <BOOLEAN> { return SqlTypeName.BOOLEAN; }
@@ -5564,6 +5571,7 @@ SqlPostfixOperator PostfixRowOperator() :
 |   < G: "G" >
 |   < GENERAL: "GENERAL" >
 |   < GENERATED: "GENERATED" >
+|   < GEOMETRY: "GEOMETRY" >
 |   < GET: "GET" >
 |   < GLOBAL: "GLOBAL" >
 |   < GO: "GO" >
@@ -6105,6 +6113,7 @@ String CommonNonReservedKeyWord() :
     |   <G>
     |   <GENERAL>
     |   <GENERATED>
+    |   <GEOMETRY>
     |   <GO>
     |   <GOTO>
     |   <GRANTED>

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
index 083d362..2e0ded2 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
@@ -27,6 +27,7 @@ import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rel.type.RelRecordType;
+import org.apache.calcite.runtime.GeoFunctions;
 import org.apache.calcite.runtime.Unit;
 import org.apache.calcite.sql.type.BasicSqlType;
 import org.apache.calcite.sql.type.IntervalSqlType;
@@ -209,6 +210,8 @@ public class JavaTypeFactoryImpl
       case BINARY:
       case VARBINARY:
         return ByteString.class;
+      case GEOMETRY:
+        return GeoFunctions.Geom.class;
       case ANY:
         return Object.class;
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/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 ee6373e..6af4395 100644
--- a/core/src/main/java/org/apache/calcite/model/ModelHandler.java
+++ b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
@@ -54,6 +54,7 @@ import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import javax.sql.DataSource;
 
@@ -86,9 +87,29 @@ public class ModelHandler {
     visit(root);
   }
 
-  /** Creates and validates a ScalarFunctionImpl. */
+  /** @deprecated Use {@link #addFunctions}. */
+  @Deprecated
   public static void create(SchemaPlus schema, String functionName,
       List<String> path, String className, String methodName) {
+    addFunctions(schema, functionName, path, className, methodName, false);
+  }
+
+  /** Creates and validates a {@link ScalarFunctionImpl}, and adds it to a
+   * schema. If {@code methodName} is "*", may add more than one function.
+   *
+   * @param schema Schema to add to
+   * @param functionName Name of function; null to derived from method name
+   * @param path Path to look for functions
+   * @param className Class to inspect for methods that may be user-defined
+   *                  functions
+   * @param methodName Method name;
+   *                  null means use the class as a UDF;
+   *                  "*" means add all methods
+   * @param upCase Whether to convert method names to upper case, so that they
+   *               can be called without using quotes
+   */
+  public static void addFunctions(SchemaPlus schema, String functionName,
+      List<String> path, String className, String methodName, boolean upCase) {
     final Class<?> clazz;
     try {
       clazz = Class.forName(className);
@@ -112,14 +133,26 @@ public class ModelHandler {
     if (methodName != null && methodName.equals("*")) {
       for (Map.Entry<String, ScalarFunction> entry
           : ScalarFunctionImpl.createAll(clazz).entries()) {
-        schema.add(entry.getKey(), entry.getValue());
+        String name = entry.getKey();
+        if (upCase) {
+          name = name.toUpperCase(Locale.ROOT);
+        }
+        schema.add(name, entry.getValue());
       }
       return;
     } else {
       final ScalarFunction function =
           ScalarFunctionImpl.create(clazz, Util.first(methodName, "eval"));
       if (function != null) {
-        schema.add(Util.first(functionName, methodName), function);
+        final String name;
+        if (functionName != null) {
+          name = functionName;
+        } else if (upCase) {
+          name = methodName.toUpperCase(Locale.ROOT);
+        } else {
+          name = methodName;
+        }
+        schema.add(name, function);
         return;
       }
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 17b6e3a..ec57a8d 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -124,6 +124,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.parser.SqlParserImplFactory;
+import org.apache.calcite.sql.type.ExtraSqlTypes;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
 import org.apache.calcite.sql.validate.SqlConformance;
@@ -922,7 +923,7 @@ public class CalcitePrepareImpl implements CalcitePrepare {
       assert rep != null;
       return ColumnMetaData.array(componentType, typeName, rep);
     } else {
-      final int typeOrdinal = getTypeOrdinal(type);
+      int typeOrdinal = getTypeOrdinal(type);
       switch (typeOrdinal) {
       case Types.STRUCT:
         final List<ColumnMetaData> columns = new ArrayList<>();
@@ -932,6 +933,9 @@ public class CalcitePrepareImpl implements CalcitePrepare {
                   field.getType(), null, null));
         }
         return ColumnMetaData.struct(columns);
+      case ExtraSqlTypes.GEOMETRY:
+        typeOrdinal = Types.VARCHAR;
+        // fall through
       default:
         final Type clazz =
             typeFactory.getJavaClass(Util.first(fieldType, type));

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/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 61956cf..b42b1a0 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -55,6 +55,9 @@ public interface CalciteResource {
   @BaseMessage("BETWEEN operator has no terminating AND")
   ExInst<SqlValidatorException> betweenWithoutAnd();
 
+  @BaseMessage("Geo-spatial extensions and the GEOMETRY data type are not enabled")
+  ExInst<SqlValidatorException> geometryDisabled();
+
   @BaseMessage("Illegal INTERVAL literal {0}; at {1}")
   @Property(name = "SQLSTATE", value = "42000")
   ExInst<CalciteException> illegalIntervalLiteral(String a0, String a1);

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/runtime/GeoFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/GeoFunctions.java b/core/src/main/java/org/apache/calcite/runtime/GeoFunctions.java
new file mode 100644
index 0000000..6ddcc8a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/GeoFunctions.java
@@ -0,0 +1,651 @@
+/*
+ * 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.runtime;
+
+import org.apache.calcite.linq4j.function.Deterministic;
+import org.apache.calcite.linq4j.function.Experimental;
+import org.apache.calcite.linq4j.function.SemiStrict;
+import org.apache.calcite.linq4j.function.Strict;
+import org.apache.calcite.util.Util;
+
+import com.esri.core.geometry.Envelope;
+import com.esri.core.geometry.Geometry;
+import com.esri.core.geometry.GeometryEngine;
+import com.esri.core.geometry.Line;
+import com.esri.core.geometry.MapGeometry;
+import com.esri.core.geometry.Operator;
+import com.esri.core.geometry.OperatorBoundary;
+import com.esri.core.geometry.OperatorFactoryLocal;
+import com.esri.core.geometry.OperatorIntersects;
+import com.esri.core.geometry.Point;
+import com.esri.core.geometry.Polygon;
+import com.esri.core.geometry.Polyline;
+import com.esri.core.geometry.SpatialReference;
+import com.esri.core.geometry.WktExportFlags;
+import com.esri.core.geometry.WktImportFlags;
+
+import com.google.common.base.Preconditions;
+
+import java.math.BigDecimal;
+
+/**
+ * Helper methods to implement Geo-spatial functions in generated code.
+ *
+ * <p>Remaining tasks:
+ *
+ * <ul>
+ *   <li>Determine type code for
+ *   {@link org.apache.calcite.sql.type.ExtraSqlTypes#GEOMETRY}
+ *   <li>Should we create aliases for functions in upper-case?
+ *   Without ST_ prefix?
+ *   <li>Consider adding spatial literals, e.g. `GEOMETRY 'POINT (30 10)'`
+ *   <li>Integer arguments, e.g. SELECT ST_MakePoint(1, 2, 1.5),
+ *     ST_MakePoint(1, 2)
+ *   <li>Are GEOMETRY values comparable? If so add ORDER BY test
+ *   <li>We have to add 'Z' to create 3D objects. This is inconsistent with
+ *   PostGIS. Who is right? At least document the difference.
+ *   <li>Should add GeometryEngine.intersects; similar to disjoint etc.
+ *   <li>Make {@link #ST_MakeLine(Geom, Geom)} varargs</li>
+ * </ul>
+ */
+@SuppressWarnings({"UnnecessaryUnboxing", "WeakerAccess", "unused"})
+@Deterministic
+@Strict
+@Experimental
+public class GeoFunctions {
+  private static final int NO_SRID = 0;
+  private static final SpatialReference SPATIAL_REFERENCE =
+      SpatialReference.create(4326);
+
+  private GeoFunctions() {}
+
+  private static UnsupportedOperationException todo() {
+    return new UnsupportedOperationException();
+  }
+
+  protected static Geom bind(Geometry geometry, int srid) {
+    if (geometry == null) {
+      return null;
+    }
+    if (srid == NO_SRID) {
+      return new SimpleGeom(geometry);
+    }
+    return bind(geometry, SpatialReference.create(srid));
+  }
+
+  private static MapGeom bind(Geometry geometry, SpatialReference sr) {
+    return new MapGeom(new MapGeometry(geometry, sr));
+  }
+
+  // Geometry conversion functions (2D and 3D) ================================
+
+  public static String ST_AsText(Geom g) {
+    return ST_AsWKT(g);
+  }
+
+  public static String ST_AsWKT(Geom g) {
+    return GeometryEngine.geometryToWkt(g.g(),
+        WktExportFlags.wktExportDefaults);
+  }
+
+  public static Geom ST_GeomFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_GeomFromText(String s, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(s,
+        WktImportFlags.wktImportDefaults, Geometry.Type.Unknown);
+    return bind(g, srid);
+  }
+
+  public static Geom ST_LineFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_LineFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.Line);
+    return bind(g, srid);
+  }
+
+  public static Geom ST_MPointFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_MPointFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.MultiPoint);
+    return bind(g, srid);
+  }
+
+  public static Geom ST_PointFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_PointFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.Point);
+    return bind(g, srid);
+  }
+
+  public static Geom ST_PolyFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_PolyFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.Polygon);
+    return bind(g, srid);
+  }
+
+  public static Geom ST_MLineFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_MLineFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.Unknown); // NOTE: there is no Geometry.Type.MultiLine
+    return bind(g, srid);
+  }
+
+  public static Geom ST_MPolyFromText(String s) {
+    return ST_GeomFromText(s, NO_SRID);
+  }
+
+  public static Geom ST_MPolyFromText(String wkt, int srid) {
+    final Geometry g = GeometryEngine.geometryFromWkt(wkt,
+        WktImportFlags.wktImportDefaults,
+        Geometry.Type.Unknown); // NOTE: there is no Geometry.Type.MultiPolygon
+    return bind(g, srid);
+  }
+
+  // Geometry creation functions ==============================================
+
+  /**  Creates a line-string from the given POINTs (or MULTIPOINTs). */
+  public static Geom ST_MakeLine(Geom geom1, Geom geom2) {
+    return makeLine(geom1, geom2);
+  }
+
+  public static Geom ST_MakeLine(Geom geom1, Geom geom2, Geom geom3) {
+    return makeLine(geom1, geom2, geom3);
+  }
+
+  public static Geom ST_MakeLine(Geom geom1, Geom geom2, Geom geom3,
+      Geom geom4) {
+    return makeLine(geom1, geom2, geom3, geom4);
+  }
+
+  public static Geom ST_MakeLine(Geom geom1, Geom geom2, Geom geom3,
+      Geom geom4, Geom geom5) {
+    return makeLine(geom1, geom2, geom3, geom4, geom5);
+  }
+
+  public static Geom ST_MakeLine(Geom geom1, Geom geom2, Geom geom3,
+      Geom geom4, Geom geom5, Geom geom6) {
+    return makeLine(geom1, geom2, geom3, geom4, geom5, geom6);
+  }
+
+  private static Geom makeLine(Geom... geoms) {
+    final Polyline g = new Polyline();
+    Point p = null;
+    for (Geom geom : geoms) {
+      if (geom.g() instanceof Point) {
+        final Point prev = p;
+        p = (Point) geom.g();
+        if (prev != null) {
+          final Line line = new Line();
+          line.setStart(prev);
+          line.setEnd(p);
+          g.addSegment(line, false);
+        }
+      }
+    }
+    return new SimpleGeom(g);
+  }
+
+  /**  Alias for {@link #ST_Point(BigDecimal, BigDecimal)}. */
+  public static Geom ST_MakePoint(BigDecimal x, BigDecimal y) {
+    return ST_Point(x, y);
+  }
+
+  /**  Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}. */
+  public static Geom ST_MakePoint(BigDecimal x, BigDecimal y, BigDecimal z) {
+    return ST_Point(x, y, z);
+  }
+
+  /**  Constructs a 2D point from coordinates. */
+  public static Geom ST_Point(BigDecimal x, BigDecimal y) {
+    // NOTE: Combine the double and BigDecimal variants of this function
+    return point(x.doubleValue(), y.doubleValue());
+  }
+
+  /**  Constructs a 3D point from coordinates. */
+  public static Geom ST_Point(BigDecimal x, BigDecimal y, BigDecimal z) {
+    final Geometry g = new Point(x.doubleValue(), y.doubleValue(),
+        z.doubleValue());
+    return new SimpleGeom(g);
+  }
+
+  private static Geom point(double x, double y) {
+    final Geometry g = new Point(x, y);
+    return new SimpleGeom(g);
+  }
+
+  // Geometry properties (2D and 3D) ==========================================
+
+  /** Returns whether {@code geom} has at least one z-coordinate. */
+  public static boolean ST_Is3D(Geom geom) {
+    return geom.g().hasZ();
+  }
+
+  /** Returns the z-value of the first coordinate of {@code geom}. */
+  public static Double ST_Z(Geom geom) {
+    return geom.g().getDescription().hasZ() && geom.g() instanceof Point
+        ? ((Point) geom.g()).getZ() : null;
+  }
+
+  /** Returns the boundary of {@code geom}. */
+  public static Geom ST_Boundary(Geom geom) {
+    OperatorBoundary op = OperatorBoundary.local();
+    Geometry result = op.execute(geom.g(), null);
+    return geom.wrap(result);
+  }
+
+  /** Returns the distance between {@code geom1} and {@code geom2}. */
+  public static double ST_Distance(Geom geom1, Geom geom2) {
+    return GeometryEngine.distance(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns the type of {@code geom}. */
+  public static String ST_GeometryType(Geom geom) {
+    return type(geom.g()).name();
+  }
+
+  /** Returns the OGC SFS type code of {@code geom}. */
+  public static int ST_GeometryTypeCode(Geom geom) {
+    return type(geom.g()).code;
+  }
+
+  /** Returns the OGC type of a geometry. */
+  private static Type type(Geometry g) {
+    switch (g.getType()) {
+    case Point:
+      return Type.POINT;
+    case Polyline:
+      return Type.LINESTRING;
+    case Polygon:
+      return Type.POLYGON;
+    case MultiPoint:
+      return Type.MULTIPOINT;
+    case Envelope:
+      return Type.POLYGON;
+    case Line:
+      return Type.LINESTRING;
+    case Unknown:
+      return Type.Geometry;
+    default:
+      throw new AssertionError(g);
+    }
+  }
+
+  /** Returns the minimum bounding box of {@code geom} (which may be a
+   *  GEOMETRYCOLLECTION). */
+  public static Geom ST_Envelope(Geom geom) {
+    final Envelope env = envelope(geom.g());
+    return geom.wrap(env);
+  }
+
+  private static Envelope envelope(Geometry g) {
+    final Envelope env = new Envelope();
+    g.queryEnvelope(env);
+    return env;
+  }
+
+  // Geometry predicates ======================================================
+
+  /** Returns whether {@code geom1} contains {@code geom2}. */
+  public static boolean ST_Contains(Geom geom1, Geom geom2) {
+    return GeometryEngine.contains(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} contains {@code geom2} but does not
+   * intersect its boundary. */
+  public static boolean ST_ContainsProperly(Geom geom1, Geom geom2) {
+    return GeometryEngine.contains(geom1.g(), geom2.g(), geom1.sr())
+        && !GeometryEngine.crosses(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether no point in {@code geom2} is outside {@code geom1}. */
+  private static boolean ST_Covers(Geom geom1, Geom geom2)  {
+    throw todo();
+  }
+
+  /** Returns whether {@code geom1} crosses {@code geom2}. */
+  public static boolean ST_Crosses(Geom geom1, Geom geom2)  {
+    return GeometryEngine.crosses(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} and {@code geom2} are disjoint. */
+  public static boolean ST_Disjoint(Geom geom1, Geom geom2)  {
+    return GeometryEngine.disjoint(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether the envelope of {@code geom1} intersects the envelope of
+   *  {@code geom2}. */
+  public static boolean ST_EnvelopesIntersect(Geom geom1, Geom geom2)  {
+    final Geometry e1 = envelope(geom1.g());
+    final Geometry e2 = envelope(geom2.g());
+    return intersects(e1, e2, geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} equals {@code geom2}. */
+  public static boolean ST_Equals(Geom geom1, Geom geom2)  {
+    return GeometryEngine.equals(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} intersects {@code geom2}. */
+  public static boolean ST_Intersects(Geom geom1, Geom geom2)  {
+    final Geometry g1 = geom1.g();
+    final Geometry g2 = geom2.g();
+    final SpatialReference sr = geom1.sr();
+    return intersects(g1, g2, sr);
+  }
+
+  private static boolean intersects(Geometry g1, Geometry g2,
+      SpatialReference sr) {
+    final OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal
+        .getInstance().getOperator(Operator.Type.Intersects);
+    return op.execute(g1, g2, sr, null);
+  }
+
+  /** Returns whether {@code geom1} equals {@code geom2} and their coordinates
+   * and component Geometries are listed in the same order. */
+  public static boolean ST_OrderingEquals(Geom geom1, Geom geom2)  {
+    return GeometryEngine.equals(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns {@code geom1} overlaps {@code geom2}. */
+  public static boolean ST_Overlaps(Geom geom1, Geom geom2)  {
+    return GeometryEngine.overlaps(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} touches {@code geom2}. */
+  public static boolean ST_Touches(Geom geom1, Geom geom2)  {
+    return GeometryEngine.touches(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} is within {@code geom2}. */
+  public static boolean ST_Within(Geom geom1, Geom geom2)  {
+    return GeometryEngine.within(geom1.g(), geom2.g(), geom1.sr());
+  }
+
+  /** Returns whether {@code geom1} and {@code geom2} are within
+   * {@code distance} of each other. */
+  public static boolean ST_DWithin(Geom geom1, Geom geom2, double distance) {
+    final double distance1 =
+        GeometryEngine.distance(geom1.g(), geom2.g(), geom1.sr());
+    return distance1 <= distance;
+  }
+
+  // Geometry operators (2D and 3D) ===========================================
+
+  /** Computes a buffer around {@code geom}. */
+  public static Geom ST_Buffer(Geom geom, double distance) {
+    final Polygon g = GeometryEngine.buffer(geom.g(), geom.sr(), distance);
+    return geom.wrap(g);
+  }
+
+  /** Computes a buffer around {@code geom} with . */
+  public static Geom ST_Buffer(Geom geom, double distance, int quadSegs) {
+    throw todo();
+  }
+
+  /** Computes a buffer around {@code geom}. */
+  public static Geom ST_Buffer(Geom geom, double bufferSize, String style) {
+    int quadSegCount = 8;
+    CapStyle endCapStyle = CapStyle.ROUND;
+    JoinStyle joinStyle = JoinStyle.ROUND;
+    float mitreLimit = 5f;
+    int i = 0;
+    parse:
+    for (;;) {
+      int equals = style.indexOf('=', i);
+      if (equals < 0) {
+        break;
+      }
+      int space = style.indexOf(' ', equals);
+      if (space < 0) {
+        space = style.length();
+      }
+      String name = style.substring(i, equals);
+      String value = style.substring(equals + 1, space);
+      switch (name) {
+      case "quad_segs":
+        quadSegCount = Integer.valueOf(value);
+        break;
+      case "endcap":
+        endCapStyle = CapStyle.of(value);
+        break;
+      case "join":
+        joinStyle = JoinStyle.of(value);
+        break;
+      case "mitre_limit":
+      case "miter_limit":
+        mitreLimit = Float.parseFloat(value);
+        break;
+      default:
+        // ignore the value
+      }
+      i = space;
+      for (;;) {
+        if (i >= style.length()) {
+          break parse;
+        }
+        if (style.charAt(i) != ' ') {
+          break;
+        }
+        ++i;
+      }
+    }
+    return buffer(geom, bufferSize, quadSegCount, endCapStyle, joinStyle,
+        mitreLimit);
+  }
+
+  private static Geom buffer(Geom geom, double bufferSize,
+      int quadSegCount, CapStyle endCapStyle, JoinStyle joinStyle,
+      float mitreLimit) {
+    Util.discard(endCapStyle + ":" + joinStyle + ":" + mitreLimit
+        + ":" + quadSegCount);
+    throw todo();
+  }
+
+  /** Computes the union of {@code geom1} and {@code geom2}. */
+  public static Geom ST_Union(Geom geom1, Geom geom2) {
+    SpatialReference sr = geom1.sr();
+    final Geometry g =
+        GeometryEngine.union(new Geometry[]{geom1.g(), geom2.g()}, sr);
+    return bind(g, sr);
+  }
+
+  /** Computes the union of the geometries in {@code geomCollection}. */
+  @SemiStrict public static Geom ST_Union(Geom geomCollection) {
+    SpatialReference sr = geomCollection.sr();
+    final Geometry g =
+        GeometryEngine.union(new Geometry[] {geomCollection.g()}, sr);
+    return bind(g, sr);
+  }
+
+  // Geometry projection functions ============================================
+
+  /** Transforms {@code geom} from one coordinate reference
+   * system (CRS) to the CRS specified by {@code srid}. */
+  public static Geom ST_Transform(Geom geom, int srid) {
+    return geom.transform(srid);
+  }
+
+  /** Returns a copy of {@code geom} with a new SRID. */
+  public static Geom ST_SetSRID(Geom geom, int srid) {
+    return geom.transform(srid);
+  }
+
+  // Inner classes ============================================================
+
+  /** How the "buffer" command terminates the end of a line. */
+  enum CapStyle {
+    ROUND, FLAT, SQUARE;
+
+    static CapStyle of(String value) {
+      switch (value) {
+      case "round":
+        return ROUND;
+      case "flat":
+      case "butt":
+        return FLAT;
+      case "square":
+        return SQUARE;
+      default:
+        throw new IllegalArgumentException("unknown endcap value: " + value);
+      }
+    }
+  }
+
+  /** How the "buffer" command decorates junctions between line segments. */
+  enum JoinStyle {
+    ROUND, MITRE, BEVEL;
+
+    static JoinStyle of(String value) {
+      switch (value) {
+      case "round":
+        return ROUND;
+      case "mitre":
+      case "miter":
+        return MITRE;
+      case "bevel":
+        return BEVEL;
+      default:
+        throw new IllegalArgumentException("unknown join value: " + value);
+      }
+    }
+  }
+
+  /** Geometry. It may or may not have a spatial reference
+   * associated with it. */
+  public interface Geom {
+    Geometry g();
+
+    SpatialReference sr();
+
+    Geom transform(int srid);
+
+    Geom wrap(Geometry g);
+  }
+
+  /** Sub-class of geometry that has no spatial reference. */
+  static class SimpleGeom implements Geom {
+    final Geometry g;
+
+    SimpleGeom(Geometry g) {
+      this.g = Preconditions.checkNotNull(g);
+    }
+
+    @Override public String toString() {
+      return g.toString();
+    }
+
+    public Geometry g() {
+      return g;
+    }
+
+    public SpatialReference sr() {
+      return SPATIAL_REFERENCE;
+    }
+
+    public Geom transform(int srid) {
+      if (srid == SPATIAL_REFERENCE.getID()) {
+        return this;
+      }
+      return bind(g, srid);
+    }
+
+    public Geom wrap(Geometry g) {
+      return new SimpleGeom(g);
+    }
+  }
+
+  /** Sub-class of geometry that has a spatial reference. */
+  static class MapGeom implements Geom {
+    final MapGeometry mg;
+
+    MapGeom(MapGeometry mg) {
+      this.mg = Preconditions.checkNotNull(mg);
+    }
+
+    @Override public String toString() {
+      return mg.toString();
+    }
+
+    public Geometry g() {
+      return mg.getGeometry();
+    }
+
+    public SpatialReference sr() {
+      return mg.getSpatialReference();
+    }
+
+    public Geom transform(int srid) {
+      if (srid == NO_SRID) {
+        return new SimpleGeom(mg.getGeometry());
+      }
+      if (srid == mg.getSpatialReference().getID()) {
+        return this;
+      }
+      return bind(mg.getGeometry(), srid);
+    }
+
+    public Geom wrap(Geometry g) {
+      return bind(g, this.mg.getSpatialReference());
+    }
+  }
+
+  /** Geometry types, with the names and codes assigned by OGC. */
+  enum Type {
+    Geometry(0),
+    POINT(1),
+    LINESTRING(2),
+    POLYGON(3),
+    MULTIPOINT(4),
+    MULTILINESTRING(5),
+    MULTIPOLYGON(6),
+    GEOMCOLLECTION(7),
+    CURVE(13),
+    SURFACE(14),
+    POLYHEDRALSURFACE(15);
+
+    final int code;
+
+    Type(int code) {
+      this.code = code;
+    }
+  }
+}
+
+// End GeoFunctions.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/type/ExtraSqlTypes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ExtraSqlTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ExtraSqlTypes.java
index c9d775d..e883115 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ExtraSqlTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ExtraSqlTypes.java
@@ -42,6 +42,9 @@ public interface ExtraSqlTypes {
   int REF_CURSOR = 2012;
   int TIME_WITH_TIMEZONE = 2013;
   int TIMESTAMP_WITH_TIMEZONE = 2014;
+
+  // From OpenGIS
+  int GEOMETRY = 2015; // TODO: confirm
 }
 
 // End ExtraSqlTypes.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
index c096dae..f2f3c80 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/JavaToSqlTypeConversionRules.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.sql.type;
 
 import org.apache.calcite.avatica.util.ArrayImpl;
+import org.apache.calcite.runtime.GeoFunctions;
 
 import com.google.common.collect.ImmutableMap;
 
@@ -70,6 +71,8 @@ public class JavaToSqlTypeConversionRules {
           .put(Time.class, SqlTypeName.TIME)
           .put(BigDecimal.class, SqlTypeName.DECIMAL)
 
+          .put(GeoFunctions.Geom.class, SqlTypeName.GEOMETRY)
+
           .put(ResultSet.class, SqlTypeName.CURSOR)
           .put(ColumnList.class, SqlTypeName.COLUMN_LIST)
           .put(ArrayImpl.class, SqlTypeName.ARRAY)

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
index 9753f46..f77d639 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
@@ -161,6 +161,9 @@ public class SqlTypeAssignmentRules {
     // Timestamp is assignable from ...
     rules.put(SqlTypeName.TIMESTAMP, EnumSet.of(SqlTypeName.TIMESTAMP));
 
+    // Geometry is assignable from ...
+    rules.put(SqlTypeName.GEOMETRY, EnumSet.of(SqlTypeName.GEOMETRY));
+
     // Array is assignable from ...
     rules.put(SqlTypeName.ARRAY, EnumSet.of(SqlTypeName.ARRAY));
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
index 4543b00..e88f96a 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
@@ -66,7 +66,8 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
   NULL,
   ANY,
   CURSOR,
-  COLUMN_LIST;
+  COLUMN_LIST,
+  GEO;
 
   private static final Map<Integer, SqlTypeFamily> JDBC_TYPE_TO_FAMILY =
       ImmutableMap.<Integer, SqlTypeFamily>builder()
@@ -150,6 +151,8 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
       return SqlTypeName.DATETIME_TYPES;
     case DATETIME_INTERVAL:
       return SqlTypeName.INTERVAL_TYPES;
+    case GEO:
+      return ImmutableList.of(SqlTypeName.GEOMETRY);
     case MULTISET:
       return ImmutableList.of(SqlTypeName.MULTISET);
     case ARRAY:

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index f1b4e6a..fcae1ec 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -115,7 +115,8 @@ public enum SqlTypeName {
   COLUMN_LIST(PrecScale.NO_NO, false, Types.OTHER + 2,
       SqlTypeFamily.COLUMN_LIST),
   DYNAMIC_STAR(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true,
-      Types.JAVA_OBJECT, SqlTypeFamily.ANY);
+      Types.JAVA_OBJECT, SqlTypeFamily.ANY),
+  GEOMETRY(PrecScale.NO_NO, true, ExtraSqlTypes.GEOMETRY, SqlTypeFamily.GEO);
 
   public static final int MAX_DATETIME_PRECISION = 3;
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
index 6fa2483..dbf8b6b 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
@@ -82,6 +82,10 @@ public abstract class SqlAbstractConformance implements SqlConformance {
   public boolean isPercentRemainderAllowed() {
     return SqlConformanceEnum.DEFAULT.isPercentRemainderAllowed();
   }
+
+  public boolean allowGeometry() {
+    return SqlConformanceEnum.DEFAULT.allowGeometry();
+  }
 }
 
 // End SqlAbstractConformance.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
index 2b37026..76b6da9 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
@@ -294,8 +294,18 @@ public interface SqlConformance {
    * {@link SqlConformanceEnum#MYSQL_5};
    * false otherwise.
    */
-
   boolean isLimitStartCountAllowed();
+
+  /**
+   * Whether to allow geo-spatial extensions, including the GEOMETRY type.
+   *
+   * <p>Among the built-in conformance levels, true in
+   * {@link SqlConformanceEnum#LENIENT},
+   * {@link SqlConformanceEnum#MYSQL_5},
+   * {@link SqlConformanceEnum#SQL_SERVER_2008};
+   * false otherwise.
+   */
+  boolean allowGeometry();
 }
 
 // End SqlConformance.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
index 9f06c18..1a5752f 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
@@ -228,6 +228,17 @@ public enum SqlConformanceEnum implements SqlConformance {
       return false;
     }
   }
+
+  public boolean allowGeometry() {
+    switch (this) {
+    case LENIENT:
+    case MYSQL_5:
+    case SQL_SERVER_2008:
+      return true;
+    default:
+      return false;
+    }
+  }
 }
 
 // End SqlConformanceEnum.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/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 52168b0..1f52475 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -25,6 +25,7 @@ IllegalLiteral=Illegal {0} literal {1}: {2}
 IdentifierTooLong=Length of identifier ''{0}'' must be less than or equal to {1,number,#} characters
 BadFormat=not in format ''{0}''
 BetweenWithoutAnd=BETWEEN operator has no terminating AND
+GeometryDisabled=Geo-spatial extensions and the GEOMETRY data type are not enabled
 IllegalIntervalLiteral=Illegal INTERVAL literal {0}; at {1}
 IllegalMinusDate=Illegal expression. Was expecting "(DATETIME - DATETIME) INTERVALQUALIFIER"
 IllegalOverlaps=Illegal overlaps expression. Was expecting expression on the form "(DATETIME, EXPRESSION) OVERLAPS (DATETIME, EXPRESSION)"

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index ab68285..9dd3019 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -274,7 +274,7 @@ public class CalciteRemoteDriverTest {
   @Test public void testRemoteTypeInfo() throws Exception {
     CalciteAssert.hr().with(REMOTE_CONNECTION_FACTORY)
         .metaData(GET_TYPEINFO)
-        .returns(CalciteAssert.checkResultCount(is(42)));
+        .returns(CalciteAssert.checkResultCount(is(43)));
   }
 
   @Test public void testRemoteTableTypes() throws Exception {

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 435e906..e91a76c 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -6710,6 +6710,13 @@ public class SqlParserTest {
         "(?s)Encountered \"to\".*");
   }
 
+  @Test public void testGeometry() {
+    checkExpFails("cast(null as geometry)",
+        "Geo-spatial extensions and the GEOMETRY data type are not enabled");
+    conformance = SqlConformanceEnum.LENIENT;
+    checkExp("cast(null as geometry)", "CAST(NULL AS GEOMETRY)");
+  }
+
   @Test public void testIntervalArithmetics() {
     checkExp(
         "TIME '23:59:59' - interval '1' hour ",

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 490c7e5..6db9435 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -28,15 +28,19 @@ import org.apache.calcite.jdbc.CalciteMetaImpl;
 import org.apache.calcite.jdbc.CalcitePrepare;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.materialize.Lattice;
+import org.apache.calcite.model.ModelHandler;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.runtime.CalciteException;
 import org.apache.calcite.runtime.FlatLists;
+import org.apache.calcite.runtime.GeoFunctions;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.impl.AbstractSchema;
 import org.apache.calcite.schema.impl.ViewTable;
+import org.apache.calcite.schema.impl.ViewTableMacro;
+import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.sql.validate.SqlValidatorException;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.RelBuilder;
@@ -753,6 +757,17 @@ public class CalciteAssert {
             CalciteAssert.addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
       }
       return rootSchema.add("foodmart2", new CloneSchema(foodmart));
+    case GEO:
+      ModelHandler.addFunctions(rootSchema, null, ImmutableList.<String>of(),
+          GeoFunctions.class.getName(), "*", true);
+      final SchemaPlus s = rootSchema.add("GEO", new AbstractSchema());
+      ModelHandler.addFunctions(s, "countries", ImmutableList.<String>of(),
+          CountriesTableFunction.class.getName(), null, false);
+      final String sql = "select * from table(\"countries\"(true))";
+      final ViewTableMacro viewMacro = ViewTable.viewMacro(rootSchema, sql,
+          ImmutableList.of("GEO"), ImmutableList.<String>of(), false);
+      s.add("countries", viewMacro);
+      return s;
     case HR:
       return rootSchema.add("hr",
           new ReflectiveSchema(new JdbcTest.HrSchema()));
@@ -863,6 +878,10 @@ public class CalciteAssert {
             SchemaSpec.POST);
       case REGULAR_PLUS_METADATA:
         return with(SchemaSpec.HR, SchemaSpec.REFLECTIVE_FOODMART);
+      case GEO:
+        return with(SchemaSpec.GEO)
+            .with(CalciteConnectionProperty.CONFORMANCE.camelName(),
+                SqlConformanceEnum.LENIENT);
       case LINGUAL:
         return with(SchemaSpec.LINGUAL);
       case JDBC_FOODMART:
@@ -1636,6 +1655,9 @@ public class CalciteAssert {
      * database. */
     FOODMART_CLONE,
 
+    /** Configuration that contains geo-spatial functions. */
+    GEO,
+
     /** Configuration that contains an in-memory clone of the FoodMart
      * database, plus a lattice to enable on-the-fly materializations. */
     JDBC_FOODMART_WITH_LATTICE,
@@ -1761,6 +1783,7 @@ public class CalciteAssert {
     JDBC_FOODMART,
     CLONE_FOODMART,
     JDBC_FOODMART_WITH_LATTICE,
+    GEO,
     HR,
     JDBC_SCOTT,
     SCOTT,

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/test/java/org/apache/calcite/test/CountriesTableFunction.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CountriesTableFunction.java b/core/src/test/java/org/apache/calcite/test/CountriesTableFunction.java
new file mode 100644
index 0000000..b909241
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/CountriesTableFunction.java
@@ -0,0 +1,327 @@
+/*
+ * 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.apache.calcite.DataContext;
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Linq4j;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.ScannableTable;
+import org.apache.calcite.schema.Schema;
+import org.apache.calcite.schema.Statistic;
+import org.apache.calcite.schema.Statistics;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.collect.ImmutableList;
+
+/** A table function that returns all countries in the world.
+ *
+ * <p>Has same content as
+ * <code>file/src/test/resources/geo/countries.csv</code>. */
+public class CountriesTableFunction {
+  private CountriesTableFunction() {}
+
+  private static final Object[][] ROWS = {
+      {"AD", 42.546245, 1.601554, "Andorra"},
+      {"AE", 23.424076, 53.847818, "United Arab Emirates"},
+      {"AF", 33.93911, 67.709953, "Afghanistan"},
+      {"AG", 17.060816, -61.796428, "Antigua and Barbuda"},
+      {"AI", 18.220554, -63.068615, "Anguilla"},
+      {"AL", 41.153332, 20.168331, "Albania"},
+      {"AM", 40.069099, 45.038189, "Armenia"},
+      {"AN", 12.226079, -69.060087, "Netherlands Antilles"},
+      {"AO", -11.202692, 17.873887, "Angola"},
+      {"AQ", -75.250973, -0.071389, "Antarctica"},
+      {"AR", -38.416097, -63.616672, "Argentina"},
+      {"AS", -14.270972, -170.132217, "American Samoa"},
+      {"AT", 47.516231, 14.550072, "Austria"},
+      {"AU", -25.274398, 133.775136, "Australia"},
+      {"AW", 12.52111, -69.968338, "Aruba"},
+      {"AZ", 40.143105, 47.576927, "Azerbaijan"},
+      {"BA", 43.915886, 17.679076, "Bosnia and Herzegovina"},
+      {"BB", 13.193887, -59.543198, "Barbados"},
+      {"BD", 23.684994, 90.356331, "Bangladesh"},
+      {"BE", 50.503887, 4.469936, "Belgium"},
+      {"BF", 12.238333, -1.561593, "Burkina Faso"},
+      {"BG", 42.733883, 25.48583, "Bulgaria"},
+      {"BH", 25.930414, 50.637772, "Bahrain"},
+      {"BI", -3.373056, 29.918886, "Burundi"},
+      {"BJ", 9.30769, 2.315834, "Benin"},
+      {"BM", 32.321384, -64.75737, "Bermuda"},
+      {"BN", 4.535277, 114.727669, "Brunei"},
+      {"BO", -16.290154, -63.588653, "Bolivia"},
+      {"BR", -14.235004, -51.92528, "Brazil"},
+      {"BS", 25.03428, -77.39628, "Bahamas"},
+      {"BT", 27.514162, 90.433601, "Bhutan"},
+      {"BV", -54.423199, 3.413194, "Bouvet Island"},
+      {"BW", -22.328474, 24.684866, "Botswana"},
+      {"BY", 53.709807, 27.953389, "Belarus"},
+      {"BZ", 17.189877, -88.49765, "Belize"},
+      {"CA", 56.130366, -106.346771, "Canada"},
+      {"CC", -12.164165, 96.870956, "Cocos [Keeling] Islands"},
+      {"CD", -4.038333, 21.758664, "Congo [DRC]"},
+      {"CF", 6.611111, 20.939444, "Central African Republic"},
+      {"CG", -0.228021, 15.827659, "Congo [Republic]"},
+      {"CH", 46.818188, 8.227512, "Switzerland"},
+      {"CI", 7.539989, -5.54708, "Côte d'Ivoire"},
+      {"CK", -21.236736, -159.777671, "Cook Islands"},
+      {"CL", -35.675147, -71.542969, "Chile"},
+      {"CM", 7.369722, 12.354722, "Cameroon"},
+      {"CN", 35.86166, 104.195397, "China"},
+      {"CO", 4.570868, -74.297333, "Colombia"},
+      {"CR", 9.748917, -83.753428, "Costa Rica"},
+      {"CU", 21.521757, -77.781167, "Cuba"},
+      {"CV", 16.002082, -24.013197, "Cape Verde"},
+      {"CX", -10.447525, 105.690449, "Christmas Island"},
+      {"CY", 35.126413, 33.429859, "Cyprus"},
+      {"CZ", 49.817492, 15.472962, "Czech Republic"},
+      {"DE", 51.165691, 10.451526, "Germany"},
+      {"DJ", 11.825138, 42.590275, "Djibouti"},
+      {"DK", 56.26392, 9.501785, "Denmark"},
+      {"DM", 15.414999, -61.370976, "Dominica"},
+      {"DO", 18.735693, -70.162651, "Dominican Republic"},
+      {"DZ", 28.033886, 1.659626, "Algeria"},
+      {"EC", -1.831239, -78.183406, "Ecuador"},
+      {"EE", 58.595272, 25.013607, "Estonia"},
+      {"EG", 26.820553, 30.802498, "Egypt"},
+      {"EH", 24.215527, -12.885834, "Western Sahara"},
+      {"ER", 15.179384, 39.782334, "Eritrea"},
+      {"ES", 40.463667, -3.74922, "Spain"},
+      {"ET", 9.145, 40.489673, "Ethiopia"},
+      {"FI", 61.92411, 25.748151, "Finland"},
+      {"FJ", -16.578193, 179.414413, "Fiji"},
+      {"FK", -51.796253, -59.523613, "Falkland Islands [Islas Malvinas]"},
+      {"FM", 7.425554, 150.550812, "Micronesia"},
+      {"FO", 61.892635, -6.911806, "Faroe Islands"},
+      {"FR", 46.227638, 2.213749, "France"},
+      {"GA", -0.803689, 11.609444, "Gabon"},
+      {"GB", 55.378051, -3.435973, "United Kingdom"},
+      {"GD", 12.262776, -61.604171, "Grenada"},
+      {"GE", 42.315407, 43.356892, "Georgia"},
+      {"GF", 3.933889, -53.125782, "French Guiana"},
+      {"GG", 49.465691, -2.585278, "Guernsey"},
+      {"GH", 7.946527, -1.023194, "Ghana"},
+      {"GI", 36.137741, -5.345374, "Gibraltar"},
+      {"GL", 71.706936, -42.604303, "Greenland"},
+      {"GM", 13.443182, -15.310139, "Gambia"},
+      {"GN", 9.945587, -9.696645, "Guinea"},
+      {"GP", 16.995971, -62.067641, "Guadeloupe"},
+      {"GQ", 1.650801, 10.267895, "Equatorial Guinea"},
+      {"GR", 39.074208, 21.824312, "Greece"},
+      {"GS", -54.429579, -36.587909, "South Georgia and the South Sandwich Islands"},
+      {"GT", 15.783471, -90.230759, "Guatemala"},
+      {"GU", 13.444304, 144.793731, "Guam"},
+      {"GW", 11.803749, -15.180413, "Guinea-Bissau"},
+      {"GY", 4.860416, -58.93018, "Guyana"},
+      {"GZ", 31.354676, 34.308825, "Gaza Strip"},
+      {"HK", 22.396428, 114.109497, "Hong Kong"},
+      {"HM", -53.08181, 73.504158, "Heard Island and McDonald Islands"},
+      {"HN", 15.199999, -86.241905, "Honduras"},
+      {"HR", 45.1, 15.2, "Croatia"},
+      {"HT", 18.971187, -72.285215, "Haiti"},
+      {"HU", 47.162494, 19.503304, "Hungary"},
+      {"ID", -0.789275, 113.921327, "Indonesia"},
+      {"IE", 53.41291, -8.24389, "Ireland"},
+      {"IL", 31.046051, 34.851612, "Israel"},
+      {"IM", 54.236107, -4.548056, "Isle of Man"},
+      {"IN", 20.593684, 78.96288, "India"},
+      {"IO", -6.343194, 71.876519, "British Indian Ocean Territory"},
+      {"IQ", 33.223191, 43.679291, "Iraq"},
+      {"IR", 32.427908, 53.688046, "Iran"},
+      {"IS", 64.963051, -19.020835, "Iceland"},
+      {"IT", 41.87194, 12.56738, "Italy"},
+      {"JE", 49.214439, -2.13125, "Jersey"},
+      {"JM", 18.109581, -77.297508, "Jamaica"},
+      {"JO", 30.585164, 36.238414, "Jordan"},
+      {"JP", 36.204824, 138.252924, "Japan"},
+      {"KE", -0.023559, 37.906193, "Kenya"},
+      {"KG", 41.20438, 74.766098, "Kyrgyzstan"},
+      {"KH", 12.565679, 104.990963, "Cambodia"},
+      {"KI", -3.370417, -168.734039, "Kiribati"},
+      {"KM", -11.875001, 43.872219, "Comoros"},
+      {"KN", 17.357822, -62.782998, "Saint Kitts and Nevis"},
+      {"KP", 40.339852, 127.510093, "North Korea"},
+      {"KR", 35.907757, 127.766922, "South Korea"},
+      {"KW", 29.31166, 47.481766, "Kuwait"},
+      {"KY", 19.513469, -80.566956, "Cayman Islands"},
+      {"KZ", 48.019573, 66.923684, "Kazakhstan"},
+      {"LA", 19.85627, 102.495496, "Laos"},
+      {"LB", 33.854721, 35.862285, "Lebanon"},
+      {"LC", 13.909444, -60.978893, "Saint Lucia"},
+      {"LI", 47.166, 9.555373, "Liechtenstein"},
+      {"LK", 7.873054, 80.771797, "Sri Lanka"},
+      {"LR", 6.428055, -9.429499, "Liberia"},
+      {"LS", -29.609988, 28.233608, "Lesotho"},
+      {"LT", 55.169438, 23.881275, "Lithuania"},
+      {"LU", 49.815273, 6.129583, "Luxembourg"},
+      {"LV", 56.879635, 24.603189, "Latvia"},
+      {"LY", 26.3351, 17.228331, "Libya"},
+      {"MA", 31.791702, -7.09262, "Morocco"},
+      {"MC", 43.750298, 7.412841, "Monaco"},
+      {"MD", 47.411631, 28.369885, "Moldova"},
+      {"ME", 42.708678, 19.37439, "Montenegro"},
+      {"MG", -18.766947, 46.869107, "Madagascar"},
+      {"MH", 7.131474, 171.184478, "Marshall Islands"},
+      {"MK", 41.608635, 21.745275, "Macedonia [FYROM]"},
+      {"ML", 17.570692, -3.996166, "Mali"},
+      {"MM", 21.913965, 95.956223, "Myanmar [Burma]"},
+      {"MN", 46.862496, 103.846656, "Mongolia"},
+      {"MO", 22.198745, 113.543873, "Macau"},
+      {"MP", 17.33083, 145.38469, "Northern Mariana Islands"},
+      {"MQ", 14.641528, -61.024174, "Martinique"},
+      {"MR", 21.00789, -10.940835, "Mauritania"},
+      {"MS", 16.742498, -62.187366, "Montserrat"},
+      {"MT", 35.937496, 14.375416, "Malta"},
+      {"MU", -20.348404, 57.552152, "Mauritius"},
+      {"MV", 3.202778, 73.22068, "Maldives"},
+      {"MW", -13.254308, 34.301525, "Malawi"},
+      {"MX", 23.634501, -102.552784, "Mexico"},
+      {"MY", 4.210484, 101.975766, "Malaysia"},
+      {"MZ", -18.665695, 35.529562, "Mozambique"},
+      {"NA", -22.95764, 18.49041, "Namibia"},
+      {"NC", -20.904305, 165.618042, "New Caledonia"},
+      {"NE", 17.607789, 8.081666, "Niger"},
+      {"NF", -29.040835, 167.954712, "Norfolk Island"},
+      {"NG", 9.081999, 8.675277, "Nigeria"},
+      {"NI", 12.865416, -85.207229, "Nicaragua"},
+      {"NL", 52.132633, 5.291266, "Netherlands"},
+      {"NO", 60.472024, 8.468946, "Norway"},
+      {"NP", 28.394857, 84.124008, "Nepal"},
+      {"NR", -0.522778, 166.931503, "Nauru"},
+      {"NU", -19.054445, -169.867233, "Niue"},
+      {"NZ", -40.900557, 174.885971, "New Zealand"},
+      {"OM", 21.512583, 55.923255, "Oman"},
+      {"PA", 8.537981, -80.782127, "Panama"},
+      {"PE", -9.189967, -75.015152, "Peru"},
+      {"PF", -17.679742, -149.406843, "French Polynesia"},
+      {"PG", -6.314993, 143.95555, "Papua New Guinea"},
+      {"PH", 12.879721, 121.774017, "Philippines"},
+      {"PK", 30.375321, 69.345116, "Pakistan"},
+      {"PL", 51.919438, 19.145136, "Poland"},
+      {"PM", 46.941936, -56.27111, "Saint Pierre and Miquelon"},
+      {"PN", -24.703615, -127.439308, "Pitcairn Islands"},
+      {"PR", 18.220833, -66.590149, "Puerto Rico"},
+      {"PS", 31.952162, 35.233154, "Palestinian Territories"},
+      {"PT", 39.399872, -8.224454, "Portugal"},
+      {"PW", 7.51498, 134.58252, "Palau"},
+      {"PY", -23.442503, -58.443832, "Paraguay"},
+      {"QA", 25.354826, 51.183884, "Qatar"},
+      {"RE", -21.115141, 55.536384, "Réunion"},
+      {"RO", 45.943161, 24.96676, "Romania"},
+      {"RS", 44.016521, 21.005859, "Serbia"},
+      {"RU", 61.52401, 105.318756, "Russia"},
+      {"RW", -1.940278, 29.873888, "Rwanda"},
+      {"SA", 23.885942, 45.079162, "Saudi Arabia"},
+      {"SB", -9.64571, 160.156194, "Solomon Islands"},
+      {"SC", -4.679574, 55.491977, "Seychelles"},
+      {"SD", 12.862807, 30.217636, "Sudan"},
+      {"SE", 60.128161, 18.643501, "Sweden"},
+      {"SG", 1.352083, 103.819836, "Singapore"},
+      {"SH", -24.143474, -10.030696, "Saint Helena"},
+      {"SI", 46.151241, 14.995463, "Slovenia"},
+      {"SJ", 77.553604, 23.670272, "Svalbard and Jan Mayen"},
+      {"SK", 48.669026, 19.699024, "Slovakia"},
+      {"SL", 8.460555, -11.779889, "Sierra Leone"},
+      {"SM", 43.94236, 12.457777, "San Marino"},
+      {"SN", 14.497401, -14.452362, "Senegal"},
+      {"SO", 5.152149, 46.199616, "Somalia"},
+      {"SR", 3.919305, -56.027783, "Suriname"},
+      {"ST", 0.18636, 6.613081, "São Tomé and Príncipe"},
+      {"SV", 13.794185, -88.89653, "El Salvador"},
+      {"SY", 34.802075, 38.996815, "Syria"},
+      {"SZ", -26.522503, 31.465866, "Swaziland"},
+      {"TC", 21.694025, -71.797928, "Turks and Caicos Islands"},
+      {"TD", 15.454166, 18.732207, "Chad"},
+      {"TF", -49.280366, 69.348557, "French Southern Territories"},
+      {"TG", 8.619543, 0.824782, "Togo"},
+      {"TH", 15.870032, 100.992541, "Thailand"},
+      {"TJ", 38.861034, 71.276093, "Tajikistan"},
+      {"TK", -8.967363, -171.855881, "Tokelau"},
+      {"TL", -8.874217, 125.727539, "Timor-Leste"},
+      {"TM", 38.969719, 59.556278, "Turkmenistan"},
+      {"TN", 33.886917, 9.537499, "Tunisia"},
+      {"TO", -21.178986, -175.198242, "Tonga"},
+      {"TR", 38.963745, 35.243322, "Turkey"},
+      {"TT", 10.691803, -61.222503, "Trinidad and Tobago"},
+      {"TV", -7.109535, 177.64933, "Tuvalu"},
+      {"TW", 23.69781, 120.960515, "Taiwan"},
+      {"TZ", -6.369028, 34.888822, "Tanzania"},
+      {"UA", 48.379433, 31.16558, "Ukraine"},
+      {"UG", 1.373333, 32.290275, "Uganda"},
+      {"UM", null, null, "U.S.Minor Outlying Islands"},
+      {"US", 37.09024, -95.712891, "United States"},
+      {"UY", -32.522779, -55.765835, "Uruguay"},
+      {"UZ", 41.377491, 64.585262, "Uzbekistan"},
+      {"VA", 41.902916, 12.453389, "Vatican City"},
+      {"VC", 12.984305, -61.287228, "Saint Vincent and the Grenadines"},
+      {"VE", 6.42375, -66.58973, "Venezuela"},
+      {"VG", 18.420695, -64.639968, "British Virgin Islands"},
+      {"VI", 18.335765, -64.896335, "U.S. Virgin Islands"},
+      {"VN", 14.058324, 108.277199, "Vietnam"},
+      {"VU", -15.376706, 166.959158, "Vanuatu"},
+      {"WF", -13.768752, -177.156097, "Wallis and Futuna"},
+      {"WS", -13.759029, -172.104629, "Samoa"},
+      {"XK", 42.602636, 20.902977, "Kosovo"},
+      {"YE", 15.552727, 48.516388, "Yemen"},
+      {"YT", -12.8275, 45.166244, "Mayotte"},
+      {"ZA", -30.559482, 22.937506, "South Africa"},
+      {"ZM", -13.133897, 27.849332, "Zambia"},
+      {"ZW", -19.015438, 29.154857, "Zimbabwe"},
+  };
+
+  public static ScannableTable eval(boolean b) {
+    return new ScannableTable() {
+      public Enumerable<Object[]> scan(DataContext root) {
+        return Linq4j.asEnumerable(ROWS);
+      };
+
+      public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+        return typeFactory.builder()
+            .add("country", SqlTypeName.VARCHAR)
+            .add("latitude", SqlTypeName.DECIMAL).nullable(true)
+            .add("longitude", SqlTypeName.DECIMAL).nullable(true)
+            .add("name", SqlTypeName.VARCHAR)
+            .build();
+      }
+
+      public Statistic getStatistic() {
+        return Statistics.of(246D,
+            ImmutableList.of(ImmutableBitSet.of(0), ImmutableBitSet.of(3)));
+      }
+
+      public Schema.TableType getJdbcTableType() {
+        return Schema.TableType.TABLE;
+      }
+
+      public boolean isRolledUp(String column) {
+        return false;
+      }
+
+      public boolean rolledUpColumnValidInsideAgg(String column, SqlCall call,
+          SqlNode parent, CalciteConnectionConfig config) {
+        return false;
+      }
+    };
+  }
+}
+
+// End CountriesTableFunction.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/core/src/test/java/org/apache/calcite/test/QuidemTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/QuidemTest.java b/core/src/test/java/org/apache/calcite/test/QuidemTest.java
index 28994a2..f30ebc8 100644
--- a/core/src/test/java/org/apache/calcite/test/QuidemTest.java
+++ b/core/src/test/java/org/apache/calcite/test/QuidemTest.java
@@ -279,6 +279,10 @@ public class QuidemTest {
         return CalciteAssert.that()
             .with(CalciteAssert.Config.FOODMART_CLONE)
             .connect();
+      case "geo":
+        return CalciteAssert.that()
+            .with(CalciteAssert.Config.GEO)
+            .connect();
       case "scott":
         return CalciteAssert.that()
             .with(CalciteAssert.Config.SCOTT)

http://git-wip-us.apache.org/repos/asf/calcite/blob/cc20ca13/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 f8c644b..44e510e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -992,6 +992,24 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         "Binary literal string must contain an even number of hexits");
   }
 
+  /**
+   * Tests whether the GEOMETRY data type is allowed.
+   *
+   * @see SqlConformance#allowGeometry()
+   */
+  @Test public void testGeometry() {
+    final SqlTester lenient =
+        tester.withConformance(SqlConformanceEnum.LENIENT);
+    final SqlTester strict =
+        tester.withConformance(SqlConformanceEnum.STRICT_2003);
+
+    final String err =
+        "Geo-spatial extensions and the GEOMETRY data type are not enabled";
+    sql("select cast(null as geometry) as g from emp")
+        .tester(strict).fails(err)
+        .tester(lenient).sansCarets().ok();
+  }
+
   @Test public void testDateTime() {
     // LOCAL_TIME
     checkExp("LOCALTIME(3)");