You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2022/09/09 06:04:19 UTC

[calcite] branch main updated (d20fd09a1d -> 262492527f)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


    from d20fd09a1d [CALCITE-5274] Improve DocumentBuilderFactory in DiffRepository test class by using secure features
     new 89c940cc8b [CALCITE-5241] Implement CHAR function for MySQL and Spark, also JDBC '{fn CHAR(n)}'
     new 1167b12574 [CALCITE-5270] JDBC adapter should not generate 'FILTER (WHERE)' in Firebolt dialect
     new 479afa6813 [CALCITE-5278] Upgrade Janino from 3.1.6 to 3.1.8
     new 262492527f [CALCITE-5262] Add many spatial functions, including support for WKB (well-known binary) and GeoJSON

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 core/src/main/codegen/templates/Parser.jj          |    1 +
 .../calcite/adapter/enumerable/RexImpTable.java    |    5 +-
 .../calcite/runtime/CoordinateTransformer.java     |   51 +
 .../calcite/runtime/SpatialTypeFunctions.java      | 1115 +++++++++++++++++---
 .../apache/calcite/runtime/SpatialTypeUtils.java   |   44 +-
 .../org/apache/calcite/runtime/SqlFunctions.java   |   19 +-
 .../apache/calcite/sql/SqlJdbcFunctionCall.java    |    1 +
 .../calcite/sql/dialect/FireboltSqlDialect.java    |    4 +
 .../calcite/sql/fun/SqlLibraryOperators.java       |   15 +-
 .../org/apache/calcite/sql/type/ReturnTypes.java   |    7 +
 .../org/apache/calcite/util/BuiltInMethod.java     |    2 +
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java |   34 +-
 .../apache/calcite/sql/test/SqlAdvisorTest.java    |    1 +
 core/src/test/resources/sql/functions.iq           |   15 +-
 core/src/test/resources/sql/spatial.iq             |  991 +++++++++++++++--
 gradle.properties                                  |    2 +-
 site/_docs/reference.md                            |  175 +--
 site/_docs/spatial.md                              |    2 +-
 .../org/apache/calcite/test/SqlOperatorTest.java   |   22 +-
 19 files changed, 2173 insertions(+), 333 deletions(-)
 create mode 100644 core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java


[calcite] 03/04: [CALCITE-5278] Upgrade Janino from 3.1.6 to 3.1.8

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 479afa6813c734167a69ae7e6d2168a14a3e9d56
Author: Julian Hyde <jh...@apache.org>
AuthorDate: Thu Sep 8 16:01:02 2022 -0700

    [CALCITE-5278] Upgrade Janino from 3.1.6 to 3.1.8
---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 66f4ccb1d9..0c7b4e5026 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -115,7 +115,7 @@ immutables.version=2.8.8
 innodb-java-reader.version=1.0.10
 jackson-databind.version=2.13.2.1
 jackson.version=2.13.2.1
-janino.version=3.1.6
+janino.version=3.1.8
 java-diff.version=1.1.2
 jcip-annotations.version=1.0-1
 jcommander.version=1.72


[calcite] 01/04: [CALCITE-5241] Implement CHAR function for MySQL and Spark, also JDBC '{fn CHAR(n)}'

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 89c940cc8b6e5ded4c0fa4fa044bab212a7f8599
Author: xiejiajun <ji...@foxmail.com>
AuthorDate: Sun Aug 21 14:47:58 2022 +0800

    [CALCITE-5241] Implement CHAR function for MySQL and Spark, also JDBC '{fn CHAR(n)}'
    
    Close apache/calcite#2878
---
 core/src/main/codegen/templates/Parser.jj          |  1 +
 .../calcite/adapter/enumerable/RexImpTable.java    |  5 ++++-
 .../org/apache/calcite/runtime/SqlFunctions.java   | 19 ++++++++++++++++---
 .../apache/calcite/sql/SqlJdbcFunctionCall.java    |  1 +
 .../calcite/sql/fun/SqlLibraryOperators.java       | 15 ++++++++++++++-
 .../org/apache/calcite/sql/type/ReturnTypes.java   |  7 +++++++
 .../org/apache/calcite/util/BuiltInMethod.java     |  2 ++
 .../apache/calcite/sql/test/SqlAdvisorTest.java    |  1 +
 core/src/test/resources/sql/functions.iq           | 15 +++++++++++++--
 site/_docs/reference.md                            | 10 ++++------
 .../org/apache/calcite/test/SqlOperatorTest.java   | 22 +++++++++++++++++++---
 11 files changed, 82 insertions(+), 16 deletions(-)

diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 506661ed19..9ad6582315 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -6949,6 +6949,7 @@ SqlIdentifier ReservedFunctionName() :
     |   <AVG>
     |   <CARDINALITY>
     |   <CEILING>
