diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/FilterEncoderSql.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/FilterEncoderSql.java
index 0c896998c..2cf05d4d3 100644
--- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/FilterEncoderSql.java
+++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/FilterEncoderSql.java
@@ -1947,7 +1947,8 @@ private String mapToSubDecoder(
.filter("EXPRESSION"::equals)
.isPresent()) {
String columnResolved =
- SqlQueryColumnOperations.getQualifiedColumnResolved(alias, column, sqlDialect);
+ SqlQueryColumnOperations.getQualifiedColumnResolved(
+ alias, column, sqlDialect, Set.of(Operation.WKB, Operation.WKT), false);
return columnResolved.replace("AS " + column.getName(), "");
}
diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperations.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperations.java
index 946e8e4df..a7bbe9208 100644
--- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperations.java
+++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperations.java
@@ -14,6 +14,7 @@
import de.ii.xtraplatform.features.sql.domain.SqlQueryColumn;
import de.ii.xtraplatform.features.sql.domain.SqlQueryColumn.Operation;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
public interface SqlQueryColumnOperations {
@@ -45,6 +46,10 @@ static String getQualifiedColumnResolved(
return "'%s' AS %s"
.formatted(column.getOperationParameter(Operation.CONSTANT, ""), column.getName());
}
+ boolean isForcePolygonCCW = ops.containsKey(Operation.FORCE_POLYGON_CCW);
+ boolean shouldLinearizeCurves =
+ ops.containsKey(Operation.LINEARIZE_CURVES) || forceLinearizeCurves;
+
if (ops.containsKey(Operation.EXPRESSION)
&& !excludeOperations.contains(Operation.EXPRESSION)) {
final int[] i = {0};
@@ -54,18 +59,14 @@ static String getQualifiedColumnResolved(
column.getOperationParameters(Operation.EXPRESSION).stream()
.map(param -> Map.entry("" + i[0]++, param))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)),
- ops.containsKey(Operation.WKT));
+ geometryOperation(ops, excludeOperations),
+ isForcePolygonCCW,
+ shouldLinearizeCurves);
}
if (ops.containsKey(Operation.WKT) && !excludeOperations.contains(Operation.WKT)) {
- boolean isForcePolygonCCW = ops.containsKey(Operation.FORCE_POLYGON_CCW);
- boolean shouldLinearizeCurves =
- ops.containsKey(Operation.LINEARIZE_CURVES) || forceLinearizeCurves;
return sqlDialect.applyToWkt(name, isForcePolygonCCW, shouldLinearizeCurves);
}
if (ops.containsKey(Operation.WKB) && !excludeOperations.contains(Operation.WKB)) {
- boolean isForcePolygonCCW = ops.containsKey(Operation.FORCE_POLYGON_CCW);
- boolean shouldLinearizeCurves =
- ops.containsKey(Operation.LINEARIZE_CURVES) || forceLinearizeCurves;
return sqlDialect.applyToWkb(name, isForcePolygonCCW, shouldLinearizeCurves);
}
if (ops.containsKey(Operation.DATE) && !excludeOperations.contains(Operation.DATE)) {
@@ -77,6 +78,26 @@ static String getQualifiedColumnResolved(
return name;
}
+ /**
+ * The encoding a geometry column has to be wrapped in for output, {@link Operation#WKB} or {@link
+ * Operation#WKT}, or empty when the column is not a geometry or the caller excluded the wrapping
+ * because it wraps the raw geometry itself.
+ *
+ *
A geometry column carries exactly one of the two, chosen by {@code
+ * queryGeneration.geometryEncoding}, so both have to be looked at — as every other consumer of
+ * these operations does.
+ */
+ private static Optional geometryOperation(
+ Map ops, Set excludeOperations) {
+ if (ops.containsKey(Operation.WKB) && !excludeOperations.contains(Operation.WKB)) {
+ return Optional.of(Operation.WKB);
+ }
+ if (ops.containsKey(Operation.WKT) && !excludeOperations.contains(Operation.WKT)) {
+ return Optional.of(Operation.WKT);
+ }
+ return Optional.empty();
+ }
+
static SqlQueryColumn dateToDatetime(SqlQueryColumn column) {
if (!column.getOperations().containsKey(Operation.DATE)) {
return column;
diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialect.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialect.java
index 2c3733e1b..addbfaa60 100644
--- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialect.java
+++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialect.java
@@ -169,9 +169,40 @@ default Set getTemporalOperators() {
return ImmutableSet.of();
}
+ /**
+ * Renders a column that a sub-decoder produces from an expression: the {@code $T$} placeholders
+ * of the expression are resolved to {@code table}, a geometry is wrapped for output, and the
+ * result is aliased to {@code name}.
+ *
+ * @param geometryOperation {@link SqlQueryColumn.Operation#WKB} or {@link
+ * SqlQueryColumn.Operation#WKT} when the expression yields a geometry that has to be wrapped
+ * for output in that encoding, empty when it does not. The encoding is a per-provider setting
+ * ({@code queryGeneration.geometryEncoding}) and a dialect is shared by all providers of its
+ * DBMS, so it has to be passed in rather than held by the dialect.
+ * @param forcePolygonCCW whether the geometry has to be returned with counter-clockwise polygons
+ * @param linearizeCurves whether curves in the geometry have to be linearized
+ */
default String applyToExpression(
- String table, String name, Map subDecoderPaths, boolean spatial) {
- return name;
+ String table,
+ String name,
+ Map subDecoderPaths,
+ Optional geometryOperation,
+ boolean forcePolygonCCW,
+ boolean linearizeCurves) {
+ if (subDecoderPaths.isEmpty()) {
+ return name;
+ }
+
+ String expression =
+ subDecoderPaths.values().iterator().next().replaceAll("\\$(?:t|T|table)\\$", table);
+
+ if (geometryOperation.filter(SqlQueryColumn.Operation.WKB::equals).isPresent()) {
+ expression = applyToWkb(expression, forcePolygonCCW, linearizeCurves);
+ } else if (geometryOperation.filter(SqlQueryColumn.Operation.WKT::equals).isPresent()) {
+ expression = applyToWkt(expression, forcePolygonCCW, linearizeCurves);
+ }
+
+ return String.format("(%s) AS %s", expression, name);
}
Map SPATIAL_OPERATORS =
diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkg.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkg.java
index 797bb8554..a2e67ff2d 100644
--- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkg.java
+++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkg.java
@@ -11,7 +11,6 @@
import de.ii.xtraplatform.crs.domain.BoundingBox;
import de.ii.xtraplatform.crs.domain.EpsgCrs;
import de.ii.xtraplatform.features.domain.SchemaBase.Type;
-import de.ii.xtraplatform.features.sql.domain.FeatureProviderSqlData.QueryGeneratorSettings;
import de.ii.xtraplatform.features.sql.domain.SchemaSql.PropertyTypeInfo;
import de.ii.xtraplatform.features.sql.infra.db.SqlDbmsAdapterGpkg;
import java.time.Instant;
@@ -19,7 +18,6 @@
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.threeten.extra.Interval;
@@ -31,8 +29,6 @@ public String getId() {
return SqlDbmsAdapterGpkg.ID;
}
- private QueryGeneratorSettings settings;
-
private static final Splitter BBOX_SPLITTER =
Splitter.onPattern("[(), ]").omitEmptyStrings().trimResults();
@@ -242,20 +238,4 @@ private static String toNumericLiteral(double value) {
// Double.toString is independent of the default locale, unlike String.format("%f", ...)
return Double.toString(value);
}
-
- @Override
- public String applyToExpression(
- String table, String name, Map subDecoderPaths, boolean spatial) {
- if (!subDecoderPaths.isEmpty()) {
- String expression =
- subDecoderPaths.values().iterator().next().replaceAll("\\$(?:t|T|table)\\$", table);
- if (spatial && settings.getGeometryAsWkb()) {
- expression = applyToWkb(expression, false, false);
- } else if (spatial) {
- expression = applyToWkt(expression, false, false);
- }
- return String.format("(%s) AS %s", expression, name);
- }
- return SqlDialect.super.applyToExpression(table, name, subDecoderPaths, spatial);
- }
}
diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectPgis.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectPgis.java
index 97a5e22cd..bdc7fb4bc 100644
--- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectPgis.java
+++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/domain/SqlDialectPgis.java
@@ -349,18 +349,4 @@ public String applyToJsonArrayOp(
public String escapeString(String value) {
return value.replaceAll("'", "''");
}
-
- @Override
- public String applyToExpression(
- String table, String name, Map subDecoderPaths, boolean spatial) {
- if (!subDecoderPaths.isEmpty()) {
- String expression =
- subDecoderPaths.values().iterator().next().replaceAll("\\$(?:t|T|table)\\$", table);
- if (spatial) {
- expression = applyToWkt(expression, false, false);
- }
- return String.format("(%s) AS %s", expression, name);
- }
- return SqlDialect.super.applyToExpression(table, name, subDecoderPaths, spatial);
- }
}
diff --git a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/FilterEncoderSqlSpatialIndexSpec.groovy b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/FilterEncoderSqlSpatialIndexSpec.groovy
index fd554ebe6..db1a99854 100644
--- a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/FilterEncoderSqlSpatialIndexSpec.groovy
+++ b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/FilterEncoderSqlSpatialIndexSpec.groovy
@@ -20,6 +20,7 @@ import de.ii.xtraplatform.cql.domain.SWithin
import de.ii.xtraplatform.cql.domain.SpatialLiteral
import de.ii.xtraplatform.crs.domain.OgcCrs
import de.ii.xtraplatform.features.domain.FeatureSchemaFixtures
+import de.ii.xtraplatform.features.domain.ImmutableFeatureSchema
import de.ii.xtraplatform.features.domain.MappingOperationResolver
import de.ii.xtraplatform.features.domain.MappingRuleFixtures
import de.ii.xtraplatform.features.json.app.DecoderFactoryJson
@@ -27,9 +28,13 @@ import de.ii.xtraplatform.features.domain.SchemaBase
import de.ii.xtraplatform.features.sql.domain.ImmutableQueryGeneratorSettings
import de.ii.xtraplatform.features.sql.domain.ImmutableSchemaSql
import de.ii.xtraplatform.features.sql.domain.ImmutableSqlPathDefaults
+import de.ii.xtraplatform.features.sql.domain.ImmutableSqlQueryColumn
+import de.ii.xtraplatform.features.sql.domain.ImmutableSqlQueryMapping
+import de.ii.xtraplatform.features.sql.domain.ImmutableSqlQuerySchema
import de.ii.xtraplatform.features.sql.domain.SqlDialectGpkg
import de.ii.xtraplatform.features.sql.domain.SqlDialectPgis
import de.ii.xtraplatform.features.sql.domain.SqlPathParser
+import de.ii.xtraplatform.features.sql.domain.SqlQueryColumn
import de.ii.xtraplatform.features.sql.domain.SqlQueryMapping
import spock.lang.Shared
import spock.lang.Specification
@@ -203,4 +208,46 @@ class FilterEncoderSqlSpatialIndexSpec extends Specification {
actual == "(A.\"fid\" IN (SELECT id FROM \"rtree_unfaelle_point_geom\" WHERE maxx >= 7.0 AND minx <= 7.1 AND maxy >= 50.0 AND miny <= 50.1) AND " +
"ST_Intersects(A.geom, ST_GeomFromText('POLYGON((7.0 50.0,7.1 50.0,7.1 50.1,7.0 50.1,7.0 50.0))',4326)))"
}
+
+ def 'a geometry expression stays a geometry in a spatial predicate'() {
+
+ given: 'a WKB-encoded geometry that is derived from an expression'
+ def geometry = new ImmutableSqlQueryColumn.Builder()
+ .name("geom")
+ .pathSegment("geom")
+ .type(SchemaBase.Type.GEOMETRY)
+ .role(SchemaBase.Role.PRIMARY_GEOMETRY)
+ .operations(Map.of(
+ SqlQueryColumn.Operation.CONNECTOR, ["EXPRESSION"] as String[],
+ SqlQueryColumn.Operation.EXPRESSION, ["ST_Force2D(\$T\$.geom)"] as String[],
+ SqlQueryColumn.Operation.WKB, [] as String[]))
+ .schemaIndex(0)
+ .build()
+ def table = new ImmutableSqlQuerySchema.Builder()
+ .name("airports")
+ .pathSegment("airports")
+ .addColumns(geometry)
+ .build()
+ def geometrySchema = new ImmutableFeatureSchema.Builder()
+ .name("geom")
+ .type(SchemaBase.Type.GEOMETRY)
+ .role(SchemaBase.Role.PRIMARY_GEOMETRY)
+ .build()
+ def mapping = new ImmutableSqlQueryMapping.Builder()
+ .addTables(table)
+ .putValueTables("geom", table)
+ .putValueColumns("geom", geometry)
+ .putValueSchemas("geom", geometrySchema)
+ .build()
+ def filterEncoder = encoder(new SqlDialectPgis(), Map.of())
+ def filter = SIntersects.of(Property.of("geom"),
+ SpatialLiteral.of(Bbox.of(7.0, 50.0, 7.1, 50.1, OgcCrs.CRS84)))
+
+ when:
+ String actual = filterEncoder.encode(filter, mapping)
+
+ then: 'the output-only WKB wrapper is not passed to ST_Intersects'
+ actual == "ST_Intersects((ST_Force2D(A.geom)) , " +
+ "ST_GeomFromText('POLYGON((7.0 50.0,7.1 50.0,7.1 50.1,7.0 50.1,7.0 50.0))',4326))"
+ }
}
diff --git a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperationsSpec.groovy b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperationsSpec.groovy
new file mode 100644
index 000000000..41bd01520
--- /dev/null
+++ b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/SqlQueryColumnOperationsSpec.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2026 interactive instruments GmbH
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package de.ii.xtraplatform.features.sql.app
+
+import de.ii.xtraplatform.features.domain.SchemaBase
+import de.ii.xtraplatform.features.sql.domain.ImmutableSqlQueryColumn
+import de.ii.xtraplatform.features.sql.domain.SqlDialectGpkg
+import de.ii.xtraplatform.features.sql.domain.SqlDialectPgis
+import de.ii.xtraplatform.features.sql.domain.SqlQueryColumn.Operation
+import spock.lang.Specification
+
+class SqlQueryColumnOperationsSpec extends Specification {
+
+ static def column(String name, SchemaBase.Type type, Map operations) {
+ return new ImmutableSqlQueryColumn.Builder()
+ .name(name)
+ .pathSegment(name)
+ .type(type)
+ .schemaIndex(0)
+ .operations(operations)
+ .build()
+ }
+
+ static def geometryExpression(Operation encoding) {
+ return column("geom", SchemaBase.Type.GEOMETRY, Map.of(
+ Operation.EXPRESSION, ['ST_GeomFromText($T$.wkt)'] as String[],
+ encoding, [] as String[]))
+ }
+
+ def 'a geometry expression is wrapped in the encoding the column carries'() {
+
+ given: 'a sub-decoder expression column whose geometry is encoded as #encoding'
+ def col = geometryExpression(encoding)
+
+ when:
+ String actual = SqlQueryColumnOperations.getQualifiedColumnResolved("A", col, dialect)
+
+ then:
+ actual == expected
+
+ where:
+ dialect | encoding || expected
+ new SqlDialectGpkg() | Operation.WKB || "(ST_AsBinary(ST_GeomFromText(A.wkt))) AS geom"
+ new SqlDialectGpkg() | Operation.WKT || "(ST_AsText(ST_GeomFromText(A.wkt))) AS geom"
+ new SqlDialectPgis() | Operation.WKB || "(ST_AsBinary(ST_GeomFromText(A.wkt))) AS geom"
+ new SqlDialectPgis() | Operation.WKT || "(ST_AsText(ST_GeomFromText(A.wkt))) AS geom"
+ }
+
+ def 'a caller that excludes the geometry wrapping gets the raw expression'() {
+
+ given: 'the spatial extent query wraps the raw geometry itself, so it excludes WKB and WKT'
+ def col = geometryExpression(Operation.WKB)
+
+ when:
+ String actual = SqlQueryColumnOperations.getQualifiedColumnResolved(
+ "A", col, new SqlDialectGpkg(), Set.of(Operation.WKB, Operation.WKT), false)
+
+ then: 'the expression is not wrapped, so it is not wrapped twice'
+ actual == "(ST_GeomFromText(A.wkt)) AS geom"
+ }
+
+ def 'FORCE_POLYGON_CCW on the column reaches a geometry expression'() {
+
+ given: 'a geometry expression column that also carries FORCE_POLYGON_CCW'
+ def col = column("geom", SchemaBase.Type.GEOMETRY, Map.of(
+ Operation.EXPRESSION, ['ST_GeomFromText($T$.wkt)'] as String[],
+ Operation.WKB, [] as String[],
+ Operation.FORCE_POLYGON_CCW, [] as String[]))
+
+ when:
+ String actual = SqlQueryColumnOperations.getQualifiedColumnResolved("A", col, new SqlDialectGpkg())
+
+ then: 'the expression is wrapped the same way a plain geometry column would be'
+ actual == "(ST_AsBinary(ST_ForcePolygonCCW(ST_GeomFromText(A.wkt)))) AS geom"
+ }
+
+ def 'a plain geometry column and a geometry expression agree on the flags'() {
+
+ given: 'the same operations, once as a plain column and once via an expression'
+ def ops = Map.of(Operation.WKB, [] as String[], Operation.FORCE_POLYGON_CCW, [] as String[])
+ def plain = column("geom", SchemaBase.Type.GEOMETRY, ops)
+ def viaExpression = column("geom", SchemaBase.Type.GEOMETRY,
+ ops + [(Operation.EXPRESSION): ['$T$.geom'] as String[]])
+ def dialect = new SqlDialectGpkg()
+
+ when:
+ String plainSql = SqlQueryColumnOperations.getQualifiedColumnResolved("A", plain, dialect)
+ String expressionSql = SqlQueryColumnOperations.getQualifiedColumnResolved("A", viaExpression, dialect)
+
+ then: 'both force counter-clockwise polygons'
+ plainSql == "ST_AsBinary(ST_ForcePolygonCCW(A.geom))"
+ expressionSql == "(ST_AsBinary(ST_ForcePolygonCCW(A.geom))) AS geom"
+ }
+
+ def 'forceLinearizeCurves from the caller reaches a geometry expression'() {
+
+ given:
+ def col = column("geom", SchemaBase.Type.GEOMETRY, Map.of(
+ Operation.EXPRESSION, ['$T$.geom'] as String[],
+ Operation.WKB, [] as String[]))
+
+ when: 'the caller asks for curves to be linearized'
+ String actual = SqlQueryColumnOperations.getQualifiedColumnResolved(
+ "A", col, new SqlDialectPgis(), Set.of(), true)
+
+ then: 'the dialect that linearizes does so for the expression too'
+ actual == "(ST_AsBinary(ST_CurveToLine(A.geom,32,0,1))) AS geom"
+ }
+
+ def 'a non-geometry expression is never wrapped'() {
+
+ given:
+ def col = column("name", SchemaBase.Type.STRING, Map.of(
+ Operation.EXPRESSION, ['json_extract($T$.props, \'$.name\')'] as String[]))
+
+ when:
+ String actual = SqlQueryColumnOperations.getQualifiedColumnResolved("A", col, new SqlDialectGpkg())
+
+ then:
+ actual == "(json_extract(A.props, '\$.name')) AS name"
+ }
+}
diff --git a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkgSpec.groovy b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkgSpec.groovy
index b694030aa..defd52a09 100644
--- a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkgSpec.groovy
+++ b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/domain/SqlDialectGpkgSpec.groovy
@@ -19,6 +19,61 @@ class SqlDialectGpkgSpec extends Specification {
dialect = new SqlDialectGpkg()
}
+ def 'a sub-decoder expression without a geometry is passed through'() {
+
+ given:
+ def paths = Map.of("0", 'json_extract($T$.props, \'$.name\')')
+
+ when:
+ String actual = dialect.applyToExpression("A", "name", paths, Optional.empty(), false, false)
+
+ then: 'the table placeholder is resolved and the expression is aliased'
+ actual == "(json_extract(A.props, '\$.name')) AS name"
+ }
+
+ def 'a geometry expression is wrapped in the configured encoding'() {
+
+ given:
+ def paths = Map.of("0", 'ST_GeomFromText($T$.wkt)')
+
+ when:
+ String actual = dialect.applyToExpression("A", "geom", paths, Optional.of(operation), false, false)
+
+ then:
+ actual == expected
+
+ where:
+ operation || expected
+ SqlQueryColumn.Operation.WKB || "(ST_AsBinary(ST_GeomFromText(A.wkt))) AS geom"
+ SqlQueryColumn.Operation.WKT || "(ST_AsText(ST_GeomFromText(A.wkt))) AS geom"
+ }
+
+ def 'forcePolygonCCW reaches the wrapping of a geometry expression'() {
+
+ given:
+ def paths = Map.of("0", 'ST_GeomFromText($T$.wkt)')
+
+ when:
+ String actual = dialect.applyToExpression("A", "geom", paths, Optional.of(operation), true, false)
+
+ then:
+ actual == expected
+
+ where:
+ operation || expected
+ SqlQueryColumn.Operation.WKB || "(ST_AsBinary(ST_ForcePolygonCCW(ST_GeomFromText(A.wkt)))) AS geom"
+ SqlQueryColumn.Operation.WKT || "(ST_AsText(ST_ForcePolygonCCW(ST_GeomFromText(A.wkt)))) AS geom"
+ }
+
+ def 'without sub-decoder paths the column name is returned unchanged'() {
+
+ when:
+ String actual = dialect.applyToExpression("A", "name", Map.of(), Optional.empty(), false, false)
+
+ then:
+ actual == "name"
+ }
+
def 'the spatial index predicate names the r-tree and the key column'() {
when: