Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
57 changes: 41 additions & 16 deletions src/test/java/org/mobilitydb/spark/GeneratedSurfaceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,21 @@ class GeneratedSurfaceTest {

private static SparkSession spark;
private static Map<String, String> byOperator;
private static String json;

@BeforeAll
static void setup() throws Exception {
spark = SparkSession.builder().appName("gen-verify").master("local[1]")
.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)));
Expand All @@ -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): &lt;&lt; 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);
Expand Down Expand Up @@ -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(
Expand Down
Loading