+    |   <CHAR>
     |   <CHAR_LENGTH>
     |   <CHARACTER_LENGTH>
     |   <COALESCE>
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 9738523f2c..1864b1e459 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -115,6 +115,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_LENGTH;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_REVERSE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_AND;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_OR;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.CHAR;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.CHR;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.COMPRESS;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT2;
@@ -366,7 +367,7 @@ public class RexImpTable {
     defineMethod(RIGHT, BuiltInMethod.RIGHT.method, NullPolicy.ANY);
     defineMethod(REPLACE, BuiltInMethod.REPLACE.method, NullPolicy.STRICT);
     defineMethod(TRANSLATE3, BuiltInMethod.TRANSLATE3.method, NullPolicy.STRICT);
-    defineMethod(CHR, "chr", NullPolicy.STRICT);
+    defineMethod(CHR, BuiltInMethod.CHAR_FROM_UTF8.method, NullPolicy.STRICT);
     defineMethod(CHARACTER_LENGTH, BuiltInMethod.CHAR_LENGTH.method,
         NullPolicy.STRICT);
     defineMethod(CHAR_LENGTH, BuiltInMethod.CHAR_LENGTH.method,
@@ -380,6 +381,8 @@ public class RexImpTable {
     defineMethod(OVERLAY, BuiltInMethod.OVERLAY.method, NullPolicy.STRICT);
     defineMethod(POSITION, BuiltInMethod.POSITION.method, NullPolicy.STRICT);
     defineMethod(ASCII, BuiltInMethod.ASCII.method, NullPolicy.STRICT);
+    defineMethod(CHAR, BuiltInMethod.CHAR_FROM_ASCII.method,
+        NullPolicy.SEMI_STRICT);
     defineMethod(REPEAT, BuiltInMethod.REPEAT.method, NullPolicy.STRICT);
     defineMethod(SPACE, BuiltInMethod.SPACE.method, NullPolicy.STRICT);
     defineMethod(STRCMP, BuiltInMethod.STRCMP.method, NullPolicy.STRICT);
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 636b27bfd8..61914293ef 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -462,9 +462,22 @@ public class SqlFunctions {
     return s.substring(len - n);
   }
 
-  /** SQL CHR(long) function. */
-  public static String chr(long n) {
-    return String.valueOf(Character.toChars((int) n));
+  /** SQL CHAR(integer) function, as in MySQL and Spark.
+   *
+   * <p>Returns the ASCII character of {@code n} modulo 256,
+   * or null if {@code n} &lt; 0. */
+  public static @Nullable String charFromAscii(int n) {
+    if (n < 0) {
+      return null;
+    }
+    return String.valueOf(Character.toChars(n % 256));
+  }
+
+  /** SQL CHR(integer) function, as in Oracle and Postgres.
+   *
+   * <p>Returns the UTF-8 character whose code is {@code n}. */
+  public static String charFromUtf8(int n) {
+    return String.valueOf(Character.toChars(n));
   }
 
   /** SQL OCTET_LENGTH(binary) function. */
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlJdbcFunctionCall.java b/core/src/main/java/org/apache/calcite/sql/SqlJdbcFunctionCall.java
index ccb804912d..4ef5e9d4ed 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlJdbcFunctionCall.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlJdbcFunctionCall.java
@@ -708,6 +708,7 @@ public class SqlJdbcFunctionCall extends SqlFunction {
       map.put("TRUNCATE", simple(SqlStdOperatorTable.TRUNCATE));
 
       map.put("ASCII", simple(SqlStdOperatorTable.ASCII));
+      map.put("CHAR", simple(SqlLibraryOperators.CHAR));
       map.put("CONCAT", simple(SqlStdOperatorTable.CONCAT));
       map.put("DIFFERENCE", simple(SqlLibraryOperators.DIFFERENCE));
       map.put("INSERT",
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index acec95ff1e..794270682c 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -650,7 +650,20 @@ public abstract class SqlLibraryOperators {
           ReturnTypes.BIGINT_NULLABLE, null, OperandTypes.TIMESTAMP,
           SqlFunctionCategory.TIMEDATE);
 
-  @LibraryOperator(libraries = {ORACLE})
+  /** The "CHAR(n)" function; returns the character whose ASCII code is
+   * {@code n} % 256, or null if {@code n} &lt; 0. */
+  @LibraryOperator(libraries = {MYSQL, SPARK})
+  public static final SqlFunction CHAR =
+      new SqlFunction("CHAR",
+          SqlKind.OTHER_FUNCTION,
+          ReturnTypes.CHAR_FORCE_NULLABLE,
+          null,
+          OperandTypes.INTEGER,
+          SqlFunctionCategory.STRING);
+
+  /** The "CHR(n)" function; returns the character whose UTF-8 code is
+   * {@code n}. */
+  @LibraryOperator(libraries = {ORACLE, POSTGRESQL})
   public static final SqlFunction CHR =
       new SqlFunction("CHR",
           SqlKind.OTHER_FUNCTION,
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 3512ba3a3e..99750c535b 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -347,6 +347,13 @@ public abstract class ReturnTypes {
   public static final SqlReturnTypeInference CHAR =
           explicit(SqlTypeName.CHAR);
 
+  /**
+   * Type-inference strategy whereby the result type of a call is a nullable
+   * CHAR(1).
+   */
+  public static final SqlReturnTypeInference CHAR_FORCE_NULLABLE =
+      CHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE);
+
   /**
    * Type-inference strategy whereby the result type of a call is an Integer.
    */
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index a3e11dba27..2c3c2d9ba1 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -339,6 +339,8 @@ public enum BuiltInMethod {
   UPPER(SqlFunctions.class, "upper", String.class),
   LOWER(SqlFunctions.class, "lower", String.class),
   ASCII(SqlFunctions.class, "ascii", String.class),
+  CHAR_FROM_ASCII(SqlFunctions.class, "charFromAscii", int.class),
+  CHAR_FROM_UTF8(SqlFunctions.class, "charFromUtf8", int.class),
   REPEAT(SqlFunctions.class, "repeat", String.class, int.class),
   SPACE(SqlFunctions.class, "space", int.class),
   SOUNDEX(SqlFunctions.class, "soundex", String.class),
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 82075d2ae1..54032f3db1 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -138,6 +138,7 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(CAST)",
           "KEYWORD(CEIL)",
           "KEYWORD(CEILING)",
+          "KEYWORD(CHAR)",
           "KEYWORD(CHARACTER_LENGTH)",
           "KEYWORD(CHAR_LENGTH)",
           "KEYWORD(CLASSIFIER)",
diff --git a/core/src/test/resources/sql/functions.iq b/core/src/test/resources/sql/functions.iq
index dbba1a895d..9b9ab67089 100644
--- a/core/src/test/resources/sql/functions.iq
+++ b/core/src/test/resources/sql/functions.iq
@@ -56,7 +56,18 @@ SELECT ExtractValue('<a>c</a>', '//a');
 
 # STRING Functions
 
-#CONCAT
+# CHAR
+SELECT char(null), char(-1), char(65), char(233), char(256+66);
++--------+--------+--------+--------+--------+
+| EXPR$0 | EXPR$1 | EXPR$2 | EXPR$3 | EXPR$4 |
++--------+--------+--------+--------+--------+
+|        |        | A      | é      | B      |
++--------+--------+--------+--------+--------+
+(1 row)
+
+!ok
+
+# CONCAT
 SELECT CONCAT('c', 'h', 'a', 'r');
 +--------+
 | EXPR$0 |
@@ -115,7 +126,7 @@ select sinh(1);
 
 !ok
 
-#CONCAT
+# CONCAT
 select concat('a', 'b');
 +--------+
 | EXPR$0 |
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 1f4f08efeb..54befa1199 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1749,6 +1749,7 @@ period:
 | Operator syntax | Description
 |:--------------- |:-----------
 | {fn ASCII(string)} | Returns the ASCII code of the first character of *string*; if the first character is a non-ASCII character, returns its Unicode code point; returns 0 if *string* is empty
+| {fn CHAR(integer)} | Returns the character whose ASCII code is *integer* % 256, or null if *integer* &lt; 0
 | {fn CONCAT(character, character)} | Returns the concatenation of character strings
 | {fn INSERT(string1, start, length, string2)} | Inserts *string2* into a slot in *string1*
 | {fn LCASE(string)} | Returns a string in which all alphabetic characters in *string* have been converted to lower case
@@ -1758,15 +1759,11 @@ period:
 | {fn LTRIM(string)} | Returns *string* with leading space characters removed
 | {fn REPLACE(string, search, replacement)} | Returns a string in which all the occurrences of *search* in *string* are replaced with *replacement*; if *replacement* is the empty string, the occurrences of *search* are removed
 | {fn REVERSE(string)} | Returns *string* with the order of the characters reversed
-| {fn RIGHT(string, integer)} | Returns the rightmost *length* characters from *string*
+| {fn RIGHT(string, length)} | Returns the rightmost *length* characters from *string*
 | {fn RTRIM(string)} | Returns *string* with trailing space characters removed
 | {fn SUBSTRING(string, offset, length)} | Returns a character string that consists of *length* characters from *string* starting at the *offset* position
 | {fn UCASE(string)} | Returns a string in which all alphabetic characters in *string* have been converted to upper case
 
-Not implemented:
-
-* {fn CHAR(string)}
-
 #### Date/time
 
 | Operator syntax | Description
@@ -2564,7 +2561,8 @@ semantics.
 | b | ARRAY_CONCAT(array [, array ]*)                | Concatenates one or more arrays. If any input argument is `NULL` the function returns `NULL`
 | b | ARRAY_LENGTH(array)                            | Synonym for `CARDINALITY`
 | b | ARRAY_REVERSE(array)                           | Reverses elements of *array*
-| o | CHR(integer) | Returns the character having the binary equivalent to *integer* as a CHAR value
+| m s | CHAR(integer)                                | Returns the character whose ASCII code is *integer* % 256, or null if *integer* &lt; 0
+| o p | CHR(integer)                                 | Returns the character whose UTF-8 code is *integer*
 | o | COSH(numeric)                                  | Returns the hyperbolic cosine of *numeric*
 | o | CONCAT(string, string)                         | Concatenates two strings
 | m p | CONCAT(string [, string ]*)                  | Concatenates two or more strings
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 5bac48f953..8c94c1ed4b 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1492,9 +1492,8 @@ public class SqlOperatorTest {
     f.checkScalar("{fn ASCII('ABC')}", "65", "INTEGER NOT NULL");
     f.checkNull("{fn ASCII(cast(null as varchar(1)))}");
 
-    if (false) {
-      f.checkScalar("{fn CHAR(code)}", null, "");
-    }
+    f.checkScalar("{fn CHAR(97)}", "a", "CHAR(1)");
+
     f.checkScalar("{fn CONCAT('foo', 'bar')}", "foobar", "CHAR(6) NOT NULL");
 
     f.checkScalar("{fn DIFFERENCE('Miller', 'miller')}", "4",
@@ -1630,6 +1629,23 @@ public class SqlOperatorTest {
 
   }
 
+  @Test void testChar() {
+    final SqlOperatorFixture f0 = fixture()
+        .setFor(SqlLibraryOperators.CHR, VM_FENNEL, VM_JAVA);
+    f0.checkFails("^char(97)^",
+        "No match found for function signature CHAR\\(<NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.MYSQL);
+    f.checkScalar("char(null)", isNullValue(), "CHAR(1)");
+    f.checkScalar("char(-1)", isNullValue(), "CHAR(1)");
+    f.checkScalar("char(97)", "a", "CHAR(1)");
+    f.checkScalar("char(48)", "0", "CHAR(1)");
+    f.checkScalar("char(0)", String.valueOf('\u0000'), "CHAR(1)");
+    f.checkFails("^char(97.1)^",
+        "Cannot apply 'CHAR' to arguments of type 'CHAR\\(<DECIMAL\\(3, 1\\)>\\)'\\. "
+            + "Supported form\\(s\\): 'CHAR\\(<INTEGER>\\)'",
+        false);
+  }
+
   @Test void testChr() {
     final SqlOperatorFixture f0 = fixture()
         .setFor(SqlLibraryOperators.CHR, VM_FENNEL, VM_JAVA);


[calcite] 04/04: [CALCITE-5262] Add many spatial functions, including support for WKB (well-known binary) and GeoJSON

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 262492527fbd450892df36cda38080d447fc6498
Author: Bertil Chapuis <bc...@gmail.com>
AuthorDate: Sat Sep 3 14:02:53 2022 +0200

    [CALCITE-5262] Add many spatial functions, including support for WKB (well-known binary) and GeoJSON
    
    Add functions:
     * ST_AsBinary
     * ST_AsEWKT and ST_GeomFromEWKT
     * ST_AsGeoJSON and ST_GeomFromGeoJSON
     * ST_AsGML and ST_GeomFromGML
     * ST_AsWKB and ST_GeomFromWKB
     * ST_Centroid
     * ST_ConvexHull
     * ST_CoordDim
     * ST_Covers
     * ST_Difference and ST_SymDifference
     * ST_Dimension and ST_BoundingCircle
     * ST_Expand
     * ST_Extent
     * ST_ExteriorRing and ST_InteriorRing
     * ST_Force2D and ST_Force3D
     * ST_GeometryN
     * ST_Intersection
     * ST_IsClosed, ST_IsRectangle, ST_IsRing, ST_IsSimple, ST_IsValid, ST_IsEmpty
     * ST_LineMerge
     * ST_MakeEllipse and ST_MakePolygon
     * ST_MakeValid
     * ST_MinimumDiameter
     * ST_MinimumRectangle
     * ST_NumGeometries
     * ST_NumInteriorRing and ST_NumInteriorRings
     * ST_NumPoints
     * ST_OctagonalEnvelope
     * ST_PointN
     * ST_PointOnSurface
     * ST_Polygonize
     * ST_PrecisionReducer
     * ST_Relate
     * ST_Rotate
     * ST_Scale
     * ST_Simplify and ST_SimplifyPreserveTopology
     * ST_Snap
     * ST_SRID
     * ST_StartPoint and ST_EndPoint
     * ST_ToMultiLine and ST_ToMultiSegments
     * ST_ToMultiPoint
     * ST_Translate
     * ST_XMax, ST_XMin, ST_YMax, ST_YMin, ST_ZMin, ST_ZMax
    
    Improve documentation, aliases, and variable names.
    
    Set the extractOnlyPolygonal option to true in ST_Polygonize.
    
    Replace Esri by JTS in the acknowledgements.
    
    Close apache/calcite#2893
---
 .../calcite/runtime/CoordinateTransformer.java     |   51 +
 .../calcite/runtime/SpatialTypeFunctions.java      | 1115 +++++++++++++++++---
 .../apache/calcite/runtime/SpatialTypeUtils.java   |   44 +-
 core/src/test/resources/sql/spatial.iq             |  991 +++++++++++++++--
 site/_docs/reference.md                            |  165 +--
 site/_docs/spatial.md                              |    2 +-
 6 files changed, 2066 insertions(+), 302 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java b/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java
new file mode 100644
index 0000000000..b82efd4b6f
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/runtime/CoordinateTransformer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Transforms the coordinates of a geometry.
+ */
+public class CoordinateTransformer extends GeometryTransformer {
+
+  private final Function<Coordinate, Coordinate> transform;
+
+  /**
+   * Creates a transformer that applies the {@code transform} to all coordinates.
+   */
+  public CoordinateTransformer(Function<Coordinate, Coordinate> transform) {
+    this.transform = transform;
+  }
+
+  @Override protected CoordinateSequence transformCoordinates(
+      CoordinateSequence coordinateSequence, Geometry parent) {
+    Coordinate[] coordinateArray =
+        Stream.of(coordinateSequence.toCoordinateArray())
+            .map(transform)
+            .toArray(Coordinate[]::new);
+    return new CoordinateArraySequence(coordinateArray);
+  }
+
+}
diff --git a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
index d7808a475f..bba3e13edf 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeFunctions.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.runtime;
 
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.AbstractEnumerable;
 import org.apache.calcite.linq4j.Enumerator;
 import org.apache.calcite.linq4j.function.Deterministic;
@@ -26,20 +27,54 @@ import org.apache.calcite.linq4j.function.Strict;
 import org.apache.calcite.runtime.SpatialTypeUtils.SpatialType;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
+import org.locationtech.jts.algorithm.InteriorPoint;
+import org.locationtech.jts.algorithm.MinimumBoundingCircle;
+import org.locationtech.jts.algorithm.MinimumDiameter;
 import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryComponentFilter;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.GeometryFilter;
+import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.OctagonalEnvelope;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.PrecisionModel;
+import org.locationtech.jts.geom.util.AffineTransformation;
+import org.locationtech.jts.geom.util.GeometryFixer;
+import org.locationtech.jts.operation.linemerge.LineMerger;
+import org.locationtech.jts.operation.overlay.snap.GeometrySnapper;
+import org.locationtech.jts.operation.polygonize.Polygonizer;
+import org.locationtech.jts.precision.GeometryPrecisionReducer;
+import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
+import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
+import org.locationtech.jts.util.GeometricShapeFactory;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Stream;
 
 import static org.apache.calcite.runtime.SpatialTypeUtils.GEOMETRY_FACTORY;
 import static org.apache.calcite.runtime.SpatialTypeUtils.NO_SRID;
 import static org.apache.calcite.runtime.SpatialTypeUtils.asEwkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asGeoJson;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asGml;
+import static org.apache.calcite.runtime.SpatialTypeUtils.asWkb;
 import static org.apache.calcite.runtime.SpatialTypeUtils.asWkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromEwkt;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromGeoJson;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromGml;
+import static org.apache.calcite.runtime.SpatialTypeUtils.fromWkb;
 import static org.apache.calcite.runtime.SpatialTypeUtils.fromWkt;
 
 /**
@@ -68,95 +103,256 @@ import static org.apache.calcite.runtime.SpatialTypeUtils.fromWkt;
 @Experimental
 public class SpatialTypeFunctions {
 
-  private SpatialTypeFunctions() {}
+  private SpatialTypeFunctions() {
+  }
 
-  // Geometry conversion functions (2D and 3D) ================================
+  // Geometry conversion functions (2D)
 
-  public static @Nullable String ST_AsEWKT(Geometry g) {
-    return asEwkt(g);
+  public static @Nullable ByteString ST_AsBinary(Geometry geometry) {
+    return ST_AsWKB(geometry);
   }
 
-  public static @Nullable String ST_AsText(Geometry g) {
-    return asWkt(g);
+  public static @Nullable String ST_AsEWKT(Geometry geometry) {
+    return asEwkt(geometry);
   }
 
-  public static @Nullable String ST_AsWKT(Geometry g) {
-    return asWkt(g);
+  public static @Nullable String ST_AsGeoJSON(Geometry geometry) {
+    return asGeoJson(geometry);
   }
 
-  public static @Nullable Geometry ST_GeomFromText(String s) {
-    return fromWkt(s);
+  public static @Nullable String ST_AsGML(Geometry geometry) {
+    return asGml(geometry);
   }
 
-  public static @Nullable Geometry ST_GeomFromText(String s, int srid) {
-    final Geometry g = fromWkt(s);
-    g.setSRID(srid);
-    return g;
+  public static @Nullable String ST_AsText(Geometry geometry) {
+    return ST_AsWKT(geometry);
+  }
+
+  public static @Nullable ByteString ST_AsEWKB(Geometry geometry) {
+    return ST_AsWKB(geometry);
+  }
+
+  public static @Nullable ByteString ST_AsWKB(Geometry geometry) {
+    return asWkb(geometry);
+  }
+
+  public static @Nullable String ST_AsWKT(Geometry geometry) {
+    return asWkt(geometry);
   }
 
-  public static @Nullable Geometry ST_LineFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_Force2D(Geometry geometry) {
+    Function<Coordinate, Coordinate> transform =
+        coordinate -> new Coordinate(coordinate.getX(), coordinate.getY());
+    CoordinateTransformer transformer = new CoordinateTransformer(transform);
+    return transformer.transform(geometry);
+  }
+
+  public static @Nullable Geometry ST_GeomFromEWKB(ByteString ewkb) {
+    return ST_GeomFromWKB(ewkb);
+  }
+
+  public static @Nullable Geometry ST_GeomFromEWKT(String ewkt) {
+    return fromEwkt(ewkt);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGeoJSON(String geojson) {
+    return fromGeoJson(geojson);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGML(String gml) {
+    return ST_GeomFromGML(gml, NO_SRID);
+  }
+
+  public static @Nullable Geometry ST_GeomFromGML(String gml, int srid) {
+    Geometry geometry = fromGml(gml);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_GeomFromText(String wkt) {
+    return ST_GeomFromWKT(wkt);
+  }
+
+  public static @Nullable Geometry ST_GeomFromText(String wkt, int srid) {
+    return ST_GeomFromWKT(wkt, srid);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKB(ByteString wkb) {
+    return fromWkb(wkb);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = fromWkb(wkb);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKT(String wkt) {
+    return ST_GeomFromWKT(wkt, NO_SRID);
+  }
+
+  public static @Nullable Geometry ST_GeomFromWKT(String wkt, int srid) {
+    Geometry geometry = fromWkt(wkt);
+    geometry.setSRID(srid);
+    return geometry;
+  }
+
+  public static @Nullable Geometry ST_LineFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof LineString ? geometry : null;
   }
 
   public static @Nullable Geometry ST_LineFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof LineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_LineFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof LineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_LineFromWKB(ByteString wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkt, srid);
+    return geometry instanceof LineString ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MPointFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_MLineFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiLineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MLineFromText(String wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiLineString ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MPointFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiPoint ? geometry : null;
   }
 
   public static @Nullable Geometry ST_MPointFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiPoint ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_MPolyFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof MultiPolygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_PointFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_MPolyFromText(String wkt, int srid) {
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof MultiPolygon ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof Point ? geometry : null;
   }
 
   public static @Nullable Geometry ST_PointFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof Point ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof Point ? geometry : null;
+  }
+
+  public static @Nullable Geometry ST_PointFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkb, srid);
+    return geometry instanceof Point ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_PolyFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_PolyFromText(String wkt) {
+    Geometry geometry = ST_GeomFromWKT(wkt);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
   public static @Nullable Geometry ST_PolyFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+    Geometry geometry = ST_GeomFromWKT(wkt, srid);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MLineFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  public static @Nullable Geometry ST_PolyFromWKB(ByteString wkb) {
+    Geometry geometry = ST_GeomFromWKB(wkb);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MLineFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+  public static @Nullable Geometry ST_PolyFromWKB(ByteString wkb, int srid) {
+    Geometry geometry = ST_GeomFromWKB(wkb, srid);
+    return geometry instanceof Polygon ? geometry : null;
   }
 
-  public static @Nullable Geometry ST_MPolyFromText(String s) {
-    return ST_GeomFromText(s, NO_SRID);
+  /**
+   * Converts the coordinates of a {@code geom} into a MULTIPOINT.
+   */
+  public static Geometry ST_ToMultiPoint(Geometry geom) {
+    CoordinateSequence coordinateSequence = GEOMETRY_FACTORY
+        .getCoordinateSequenceFactory().create(geom.getCoordinates());
+    return GEOMETRY_FACTORY.createMultiPoint(coordinateSequence);
   }
 
-  public static @Nullable Geometry ST_MPolyFromText(String wkt, int srid) {
-    final Geometry g = fromWkt(wkt);
-    g.setSRID(srid);
-    return g == null ? null : g;
+  /**
+   * Converts the a {@code geom} into a MULTILINESTRING.
+   */
+  public static Geometry ST_ToMultiLine(Geometry geom) {
+    GeometryFactory factory = geom.getFactory();
+    ArrayList<LineString> lines = new ArrayList<>();
+    geom.apply((GeometryComponentFilter) inputGeom -> {
+      if (inputGeom instanceof LineString) {
+        lines.add(factory.createLineString(inputGeom.getCoordinates()));
+      }
+    });
+    if (lines.isEmpty()) {
+      return factory.createMultiLineString();
+    } else {
+      return factory.createMultiLineString(lines.toArray(new LineString[lines.size()]));
+    }
+  }
+
+  /**
+   * Converts a {@code geom} into a set of distinct segments stored in a MULTILINESTRING.
+   */
+  public static Geometry ST_ToMultiSegments(Geometry geom) {
+    GeometryFactory factory = geom.getFactory();
+    ArrayList<LineString> lines = new ArrayList<>();
+    geom.apply((GeometryComponentFilter) inputGeom -> {
+      if (inputGeom instanceof LineString) {
+        Coordinate[] coordinates = inputGeom.getCoordinates();
+        for (int i = 1; i < coordinates.length; i++) {
+          Coordinate[] pair = new Coordinate[]{coordinates[i - 1], coordinates[i]};
+          lines.add(factory.createLineString(pair));
+        }
+      }
+    });
+    if (lines.isEmpty()) {
+      return factory.createMultiLineString();
+    } else {
+      return factory.createMultiLineString(lines.toArray(new LineString[lines.size()]));
+    }
+  }
+
+  // Geometry conversion functions (3D)
+
+  public static @Nullable Geometry ST_Force3D(Geometry geometry) {
+    Function<Coordinate, Coordinate> transform =
+        coordinate -> new Coordinate(
+            coordinate.getX(),
+            coordinate.getY(),
+            Double.isNaN(coordinate.getZ()) ? 0d : coordinate.getZ());
+    CoordinateTransformer transformer = new CoordinateTransformer(transform);
+    return transformer.transform(geometry);
   }
 
   // Geometry creation functions ==============================================
 
-  /** Calculates a regular grid of polygons based on {@code geom}. */
+  /**
+   * Calculates a regular grid of polygons based on {@code geom}.
+   */
   private static void ST_MakeGrid(final Geometry geom,
       final BigDecimal deltaX, final BigDecimal deltaY) {
     // This is a dummy function. We cannot include table functions in this
@@ -164,7 +360,9 @@ public class SpatialTypeFunctions {
     // in SqlSpatialTypeFunctions.
   }
 
-  /** Calculates a regular grid of points based on {@code geom}. */
+  /**
+   * Calculates a regular grid of points based on {@code geom}.
+   */
   private static void ST_MakeGridPoints(final Geometry geom,
       final BigDecimal deltaX, final BigDecimal deltaY) {
     // This is a dummy function. We cannot include table functions in this
@@ -172,7 +370,200 @@ public class SpatialTypeFunctions {
     // in SqlSpatialTypeFunctions.
   }
 
-  /** Creates a rectangular Polygon. */
+  // Geometry creation functions (2D)
+
+  /**
+   * Returns the minimum bounding circle of {@code geom}.
+   */
+  public static Geometry ST_BoundingCircle(Geometry geom) {
+    return new MinimumBoundingCircle(geom).getCircle();
+  }
+
+  /**
+   * Expands {@code geom}'s envelope.
+   */
+  public static Geometry ST_Expand(Geometry geom, BigDecimal distance) {
+    Envelope envelope = geom.getEnvelopeInternal().copy();
+    envelope.expandBy(distance.doubleValue());
+    return geom.getFactory().toGeometry(envelope);
+  }
+
+  /**
+   * Makes an ellipse.
+   */
+  public static @Nullable Geometry ST_MakeEllipse(Geometry point, BigDecimal width,
+      BigDecimal height) {
+    if (!(point instanceof Point)) {
+      return null;
+    }
+    GeometricShapeFactory factory = new GeometricShapeFactory(point.getFactory());
+    factory.setCentre(point.getCoordinate());
+    factory.setWidth(width.doubleValue());
+    factory.setHeight(height.doubleValue());
+    return factory.createEllipse();
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell) {
+    return makePolygon(shell);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell, Geometry hole0) {
+    return makePolygon(shell, hole0);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1) {
+    return makePolygon(shell,
+        hole0, hole1);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2) {
+    return makePolygon(shell,
+        hole0, hole1, hole2);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4,
+      Geometry hole5) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4,
+      Geometry hole5, Geometry hole6) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7, Geometry hole8) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7, hole8);
+  }
+
+  /**
+   * Makes a polygon.
+   */
+  public static @Nullable Geometry ST_MakePolygon(Geometry shell,
+      Geometry hole0, Geometry hole1, Geometry hole2, Geometry hole3, Geometry hole4,
+      Geometry hole5, Geometry hole6, Geometry hole7, Geometry hole8, Geometry hole9) {
+    return makePolygon(shell,
+        hole0, hole1, hole2, hole3, hole4,
+        hole5, hole6, hole7, hole8, hole9);
+  }
+
+  private static @Nullable Geometry makePolygon(Geometry shell, Geometry... holes) {
+    if (!(shell instanceof LineString)) {
+      throw new RuntimeException("Only supports LINESTRINGs.");
+    }
+    if (!((LineString) shell).isClosed()) {
+      throw new RuntimeException("The LINESTRING must be closed.");
+    }
+    for (Geometry hole : holes) {
+      if (!(hole instanceof LineString)) {
+        throw new RuntimeException("Only supports LINESTRINGs.");
+      }
+      if (!((LineString) hole).isClosed()) {
+        throw new RuntimeException("The LINESTRING must be closed.");
+      }
+    }
+    LinearRing shellRing = shell.getFactory().createLinearRing(shell.getCoordinates());
+    LinearRing[] holeRings = new LinearRing[holes.length];
+    for (int i = 0; i < holes.length; i++) {
+      holeRings[i] = holes[i].getFactory().createLinearRing(holes[i].getCoordinates());
+    }
+    return shell.getFactory().createPolygon(shellRing, holeRings);
+  }
+
+  /**
+   * Returns the minimum diameter of {@code geom}.
+   */
+  public static @Nullable Geometry ST_MinimumDiameter(Geometry geom) {
+    return new MinimumDiameter(geom).getDiameter();
+  }
+
+  /**
+   * Returns the minimum rectangle enclosing {@code geom}.
+   */
+  public static @Nullable Geometry ST_MinimumRectangle(Geometry geom) {
+    return new MinimumDiameter(geom).getMinimumRectangle();
+  }
+
+  /**
+   * Returns the octagonal envelope of {@code geom}.
+   */
+  public static @Nullable Geometry ST_OctagonalEnvelope(Geometry geom) {
+    return new OctagonalEnvelope(geom).toGeometry(geom.getFactory());
+  }
+
+  /**
+   * Expands {@code geom}'s envelope.
+   */
+  public static Geometry ST_Expand(Geometry geom, BigDecimal deltaX, BigDecimal deltaY) {
+    Envelope envelope = geom.getEnvelopeInternal().copy();
+    envelope.expandBy(deltaX.doubleValue(), deltaY.doubleValue());
+    return geom.getFactory().toGeometry(envelope);
+  }
+
+  /**
+   * Creates a rectangular Polygon.
+   */
   public static Geometry ST_MakeEnvelope(BigDecimal xMin, BigDecimal yMin,
       BigDecimal xMax, BigDecimal yMax, int srid) {
     Geometry geom = ST_GeomFromText("POLYGON(("
@@ -184,16 +575,20 @@ public class SpatialTypeFunctions {
     return Objects.requireNonNull(geom, "geom");
   }
 
-  /** Creates a rectangular Polygon. */
+  /**
+   * Creates a rectangular Polygon.
+   */
   public static Geometry ST_MakeEnvelope(BigDecimal xMin, BigDecimal yMin,
       BigDecimal xMax, BigDecimal yMax) {
     return ST_MakeEnvelope(xMin, yMin, xMax, yMax, NO_SRID);
   }
 
-  /** Creates a line-string from the given POINTs (or MULTIPOINTs). */
+  /**
+   * Creates a line-string from the given POINTs (or MULTIPOINTs).
+   */
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
     });
@@ -201,7 +596,7 @@ public class SpatialTypeFunctions {
 
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry geom3) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -211,7 +606,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry geom3,
       Geometry geom4) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -222,7 +617,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry geom3,
       Geometry geom4, Geometry geom5) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -234,7 +629,7 @@ public class SpatialTypeFunctions {
   @Hints({"SqlKind:ST_MAKE_LINE"})
   public static Geometry ST_MakeLine(Geometry geom1, Geometry geom2, Geometry geom3,
       Geometry geom4, Geometry geom5, Geometry geom6) {
-    return GEOMETRY_FACTORY.createLineString(new Coordinate[] {
+    return GEOMETRY_FACTORY.createLineString(new Coordinate[]{
         geom1.getCoordinate(),
         geom2.getCoordinate(),
         geom3.getCoordinate(),
@@ -244,160 +639,455 @@ public class SpatialTypeFunctions {
     });
   }
 
-  /** Alias for {@link #ST_Point(BigDecimal, BigDecimal)}. */
+  /**
+   * Alias for {@link #ST_Point(BigDecimal, BigDecimal)}.
+   */
   @Hints({"SqlKind:ST_POINT"})
   public static Geometry ST_MakePoint(BigDecimal x, BigDecimal y) {
     return ST_Point(x, y);
   }
 
-  /** Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}. */
+  /**
+   * Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}.
+   */
   @Hints({"SqlKind:ST_POINT3"})
   public static Geometry ST_MakePoint(BigDecimal x, BigDecimal y, BigDecimal z) {
     return ST_Point(x, y, z);
   }
 
-  /** Constructs a 2D point from coordinates. */
+  /**
+   * Constructs a 2D point from coordinates.
+   */
   @Hints({"SqlKind:ST_POINT"})
   public static Geometry ST_Point(BigDecimal x, BigDecimal y) {
     // NOTE: Combine the double and BigDecimal variants of this function
     return GEOMETRY_FACTORY.createPoint(new Coordinate(x.doubleValue(), y.doubleValue()));
   }
 
-  /** Constructs a 3D point from coordinates. */
+  /**
+   * Constructs a 3D point from coordinates.
+   */
   @Hints({"SqlKind:ST_POINT3"})
   public static Geometry ST_Point(BigDecimal x, BigDecimal y, BigDecimal z) {
     final Geometry g = GEOMETRY_FACTORY.createPoint(
         new Coordinate(x.doubleValue(), y.doubleValue(),
-        z.doubleValue()));
+            z.doubleValue()));
     return g;
   }
 
-  // Geometry properties (2D and 3D) ==========================================
+  // Geometry properties (2D)
+
+  /**
+   * Returns the minimum bounding box that encloses geom as a Geometry.
+   */
+  public static @Nullable Geometry ST_Extent(Geometry geom) {
+    // Note: check whether the extent and the envelope are the same.
+    return geom.getEnvelope();
+  }
+
+  /**
+   * Returns the nth geometry of a geometry collection.
+   */
+  public static @Nullable Geometry ST_GeometryN(Geometry geom, int n) {
+    if (!(geom instanceof GeometryCollection)) {
+      return null;
+    }
+    return geom.getGeometryN(n);
+  }
+
+  /**
+   * Returns the exterior ring of {@code geom}, or null if {@code geom} is not a polygon.
+   */
+  public static @Nullable Geometry ST_ExteriorRing(Geometry geom) {
+    if (geom instanceof Polygon) {
+      return ((Polygon) geom).getExteriorRing();
+    }
+    return null;
+  }
 
-  /** Returns whether {@code geom} has at least one z-coordinate. */
+  /**
+   * Returns the first point of {@code geom}.
+   */
+  public static Geometry ST_EndPoint(Geometry geom) {
+    return ST_PointN(geom, -1);
+  }
+
+  /**
+   * Returns the nth interior ring of {@code geom}, or null if {@code geom} is not a polygon.
+   */
+  public static @Nullable Geometry ST_InteriorRing(Geometry geom, int n) {
+    if (geom instanceof Polygon) {
+      return ((Polygon) geom).getInteriorRingN(n);
+    }
+    return null;
+  }
+
+  /**
+   * Returns whether {@code geom} is a closed LINESTRING or MULTILINESTRING.
+   */
+  public static boolean ST_IsClosed(Geometry geom) {
+    if (geom instanceof LineString) {
+      return ((LineString) geom).isClosed();
+    }
+    if (geom instanceof MultiLineString) {
+      return ((MultiLineString) geom).isClosed();
+    }
+    return false;
+  }
+
+  /**
+   * Returns whether {@code geom} has at least one z-coordinate.
+   */
   public static boolean ST_Is3D(Geometry geom) {
-    for (Coordinate coordinate : geom.getCoordinates()) {
-      if (!Double.isNaN(coordinate.getZ())) {
-        return true;
-      }
+    return ST_CoordDim(geom) == 3;
+  }
+
+  /**
+   * Returns true if geom is empty.
+   */
+  public static boolean ST_IsEmpty(Geometry geom) {
+    return geom.isEmpty();
+  }
+
+  /**
+   * Returns true if geom is rectangle.
+   */
+  public static boolean ST_IsRectangle(Geometry geom) {
+    return geom.isRectangle();
+  }
+
+  /**
+   * Returns whether {@code geom} is a closed and simple linestring or multi-linestring.
+   */
+  public static boolean ST_IsRing(Geometry geom) {
+    if (geom instanceof LineString) {
+      return ((LineString) geom).isClosed() && geom.isSimple();
+    }
+    if (geom instanceof MultiLineString) {
+      return ((MultiLineString) geom).isClosed() && geom.isSimple();
     }
     return false;
   }
 
-  /** Returns the x-value of the first coordinate of {@code geom}. */
+  /**
+   * Returns true if geom is simple.
+   */
+  public static boolean ST_IsSimple(Geometry geom) {
+    return geom.isSimple();
+  }
+
+  /**
+   * Returns true if geom is valid.
+   */
+  public static boolean ST_IsValid(Geometry geom) {
+    return geom.isValid();
+  }
+
+  /**
+   * Returns the number of points in {@code geom}.
+   */
+  public static int ST_NPoints(Geometry geom) {
+    return ST_NumPoints(geom);
+  }
+
+  /**
+   * Returns the number of geometries in {@code geom} (1 if it is not a GEOMETRYCOLLECTION).
+   */
+  public static int ST_NumGeometries(Geometry geom) {
+    return geom.getNumGeometries();
+  }
+
+  /**
+   * Returns the number of interior rings of {@code geom}.
+   */
+  public static int ST_NumInteriorRing(Geometry geom) {
+    return ST_NumInteriorRings(geom);
+  }
+
+  /**
+   * Returns the number of interior rings of {@code geom}.
+   */
+  public static int ST_NumInteriorRings(Geometry geom) {
+    int[] num = new int[]{0};
+    geom.apply(new GeometryFilter() {
+      @Override public void filter(Geometry geom) {
+        if (geom instanceof Polygon) {
+          num[0] += ((Polygon) geom).getNumInteriorRing();
+        }
+      }
+    });
+    return num[0];
+  }
+
+  /**
+   * Returns the number of points in {@code geom}.
+   */
+  public static int ST_NumPoints(Geometry geom) {
+    return geom.getCoordinates().length;
+  }
+
+  /**
+   * Returns the nth point of a {@code geom}.
+   */
+  public static Geometry ST_PointN(Geometry geom, int n) {
+    Coordinate[] coordinates = geom.getCoordinates();
+    int i = (coordinates.length + (n % coordinates.length)) % coordinates.length;
+    return geom.getFactory().createPoint(coordinates[i]);
+  }
+
+  /**
+   * Returns an interior or boundary point of {@code geom}.
+   */
+  public static Geometry ST_PointOnSurface(Geometry geom) {
+    return geom.getFactory().createPoint(InteriorPoint.getInteriorPoint(geom));
+  }
+
+  /**
+   * Returns SRID value or 0 if input Geometry does not have one.
+   */
+  public static int ST_SRID(Geometry geom) {
+    return geom.getSRID();
+  }
+
+  /**
+   * Returns the first point of {@code geom}.
+   */
+  public static Geometry ST_StartPoint(Geometry geom) {
+    return ST_PointN(geom, 0);
+  }
+
+  /**
+   * Return the X coordinate of the point, or NULL if not available. Input must be a point..
+   */
   public static @Nullable Double ST_X(Geometry geom) {
     return geom instanceof Point ? ((Point) geom).getX() : null;
   }
 
-  /** Returns the y-value of the first coordinate of {@code geom}. */
+  /**
+   * Returns the X maxima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_XMax(Geometry geom) {
+    return geom.getEnvelopeInternal().getMaxX();
+  }
+
+  /**
+   * Returns the X minima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_XMin(Geometry geom) {
+    return geom.getEnvelopeInternal().getMinX();
+  }
+
+  /**
+   * Returns the y-value of the first coordinate of {@code geom}.
+   */
   public static @Nullable Double ST_Y(Geometry geom) {
     return geom instanceof Point ? ((Point) geom).getY() : null;
   }
 
-  /** Returns the z-value of the first coordinate of {@code geom}. */
-  public static @Nullable Double ST_Z(Geometry geom) {
-    return geom instanceof Point
-        && !Double.isNaN(geom.getCoordinate().getZ())
-        ? geom.getCoordinate().getZ() : null;
+  /**
+   * Returns the Y maxima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_YMax(Geometry geom) {
+    return geom.getEnvelopeInternal().getMaxY();
+  }
+
+  /**
+   * Returns the Y minima of a 2D or 3D bounding box or a geometry.
+   */
+  public static @Nullable Double ST_YMin(Geometry geom) {
+    return geom.getEnvelopeInternal().getMinY();
   }
 
-  /** Returns the boundary of {@code geom}. */
+  /**
+   * Returns the z-value of the first coordinate of {@code geom}.
+   */
+  public static Double ST_Z(Geometry geom) {
+    if (geom.getCoordinate() != null) {
+      return geom.getCoordinate().getZ();
+    } else {
+      return Double.NaN;
+    }
+
+  }
+
+  /**
+   * Returns the maximum z-value of {@code geom}.
+   */
+  public static Double ST_ZMax(Geometry geom) {
+    return Arrays.stream(geom.getCoordinates())
+        .filter(c -> !Double.isNaN(c.getZ()))
+        .map(c -> c.getZ())
+        .max(Double::compareTo)
+        .orElse(Double.NaN);
+  }
+
+  /**
+   * Returns the minimum z-value of {@code geom}.
+   */
+  public static Double ST_ZMin(Geometry geom) {
+    return Arrays.stream(geom.getCoordinates())
+        .filter(c -> !Double.isNaN(c.getZ()))
+        .map(c -> c.getZ())
+        .min(Double::compareTo)
+        .orElse(Double.NaN);
+  }
+
+  // Geometry properties (2D)
+
+  /**
+   * Returns the boundary of {@code geom}.
+   */
   public static Geometry ST_Boundary(Geometry geom) {
     return geom.getBoundary();
   }
 
-  /** Returns the distance between {@code geom1} and {@code geom2}. */
+  /**
+   * Returns the centroid of {@code geom}.
+   */
+  public static Geometry ST_Centroid(Geometry geom) {
+    return geom.getCentroid();
+  }
+
+  /**
+   * Returns the dimension of the coordinates of {@code geom}.
+   */
+  public static int ST_CoordDim(Geometry geom) {
+    Coordinate coordinate = geom.getCoordinate();
+    if (coordinate != null && !Double.isNaN(coordinate.getZ())) {
+      return 3;
+    }
+    return 2;
+  }
+
+  /**
+   * Returns the dimension of {@code geom}.
+   */
+  public static int ST_Dimension(Geometry geom) {
+    return geom.getDimension();
+  }
+
+  /**
+   * Returns the distance between {@code geom1} and {@code geom2}.
+   */
   public static double ST_Distance(Geometry geom1, Geometry geom2) {
     return geom1.distance(geom2);
   }
 
-  /** Returns the type of {@code geom}. */
+  /**
+   * Returns the type of {@code geom}.
+   */
   public static String ST_GeometryType(Geometry geom) {
     return SpatialType.fromGeometry(geom).name();
   }
 
-  /** Returns the OGC SFS type code of {@code geom}. */
+  /**
+   * Returns the OGC SFS type code of {@code geom}.
+   */
   public static int ST_GeometryTypeCode(Geometry geom) {
     return SpatialType.fromGeometry(geom).code();
   }
 
-  /** Returns the minimum bounding box of {@code geom} (which may be a
-   *  GEOMETRYCOLLECTION). */
+  /**
+   * Returns the minimum bounding box of {@code geom} (which may be a GEOMETRYCOLLECTION).
+   */
   public static Geometry ST_Envelope(Geometry geom) {
     return geom.getEnvelope();
   }
 
   // Geometry predicates ======================================================
 
-  /** Returns whether {@code geom1} contains {@code geom2}. */
+  /**
+   * Returns whether {@code geom1} contains {@code geom2}.
+   */
   @Hints({"SqlKind:ST_CONTAINS"})
   public static boolean ST_Contains(Geometry geom1, Geometry geom2) {
     return geom1.contains(geom2);
   }
 
-  /** Returns whether {@code geom1} contains {@code geom2} but does not
-   * intersect its boundary. */
+  /**
+   * Returns whether {@code geom1} contains {@code geom2} but does not intersect its boundary.
+   */
   public static boolean ST_ContainsProperly(Geometry geom1, Geometry geom2) {
     return geom1.contains(geom2)
         && !geom1.crosses(geom2);
   }
 
-  /** Returns whether no point in {@code geom2} is outside {@code geom1}. */
-  private static boolean ST_Covers(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether no point in {@code geom2} is outside {@code geom1}.
+   */
+  public static boolean ST_Covers(Geometry geom1, Geometry geom2) {
     return geom1.covers(geom2);
   }
 
-  /** Returns whether {@code geom1} crosses {@code geom2}. */
-  public static boolean ST_Crosses(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} crosses {@code geom2}.
+   */
+  public static boolean ST_Crosses(Geometry geom1, Geometry geom2) {
     return geom1.crosses(geom2);
   }
 
-  /** Returns whether {@code geom1} and {@code geom2} are disjoint. */
-  public static boolean ST_Disjoint(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} and {@code geom2} are disjoint.
+   */
+  public static boolean ST_Disjoint(Geometry geom1, Geometry geom2) {
     return geom1.disjoint(geom2);
   }
 
-  /** Returns whether the envelope of {@code geom1} intersects the envelope of
-   *  {@code geom2}. */
-  public static boolean ST_EnvelopesIntersect(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether the envelope of {@code geom1} intersects the envelope of {@code geom2}.
+   */
+  public static boolean ST_EnvelopesIntersect(Geometry geom1, Geometry geom2) {
     final Geometry e1 = geom1.getEnvelope();
     final Geometry e2 = geom2.getEnvelope();
     return e1.intersects(e2);
   }
 
-  /** Returns whether {@code geom1} equals {@code geom2}. */
-  public static boolean ST_Equals(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} equals {@code geom2}.
+   */
+  public static boolean ST_Equals(Geometry geom1, Geometry geom2) {
     return geom1.equals(geom2);
   }
 
-  /** Returns whether {@code geom1} intersects {@code geom2}. */
-  public static boolean ST_Intersects(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} intersects {@code geom2}.
+   */
+  public static boolean ST_Intersects(Geometry geom1, Geometry geom2) {
     return geom1.intersects(geom2);
   }
 
-  /** Returns whether {@code geom1} equals {@code geom2} and their coordinates
-   * and component Geometries are listed in the same order. */
-  public static boolean ST_OrderingEquals(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} equals {@code geom2} and their coordinates and component
+   * Geometries are listed in the same order.
+   */
+  public static boolean ST_OrderingEquals(Geometry geom1, Geometry geom2) {
     return geom1.equals(geom2);
   }
 
-  /** Returns {@code geom1} overlaps {@code geom2}. */
-  public static boolean ST_Overlaps(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns {@code geom1} overlaps {@code geom2}.
+   */
+  public static boolean ST_Overlaps(Geometry geom1, Geometry geom2) {
     return geom1.overlaps(geom2);
   }
 
-  /** Returns whether {@code geom1} touches {@code geom2}. */
-  public static boolean ST_Touches(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} touches {@code geom2}.
+   */
+  public static boolean ST_Touches(Geometry geom1, Geometry geom2) {
     return geom1.touches(geom2);
   }
 
-  /** Returns whether {@code geom1} is within {@code geom2}. */
-  public static boolean ST_Within(Geometry geom1, Geometry geom2)  {
+  /**
+   * Returns whether {@code geom1} is within {@code geom2}.
+   */
+  public static boolean ST_Within(Geometry geom1, Geometry geom2) {
     return geom1.within(geom2);
   }
 
-  /** Returns whether {@code geom1} and {@code geom2} are within
-   * {@code distance} of each other. */
+  /**
+   * Returns whether {@code geom1} and {@code geom2} are within {@code distance} of each other.
+   */
   @Hints({"SqlKind:ST_DWITHIN"})
   public static boolean ST_DWithin(Geometry geom1, Geometry geom2, double distance) {
     final double distance1 = geom1.distance(geom2);
@@ -406,51 +1096,227 @@ public class SpatialTypeFunctions {
 
   // Geometry operators (2D and 3D) ===========================================
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance) {
     return geom.buffer(distance);
   }
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance, int quadSegs) {
     return geom.buffer(distance, quadSegs);
   }
 
-  /** Computes a buffer around {@code geom}. */
+  /**
+   * Computes a buffer around {@code geom}.
+   */
   public static Geometry ST_Buffer(Geometry geom, double distance, int quadSegs, int endCapStyle) {
     return geom.buffer(distance, quadSegs, endCapStyle);
   }
 
-  /** Computes the union of {@code geom1} and {@code geom2}. */
+  /**
+   * Computes the smallest convex POLYGON that contains all the points of geom.
+   */
+  public static Geometry ST_ConvexHull(Geometry geom) {
+    return geom.convexHull();
+  }
+
+  /**
+   * Computes the difference between geom1 and geom2.
+   */
+  public static Geometry ST_Difference(Geometry geom1, Geometry geom2) {
+    return geom1.difference(geom2);
+  }
+
+  /**
+   * Computes the symmetric difference between geom1 and geom2.
+   */
+  public static Geometry ST_SymDifference(Geometry geom1, Geometry geom2) {
+    return geom1.symDifference(geom2);
+  }
+
+  /**
+   * Computes the intersection between geom1 and geom2.
+   */
+  public static Geometry ST_Intersection(Geometry geom1, Geometry geom2) {
+    return geom1.intersection(geom2);
+  }
+
+  /**
+   * Returns the DE-9IM intersection matrix for geom1 and geom2.
+   */
+  public static String ST_Relate(Geometry geom1, Geometry geom2) {
+    return geom1.relate(geom2).toString();
+  }
+
+  /**
+   * Returns true if geom1 and geom2 are related by the intersection matrix specified by iMatrix.
+   */
+  public static boolean ST_Relate(Geometry geom1, Geometry geom2, String iMatrix) {
+    return geom1.relate(geom2, iMatrix);
+  }
+
+  /**
+   * Computes the union of {@code geom1} and {@code geom2}.
+   */
   public static Geometry ST_Union(Geometry geom1, Geometry geom2) {
     return geom1.union(geom2);
   }
 
-  /** Computes the union of the geometries in {@code geomCollection}. */
+  /**
+   * Computes the union of the geometries in {@code geomCollection}.
+   */
   @SemiStrict public static Geometry ST_Union(Geometry geomCollection) {
     return geomCollection.union();
   }
 
   // Geometry projection functions ============================================
 
-  /** Transforms {@code geom} from one coordinate reference
-   * system (CRS) to the CRS specified by {@code srid}. */
+  /**
+   * Transforms {@code geom} from one coordinate reference system (CRS) to the CRS specified by
+   * {@code srid}.
+   */
   public static Geometry ST_Transform(Geometry geom, int srid) {
     ProjectionTransformer projectionTransformer =
         new ProjectionTransformer(geom.getSRID(), srid);
     return projectionTransformer.transform(geom);
   }
 
-  /** Returns a copy of {@code geom} with a new SRID. */
+  /**
+   * Returns a copy of {@code geom} with a new SRID.
+   */
   public static Geometry ST_SetSRID(Geometry geom, int srid) {
     geom.setSRID(srid);
     return geom;
   }
 
+  // Process Geometries
+
+  /**
+   * Merges a collection of linear components to form a line-string of maximal length.
+   */
+  public static Geometry ST_LineMerge(Geometry geom) {
+    LineMerger merger = new LineMerger();
+    merger.add(geom);
+    LineString[] geometries = ((Stream<Object>) merger.getMergedLineStrings().stream())
+        .map(LineString.class::cast)
+        .toArray(size -> new LineString[size]);
+    return GEOMETRY_FACTORY.createMultiLineString(geometries);
+  }
+
+  /**
+   * Makes a valid geometry of a given invalid geometry.
+   */
+  public static Geometry ST_MakeValid(Geometry geometry) {
+    return new GeometryFixer(geometry).getResult();
+  }
+
+  /**
+   * Creates a multipolygon from the geometry.
+   */
+  public static Geometry ST_Polygonize(Geometry geometry) {
+    Polygonizer polygonizer = new Polygonizer(true);
+    polygonizer.add(geometry);
+    return polygonizer.getGeometry();
+  }
+
+  /**
+   * Reduces the geometry's precision to n decimal places.
+   */
+  public static Geometry ST_PrecisionReducer(Geometry geometry, BigDecimal decimal) {
+    double scale = Math.pow(10, decimal.doubleValue());
+    PrecisionModel precisionModel = new PrecisionModel(scale);
+    GeometryPrecisionReducer precisionReducer = new GeometryPrecisionReducer(precisionModel);
+    return precisionReducer.reduce(geometry);
+  }
+
+  /**
+   * Simplifies geom a geometry using the Douglas-Peuker algorithm.
+   */
+  public static Geometry ST_Simplify(Geometry geom, BigDecimal distance) {
+    DouglasPeuckerSimplifier simplifier = new DouglasPeuckerSimplifier(geom);
+    simplifier.setDistanceTolerance(distance.doubleValue());
+    return simplifier.getResultGeometry();
+  }
+
+  /**
+   * Simplifies a geometry and preserves its topology.
+   */
+  public static Geometry ST_SimplifyPreserveTopology(Geometry geom, BigDecimal distance) {
+    TopologyPreservingSimplifier simplifier = new TopologyPreservingSimplifier(geom);
+    simplifier.setDistanceTolerance(distance.doubleValue());
+    return simplifier.getResultGeometry();
+  }
+
+  /**
+   * Snaps geom1 and geom2 together with the given snapTolerance.
+   */
+  public static Geometry ST_Snap(Geometry geom1, Geometry geom2, BigDecimal snapTolerance) {
+    GeometrySnapper snapper = new GeometrySnapper(geom1);
+    return snapper.snapTo(geom2, snapTolerance.doubleValue());
+  }
+
+  // Affine transformation functions (3D and 2D)
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle, Geometry origin) {
+    // Note: check whether we can add support for the Point type.
+    if (!(origin instanceof Point)) {
+      throw new RuntimeException("The origin must be a point");
+    }
+    Point point = (Point) origin;
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue(), point.getX(), point.getY());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Rotates geom counter-clockwise by angle (in radians) about the point origin.
+   */
+  public static Geometry ST_Rotate(Geometry geom, BigDecimal angle, BigDecimal x, BigDecimal y) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.rotate(angle.doubleValue(), x.doubleValue(), y.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Scales geom Geometry by multiplying the ordinates by the indicated scale factors.
+   */
+  public static Geometry ST_Scale(Geometry geom, BigDecimal xFactor, BigDecimal yFactor) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.scale(xFactor.doubleValue(), yFactor.doubleValue());
+    return transformation.transform(geom);
+  }
+
+  /**
+   * Translates geom by the vector (x, y).
+   */
+  public static Geometry ST_Translate(Geometry geom, BigDecimal x, BigDecimal y) {
+    AffineTransformation transformation = new AffineTransformation();
+    transformation.translate(x.doubleValue(), y.doubleValue());
+    return transformation.transform(geom);
+  }
+
   // Space-filling curves
 
-  /** Returns the position of a point on the Hilbert curve,
-   * or null if it is not a 2-dimensional point. */
+  /**
+   * Returns the position of a point on the Hilbert curve, or null if it is not a 2-dimensional
+   * point.
+   */
   @Hints({"SqlKind:HILBERT"})
   public static @Nullable Long hilbert(Geometry geom) {
     if (geom instanceof Point) {
@@ -461,16 +1327,21 @@ public class SpatialTypeFunctions {
     return null;
   }
 
-  /** Returns the position of a point on the Hilbert curve. */
+  /**
+   * Returns the position of a point on the Hilbert curve.
+   */
   @Hints({"SqlKind:HILBERT"})
   public static long hilbert(BigDecimal x, BigDecimal y) {
     return new HilbertCurve2D(8).toIndex(x.doubleValue(), y.doubleValue());
   }
 
+
   // Inner classes ============================================================
 
-  /** Used at run time by the {@link #ST_MakeGrid} and
-   * {@link #ST_MakeGridPoints} functions. */
+
+  /**
+   * Used at run time by the {@link #ST_MakeGrid} and {@link #ST_MakeGridPoints} functions.
+   */
   public static class GridEnumerable extends AbstractEnumerable<Object[]> {
     private final Envelope envelope;
     private final boolean point;
@@ -520,7 +1391,7 @@ public class SpatialTypeFunctions {
             final double bottom = minY + y * deltaY;
             final double top = bottom + deltaY;
 
-            Coordinate[] coordinates = new Coordinate[] {
+            Coordinate[] coordinates = new Coordinate[]{
                 new Coordinate(left, bottom),
                 new Coordinate(left, top),
                 new Coordinate(right, top),
@@ -533,7 +1404,7 @@ public class SpatialTypeFunctions {
 
             geom = polygon;
           }
-          return new Object[] {geom, id, x + 1, y + 1, baseX + x, baseY + y};
+          return new Object[]{geom, id, x + 1, y + 1, baseX + x, baseY + y};
         }
 
         @Override public boolean moveNext() {
diff --git a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
index 7828083203..5a5dba34cf 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SpatialTypeUtils.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.runtime;
 
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.function.Deterministic;
 import org.apache.calcite.linq4j.function.Experimental;
 import org.apache.calcite.linq4j.function.Strict;
@@ -30,9 +31,14 @@ import org.locationtech.jts.io.WKTReader;
 import org.locationtech.jts.io.WKTWriter;
 import org.locationtech.jts.io.geojson.GeoJsonReader;
 import org.locationtech.jts.io.geojson.GeoJsonWriter;
+import org.locationtech.jts.io.gml2.GMLReader;
+import org.locationtech.jts.io.gml2.GMLWriter;
+import org.xml.sax.SAXException;
 
+import java.io.IOException;
 import java.util.Locale;
 import java.util.regex.Pattern;
+import javax.xml.parsers.ParserConfigurationException;
 
 /**
  * Utilities for spatial types.
@@ -119,16 +125,31 @@ public class SpatialTypeUtils {
     }
   }
 
+  /**
+   * Constructs a geometry from a GML representation.
+   *
+   * @param gml a GML
+   * @return a geometry
+   */
+  public static Geometry fromGml(String gml) {
+    try {
+      GMLReader reader = new GMLReader();
+      return reader.read(gml, GEOMETRY_FACTORY);
+    } catch (SAXException | IOException | ParserConfigurationException e) {
+      throw new RuntimeException("Unable to parse GML");
+    }
+  }
+
   /**
    * Constructs a geometry from a Well-Known binary (WKB) representation.
    *
    * @param wkb a WKB
    * @return a geometry
    */
-  public static Geometry fromWkb(byte[] wkb) {
+  public static Geometry fromWkb(ByteString wkb) {
     try {
       WKBReader reader = new WKBReader();
-      return reader.read(wkb);
+      return reader.read(wkb.getBytes());
     } catch (ParseException e) {
       throw new RuntimeException("Unable to parse WKB");
     }
@@ -188,16 +209,31 @@ public class SpatialTypeUtils {
     return geoJsonWriter.write(geometry);
   }
 
+  /**
+   * Returns the GML representation of the geometry.
+   *
+   * @param geometry a geometry
+   * @return a GML
+   */
+  public static String asGml(Geometry geometry) {
+    GMLWriter gmlWriter = new GMLWriter();
+    // remove line breaks and indentation
+    String minified = gmlWriter.write(geometry)
+        .replace("\n", "")
+        .replace("  ", "");
+    return minified;
+  }
+
   /**
    * Returns the Extended Well-Known binary (WKB) representation of the geometry.
    *
    * @param geometry a geometry
    * @return an WKB
    */
-  public static byte[] asWkb(Geometry geometry) {
+  public static ByteString asWkb(Geometry geometry) {
     int outputDimension = dimension(geometry);
     WKBWriter wkbWriter = new WKBWriter(outputDimension);
-    return wkbWriter.write(geometry);
+    return new ByteString(wkbWriter.write(geometry));
   }
 
   /**
diff --git a/core/src/test/resources/sql/spatial.iq b/core/src/test/resources/sql/spatial.iq
index dfafb93e2c..892980e228 100644
--- a/core/src/test/resources/sql/spatial.iq
+++ b/core/src/test/resources/sql/spatial.iq
@@ -36,11 +36,29 @@ C
 
 #### Geometry conversion functions (2D)
 
-# ST_AsBinary(geom) Geometry to Well Known Binary
-# Not implemented
+# ST_AsBinary(geom) Geometry to WKB
+SELECT ST_AsBinary(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+0000000002000000023ff0000000000000400000000000000040080000000000004010000000000000
+!ok
+
+# ST_AsEWKT(geom) Geometry to EWKT
+SELECT ST_AsEWKT(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+srid:0;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_AsGeoJSON(geom) Geometry to GeoJSON
+SELECT ST_AsGeoJSON(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+{"type":"LineString","coordinates":[[1,2],[3,4]],"crs":{"type":"name","properties":{"name":"EPSG:0"}}}
+!ok
 
 # ST_AsGML(geom) Geometry to GML
-# Not implemented
+SELECT ST_AsGML(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+<gml:LineString><gml:coordinates>1.0,2.0 3.0,4.0 </gml:coordinates></gml:LineString>
+!ok
 
 # ST_AsText(geom) Alias for `ST_AsWKT`
 SELECT ST_AsText(ST_GeomFromText('POINT(-71.064544 42.28787)'));
@@ -49,12 +67,17 @@ POINT (-71.064544 42.28787)
 !ok
 
 # ST_AsWKT(geom) Converts *geom* → Well-Known Text
-
 SELECT ST_AsWKT(ST_GeomFromText('POINT(-71.064544 42.28787)'));
 EXPR$0
 POINT (-71.064544 42.28787)
 !ok
 
+# ST_AsEWKB(geom) Geometry to WKB
+SELECT ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 4)'));
+EXPR$0
+0000000002000000023ff0000000000000400000000000000040080000000000004010000000000000
+!ok
+
 # PostGIS can implicitly assign from CHAR to GEOMETRY; we can't
 !if (false) {
 # ST_AsWKT(geom) Geometry to Well Known Text
@@ -71,13 +94,47 @@ null
 !ok
 
 # ST_Force2D(geom) 3D Geometry to 2D Geometry
-# Not implemented
 
-# ST_GeomFromGML(gml [, srid ]) GML to Geometry
-# Not implemented
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('POINT(-10 10)')));
+EXPR$0
+POINT (-10 10)
+!ok
 
-# ST_GeomFromText(wkt [, srid ]) Returns a specified geometry value from Well-Known Text representation
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('POINT(-10 10 6)')));
+EXPR$0
+POINT (-10 10)
+!ok
+
+SELECT ST_AsText(ST_Force2D(ST_GeomFromText('LINESTRING(-10 10 2, 10 10 3)')));
+EXPR$0
+LINESTRING (-10 10, 10 10)
+!ok
 
+# ST_GeomFromEWKT(ewkt) EWKT to Geometry
+SELECT ST_AsEWKT(ST_GeomFromEWKT('srid:4326;LINESTRING (1 2, 3 4)'));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGeoJSON(geoJSON) GeoJSON to Geometry
+SELECT ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[1,2],[3,4]],"crs":{"type":"name","properties":{"name":"EPSG:0"}}}');
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGML(gml) GML to Geometry
+SELECT ST_GeomFromGML('<gml:LineString><gml:coordinates>1.0,2.0 3.0,4.0</gml:coordinates></gml:LineString>');
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromGML(gml, srid) GML to Geometry
+SELECT ST_AsEWKT(ST_GeomFromGML('<gml:LineString><gml:coordinates>1.0,2.0 3.0,4.0</gml:coordinates></gml:LineString>', 4326));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromText(wkt [, srid ]) Returns a specified geometry value from Well-Known Text representation
 SELECT ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)');
 EXPR$0
 LINESTRING (-71.160281 42.258729, -71.160837 42.259113, -71.161144 42.25932)
@@ -155,8 +212,17 @@ EXPR$0
 GEOMETRYCOLLECTION EMPTY
 !ok
 
-# ST_GeomFromWKB(wkb [, srid ]) Well Known Binary to Geometry
-# Not implemented
+# ST_GeomFromWKB(wkb) WKB to Geometry
+SELECT ST_GeomFromWKB(ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 4)')));
+EXPR$0
+LINESTRING (1 2, 3 4)
+!ok
+
+# ST_GeomFromWKB(wkb, srid) WKB to Geometry
+SELECT ST_AsEWKT(ST_GeomFromWKB(ST_AsWKB(ST_GeomFromText('LINESTRING (1 2, 3 4)')), 4326));
+EXPR$0
+srid:4326;LINESTRING (1 2, 3 4)
+!ok
 
 # ST_GoogleMapLink(geom [, layerType [, zoom ]]) Geometry to Google map link
 # Not implemented
@@ -165,7 +231,7 @@ GEOMETRYCOLLECTION EMPTY
 SELECT ST_LineFromText('LINESTRING(1 2, 3 4)') AS aline,
   ST_LineFromText('POINT(1 2)') AS null_return;
 ALINE, NULL_RETURN
-LINESTRING (1 2, 3 4), POINT (1 2)
+LINESTRING (1 2, 3 4), null
 !ok
 
 # ST_LineFromWKB(wkb [, srid ]) Well Known Binary to LINESTRING
@@ -237,36 +303,118 @@ POLYGON ((0 0, 0 1, 1 1, 0 0))
 # Not implemented
 
 # ST_ToMultiLine(geom) Converts the coordinates of *geom* (which may be a geometry-collection) into a multi-line-string
-# Not implemented
+
+SELECT ST_ToMultiLine(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1))'));
+EXPR$0
+MULTILINESTRING ((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1))
+!ok
+
+SELECT ST_ToMultiLine(ST_GeomFromText('GEOMETRYCOLLECTION(LINESTRING(1 4 3, 10 7 9, 12 9 22), POLYGON((1 1 -1, 3 1 0, 3 2 1, 1 2 2, 1 1 -1)))'));
+EXPR$0
+MULTILINESTRING ((1 4, 10 7, 12 9), (1 1, 3 1, 3 2, 1 2, 1 1))
+!ok
 
 # ST_ToMultiPoint(geom)) Converts the coordinates of *geom* (which may be a geometry-collection) into a multi-point
-# Not implemented
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('POINT(5 5)'));
+EXPR$0
+MULTIPOINT ((5 5))
+!ok
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('MULTIPOINT(5 5, 1 2, 3 4, 99 3)'));
+EXPR$0
+MULTIPOINT ((5 5), (1 2), (3 4), (99 3))
+!ok
+
+SELECT ST_ToMultiPoint(ST_GeomFromText('LINESTRING(5 5, 1 2, 3 4, 1 5)'));
+EXPR$0
+MULTIPOINT ((5 5), (1 2), (3 4), (1 5))
+!ok
 
 # ST_ToMultiSegments(geom) Converts *geom* (which may be a geometry-collection) into a set of distinct segments stored in a multi-line-string
-# Not implemented
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('LINESTRING(5 4, 1 1, 3 4, 4 5)'));
+EXPR$0
+MULTILINESTRING ((5 4, 1 1), (1 1, 3 4), (3 4, 4 5))
+!ok
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('MULTILINESTRING((1 4 3, 15 7 9, 16 17 22), (0 0 0, 1 0 0, 1 2 0, 0 2 1))'));
+EXPR$0
+MULTILINESTRING ((1 4, 15 7), (15 7, 16 17), (0 0, 1 0), (1 0, 1 2), (1 2, 0 2))
+!ok
+
+SELECT ST_ToMultiSegments(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1))'));
+EXPR$0
+MULTILINESTRING ((0 0, 10 0), (10 0, 10 6), (10 6, 0 6), (0 6, 0 0), (1 1, 2 1), (2 1, 2 5), (2 5, 1 5), (1 5, 1 1))
+!ok
 
 #### Geometry conversion functions (3D)
 
 # ST_Force3D(geom) 2D Geometry to 3D Geometry
-# Not implemented
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('POINT(-10 10 6)')));
+EXPR$0
+POINT Z(-10 10 6)
+!ok
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('POINT(-10 10)')));
+EXPR$0
+POINT Z(-10 10 0)
+!ok
+
+SELECT ST_AsText(ST_Force3D(ST_GeomFromText('LINESTRING(-10 10, 10 10 3)')));
+EXPR$0
+LINESTRING Z(-10 10 0, 10 10 3)
+!ok
 
 #### Geometry creation functions (2D)
 
 # ST_BoundingCircle(geom) Returns the minimum bounding circle of *geom*
-# Not implemented
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('POLYGON((1 1, 1 4, 4 4, 4 1, 1 1))'))), 2);
+EXPR$0
+POLYGON ((4.63 2.5, 4.59 2.09, 4.46 1.69, 4.27 1.33, 4 1.01, 3.68 0.74, 3.32 0.55, 2.92 0.42, 2.5 0.38, 2.09 0.42, 1.69 0.55, 1.33 0.74, 1.01 1, 0.74 1.33, 0.55 1.69, 0.42 2.09, 0.38 2.50, 0.42 2.92, 0.55 3.32, 0.74 3.68, 1.00 4, 1.33 4.27, 1.69 4.46, 2.09 4.59, 2.50 4.63, 2.92 4.59, 3.32 4.46, 3.68 4.27, 4.00 4, 4.27 3.68, 4.46 3.32, 4.59 2.92, 4.63 2.5))
+!ok
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('MULTIPOINT((1 1), (4 2))'))), 2);
+EXPR$0
+POLYGON ((4.09 1.5, 4.06 1.20, 3.97 0.90, 3.82 0.63, 3.62 0.39, 3.38 0.19, 3.11 0.04, 2.81 -0.06, 2.5 -0.09, 2.20 -0.06, 1.90 0.04, 1.63 0.19, 1.39 0.39, 1.19 0.63, 1.04 0.90, 0.95 1.20, 0.92 1.50, 0.95 1.81, 1.04 2.11, 1.19 2.38, 1.39 2.62, 1.63 2.82, 1.90 2.97, 2.20 3.06, 2.50 3.09, 2.81 3.06, 3.11 2.97, 3.38 2.82, 3.62 2.62, 3.82 2.38, 3.97 2.11, 4.06 1.81, 4.09 1.5))
+!ok
+
+SELECT roundGeom(ST_asText(ST_BoundingCircle(ST_GeomFromText('LINESTRING(1 1, 4 5, 3 2)'))), 2);
+EXPR$0
+POLYGON ((5 3, 4.96 2.52, 4.81 2.05, 4.58 1.62, 4.27 1.24, 3.89 0.93, 3.46 0.70, 2.99 0.55, 2.5 0.5, 2.02 0.55, 1.55 0.70, 1.12 0.93, 0.74 1.24, 0.43 1.62, 0.20 2.05, 0.05 2.52, 0 3.00, 0.05 3.49, 0.20 3.96, 0.43 4.39, 0.74 4.77, 1.12 5.08, 1.55 5.31, 2.02 5.46, 2.50 5.5, 2.99 5.46, 3.46 5.31, 3.89 5.08, 4.27 4.77, 4.58 4.39, 4.81 3.96, 4.96 3.49, 5 3))
+!ok
 
 # ST_Expand(geom, distance) Expands *geom*'s envelope
-# Not implemented
+
+SELECT ST_Expand(ST_GeomFromText('POINT(1 1)'), 1);
+EXPR$0
+POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))
+!ok
 
 # ST_Expand(geom, deltaX, deltaY) Expands *geom*'s envelope
-# Not implemented
+
+SELECT ST_Expand(ST_GeomFromText('POINT(4 4)'), 5, 2);
+EXPR$0
+POLYGON ((-1 2, -1 6, 9 6, 9 2, -1 2))
+!ok
+
+SELECT ST_Expand(ST_GeomFromText('LINESTRING(3 2, 7 5, 2 7)'), 2, 1);
+EXPR$0
+POLYGON ((0 1, 0 8, 9 8, 9 1, 0 1))
+!ok
 
 # ST_MakeEllipse(point, width, height) Constructs an ellipse
-# Not implemented
+
+SELECT roundGeom(ST_AsText(ST_MakeEllipse(ST_GeomFromText('POINT(5 5)'), 2, 5)), 2);
+EXPR$0
+POLYGON ((6 5, 6.00 5.16, 6.00 5.32, 5.99 5.47, 5.97 5.63, 5.96 5.78, 5.93 5.93, 5.91 6.07, 5.88 6.21, 5.85 6.34, 5.81 6.47, 5.78 6.60, 5.73 6.72, 5.69 6.83, 5.64 6.93, 5.59 7.03, 5.54 7.12, 5.49 7.20, 5.43 7.27, 5.37 7.33, 5.31 7.38, 5.25 7.43, 5.19 7.46, 5.13 7.49, 5.07 7.50, 5 7.5, 4.94 7.50, 4.88 7.49, 4.82 7.46, 4.76 7.43, 4.70 7.38, 4.64 7.33, 4.58 7.27, 4.52 7.20, 4.47 7.12, 4.42 7.03, 4.37 6.93, 4.32 6.83, 4.28 6.72, 4.23 6.60, 4.20 6.47, 4.16 6.34, 4.13 6.21, 4.10 6.07, 4.08 5.9 [...]
+!ok
 
 # ST_MakeEnvelope(xMin, yMin, xMax, yMax  [, srid ]) Creates a rectangular Polygon
-SELECT ST_AsText(ST_MakeEnvelope(10.0, 10.0, 11.0, 11.0, 4326));
 
+SELECT ST_AsText(ST_MakeEnvelope(10.0, 10.0, 11.0, 11.0, 4326));
 EXPR$0
 POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10))
 !ok
@@ -361,16 +509,51 @@ Greenland, POINT (-42.604303 71.706936)
 !ok
 
 # ST_MakePolygon(lineString [, hole ]*) Creates a polygon from *lineString* with the given holes (which are required to be closed line-strings)
-# Not implemented
+
+SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(100 250, 100 350, 200 350, 200 250, 100 250)'));
+EXPR$0
+POLYGON ((100 250, 100 350, 200 350, 200 250, 100 250))
+!ok
+
+SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(0 5, 4 5, 4 0, 0 0, 0 5)'), ST_GeomFromText('LINESTRING(1 1, 1 2, 2 2, 2 1, 1 1)'));
+EXPR$0
+POLYGON ((0 5, 4 5, 4 0, 0 0, 0 5), (1 1, 1 2, 2 2, 2 1, 1 1))
+!ok
+
+SELECT ST_MakePolygon(ST_GeomFromText('POINT(1 1)'));
+Only supports LINESTRINGs.
+!error
 
 # ST_MinimumDiameter(geom) Returns the minimum diameter of *geom*
-# Not implemented
+
+SELECT ST_MinimumDiameter(ST_GeomFromText('POINT(395 278)'));
+EXPR$0
+LINESTRING (395 278, 395 278)
+!ok
+
+SELECT ST_MinimumDiameter(ST_GeomFromText('LINESTRING(0 0, 1 1, 3 9, 7 1)'));
+EXPR$0
+LINESTRING (1 3, 7 1)
+!ok
 
 # ST_MinimumRectangle(geom) Returns the minimum rectangle enclosing *geom*
-# Not implemented
 
-# ST_OctogonalEnvelope(geom) Returns the octogonal envelope of *geom*
-# Not implemented
+SELECT ST_MinimumRectangle(ST_GeomFromText('MULTIPOINT((8 3), (4 6))'));
+EXPR$0
+LINESTRING (4 6, 8 3)
+!ok
+
+SELECT ST_MinimumRectangle(ST_GeomFromText('POLYGON((1 2, 3 0, 5 2, 3 2, 2 3, 1 2))'));
+EXPR$0
+POLYGON ((1.4 3.1999999999999993, 0.6 0.8, 4.2 -0.3999999999999996, 4.999999999999999 1.9999999999999993, 1.4 3.1999999999999993))
+!ok
+
+# ST_OctagonalEnvelope(geom) Returns the octogonal envelope of *geom*
+
+SELECT ST_OctagonalEnvelope(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 4, 3 5, 3 3, 5 5, 7 2, 5 2, 6 1, 2 1))'));
+EXPR$0
+POLYGON ((1 2, 1 3, 3 5, 5 5, 7 3, 7 2, 6 1, 2 1, 1 2))
+!ok
 
 # ST_RingBuffer(geom, bufferSize, bufferCount [, endCapStyle [, doDifference]]) Returns a multi-polygon of buffers centered at *geom* and of increasing buffer size
 # Not implemented
@@ -412,16 +595,77 @@ MULTIPOINT Z((-1 1 1), (1 1 1))
 !ok
 
 # ST_Centroid(geom) Returns the centroid of *geom* (which may be a geometry-collection)
-# Not implemented
+
+SELECT ST_Centroid(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+POINT (1.5 2)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+POINT (2.472556942838389 2.3241856476127962)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('MULTILINESTRING((1 5, 6 5), (5 1, 5 4))'));
+EXPR$0
+POINT (4.0625 4.0625)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('POLYGON((1 5, 1 2, 6 2, 3 3, 3 4, 5 6, 1 5))'));
+EXPR$0
+POINT (2.5964912280701755 3.666666666666667)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('MULTIPOLYGON(((0 2, 3 2, 3 6, 0 6, 0 2)), ((5 0, 7 0, 7 1, 5 1, 5 0)))'));
+EXPR$0
+POINT (2.142857142857143 3.5)
+!ok
+
+SELECT ST_Centroid(ST_GeomFromText('GEOMETRYCOLLECTION(
+                      POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)),
+                      LINESTRING(2 6, 6 2),
+                      MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+POINT (2.5 4)
+!ok
+
 
 # ST_CompactnessRatio(polygon) Returns the square root of *polygon*'s area divided by the area of the circle with circumference equal to its perimeter
 # Not implemented
 
 # ST_CoordDim(geom) Returns the dimension of the coordinates of *geom*
-# Not implemented
+
+SELECT ST_CoordDim(ST_GeomFromText('Point(1 2 3)'));
+EXPR$0
+3
+!ok
+
+SELECT ST_CoordDim(ST_GeomFromText('Point(1 2)'));
+EXPR$0
+2
+!ok
+
+SELECT ST_CoordDim(ST_GeomFromText('Point Empty'));
+EXPR$0
+2
+!ok
 
 # ST_Dimension(geom) Returns the dimension of *geom*
-# Not implemented
+
+SELECT ST_Dimension(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+0
+!ok
+
+SELECT ST_Dimension(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_Dimension(ST_GeomFromText('MULTIPOLYGON(((0 2, 3 2, 3 6, 0 6, 0 2)), ((5 0, 7 0, 7 1, 5 1, 5 0)))'));
+EXPR$0
+2
+!ok
 
 # ST_Distance(geom1, geom2) Returns the distance between *geom1* and *geom2*
 
@@ -478,8 +722,12 @@ FROM (SELECT
 !ok
 !}
 
-# ST_EndPoint(lineString) Returns the last coordinate of *lineString*
-# Not implemented
+# ST_EndPoint(geom) Returns the last coordinate of *geom*
+
+SELECT ST_EndPoint(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2))'));
+EXPR$0
+POINT (-1 2)
+!ok
 
 # ST_Envelope(geom [, srid ]) Returns the envelope of *geom* (which may be a geometry-collection) as a geometry
 
@@ -507,19 +755,50 @@ POLYGON ((0 0, 0 1, 1.0000000001 1, 1.0000000001 0, 0 0))
 # Not implemented
 
 # ST_Extent(geom) Returns the minimum bounding box of *geom* (which may be a geometry-collection)
-# Not implemented
+
+SELECT ST_Extent(ST_GeomFromText('MULTIPOINT((5 6), (1 2), (3 4), (10 3))'));
+EXPR$0
+POLYGON ((1 2, 1 6, 10 6, 10 2, 1 2))
+!ok
+
+SELECT ST_Extent(ST_GeomFromText('POINT(5 6)'));
+EXPR$0
+POINT (5 6)
+!ok
+
+SELECT ST_Extent(ST_GeomFromText('GEOMETRYCOLLECTION(
+  POLYGON((0 0, 3 -1, 1.5 2, 0 0)),
+  POLYGON((2 0, 3 3, 4 2, 2 0)),
+  POINT(5 6),
+  LINESTRING(1 1, 1 6))'));
+EXPR$0
+POLYGON ((0 -1, 0 6, 5 6, 5 -1, 0 -1))
+!ok
 
 # ST_ExteriorRing(polygon) Returns the exterior ring of *polygon* as a linear-ring
-# Not implemented
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POLYGON((0 -1, 0 2, 3 2, 3 -1, 0 -1))'));
+EXPR$0
+LINEARRING (0 -1, 0 2, 3 2, 3 -1, 0 -1)
+!ok
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POINT(1 2)'));
+EXPR$0
+null
+!ok
 
 # ST_GeometryN(geomCollection, n) Returns the *n*th geometry of *geomCollection*
-# Not implemented
+
+SELECT ST_ExteriorRing(ST_GeomFromText('POINT(1 2)'));
+EXPR$0
+null
+!ok
 
 # ST_GeometryType(geom) Returns the type of *geom*
 
-SELECT ST_GeometryType(ST_Point(0.0, 0.0));
+SELECT ST_GeometryN(ST_GeomFromText('MULTIPOLYGON(((0 0, 3 -1, 1.5 2, 0 0)), ((1 2, 4 2, 4 6, 1 6, 1 2)))'), 0);
 EXPR$0
-POINT
+POLYGON ((0 0, 3 -1, 1.5 2, 0 0))
 !ok
 
 # ST_GeometryTypeCode(geom) Returns the type code of *geom*
@@ -537,59 +816,308 @@ np, null, null
 p , POINT, 1
 !ok
 
-# ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
-# Not implemented
+# ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
+
+SELECT ST_InteriorRing(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'), 0);
+EXPR$0
+LINEARRING (1 1, 2 1, 2 5, 1 5, 1 1)
+!ok
+
+SELECT ST_InteriorRing(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'), 1);
+EXPR$0
+LINEARRING (8 5, 8 4, 9 4, 9 5, 8 5)
+!ok
+
+# ST_IsClosed(geom) Returns whether *geom* is a closed line-string or multi-line-string
+
+SELECT ST_IsClosed(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsClosed(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2, 2 1)'));
+EXPR$0
+true
+!ok
+
+# ST_IsEmpty(geom) Returns whether *geom* is empty
+
+SELECT ST_IsEmpty(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsEmpty(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsEmpty(ST_GeomFromText('POLYGON EMPTY'));
+EXPR$0
+true
+!ok
+
+# ST_IsRectangle(geom) Returns whether *geom* is a rectangle
+
+SELECT ST_IsRectangle(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsRectangle(ST_GeomFromText('POLYGON((0 0, 10 0, 10 7, 0 5, 0 0))'));
+EXPR$0
+false
+!ok
+
+# ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or multi-line-string
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 2 1)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2, 2 1)'));
+EXPR$0
+false
+!ok
+
+SELECT ST_IsRing(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2)'));
+EXPR$0
+false
+!ok
+
+# ST_IsSimple(geom) Returns whether *geom* is simple
+
+SELECT ST_IsSimple(ST_GeomFromText(
+ 'POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText(
+  'MULTILINESTRING((0 2, 3 2, 3 6, 0 6, 0 2), (5 0, 7 0, 7 1, 5 1, 5 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText(
+ 'GEOMETRYCOLLECTION(
+   MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+   LINESTRING(2 6, 6 2),
+   POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 6)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsSimple(ST_GeomFromText('LINESTRING(2 1, 1 3, 6 6, 5 7, 5 2)'));
+EXPR$0
+false
+!ok
+
+# ST_IsValid(geom) Returns whether *geom* is valid
+
+SELECT ST_IsValid(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 0 5, 0 0))'));
+EXPR$0
+true
+!ok
+
+SELECT ST_IsValid(ST_GeomFromText('POLYGON((0 0, 10 0, 10 5, 6 -2, 0 0))'));
+EXPR$0
+false
+!ok
+
+# ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an array of objects
+# Not implemented
+
+# ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether *geom* is valid, and if not valid, a reason why
+# Not implemented
+
+# ST_NPoints(geom) Returns the number of points in *geom*
+# Not implemented
+
+# ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it is not a geometry-collection)
+
+SELECT ST_NumGeometries(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTILINESTRING(
+  (0 2, 3 2, 3 6, 0 6, 0 1),
+  (5 0, 7 0, 7 1, 5 1, 5 0))'));
+EXPR$0
+2
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('POLYGON(
+  (0 0, 10 0, 10 6, 0 6, 0 0),
+  (1 1, 2 1, 2 5, 1 5, 1 1),
+  (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTIPOLYGON(
+  ((0 0, 10 0, 10 6, 0 6, 0 0)),
+  ((1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((8 5, 8 4, 9 4, 9 5, 8 5)))'));
+EXPR$0
+3
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+3
+!ok
+
+SELECT ST_NumGeometries(ST_GeomFromText('MULTIPOINT(
+  (0 2), (3 2), (3 6), (0 6),
+  (0 1), (5 0), (7 0))'));
+EXPR$0
+7
+!ok
+
+# ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('POLYGON(
+  (0 0, 10 0, 10 6, 0 6, 0 0),
+  (1 1, 2 1, 2 5, 1 5, 1 1),
+  (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+2
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('MULTIPOLYGON(
+  ((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((1 1, 2 1, 2 5, 1 5, 1 1)),
+  ((8 5, 8 4, 9 4, 9 5, 8 5)))'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText('GEOMETRYCOLLECTION(
+  MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+  LINESTRING(2 6, 6 2),
+  POLYGON((1 2, 4 2, 4 6, 1 6, 1 2)))'));
+EXPR$0
+0
+!ok
+
+SELECT ST_NumInteriorRings(ST_GeomFromText(
+  'GEOMETRYCOLLECTION(
+    MULTIPOINT((4 4), (1 1), (1 0), (0 3)),
+    LINESTRING(2 6, 6 2),
+    POLYGON((1 2, 4 2, 4 6, 1 6, 1 2), (2 4, 3 4, 3 5, 2 5, 2 4)))'));
+EXPR$0
+1
+!ok
+
+
+# ST_NumPoints(geom) Returns the number of points in *geom*
+
+SELECT ST_NumPoints(ST_GeomFromText('POINT(2 2)'));
+EXPR$0
+1
+!ok
+
+SELECT ST_NumPoints(ST_GeomFromText('MULTIPOINT(2 2, 4 4)'));
+EXPR$0
+2
+!ok
 
-# ST_IsClosed(geom) Returns whether *geom* is a closed line-string or multi-line-string
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('MULTIPOINT(2 2, 4 4, 4 4)'));
+EXPR$0
+3
+!ok
 
-# ST_IsEmpty(geom) Returns whether *geom* is empty
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('MULTILINESTRING((2 2, 4 4), (3 1, 6 3))'));
+EXPR$0
+4
+!ok
 
-# ST_IsRectangle(geom) Returns whether *geom* is a rectangle
-# Not implemented
+SELECT ST_NumPoints(ST_GeomFromText('POLYGON((0 0, 10 0, 10 6, 0 6, 0 0), (1 1, 2 1, 2 5, 1 5, 1 1), (8 5, 8 4, 9 4, 9 5, 8 5))'));
+EXPR$0
+15
+!ok
 
-# ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or multi-line-string
-# Not implemented
+# ST_PointN(geom, n) Returns the *n*th point of a *geom*
 
-# ST_IsSimple(geom) Returns whether *geom* is simple
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('LINESTRING(1 1, 1 6, 2 2, -1 2))'), 2);
+EXPR$0
+POINT (2 2)
+!ok
 
-# ST_IsValid(geom) Returns whether *geom* is valid
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2))'), 3);
+EXPR$0
+POINT (-1 2)
+!ok
 
-# ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an array of objects
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTIPOINT(1 1, 1 6, 2 2, -1 2)'), -1);
+EXPR$0
+POINT (-1 2)
+!ok
 
-# ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether *geom* is valid, and if not valid, a reason why
-# Not implemented
+SELECT ST_PointN(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2), (0 1, 2 4))'), 4);
+EXPR$0
+POINT (0 1)
+!ok
 
-# ST_NPoints(geom) Returns the number of points in *geom*
-# Not implemented
+# ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
 
-# ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it is not a geometry-collection)
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('POINT(1 5)'));
+EXPR$0
+POINT (1 5)
+!ok
 
-# ST_NumInteriorRing(geom) Alias for `ST_NumInteriorRings`
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('MULTIPOINT((4 4), (1 1), (1 0), (0 3)))'));
+EXPR$0
+POINT (1 1)
+!ok
 
-# ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('LINESTRING(-1 5, 0 10)'));
+EXPR$0
+POINT (0 10)
+!ok
 
-# ST_NumPoints(lineString) Returns the number of points in *lineString*
-# Not implemented
+SELECT ST_PointOnSurface(ST_GeomFromText('POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))'));
+EXPR$0
+POINT (2.5 2.5)
+!ok
 
-# ST_PointN(geom, n) Returns the *n*th point of a *lineString*
-# Not implemented
+# ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
 
-# ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
-# Not implemented
+SELECT ST_SRID(ST_GeomFromText('POINT(15 25)', 2154));
+EXPR$0
+2154
+!ok
 
-# ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
-# Not implemented
+SELECT ST_SRID(ST_GeomFromText('LINESTRING(2 1, 1 3, 5 2, 2 1)', 4326));
+EXPR$0
+4326
+!ok
 
-# ST_StartPoint(lineString) Returns the first coordinate of *lineString*
-# Not implemented
+# ST_StartPoint(geom) Returns the first coordinate of *geom*
+
+SELECT ST_StartPoint(ST_GeomFromText('MULTILINESTRING((1 1, 1 6, 2 2, -1 2))'));
+EXPR$0
+POINT (1 1)
+!ok
 
 # ST_X(geom) Returns the x-value of the first coordinate of *geom*
 
@@ -604,10 +1132,16 @@ EXPR$0
 !ok
 
 # ST_XMax(geom) Returns the maximum x-value of *geom*
-# Not implemented
+SELECT ST_XMax(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+5.0
+!ok
 
 # ST_XMin(geom) Returns the minimum x-value of *geom*
-# Not implemented
+SELECT ST_XMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+1.0
+!ok
 
 # ST_Y(geom) Returns the y-value of the first coordinate of *geom*
 
@@ -622,10 +1156,16 @@ EXPR$0
 !ok
 
 # ST_YMax(geom) Returns the maximum y-value of *geom*
-# Not implemented
+SELECT ST_YMax(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+6.0
+!ok
 
 # ST_YMin(geom) Returns the minimum y-value of *geom*
-# Not implemented
+SELECT ST_YMin(ST_GeomFromText('LINESTRING(1 3 4, 5 6 7)'));
+EXPR$0
+3.0
+!ok
 
 #### Geometry properties (3D)
 
@@ -650,16 +1190,32 @@ EXPR$0
 
 SELECT ST_Z(ST_GeomFromText('POINT (1 2)'));
 EXPR$0
-null
+NaN
 !ok
 
-# Not implemented
-
 # ST_ZMax(geom) Returns the maximum z-value of *geom*
-# Not implemented
+
+SELECT ST_ZMax(ST_GeomFromText('LINESTRING(1 2 3, 4 5 6)'));
+EXPR$0
+6.0
+!ok
+
+SELECT ST_ZMax(ST_GeomFromText('LINESTRING(1 2, 4 5)'));
+EXPR$0
+NaN
+!ok
 
 # ST_ZMin(geom) Returns the minimum z-value of *geom*
-# Not implemented
+
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 2 3, 4 5 6)'));
+EXPR$0
+3.0
+!ok
+
+SELECT ST_ZMin(ST_GeomFromText('LINESTRING(1 2, 4 5)'));
+EXPR$0
+NaN
+!ok
 
 ### Geometry predicates
 
@@ -691,7 +1247,16 @@ POLYGON, true, true, false, false
 !ok
 
 # ST_Covers(geom1, geom2) Returns whether no point in *geom2* is outside *geom1*
-# Not implemented
+
+SELECT ST_Covers(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1), ST_GeomFromText('POINT(0 0)'));
+EXPR$0
+true
+!ok
+
+SELECT ST_Covers(ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1), ST_GeomFromText('POINT(1 1)'));
+EXPR$0
+false
+!ok
 
 # ST_Crosses(geom1, geom2) Returns whether *geom1* crosses *geom2*
 
@@ -782,10 +1347,41 @@ false
 !ok
 
 # ST_Relate(geom1, geom2) Returns the DE-9IM intersection matrix of *geom1* and *geom2*
-# Not implemented
+
+SELECT ST_Relate(ST_GeomFromText('LINESTRING(1 2, 3 4)'),
+                 ST_GeomFromText('LINESTRING(5 6, 7 3)'));
+EXPR$0
+FF1FF0102
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'));
+EXPR$0
+212101212
+!ok
 
 # ST_Relate(geom1, geom2, iMatrix) Returns whether *geom1* and *geom2* are related by the given intersection matrix *iMatrix*
-# Not implemented
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'),
+                 '212101212');
+EXPR$0
+true
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POLYGON((1 1, 4 1, 4 5, 1 5, 1 1))'),
+                 ST_GeomFromText('POLYGON((3 2, 6 2, 6 6, 3 6, 3 2))'),
+                 '112101212');
+EXPR$0
+false
+!ok
+
+SELECT ST_Relate(ST_GeomFromText('POINT(1 2)'),
+                 ST_Buffer(ST_GeomFromText('POINT(1 2)'), 2),
+                 '0F*FFF212');
+EXPR$0
+true
+!ok
 
 # ST_Touches(geom1, geom2) Returns whether *geom1* touches *geom2*
 
@@ -855,23 +1451,75 @@ at org.apache.calcite.runtime.Geometries.todo
 !}
 
 # ST_ConvexHull(geom) Computes the smallest convex polygon that contains all the points in the Geometry
-# Not implemented
+
+SELECT ST_ConvexHull(ST_GeomFromText('GEOMETRYCOLLECTION(
+                        POINT(1 2),
+                        LINESTRING(1 4, 4 7),
+                        POLYGON((3 1, 7 1, 7 6, 3 1)))'));
+EXPR$0
+POLYGON ((3 1, 1 2, 1 4, 4 7, 7 6, 7 1, 3 1))
+!ok
 
 # ST_Difference(geom1, geom2) Computes the difference between two geometries
-# Not implemented
+
+SELECT ST_Difference(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                     ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+POLYGON ((7 2, 7 1, 1 1, 1 6, 3 6, 3 2, 7 2))
+!ok
+
+SELECT ST_Difference(ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'),
+                     ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'));
+EXPR$0
+POLYGON ((3 6, 3 8, 8 8, 8 2, 7 2, 7 6, 3 6))
+!ok
 
 # ST_Intersection(geom1, geom2) Computes the intersection of two geometries
-# Not implemented
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+POLYGON ((3 6, 7 6, 7 2, 3 2, 3 6))
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 4 1, 4 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((4 2, 8 2, 8 8, 4 8, 4 2))'));
+EXPR$0
+LINESTRING (4 2, 4 6)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 4 1, 4 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POLYGON((4 6, 8 6, 8 8, 4 8, 4 6))'));
+EXPR$0
+POINT (4 6)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('LINESTRING(2 2, 6 6)'),
+                       ST_GeomFromText('LINESTRING(2 8, 8 2)'));
+EXPR$0
+POINT (5 5)
+!ok
+
+SELECT ST_Intersection(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                       ST_GeomFromText('POINT(3 5)'));
+EXPR$0
+POINT (3 5)
+!ok
+
 
 # ST_SymDifference(geom1, geom2) Computes the symmetric difference between two geometries
-# Not implemented
+
+SELECT ST_SymDifference(ST_GeomFromText('POLYGON((1 1, 7 1, 7 6, 1 6, 1 1))'),
+                        ST_GeomFromText('POLYGON((3 2, 8 2, 8 8, 3 8, 3 2))'));
+EXPR$0
+MULTIPOLYGON (((7 2, 7 1, 1 1, 1 6, 3 6, 3 2, 7 2)), ((7 2, 7 6, 3 6, 3 8, 8 8, 8 2, 7 2)))
+!ok
 
 # ST_Union(geom1, geom2) Computes the union of two or more geometries
 
 # NOTE: PostGIS altered the order: it returned MULTIPOINT(-2 3,1 2)
 SELECT ST_AsText(ST_Union(ST_GeomFromText('POINT(1 2)'),
     ST_GeomFromText('POINT(-2 3)')));
-
 EXPR$0
 MULTIPOINT ((-2 3), (1 2))
 !ok
@@ -915,13 +1563,63 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 #### Affine transformation functions (3D and 2D)
 
 # ST_Rotate(geom, angle [, origin | x, y]) Rotates a *geom* counter-clockwise by *angle* (in radians) about *origin* (or the point (*x*, *y*))
-# Not implemented
 
-# ST_Scale(geom, xFactor, yFactor [, zFactor ]) Scales *geom* by multiplying the ordinates by the indicated scale factors
-# Not implemented
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi());
+EXPR$0
+LINESTRING (-1.0000000000000004 -3, -1.0000000000000002 -0.9999999999999999, -2 -0.9999999999999998)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi() / 3);
+EXPR$0
+LINESTRING (-2.098076211353316 2.3660254037844393, -0.3660254037844385 1.3660254037844388, 0.1339745962155616 2.232050807568877)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), -pi() / 2, ST_PointFromText('POINT(2 1)'));
+EXPR$0
+LINESTRING (4 2, 1.9999999999999998 2, 2 1)
+!ok
+
+SELECT ST_Rotate(ST_GeomFromText('LINESTRING(1 3, 1 1, 2 1)'), pi() / 2, 1.0, 1.0);
+EXPR$0
+LINESTRING (-1 1.0000000000000002, 1 0.9999999999999999, 1 2)
+!ok
+
+# ST_Scale(geom, xFactor, yFactor) Scales *geom* by multiplying the ordinates by the indicated scale factors
+
+SELECT ST_Scale(ST_GeomFromText('LINESTRING(1 2, 4 5)'), 0.5, 0.75);
+EXPR$0
+LINESTRING (0.5 1.5, 2 3.75)
+!ok
 
 # ST_Translate(geom, x, y, [, z]) Translates *geom*
-# Not implemented
+
+SELECT ST_Translate(ST_GeomFromText('POINT(1 2)'), 10, 20);
+EXPR$0
+POINT (11 22)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('LINESTRING(0 0, 1 0)'), 1, 2);
+EXPR$0
+LINESTRING (1 2, 2 2)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('LINESTRING(-71.01 42.37, -71.11 42.38)'), 1, 0.5);
+EXPR$0
+LINESTRING (-70.01 42.87, -70.11 42.88)
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('MULTIPOINT((0 1), (2 2), (1 3))'), 1, 0);
+EXPR$0
+MULTIPOINT ((1 1), (3 2), (2 3))
+!ok
+
+SELECT ST_Translate(ST_GeomFromText('GEOMETRYCOLLECTION(
+  POLYGON((0 0, 3 5, 6  6, 0 7, 0 0)),
+  MULTIPOINT((0 1), (2 2), (1 3)))'), -1, 1);
+EXPR$0
+GEOMETRYCOLLECTION (POLYGON ((-1 1, 2 6, 5 7, -1 8, -1 1)), MULTIPOINT ((-1 2), (1 3), (0 4)))
+!ok
+
 
 #### Geometry editing functions (2D)
 
@@ -1030,16 +1728,36 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 # Not implemented
 
 # ST_LineMerge(geom) Merges a collection of linear components to form a line-string of maximal length
-# Not implemented
+
+SELECT ST_LineMerge(ST_GeomFromText('LINESTRING (1 1, 1 4)'));
+EXPR$0
+MULTILINESTRING ((1 1, 1 4))
+!ok
+
+SELECT ST_LineMerge(ST_GeomFromText('MULTILINESTRING ((1 1, 1 4), (1 4, 5 4), (5 4, 5 1), (3 3, 3 4))'));
+EXPR$0
+MULTILINESTRING ((1 1, 1 4, 5 4, 5 1), (3 3, 3 4))
+!ok
 
 # ST_MakeValid(geom [, preserveGeomDim [, preserveDuplicateCoord [, preserveCoordDim]]]) Makes *geom* valid
-# Not implemented
+
+SELECT ST_MakeValid(ST_GeomFromText('LINESTRING(0 0, 0 0)'));
+EXPR$0
+LINESTRING EMPTY
+!ok
 
 # ST_Polygonize(geom) Creates a multi-polygon from edges of *geom*
-# Not implemented
+
+SELECT ST_Polygonize(ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2, 1 2)'));
+EXPR$0
+POLYGON ((1 2, 2 4, 4 4, 5 2, 1 2))
+!ok
 
 # ST_PrecisionReducer(geom, n) Reduces *geom*'s precision to *n* decimal places
-# Not implemented
+SELECT ST_PrecisionReducer(ST_GeomFromText('MULTIPOINT((190.1239999997 300), (10 11.1233))'), 3);
+EXPR$0
+MULTIPOINT ((190.124 300), (10 11.123))
+!ok
 
 # ST_RingSideBuffer(geom, bufferSize, bufferCount [, endCapStyle [, doDifference]]) Computes a ring buffer on one side
 # Not implemented
@@ -1048,13 +1766,85 @@ MULTILINESTRING((3 4,4 5),(1 2,3 4))
 # Not implemented
 
 # ST_Simplify(geom, distance) Simplifies *geom* using the Douglas-Peuker algorithm with a *distance* tolerance
-# Not implemented
 
-# ST_SimplifyPreserveTopology(geom) Simplifies *geom*, preserving its topology
-# Not implemented
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 2, 4 1, 3 0, 2 0, 2 1))'), 0.5);
+EXPR$0
+POLYGON ((2 1, 1 2, 3 3, 4 1, 3 0, 2 0, 2 1))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 2, 4 1, 3 0, 2 0, 2 1))'), 1);
+EXPR$0
+POLYGON ((2 1, 1 2, 3 3, 4 1, 2 1))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('POLYGON((2 1, 1 2, 2 2, 2 3, 3 3, 3 2, 4 2, 4 1, 3 0, 2 0, 2 1))'), 2);
+EXPR$0
+POLYGON EMPTY
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('MULTIPOINT((190 300), (10 11))'), 4);
+EXPR$0
+MULTIPOINT ((190 300), (10 11))
+!ok
+
+SELECT ST_Simplify(ST_GeomFromText('LINESTRING(250 250, 280 290, 300 230, 340 300, 360 260, 440 310, 470 360, 604 286)'), 40);
+EXPR$0
+LINESTRING (250 250, 280 290, 300 230, 470 360, 604 286)
+!ok
+
+# ST_SimplifyPreserveTopology(geom, distance) Simplifies *geom*, preserving its topology
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 22, 35 26, 24 39, 8 25))'), 10);
+EXPR$0
+POLYGON ((8 25, 28 22, 15 11, 33 3, 56 30, 47 44, 35 36, 43 19, 24 39, 8 25))
+!ok
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 22, 35 26, 24 39, 8 25))'), 20);
+EXPR$0
+POLYGON ((8 25, 33 3, 56 30, 47 44, 43 19, 8 25))
+!ok
+
+SELECT ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((8 25, 28 22, 28 20, 15 11, 33 3, 56 30, 46 33,  46 34, 47 44, 35 36, 45 33, 43 19, 29 21, 29 22, 35 26, 24 39, 8 25))'), 30);
+EXPR$0
+POLYGON ((8 25, 33 3, 56 30, 47 44, 8 25))
+!ok
 
 # ST_Snap(geom1, geom2, tolerance) Snaps *geom1* and *geom2* together
-# Not implemented
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 1);
+EXPR$0
+LINESTRING (1 2, 2 4, 4 4, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 2);
+EXPR$0
+LINESTRING (1 2, 2 1, 2 4, 4 4, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('LINESTRING(1 2, 2 4, 4 4, 5 2)'),
+  ST_GeomFromText('LINESTRING(5 2, 2 1, 1 2)'), 3);
+EXPR$0
+LINESTRING (1 2, 1 2, 2 1, 5 2, 5 2)
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('POLYGON((1 1, 1 7, 7 7, 7 1, 1 1))'),
+  ST_GeomFromText('POLYGON((3 3, 1 2, 0 2, 0 1, -2 1, -1 7, 3 6, 4 8, 7 8, 6 6, 9 6, 8 1, 8 1, 3 3))'), 2);
+EXPR$0
+POLYGON ((0 1, 1 2, 0 2, -1 7, 1 7, 3 6, 6 6, 8 1, 0 1))
+!ok
+
+SELECT ST_Snap(
+  ST_GeomFromText('POLYGON((3 3, 1 2, 0 2, 0 1, -2 1, -1 7, 3 6, 4 8, 7 8, 6 6, 9 6, 8 1, 8 1, 3 3))'),
+  ST_GeomFromText('POLYGON((1 1, 1 7, 7 7, 7 1, 1 1))'), 2);
+EXPR$0
+POLYGON ((3 3, 1 1, 1 1, 1 1, -2 1, -1 7, 1 7, 3 6, 4 8, 7 7, 7 7, 9 6, 7 1, 7 1, 3 3))
+!ok
 
 # ST_Split(geom1, geom2 [, tolerance]) Splits *geom1* by *geom2* using *tolerance* (default 1E-6) to determine where the point splits the line
 # Not implemented
@@ -1263,4 +2053,5 @@ EXPR$0
 33204
 !ok
 
+
 # End spatial.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 54befa1199..995a4ff3f7 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1,3 +1,4 @@
+
 ---
 layout: docs
 title: SQL language
@@ -1181,11 +1182,11 @@ or binary strings encoded as
 Where you would use a literal, apply the `ST_GeomFromText` function,
 for example `ST_GeomFromText('POINT (30 10)')`.
 
-| Data type   | Type code | Examples in WKT
-|:----------- |:--------- |:---------------------
+| Data type          | Type code | Examples in WKT
+|:-------------------|:--------- |:---------------------
 | GEOMETRY           |  0 | generalization of Point, Curve, Surface, GEOMETRYCOLLECTION
 | POINT              |  1 | <code>ST_GeomFromText(&#8203;'POINT (30 10)')</code> is a point in 2D space; <code>ST_GeomFromText(&#8203;'POINT Z(30 10 2)')</code> is point in 3D space
-| CURVE            | 13 | generalization of LINESTRING
+| CURVE              | 13 | generalization of LINESTRING
 | LINESTRING         |  2 | <code>ST_GeomFromText(&#8203;'LINESTRING (30 10, 10 30, 40 40)')</code>
 | SURFACE            | 14 | generalization of Polygon, PolyhedralSurface
 | POLYGON            |  3 | <code>ST_GeomFromText(&#8203;'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))')</code> is a pentagon; <code>ST_GeomFromText(&#8203;'POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))')</code> is a pentagon with a quadrilateral hole
@@ -2159,59 +2160,67 @@ implements the OpenGIS Simple Features Implementation Specification for SQL,
 
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
+| p | ST_AsBinary(geom) | Synonym for `ST_AsWKB`
+| p | ST_AsEWKB(geom) | Synonym for `ST_AsWKB`
+| p | ST_AsEWKT(geom) | Converts GEOMETRY → EWKT
+| p | ST_AsGeoJSON(geom) | Converts GEOMETRY → GeoJSON
+| p | ST_AsGML(geom) | Converts GEOMETRY → GML
 | p | ST_AsText(geom) | Synonym for `ST_AsWKT`
-| o | ST_AsWKT(geom) | Converts *geom* → WKT
-| o | ST_GeomFromText(wkt [, srid ]) | Returns a specified GEOMETRY value from WKT representation
+| o | ST_AsWKB(geom) | Converts GEOMETRY → WKB
+| o | ST_AsWKT(geom) | Converts GEOMETRY → WKT
+| o | ST_Force2D(geom) | 3D GEOMETRY → 2D GEOMETRY
+| o | ST_GeomFromEWKB(wkb [, srid ]) | Synonym for `ST_GeomFromWKB`
+| o | ST_GeomFromEWKT(wkb [, srid ]) | Converts EWKT → GEOMETRY
+| o | ST_GeomFromGeoJSON(json) | Converts GeoJSON → GEOMETRY
+| o | ST_GeomFromGML(wkb [, srid ]) | Converts GML → GEOMETRY
+| o | ST_GeomFromText(wkt [, srid ]) | Synonym for `ST_GeomFromWKT`
+| o | ST_GeomFromWKB(wkb [, srid ]) | Converts WKB → GEOMETRY
+| o | ST_GeomFromWKT(wkb [, srid ]) | Converts WKT → GEOMETRY
 | o | ST_LineFromText(wkt [, srid ]) | Converts WKT → LINESTRING
+| o | ST_LineFromWKB(wkt [, srid ]) | Converts WKT → LINESTRING
 | o | ST_MLineFromText(wkt [, srid ]) | Converts WKT → MULTILINESTRING
 | o | ST_MPointFromText(wkt [, srid ]) | Converts WKT → MULTIPOINT
 | o | ST_MPolyFromText(wkt [, srid ]) Converts WKT → MULTIPOLYGON
 | o | ST_PointFromText(wkt [, srid ]) | Converts WKT → POINT
+| o | ST_PointFromWKB(wkt [, srid ]) | Converts WKB → POINT
 | o | ST_PolyFromText(wkt [, srid ]) | Converts WKT → POLYGON
+| o | ST_PolyFromWKB(wkt [, srid ]) | Converts WKB → POLYGON
+| h | ST_ToMultiPoint(geom) | Converts the coordinates of *geom* (which may be a GEOMETRYCOLLECTION) into a MULTIPOINT
+| h | ST_ToMultiLine(geom) | Converts the coordinates of *geom* (which may be a GEOMETRYCOLLECTION) into a MULTILINESTRING
+| h | ST_ToMultiSegments(geom) | Converts *geom* (which may be a GEOMETRYCOLLECTION) into a set of distinct segments stored in a MULTILINESTRING
 
 Not implemented:
 
-* ST_AsBinary(geom) GEOMETRY → WKB
-* ST_AsGML(geom) GEOMETRY → GML
-* ST_Force2D(geom) 3D GEOMETRY → 2D GEOMETRY
-* ST_GeomFromGML(gml [, srid ]) GML → GEOMETRY
-* ST_GeomFromWKB(wkb [, srid ]) WKB → GEOMETRY
 * ST_GoogleMapLink(geom [, layerType [, zoom ]]) GEOMETRY → Google map link
-* ST_LineFromWKB(wkb [, srid ]) WKB → LINESTRING
 * ST_OSMMapLink(geom [, marker ]) GEOMETRY → OSM map link
-* ST_PointFromWKB(wkb [, srid ]) WKB → POINT
-* ST_PolyFromWKB(wkb [, srid ]) WKB → POLYGON
-* ST_ToMultiLine(geom) Converts the coordinates of *geom* (which may be a GEOMETRYCOLLECTION) into a MULTILINESTRING
-* ST_ToMultiPoint(geom)) Converts the coordinates of *geom* (which may be a GEOMETRYCOLLECTION) into a MULTIPOINT
-* ST_ToMultiSegments(geom) Converts *geom* (which may be a GEOMETRYCOLLECTION) into a set of distinct segments stored in a MULTILINESTRING
 
 #### Geometry conversion functions (3D)
 
-Not implemented:
-
-* ST_Force3D(geom) 2D GEOMETRY → 3D GEOMETRY
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_Force3D(geom) | 2D GEOMETRY → 3D GEOMETRY
 
 #### Geometry creation functions (2D)
 
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
+| h | ST_BoundingCircle(geom) | Returns the minimum bounding circle of *geom*
+| h | ST_Expand(geom, distance) | Expands *geom*'s envelope
+| h | ST_Expand(geom, deltaX, deltaY) | Expands *geom*'s envelope
+| h | ST_MakeEllipse(point, width, height) | Constructs an ellipse
 | p | ST_MakeEnvelope(xMin, yMin, xMax, yMax  [, srid ]) | Creates a rectangular POLYGON
 | h | ST_MakeGrid(geom, deltaX, deltaY) | Calculates a regular grid of POLYGONs based on *geom*
 | h | ST_MakeGridPoints(geom, deltaX, deltaY) | Calculates a regular grid of points based on *geom*
 | o | ST_MakeLine(point1 [, point ]*) | Creates a line-string from the given POINTs (or MULTIPOINTs)
 | p | ST_MakePoint(x, y [, z ]) | Synonym for `ST_Point`
+| p | ST_MakePolygon(lineString [, hole ]*)| Creates a POLYGON from *lineString* with the given holes (which are required to be closed LINESTRINGs)
+| h | ST_MinimumDiameter(geom) | Returns the minimum diameter of *geom*
+| h | ST_MinimumRectangle(geom) | Returns the minimum rectangle enclosing *geom*
+| h | ST_OctogonalEnvelope(geom) | Returns the octogonal envelope of *geom*
 | o | ST_Point(x, y [, z ]) | Constructs a point from two or three coordinates
 
 Not implemented:
 
-* ST_BoundingCircle(geom) Returns the minimum bounding circle of *geom*
-* ST_Expand(geom, distance) Expands *geom*'s envelope
-* ST_Expand(geom, deltaX, deltaY) Expands *geom*'s envelope
-* ST_MakeEllipse(point, width, height) Constructs an ellipse
-* ST_MakePolygon(lineString [, hole ]*) Creates a POLYGON from *lineString* with the given holes (which are required to be closed LINESTRINGs)
-* ST_MinimumDiameter(geom) Returns the minimum diameter of *geom*
-* ST_MinimumRectangle(geom) Returns the minimum rectangle enclosing *geom*
-* ST_OctogonalEnvelope(geom) Returns the octogonal envelope of *geom*
 * ST_RingBuffer(geom, distance, bufferCount [, endCapStyle [, doDifference]]) Returns a MULTIPOLYGON of buffers centered at *geom* and of increasing buffer size
 
 ### Geometry creation functions (3D)
@@ -2227,47 +2236,47 @@ Not implemented:
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
 | o | ST_Boundary(geom [, srid ]) | Returns the boundary of *geom*
+| o | ST_Centroid(geom) | Returns the centroid of *geom*
+| o | ST_CoordDim(geom) | Returns the dimension of the coordinates of *geom*
+| o | ST_Dimension(geom) | Returns the dimension of *geom*
 | o | ST_Distance(geom1, geom2) | Returns the distance between *geom1* and *geom2*
+| h | ST_ExteriorRing(geom) | Returns the exterior ring of *geom*, or null if *geom* is not a polygon
 | o | ST_GeometryType(geom) | Returns the type of *geom*
 | o | ST_GeometryTypeCode(geom) | Returns the OGC SFS type code of *geom*
+| p | ST_EndPoint(lineString) | Returns the last coordinate of *geom*
 | o | ST_Envelope(geom [, srid ]) | Returns the envelope of *geom* (which may be a GEOMETRYCOLLECTION) as a GEOMETRY
+| o | ST_Extent(geom) | Returns the minimum bounding box of *geom* (which may be a GEOMETRYCOLLECTION)
+| h | ST_GeometryN(geomCollection, n) | Returns the *n*th GEOMETRY of *geomCollection*
+| h | ST_InteriorRingN(geom) | Returns the nth interior ring of *geom*, or null if *geom* is not a polygon
+| h | ST_IsClosed(geom) | Returns whether *geom* is a closed LINESTRING or MULTILINESTRING
+| o | ST_IsEmpty(geom) | Returns whether *geom* is empty
+| o | ST_IsRectangle(geom) | Returns whether *geom* is a rectangle
+| h | ST_IsRing(geom) | Returns whether *geom* is a closed and simple line-string or MULTILINESTRING
+| o | ST_IsSimple(geom) | Returns whether *geom* is simple
+| o | ST_IsValid(geom) | Returns whether *geom* is valid
+| h | ST_NPoints(geom)  | Returns the number of points in *geom*
+| h | ST_NumGeometries(geom) | Returns the number of geometries in *geom* (1 if it is not a GEOMETRYCOLLECTION)
+| h | ST_NumInteriorRing(geom) | Synonym for `ST_NumInteriorRings`
+| h | ST_NumInteriorRings(geom) | Returns the number of interior rings of *geom*
+| h | ST_NumPoints(geom) | Returns the number of points in *geom*
+| p | ST_PointN(geom, n) | Returns the *n*th point of a *geom*
+| p | ST_PointOnSurface(geom) | Returns an interior or boundary point of *geom*
+| o | ST_SRID(geom) | Returns SRID value of *geom* or 0 if it does not have one
+| p | ST_StartPoint(geom) | Returns the first point of *geom*
 | o | ST_X(geom) | Returns the x-value of the first coordinate of *geom*
+| o | ST_XMax(geom) | Returns the maximum x-value of *geom*
+| o | ST_XMin(geom) | Returns the minimum x-value of *geom*
 | o | ST_Y(geom) | Returns the y-value of the first coordinate of *geom*
+| o | ST_YMax(geom) | Returns the maximum y-value of *geom*
+| o | ST_YMin(geom) | Returns the minimum y-value of *geom*
 
 Not implemented:
 
-* ST_Centroid(geom) Returns the centroid of *geom* (which may be a GEOMETRYCOLLECTION)
 * ST_CompactnessRatio(polygon) Returns the square root of *polygon*'s area divided by the area of the circle with circumference equal to its perimeter
-* ST_CoordDim(geom) Returns the dimension of the coordinates of *geom*
-* ST_Dimension(geom) Returns the dimension of *geom*
-* ST_EndPoint(lineString) Returns the last coordinate of *lineString*
-* ST_Envelope(geom [, srid ]) Returns the envelope of *geom* (which may be a GEOMETRYCOLLECTION) as a GEOMETRY
 * ST_Explode(query [, fieldName]) Explodes the GEOMETRYCOLLECTIONs in the *fieldName* column of a query into multiple geometries
-* ST_Extent(geom) Returns the minimum bounding box of *geom* (which may be a GEOMETRYCOLLECTION)
-* ST_ExteriorRing(polygon) Returns the exterior ring of *polygon* as a linear-ring
-* ST_GeometryN(geomCollection, n) Returns the *n*th GEOMETRY of *geomCollection*
-* ST_InteriorRingN(polygon, n) Returns the *n*th interior ring of *polygon*
-* ST_IsClosed(geom) Returns whether *geom* is a closed LINESTRING or MULTILINESTRING
-* ST_IsEmpty(geom) Returns whether *geom* is empty
-* ST_IsRectangle(geom) Returns whether *geom* is a rectangle
-* ST_IsRing(geom) Returns whether *geom* is a closed and simple line-string or MULTILINESTRING
-* ST_IsSimple(geom) Returns whether *geom* is simple
-* ST_IsValid(geom) Returns whether *geom* is valid
 * ST_IsValidDetail(geom [, selfTouchValid ]) Returns a valid detail as an array of objects
 * ST_IsValidReason(geom [, selfTouchValid ]) Returns text stating whether *geom* is valid, and if not valid, a reason why
-* ST_NPoints(geom) Returns the number of points in *geom*
-* ST_NumGeometries(geom) Returns the number of geometries in *geom* (1 if it is not a GEOMETRYCOLLECTION)
-* ST_NumInteriorRing(geom) Synonym for `ST_NumInteriorRings`
-* ST_NumInteriorRings(geom) Returns the number of interior rings of *geom*
-* ST_NumPoints(lineString) Returns the number of points in *lineString*
-* ST_PointN(geom, n) Returns the *n*th point of a *lineString*
-* ST_PointOnSurface(geom) Returns an interior or boundary point of *geom*
-* ST_SRID(geom) Returns SRID value of *geom* or 0 if it does not have one
-* ST_StartPoint(lineString) Returns the first coordinate of *lineString*
-* ST_XMax(geom) Returns the maximum x-value of *geom*
-* ST_XMin(geom) Returns the minimum x-value of *geom*
-* ST_YMax(geom) Returns the maximum y-value of *geom*
-* ST_YMin(geom) Returns the minimum y-value of *geom*
+
 
 #### Geometry properties (3D)
 
@@ -2275,11 +2284,8 @@ Not implemented:
 |:- |:-------------------- |:-----------
 | p | ST_Is3D(s) | Returns whether *geom* has at least one z-coordinate
 | o | ST_Z(geom) | Returns the z-value of the first coordinate of *geom*
-
-Not implemented:
-
-* ST_ZMax(geom) Returns the maximum z-value of *geom*
-* ST_ZMin(geom) Returns the minimum z-value of *geom*
+| o | ST_ZMax(geom) | Returns the maximum z-value of *geom*
+| o | ST_ZMin(geom) | Returns the minimum z-value of *geom*
 
 ### Geometry predicates
 
@@ -2287,6 +2293,7 @@ Not implemented:
 |:- |:-------------------- |:-----------
 | o | ST_Contains(geom1, geom2) | Returns whether *geom1* contains *geom2*
 | p | ST_ContainsProperly(geom1, geom2) | Returns whether *geom1* contains *geom2* but does not intersect its boundary
+| p | ST_Covers(geom1, geom2) | Returns whether no point in *geom2* is outside *geom1*
 | o | ST_Crosses(geom1, geom2) | Returns whether *geom1* crosses *geom2*
 | o | ST_Disjoint(geom1, geom2) | Returns whether *geom1* and *geom2* are disjoint
 | p | ST_DWithin(geom1, geom2, distance) | Returns whether *geom1* and *geom* are within *distance* of one another
@@ -2294,15 +2301,14 @@ Not implemented:
 | o | ST_Equals(geom1, geom2) | Returns whether *geom1* equals *geom2*
 | o | ST_Intersects(geom1, geom2) | Returns whether *geom1* intersects *geom2*
 | o | ST_Overlaps(geom1, geom2) | Returns whether *geom1* overlaps *geom2*
+| o | ST_Relate(geom1, geom2) | Returns the DE-9IM intersection matrix of *geom1* and *geom2*
+| o | ST_Relate(geom1, geom2, iMatrix) | Returns whether *geom1* and *geom2* are related by the given intersection matrix *iMatrix*
 | o | ST_Touches(geom1, geom2) | Returns whether *geom1* touches *geom2*
 | o | ST_Within(geom1, geom2) | Returns whether *geom1* is within *geom2*
 
 Not implemented:
 
-* ST_Covers(geom1, geom2) Returns whether no point in *geom2* is outside *geom1*
 * ST_OrderingEquals(geom1, geom2) Returns whether *geom1* equals *geom2* and their coordinates and component Geometries are listed in the same order
-* ST_Relate(geom1, geom2) Returns the DE-9IM intersection matrix of *geom1* and *geom2*
-* ST_Relate(geom1, geom2, iMatrix) Returns whether *geom1* and *geom2* are related by the given intersection matrix *iMatrix*
 
 #### Geometry operators (2D)
 
@@ -2311,23 +2317,27 @@ The following functions combine 2D geometries.
 | C | Operator syntax      | Description
 |:- |:-------------------- |:-----------
 | o | ST_Buffer(geom, distance [, quadSegs \| style ]) | Computes a buffer around *geom*
+| o | ST_ConvexHull(geom) | Computes the smallest convex polygon that contains all the points in *geom*
+| o | ST_Difference(geom1, geom2) | Computes the difference between two geometries
+| o | ST_SymDifference(geom1, geom2) | Computes the symmetric difference between two geometries
+| o | ST_Intersection(geom1, geom2) | Computes the intersection of *geom1* and *geom2*
 | o | ST_Union(geom1, geom2) | Computes the union of *geom1* and *geom2*
 | o | ST_Union(geomCollection) | Computes the union of the geometries in *geomCollection*
 
 See also: the `ST_Union` aggregate function.
 
-Not implemented:
+#### Affine transformation functions (3D and 2D)
 
-* ST_ConvexHull(geom) Computes the smallest convex polygon that contains all the points in *geom*
-* ST_Difference(geom1, geom2) Computes the difference between two geometries
-* ST_Intersection(geom1, geom2) Computes the intersection of two geometries
-* ST_SymDifference(geom1, geom2) Computes the symmetric difference between two geometries
+The following functions transform 2D geometries.
 
-#### Affine transformation functions (3D and 2D)
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_Rotate(geom, angle [, origin \| x, y]) | Rotates a *geom* counter-clockwise by *angle* (in radians) about *origin* (or the point (*x*, *y*))
+| o | ST_Scale(geom, xFactor, yFactor) | Scales *geom* by multiplying the ordinates by the indicated scale factors
+| o | ST_Translate(geom, x, y) | Translates *geom* by the vector (x, y)
 
 Not implemented:
 
-* ST_Rotate(geom, angle [, origin \| x, y]) Rotates a *geom* counter-clockwise by *angle* (in radians) about *origin* (or the point (*x*, *y*))
 * ST_Scale(geom, xFactor, yFactor [, zFactor ]) Scales *geom* by multiplying the ordinates by the indicated scale factors
 * ST_Translate(geom, x, y, [, z]) Translates *geom*
 
@@ -2390,18 +2400,23 @@ Not implemented:
 
 The following functions process geometries.
 
+| C | Operator syntax      | Description
+|:- |:-------------------- |:-----------
+| o | ST_LineMerge(geom)  | Merges a collection of linear components to form a line-string of maximal length
+| o | ST_MakeValid(geom)  | Makes a valid geometry of a given invalid geometry
+| o | ST_Polygonize(geom)  | Creates a MULTIPOLYGON from edges of *geom*
+| o | ST_PrecisionReducer(geom, n) | Reduces *geom*'s precision to *n* decimal places
+| o | ST_Simplify(geom, distance)  | Simplifies *geom* using the [Douglas-Peuker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) with a *distance* tolerance
+| o | ST_SimplifyPreserveTopology(geom, distance) | Simplifies *geom*, preserving its topology
+| o | ST_Snap(geom1, geom2, tolerance) | Snaps *geom1* and *geom2* together
+
 Not implemented:
 
 * ST_LineIntersector(geom1, geom2) Splits *geom1* (a line-string) with *geom2*
 * ST_LineMerge(geom) Merges a collection of linear components to form a line-string of maximal length
 * ST_MakeValid(geom [, preserveGeomDim [, preserveDuplicateCoord [, preserveCoordDim]]]) Makes *geom* valid
-* ST_Polygonize(geom) Creates a MULTIPOLYGON from edges of *geom*
-* ST_PrecisionReducer(geom, n) Reduces *geom*'s precision to *n* decimal places
 * ST_RingSideBuffer(geom, distance, bufferCount [, endCapStyle [, doDifference]]) Computes a ring buffer on one side
 * ST_SideBuffer(geom, distance [, bufferStyle ]) Compute a single buffer on one side
-* ST_Simplify(geom, distance) Simplifies *geom* using the [Douglas-Peuker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) with a *distance* tolerance
-* ST_SimplifyPreserveTopology(geom) Simplifies *geom*, preserving its topology
-* ST_Snap(geom1, geom2, tolerance) Snaps *geom1* and *geom2* together
 * ST_Split(geom1, geom2 [, tolerance]) Splits *geom1* by *geom2* using *tolerance* (default 1E-6) to determine where the point splits the line
 
 #### Geometry projection functions
diff --git a/site/_docs/spatial.md b/site/_docs/spatial.md
index a74e4ce758..d0b2ec85d4 100644
--- a/site/_docs/spatial.md
+++ b/site/_docs/spatial.md
@@ -151,7 +151,7 @@ But for safety, Calcite applies the original predicate, to remove false positive
 ## Acknowledgements
 
 Calcite's OpenGIS implementation uses the
-[Esri geometry API](https://github.com/Esri/geometry-api-java). Thanks for the
+[JTS Topology Suite](https://github.com/locationtech/jts). Thanks for the
 help we received from their community.
 
 While developing this feature, we made extensive use of the


[calcite] 02/04: [CALCITE-5270] JDBC adapter should not generate 'FILTER (WHERE)' in Firebolt dialect

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 1167b125747cd4b157f377ae0ba64f833890a138
Author: TJ Banghart <tj...@google.com>
AuthorDate: Wed Sep 7 00:13:39 2022 +0000

    [CALCITE-5270] JDBC adapter should not generate 'FILTER (WHERE)' in Firebolt dialect
    
    Close apache/calcite#2896
---
 .../calcite/sql/dialect/FireboltSqlDialect.java    |  4 +++
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 34 +++++++++++++---------
 2 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
index 71ca131875..25a4859fd8 100644
--- a/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
@@ -158,6 +158,10 @@ public class FireboltSqlDialect extends SqlDialect {
         SqlParserPos.ZERO);
   }
 
+  @Override public boolean supportsAggregateFunctionFilter() {
+    return false;
+  }
+
   @Override public boolean supportsFunction(SqlOperator operator,
       RelDataType type, final List<RelDataType> paramTypes) {
     switch (operator.kind) {
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 2ea4231930..a77987d52f 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -266,36 +266,42 @@ class RelToSqlConverterTest {
     sql(query).ok(expected);
   }
 
-  @Test void testAggregateFilterWhereToSqlFromProductTable() {
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-4321">[CALCITE-4321]
+   * JDBC adapter omits FILTER (WHERE ...) expressions when generating SQL</a>
+   * and
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-5270">[CALCITE-5270]
+   * JDBC adapter should not generate FILTER (WHERE) in Firebolt dialect</a>. */
+  @Test void testAggregateFilterWhere() {
     String query = "select\n"
         + "  sum(\"shelf_width\") filter (where \"net_weight\" > 0),\n"
         + "  sum(\"shelf_width\")\n"
         + "from \"foodmart\".\"product\"\n"
         + "where \"product_id\" > 0\n"
         + "group by \"product_id\"";
-    final String expected = "SELECT"
+    final String expectedDefault = "SELECT"
         + " SUM(\"shelf_width\") FILTER (WHERE \"net_weight\" > 0 IS TRUE),"
         + " SUM(\"shelf_width\")\n"
         + "FROM \"foodmart\".\"product\"\n"
         + "WHERE \"product_id\" > 0\n"
         + "GROUP BY \"product_id\"";
-    sql(query).ok(expected);
-  }
-
-  @Test void testAggregateFilterWhereToBigQuerySqlFromProductTable() {
-    String query = "select\n"
-        + "  sum(\"shelf_width\") filter (where \"net_weight\" > 0),\n"
-        + "  sum(\"shelf_width\")\n"
-        + "from \"foodmart\".\"product\"\n"
-        + "where \"product_id\" > 0\n"
-        + "group by \"product_id\"";
-    final String expected = "SELECT SUM(CASE WHEN net_weight > 0 IS TRUE"
+    final String expectedBigQuery = "SELECT"
+        + " SUM(CASE WHEN net_weight > 0 IS TRUE"
         + " THEN shelf_width ELSE NULL END), "
         + "SUM(shelf_width)\n"
         + "FROM foodmart.product\n"
         + "WHERE product_id > 0\n"
         + "GROUP BY product_id";
-    sql(query).withBigQuery().ok(expected);
+    final String expectedFirebolt = "SELECT"
+        + " SUM(CASE WHEN \"net_weight\" > 0 IS TRUE"
+        + " THEN \"shelf_width\" ELSE NULL END), "
+        + "SUM(\"shelf_width\")\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_id\" > 0\n"
+        + "GROUP BY \"product_id\"";
+    sql(query).ok(expectedDefault)
+        .withBigQuery().ok(expectedBigQuery)
+        .withFirebolt().ok(expectedFirebolt);
   }
 
   @Test void testPivotToSqlFromProductTable() {