Repository navigation
Spell Q5's hull aggregation as ST_Collect_Agg in the base dialect - #141
james-willis wants to merge 1 commit into
Conversation
The base class is documented as the Sedona/Spark SQL dialect, but Q5 spells its
grouped convex hull as a scalar over a materialized array:
ST_Area(ST_ConvexHull(ST_Collect(ARRAY_AGG(ST_GeomFromWKB(t.t_dropoffloc)))))
while SedonaDBSpatialBenchBenchmark overrides Q5 for the sole purpose of spelling
it as the native aggregate ST_Collect_Agg. ST_Collect_Agg is a first-class Sedona
Spark function -- registered in
spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_aggregates.scala,
released since well before 1.9.1, with Python and Flink bindings and a docs page
at docs/api/sql/Aggregate-Functions/ST_Collect_Agg.md -- so the base dialect can
say it directly, and SedonaDB then needs no override at all.
ARRAY_AGG also builds a full array of geometries per group before ST_Collect
runs; a native aggregate accumulates into one collection incrementally.
DuckDB, however, inherits the base Q5, and DuckDB spatial has no ST_Collect_Agg:
its ST_Collect is a scalar over a GEOMETRY[], and its geometry aggregates are
ST_Union_Agg and ST_Envelope_Agg. So the ARRAY_AGG spelling moves into a DuckDB
override rather than disappearing. That puts each override where the dialect
genuinely differs: DuckDB has no ST_Collect_Agg, Databricks has neither and uses
ST_Union_Agg, and SedonaDB -- the engine that generates the ground-truth answers
-- now runs the base suite unmodified.
The Databricks Q5 comment is reworded to name the function the base now uses; its
SQL is untouched. The Q5 SQL in notebooks/queries.ipynb and the two rendered docs
pages is updated to match: it read ST_Collect(ST_GeomFromWKB(...)), which current
SedonaDB rejects outright (Invalid function 'st_collect'), so the documented
query could not be run as shown.
The geography suite (print_geography_queries.py, geography-queries.*) carries the
same pattern but is left alone: it has no DuckDB implementation, it is not run by
the benchmark workflow, and it only borrows the base class's query-collection
reflection rather than its SQL, so nothing here leaks into it.
Verified, at both committed scale factors, that no engine's executable SQL
changes except the Sedona Spark base dialect's, and that the spelling change is
answer-preserving:
- Comparing every query's SQL before and after with comments and whitespace
normalized away, the only one that changes for any dialect is the base's Q5.
SedonaDB, DuckDB and Databricks all execute character-identical SQL, so the
committed answers cannot move.
- Sedona Spark 1.9.1 on Spark 3.5.3 resolves both spellings and returns
bit-identical results for them at SF1 and at SF10; both match
benchmark/answers/sf{1,10}/q5.csv (max relative difference 4.1e-16 against a
CI tolerance of 1e-6).
- SedonaDB 0.4.1 runs the new base Q5 and reproduces benchmark/answers/sf1/q5.csv
(max relative difference 1.2e-16). It rejects the old base Q5 -- it has no
ST_Collect -- which is why the override existed.
- DuckDB 1.5.5 with the spatial extension runs the new DuckDB Q5 and reproduces
benchmark/answers/sf1/q5.csv (max relative difference 2.4e-16), and rejects
ST_Collect_Agg with "Catalog Error: Scalar Function with name st_collect_agg
does not exist!".
Only the spelling of the aggregation changes: output columns, GROUP BY, HAVING,
ORDER BY, LIMIT and the committed answers are untouched.
Signed-off-by: James Willis <james@wherobots.com>
|
Withdrawing this. Further testing undercut the premise it was built on. 1. The SedonaDB override is not redundant — it is load-bearing. DuckDB inherits the 2.
The base class is the documented Spark/Sedona dialect and surfaces in Everything else here did check out — at SF1 and SF10 both spellings are bit-identical in One finding worth separating out, unrelated to the spelling debate: |
What
Q5 computes a convex hull over each customer-month's dropoff locations. The base
class — documented as the Sedona/Spark SQL dialect — spells that as a scalar over a
materialized array:
and
SedonaDBSpatialBenchBenchmarkoverrides Q5 solely to spell it as the nativeaggregate
ST_Collect_Agg.ST_Collect_Aggis a first-class Sedona Spark function, not a SedonaDB-ism — registeredin
st_aggregates.scala,with Python and Flink bindings and its own docs page.
So the base dialect can say it directly and SedonaDB needs no override at all.
ARRAY_AGGalso builds a full array of geometries per group beforeST_Collectruns,where a native aggregate accumulates into one collection incrementally. (Motivation, not
a benchmark claim — I have not measured it.)
The catch: DuckDB
DuckDB inherits the base Q5, and DuckDB spatial has no
ST_Collect_Agg. I checkedthis by running it rather than reading docs:
Its
ST_Collectis a scalar over aGEOMETRY[]. So theARRAY_AGGspelling moves into aDuckDB override rather than disappearing. Net effect: each override now sits where the
dialect genuinely differs.
SpatialBenchBenchmark(base)ST_Collect_AggDuckDBSpatialBenchBenchmarkST_Collect(ARRAY_AGG(…))SedonaDBSpatialBenchBenchmarkDatabricksSpatialBenchBenchmarkST_Union_AggVerification
No engine's executable SQL changes except the base dialect's. Comparing every query
before and after with comments and whitespace normalized away:
so the committed answers cannot move. Confirmed by running it anyway, against
benchmark/answers/sf{1,10}/q5.csv(CI tolerance isrtol=1e-6):SHOW FUNCTIONSon Sedona Spark 1.9.1 listsst_collect_aggalongsidest_collectandst_union_agg, so both spellings resolve there. SedonaDB rejects the old base Q5(
Invalid function 'st_collect') — which is exactly why the override existed.SF10 used the committed
tripdata plus acustomertable regenerated withspatialbench-cliat SF10; the generator reproduces the existing SF1customertablerow for row, so it is the same data.
benchmark/answers/is untouched.Also updated
notebooks/queries.ipynband the two rendered docs pages showed Q5 asST_Collect(ST_GeomFromWKB(...))inside ansd.sql(...)call — a spelling currentSedonaDB rejects outright, so the documented query could not be run as shown. They now
match the base. The stored notebook output is unchanged because the result is unchanged.
Deliberately not touched
The geography suite carries the same pattern, but it has no DuckDB implementation, is not
run by the benchmark workflow, and only borrows the base class's query-collection
reflection rather than its SQL — I verified nothing here leaks into it. Worth a follow-up
if maintainers want the suites symmetric.
🤖 Generated with Claude Code