Skip to content

Name Calcite's own spatial classes on the model class allowlist, so fun=spatial works out of the box #154

Description

@wasabii

Calcite's spatial operator table cannot initialize under Calcite 1.43's own model class allowlist, so every consumer of this package has to name two of Calcite's own classes before a spatial query will run. This package is the right place to name them once.

What happens without it

SELECT ST_Point(1, 2)      -- fun=spatial, conformance=LENIENT
com.google.common.util.concurrent.UncheckedExecutionException:
  java.lang.SecurityException: Class 'org.apache.calcite.runtime.SpatialTypeFunctions' rejected by the allowlist.

Validation succeeds — ST_POINT resolves — and it fails at execution, which reads like a runtime fault rather than a configuration one.

Why

1.43 added ClassNameFilter and calcite.model.classes.allowed, so a model JSON cannot name an arbitrary class. The allowlist defaults to empty and check throws when nothing matches. SqlSpatialTypeOperatorTable's constructor then registers Calcite's own spatial classes through ModelHandler.addFunctions(SchemaPlus, ...), the overload that consults ClassNameFilter.standard():

// core/src/main/java/org/apache/calcite/sql/SqlSpatialTypeOperatorTable.java, lines 56 and 60
ModelHandler.addFunctions(schema, null, ImmutableList.of(),
    SpatialTypeFunctions.class.getName(), "*", true);
ModelHandler.addFunctions(schema, null, ImmutableList.of(),
    SqlSpatialTypeFunctions.class.getName(), "*", true);

So the operator table asks the deployer's permission to load two classes that ship inside calcite-core. Arguably an upstream bug — the allowlist exists to constrain what a model file can name, and no model names these — but it is 1.43's behaviour, and something has to name them for spatial to work at all.

Why here

Two classes, both Calcite's own, neither user-supplied. Naming them takes nothing away from what the allowlist is for: the denylist that stops JNDI, Runtime, script engines and the rest is untouched, and any pattern the hosting application set is preserved by appending.

The alternative is that every consumer does it, and doing it correctly is harder than it looks. ClassNameFilter.STANDARD is a static final built from the property the first time the class initializes, so setting the property later is silently ignored. That makes the failure order-dependent rather than deterministic: measured while adding NetTopologySuite support to the EF Core provider, setting the property inside the connection factory gave nine failures on one run of a test assembly and zero on the next, from the same build, purely on whether some earlier test had planned a query first. A module initializer in this package runs at assembly load and has no such race.

Suggested shape

The same shape the EF Core provider already uses for its own namespace (CalciteModelAllowlistInitializer in Apache.Calcite.EntityFrameworkCore): a [ModuleInitializer] that appends rather than replaces, and never throws.

[ModuleInitializer]
internal static void Initialize()
{
    try
    {
        const string property = "calcite.model.classes.allowed";
        const string spatial = "org.apache.calcite.runtime.SpatialTypeFunctions,org.apache.calcite.sql.fun.SqlSpatialTypeFunctions";

        var allowed = java.lang.System.getProperty(property) ?? "";
        if (allowed.Contains("SpatialTypeFunctions", StringComparison.Ordinal) == false)
            java.lang.System.setProperty(property, allowed.Length > 0 ? allowed + "," + spatial : spatial);
    }
    catch
    {
        // never fail assembly load
    }
}

What this does not cover

Two other things are required for a spatial query and are not bugs, listed so the three do not get conflated. They are the caller's connection configuration and should stay that way:

  • fun must name spatial. fun=all does not include it — SqlLibrary.ALL.children() is BigQuery, Calcite, Hive, MSSQL, MySQL, Oracle, PostgreSQL, Redshift, Snowflake, Spark, ClickHouse — and spatial functions under fun=all fail validation with No match found for function signature ST_POINT(<NUMERIC>, <NUMERIC>).
  • The conformance must allow the GEOMETRY type, or DDL naming it fails to parse with Geo-spatial extensions and the GEOMETRY data type are not enabled. SqlConformanceEnum.allowGeometry() is true for BABEL, LENIENT, MYSQL_5, SQL_SERVER_2008, PRESTO.

Verified

With the two classes named, and fun=spatial plus conformance=LENIENT on the connection, measured on 1.43.0-SNAPSHOT:

SELECT ST_Point(1, 2) POINT (1 2)
SELECT ST_Distance(ST_Point(0, 0), ST_Point(3, 4)) 5
SELECT ST_Contains(ST_GeomFromText('POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))'), ST_Point(1, 1)) true
CREATE TABLE/INSERT/SELECT over a GEOMETRY column round-trips, and GetCalciteValue hands back the JTS geometry

Related: ikvmnet/calcite-efcore#64 records the same finding from the provider's side, and #153 here is a separate matter about GetFieldValue<Geometry>.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions