From b6fba06f4752808d71c99e62017b80ed303dcec1 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 14 Sep 2026 16:34:50 +0200 Subject: [PATCH] Name the position operators by class in the generated-surface test The portable contract of the MEOS-API catalog names each position operator by the class of its operands instead of by one bare name: `&<#` is tboxOverbefore for a tnumber, its bounding box being a tbox, and `&<` is tboxOverleft (portableAliases.positionNames). GeneratedSurfaceTest reads those two names from positionNames, beside the bare names it reads from byOperator. The temporal comparison `#=` of a value with itself answers a temporal boolean, carried as hex-WKB, and the test asserts that it is not the boolean of the traditional `=`. The README names both maps. --- README.md | 11 ++-- .../spark/GeneratedSurfaceTest.java | 57 +++++++++++++------ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index a0840863..63b0f51b 100644 --- a/README.md +++ b/README.md @@ -123,8 +123,9 @@ FROM (SELECT explode(eDwithinPairs(trips_a, trips_b, 1000.0)) AS pr FROM trip_ar ``` The C-level entry points are registered too (`tint_in`, `tint_out`, `temporal_num_instants`, -`tnumber_twavg`, …), as is the portable bare-name operator dialect the catalog's `byOperator` map -defines. Because the names come from the catalog, a rename upstream arrives here by +`tnumber_twavg`, …), as is the portable operator dialect the catalog defines: the bare names of +its `byOperator` map and, for a position operator, the name for each class of operands in its +`positionNames` (`tboxOverbefore` for a tnumber, `stboxLeft` for a tgeompoint). Because the names come from the catalog, a rename upstream arrives here by regeneration rather than by editing this repository. Free what you keep: pointers returned across the FFI boundary are raw native addresses the JVM @@ -142,9 +143,9 @@ mvn -B clean test it *binds and executes*, not merely that it compiles: scalar accessors and I/O round-trips, double / boolean / byte marshalling, the cbuffer and npoint families, the JSON-path surface, value-array accessors, the N-by-N array UDFs, the canonical `@sqlfn` names with runtime argument-kind -dispatch, a folded out-parameter, and the H3 cell prefilter. The bare-name operators are read from -the catalog's own `byOperator` map rather than hard-coded, so a dialect rename updates the test by -itself. +dispatch, a folded out-parameter, and the H3 cell prefilter. The operator names are read from the +catalog's own `byOperator` and `positionNames` maps rather than hard-coded, so a dialect rename +updates the test by itself. MEOS keeps process-global state and cannot be re-initialised in a JVM that has finalised it, so Surefire runs one JVM per test class (`forkCount=1`, `reuseForks=false`). Keep that configuration. diff --git a/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java b/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java index 2c536e26..8810b2bd 100644 --- a/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java +++ b/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java @@ -30,6 +30,7 @@ class GeneratedSurfaceTest { private static SparkSession spark; private static Map byOperator; + private static String json; @BeforeAll static void setup() throws Exception { @@ -37,11 +38,13 @@ static void setup() throws Exception { .config("spark.ui.enabled", "false").getOrCreate(); GeneratedSpatioTemporalUDFs.registerAll(spark); // The operator->bare-name dialect, read from the SAME catalog the generator emits - // from, so a dialect rename (e.g. ?= ever_eq->eEq, <-> tdistance->tDistance) updates - // this test automatically instead of hard-coding the names. byOperator is a flat - // string->string map; parse it directly (Spark's bundled jackson is version-skewed). + // from, so a dialect rename (e.g. ?= ever_eq->eEqual, <-> tdistance->tDistance) + // updates this test automatically instead of hard-coding the names. byOperator is a + // flat string->string map; parse it directly (Spark's bundled jackson is + // version-skewed). A position operator has no bare name: positionNames gives its + // name for each class (see position()). byOperator = new HashMap<>(); - String json = Files.readString(Paths.get("tools/meos-idl.json")); + json = Files.readString(Paths.get("tools/meos-idl.json")); int b = json.indexOf('{', json.indexOf("\"byOperator\"")); Matcher m = Pattern.compile("\"([^\"]+)\"\\s*:\\s*\"([^\"]+)\"") .matcher(json.substring(b + 1, json.indexOf('}', b))); @@ -58,6 +61,23 @@ private static String op(String operator) { return n; } + /** + * The SQL name of a position operator for one class of operands, per the catalog's + * positionNames (operator -> class -> name): << is stboxLeft for an stbox, + * tboxLeft for a tbox, and a temporal operand takes the class of its bounding box. + */ + private static String position(String operator, String cls) { + int p = json.indexOf("\"positionNames\""); + assertTrue(p >= 0, "catalog has no positionNames"); + int k = json.indexOf("\"" + operator + "\"", p); + assertTrue(k >= 0, "catalog positionNames has no entry for " + operator); + int b = json.indexOf('{', k); + Matcher m = Pattern.compile("\"" + Pattern.quote(cls) + "\"\\s*:\\s*\"([^\"]+)\"") + .matcher(json.substring(b + 1, json.indexOf('}', b))); + assertTrue(m.find(), "catalog positionNames has no " + cls + " name for " + operator); + return m.group(1); + } + private Object scalar(String sql) { Row r = spark.sql(sql).collectAsList().get(0); return r.isNullAt(0) ? null : r.get(0); @@ -167,29 +187,34 @@ void scalar_value_array_accessors() { @Test void portable_bare_name_dispatch_surface() { - // The portable bare-name operator dialect, emitted by the generator's DISPATCH pass - // — NOT hand-registered. The bare names are read from the catalog's byOperator map - // (op(...)) rather than hard-coded, so the dialect (e.g. ?=->eEq, <->->tDistance) is - // the single source: a rename updates this test automatically. One assertion per - // family proves the superclass entrypoint dispatches the concrete subtype from hex-WKB. + // The portable operator dialect, emitted by the generator — NOT hand-registered. The + // names are read from the catalog (op(...) from byOperator, position(...) from + // positionNames) rather than hard-coded, so the dialect (e.g. ?=->eEqual, + // <->->tDistance, &<#->tboxOverbefore for a tnumber) is the single source: a rename + // updates this test automatically. One assertion per family proves the entrypoint + // dispatches the concrete subtype from hex-WKB. // topology (&&): two identical tints overlap in time → true assertEquals(Boolean.TRUE, scalar( "SELECT " + op("&&") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); // same (~=): a value equals itself assertEquals(Boolean.TRUE, scalar( "SELECT " + op("~=") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); - // time position (&<#): a value's period overbefore-overlaps itself → true + // time position (&<#), named for the tbox class of a tnumber: a value's period + // overbefore-overlaps itself → true assertEquals(Boolean.TRUE, scalar( - "SELECT " + op("&<#") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); - // temporal comparison (#=): of a value with itself is a temporal bool, non-null - assertNotNull(scalar("SELECT " + op("#=") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); + "SELECT " + position("&<#", "tbox") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); + // temporal comparison (#=): of a value with itself is a temporal bool, carried as + // hex-WKB, not the boolean of the traditional = + Object teq = scalar("SELECT " + op("#=") + "('" + TINT_HEX + "', '" + TINT_HEX + "')"); + assertNotNull(teq); + assertTrue(teq instanceof String, "#= answers a temporal boolean, not " + teq.getClass()); // ever comparison (?=): same value → true assertEquals(Boolean.TRUE, scalar( "SELECT " + op("?=") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); - // space-X axis classifier (&<): a value is overleft-of itself on its value axis → - // true (exercises axisBool) + // value-axis position (&<), named for the tbox class of a tnumber: a value is + // overleft-of itself on its value axis → true assertEquals(Boolean.TRUE, scalar( - "SELECT " + op("&<") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); + "SELECT " + position("&<", "tbox") + "('" + TINT_HEX + "', '" + TINT_HEX + "')")); // distance (<->): lifted distance between two coincident tgeompoints → a temporal // (hex-WKB) of all-zero distance, non-null assertNotNull(scalar(