From e96d2302440c5dc99b4ae57f3e6b263687087853 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sun, 11 Oct 2026 03:59:51 +0200 Subject: [PATCH] Check the append aggregates on the typed Spark surface GeneratedSqlSurfaceTest.appendAggregatesAnswerWhatMobilityDBAnswersOrderedByTime checks that appendInstantAgg over temporal integers, over temporal floats with the interpolation discrete, and with the interpolation linear, a distance of 1.5 and an interval of one day, over temporal booleans with the interpolation step and an interval of one day, and appendSequenceAgg over temporal integer sequences answer what MobilityDB answers over the same values ordered by their start timestamp, the values given out of that order, each in a partition of its own, so the aggregate orders them whatever order it receives; and that two values at one timestamp are refused with MobilityDB's error, "The temporal values have different value at their common timestamp". Witness: with the generator of JMEOS ba1e61c3, before #172, the surface registers neither aggregate, the 77 overloads of appendInstantAgg and appendSequenceAgg left out as stating no combine. Measured over MobilityDB 3a8e6dba09, the catalog of MEOS-API 4baf11705c and JMEOS main e8c65dc4: the 45 tests pass, 17 of them in GeneratedSqlSurfaceTest, with 0 warnings. Why: a trajectory built from its instants or its pieces answers on Spark as it does in PostgreSQL, whatever order Spark hands the rows in. --- .../spark/GeneratedSqlSurfaceTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/test/java/org/mobilitydb/spark/GeneratedSqlSurfaceTest.java b/src/test/java/org/mobilitydb/spark/GeneratedSqlSurfaceTest.java index b1799772..1e74ba3b 100644 --- a/src/test/java/org/mobilitydb/spark/GeneratedSqlSurfaceTest.java +++ b/src/test/java/org/mobilitydb/spark/GeneratedSqlSurfaceTest.java @@ -362,6 +362,58 @@ void windowAggregatesAnswerWhatMobilityDBAnswers() { windowAggregate("wSum", "tfloatFromText", "{1@2001-01-01 00:00:00+00, 3@2001-01-03 00:00:00+00}")); } + /** The text of an append aggregate over the values, given out of time order, and the further + * arguments, each value in a partition of its own, as #windowAggregate reads a window + * aggregate. */ + private static String appendAggregate(String agg, String in, String args, String... values) { + StringBuilder rows = new StringBuilder(); + for (String v : values) { + rows.append(rows.length() == 0 ? "" : ", ").append("('").append(v).append("')"); + } + return (String) scalar("SELECT asText(" + agg + "(v" + args + ")) FROM (SELECT /*+ REPARTITION(" + + values.length + ") */ " + in + "(s) AS v FROM VALUES " + rows + " AS t(s))"); + } + + @Test + void appendAggregatesAnswerWhatMobilityDBAnswersOrderedByTime() { + // What MobilityDB answers for the same aggregate over the same values ordered by their + // start timestamp, with the time zone set to UTC; the values come in another order, each + // in a partition of its own, so the aggregate orders them whatever order it receives. + assertEquals("[1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00, 3@2001-01-03 00:00:00+00]", + appendAggregate("appendInstantAgg", "tintFromText", "", + "3@2001-01-03 00:00:00+00", "1@2001-01-01 00:00:00+00", "2@2001-01-02 00:00:00+00")); + assertEquals("{1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00, 3@2001-01-03 00:00:00+00}", + appendAggregate("appendInstantAgg", "tfloatFromText", ", 'discrete'", + "3@2001-01-03 00:00:00+00", "1@2001-01-01 00:00:00+00", "2@2001-01-02 00:00:00+00")); + assertEquals("{[1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00], [5@2001-01-03 00:00:00+00], " + + "[6@2001-01-05 00:00:00+00]}", + appendAggregate("appendInstantAgg", "tfloatFromText", ", 'linear', 1.5, INTERVAL '1' DAY", + "6@2001-01-05 00:00:00+00", "1@2001-01-01 00:00:00+00", "5@2001-01-03 00:00:00+00", + "2@2001-01-02 00:00:00+00")); + assertEquals("{[t@2001-01-01 00:00:00+00, f@2001-01-02 00:00:00+00], [f@2001-01-05 00:00:00+00]}", + appendAggregate("appendInstantAgg", "tboolFromText", ", 'step', INTERVAL '1' DAY", + "f@2001-01-05 00:00:00+00", "t@2001-01-01 00:00:00+00", "f@2001-01-02 00:00:00+00")); + assertEquals("{[1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00], " + + "[2@2001-01-03 00:00:00+00, 2@2001-01-04 00:00:00+00], " + + "[3@2001-01-05 00:00:00+00, 4@2001-01-06 00:00:00+00]}", + appendAggregate("appendSequenceAgg", "tintFromText", "", + "[3@2001-01-05 00:00:00+00, 4@2001-01-06 00:00:00+00]", + "[1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00]", + "[2@2001-01-03 00:00:00+00, 2@2001-01-04 00:00:00+00]")); + // Two values at one timestamp are refused, as MobilityDB refuses them in any order. + Exception e = assertThrows(Exception.class, () -> appendAggregate("appendInstantAgg", + "tintFromText", "", "1@2001-01-01 00:00:00+00", "2@2001-01-01 00:00:00+00")); + assertTrue(String.valueOf(rootMessage(e)).contains("different value at their common timestamp"), + String.valueOf(rootMessage(e))); + } + + private static String rootMessage(Throwable e) { + while (e.getCause() != null) { + e = e.getCause(); + } + return e.getMessage(); + } + @Test void peakCountOverMergedPieces() { // The pieces of one trip are merged first, so the count counts trips, not pieces: two