From eb7e69406abf7113a49defb101b05e97253b6643 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 28 Sep 2026 13:44:45 +0200 Subject: [PATCH] Check the WKB bytes the generated surface carries its values in The generated surface returns a temporal, span, span set or set as its WKB bytes and reads a column of their hex-WKB text as well. GeneratedSurfaceTest takes the temporal boolean of #= as byte[], and values_travel_as_wkb_and_hex_wkb_stays_readable reads a temporal and its span back as byte[], answers the same instant count from the bytes and from the hex text, reads hex in the big-endian byte order, and aggregates a column of hex text to the answer over the WKB of the same value. The README states the wire format: WKB bytes for those four kinds, WKT or EWKT text for geometries, the text form for boxes, circular buffers, network points and poses. Witness. The new test fails on the generator of JMEOS 36b3d4316d, whose functions answer hex text, and on the generator of JMEOS #110 with the type read from one byte, at its big-endian check, or without the rule reading hex text that reached an aggregate as ASCII bytes, at its aggregate check. Measured. Against JMEOS main 04e0a71ee4 (#110), the catalog and libmeos of MobilityDB 3f9ee86639 and the jar JMEOS builds from them, the suite runs 20 tests, all passing. Why. The test states the form each value travels in, so a change of it reaches the suite and not only the users of the binding. --- README.md | 9 +++--- .../java/org/mobilitydb/spark/MeosMemory.java | 2 +- .../spark/GeneratedSurfaceTest.java | 28 +++++++++++++++++-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 63b0f51b..6c0e1d64 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,9 @@ SparkSession spark = SparkSession.builder().appName("mobilityspark").master("loc GeneratedSpatioTemporalUDFs.registerAll(spark); ``` -Temporal values travel as strings in MEOS hex-WKB, and geometries as hex-EWKB, so any Spark type -system carries them: +Temporal values, spans, span sets and sets travel as their MEOS WKB bytes, a binary column, and a +column of their hex-WKB text reads as well; geometries travel as WKT or EWKT text, and boxes, +circular buffers, network points and poses as their text form: ```sql -- accessors under their canonical MobilityDB SQL names @@ -140,8 +141,8 @@ mvn -B clean test ``` `GeneratedSurfaceTest` drives the generated surface from known hex-WKB literals and asserts that -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 +it *binds and executes*, not merely that it compiles: scalar accessors and I/O round-trips, the +WKB bytes the functions return and the hex-WKB text they read, 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 operator names are read from the catalog's own `byOperator` and `positionNames` maps rather than hard-coded, so a dialect rename diff --git a/src/main/java/org/mobilitydb/spark/MeosMemory.java b/src/main/java/org/mobilitydb/spark/MeosMemory.java index ef62f8c5..c62d4809 100644 --- a/src/main/java/org/mobilitydb/spark/MeosMemory.java +++ b/src/main/java/org/mobilitydb/spark/MeosMemory.java @@ -47,7 +47,7 @@ * * Usage: *
- *   Pointer tptr = GeneratedFunctions.temporal_from_hexwkb(hex);
+ *   Pointer tptr = GeneratedFunctions.temporal_from_wkb(wkb);
  *   try {
  *       // ... use tptr ...
  *   } finally {
diff --git a/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java b/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java
index 8810b2bd..844d4b96 100644
--- a/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java
+++ b/src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java
@@ -204,10 +204,10 @@ void portable_bare_name_dispatch_surface() {
         assertEquals(Boolean.TRUE, scalar(
             "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 =
+        // 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());
+        assertTrue(teq instanceof byte[], "#= answers a temporal boolean, not " + teq.getClass());
         // ever comparison (?=): same value → true
         assertEquals(Boolean.TRUE, scalar(
             "SELECT " + op("?=") + "('" + TINT_HEX + "', '" + TINT_HEX + "')"));
@@ -275,6 +275,30 @@ void as_hexwkb_family_with_swallowed_size_out_param() {
             "SELECT temporal_num_instants(temporal_from_hexwkb('" + hex + "'))")).intValue());
     }
 
+    @Test
+    void values_travel_as_wkb_and_hex_wkb_stays_readable() {
+        String tint = "tint_in('[1@2001-01-01, 2@2001-01-02, 1@2001-01-03]')";
+        // A temporal, and the span a function derives from it, travel as their WKB bytes.
+        Object t = scalar("SELECT " + tint);
+        assertTrue(t instanceof byte[], "a temporal travels as WKB, got " + t.getClass());
+        Object span = scalar("SELECT timeSpan(" + tint + ")");
+        assertTrue(span instanceof byte[], "a span travels as WKB, got " + span.getClass());
+        // The bytes and the hex-WKB text of one value answer alike.
+        assertEquals(3, ((Number) scalar("SELECT numInstants(" + tint + ")")).intValue());
+        assertEquals(3, ((Number) scalar("SELECT numInstants('" + TINT_HEX + "')")).intValue());
+        // Hex-WKB in the big-endian byte order (WKB_EXTENDED | WKB_XDR = 20) is read by its
+        // own order, the type standing in the second and third bytes.
+        assertEquals(3, ((Number) scalar("SELECT numInstants(temporal_as_hexwkb("
+            + tint + ", CAST(20 AS BYTE)))")).intValue());
+        // An aggregate over a column of hex-WKB text answers what it answers over the WKB
+        // of the same value.
+        Object overWkb = scalar("SELECT tint_out(tCount(v)) FROM (SELECT temporal_from_hexwkb('"
+            + TINT_HEX + "') AS v)");
+        assertNotNull(overWkb);
+        assertEquals(overWkb,
+            scalar("SELECT tint_out(tCount(s)) FROM VALUES ('" + TINT_HEX + "') AS t(s)"));
+    }
+
     @Test
     void bool_result_value_at_timestamptz_folds_the_value_out_param() {
         // tint_value_at_timestamptz(temp, t, strict, int *value) returns bool and writes