diff --git a/CMakeLists.txt b/CMakeLists.txt index 47640ba4..4714328b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,9 @@ else() add_definitions(-DMEOS_IS_BIG_ENDIAN=0) endif() +# Expose the MEOS raster API (meos.h #if RASTER guard). +add_definitions(-DRASTER=1) + # ----------------------------- # Dependencies (vcpkg) # ----------------------------- @@ -89,6 +92,8 @@ set(EXTENSION_SOURCES src/geo/tgeogpoint.cpp src/geo/tgeogpoint_in_out.cpp src/geo/tgeogpoint_ops.cpp + src/quadbin/tquadbin.cpp + src/quadbin/raster_quadbin.cpp src/index/rtree_module.cpp src/single_tile_getters.cpp src/index/rtree_index_create_physical.cpp diff --git a/Makefile b/Makefile index bc17d050..5ddf037c 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,49 @@ include extension-ci-tools/makefiles/duckdb_extension.Makefile # both MEOS (meos_initialize_timezone) and DuckDB (DBConfig::SetOptionByName # "TimeZone") to Europe/Brussels. Tests pass on any OS timezone — the # extension is the single source of truth, no TZ env var needed. +# +# LoadInternal also auto-loads ICU so the named "Europe/Brussels" zone +# resolves. DuckDB autoloads ICU from +# $HOME/.duckdb/extensions///icu.duckdb_extension +# and otherwise falls back to a hub download — which fails inside the CI test +# docker (empty path, no network egress) and on the macOS osx_arm64 runner +# (hub ICU not reliably resolvable). So we stage the locally-built ICU into the +# expected path before the unittester runs. +# +# Target DuckDB is the v1.4.x LTS line, with later versions (v1.5.x) supported +# in a multi-version matrix the same way MobilityDB supports PostgreSQL 13-18 — +# so the staging path must NOT hardcode the version or platform. We derive both +# from the freshly-built duckdb binary (authoritative for whatever is being +# tested); DUCKDB_VERSION_TAG and the uname map are fallbacks only. +DUCKDB_VERSION_TAG := v1.4.4 + +define stage_icu + @if [ -f ./build/$(1)/extension/icu/icu.duckdb_extension ]; then \ + duckdb_bin=./build/$(1)/duckdb; \ + version_tag=$$( [ -x "$$duckdb_bin" ] && "$$duckdb_bin" --version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1 ); \ + platform=$$( [ -x "$$duckdb_bin" ] && echo 'PRAGMA platform;' | "$$duckdb_bin" -noheader -list 2>/dev/null | tr -d '[:space:]' ); \ + [ -n "$$version_tag" ] || version_tag=$(DUCKDB_VERSION_TAG); \ + if [ -z "$$platform" ]; then \ + case "$$(uname -s)-$$(uname -m)" in \ + Linux-x86_64) platform=linux_amd64 ;; \ + Linux-aarch64) platform=linux_arm64 ;; \ + Darwin-arm64) platform=osx_arm64 ;; \ + Darwin-x86_64) platform=osx_amd64 ;; \ + *) platform=$$(uname -m) ;; \ + esac; \ + fi; \ + target=$$HOME/.duckdb/extensions/$$version_tag/$$platform; \ + mkdir -p "$$target" && cp -f ./build/$(1)/extension/icu/icu.duckdb_extension "$$target/" && \ + echo "Staged icu.duckdb_extension at $$target/ (duckdb $$version_tag / $$platform)"; \ + fi +endef + test_release_internal: + $(call stage_icu,release) ./build/release/$(TEST_PATH) "$(PROJ_DIR)test/*" test_debug_internal: + $(call stage_icu,debug) ./build/debug/$(TEST_PATH) "$(PROJ_DIR)test/*" test_reldebug_internal: + $(call stage_icu,reldebug) ./build/reldebug/$(TEST_PATH) "$(PROJ_DIR)test/*" \ No newline at end of file diff --git a/src/geo/tgeogpoint_ops.cpp b/src/geo/tgeogpoint_ops.cpp index 91136138..73cf4566 100644 --- a/src/geo/tgeogpoint_ops.cpp +++ b/src/geo/tgeogpoint_ops.cpp @@ -251,7 +251,7 @@ inline string_t TemporalToBlob(Vector &result, Temporal *t) { return out; } -template +template void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -259,14 +259,14 @@ void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, false, false); + Temporal *r = FN(t, gs); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -274,21 +274,21 @@ void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, false, false); + Temporal *r = FN(gs, t); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, false, false); + Temporal *r = FN(t1, t2); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); @@ -296,7 +296,7 @@ void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { } // tDwithin variants take an extra distance argument. -template +template void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -304,14 +304,14 @@ void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, dist, false, false); + Temporal *r = FN(t, gs, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -319,21 +319,21 @@ void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, dist, false, false); + Temporal *r = FN(gs, t, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t a, string_t b, double dist, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, dist, false, false); + Temporal *r = FN(t1, t2, dist); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); diff --git a/src/geo/tgeography_ops.cpp b/src/geo/tgeography_ops.cpp index 1eee6ed0..af2dac14 100644 --- a/src/geo/tgeography_ops.cpp +++ b/src/geo/tgeography_ops.cpp @@ -252,7 +252,7 @@ inline string_t TemporalToBlob(Vector &result, Temporal *t) { return out; } -template +template void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -260,14 +260,14 @@ void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, false, false); + Temporal *r = FN(t, gs); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -275,21 +275,21 @@ void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, false, false); + Temporal *r = FN(gs, t); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, false, false); + Temporal *r = FN(t1, t2); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); @@ -297,7 +297,7 @@ void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { } // tDwithin variants take an extra distance argument. -template +template void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -305,14 +305,14 @@ void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, dist, false, false); + Temporal *r = FN(t, gs, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -320,21 +320,21 @@ void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, dist, false, false); + Temporal *r = FN(gs, t, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t a, string_t b, double dist, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, dist, false, false); + Temporal *r = FN(t1, t2, dist); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); diff --git a/src/geo/tgeometry_ops.cpp b/src/geo/tgeometry_ops.cpp index 2db1613a..d66a936d 100644 --- a/src/geo/tgeometry_ops.cpp +++ b/src/geo/tgeometry_ops.cpp @@ -252,7 +252,7 @@ inline string_t TemporalToBlob(Vector &result, Temporal *t) { return out; } -template +template void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -260,14 +260,14 @@ void TgeoGeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, false, false); + Temporal *r = FN(t, gs); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), @@ -275,21 +275,21 @@ void GeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, false, false); + Temporal *r = FN(gs, t); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, false, false); + Temporal *r = FN(t1, t2); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); @@ -297,7 +297,7 @@ void TgeoTgeoTempExec(DataChunk &args, ExpressionState &, Vector &result) { } // tDwithin variants take an extra distance argument. -template +template void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -305,14 +305,14 @@ void TgeoGeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(t, gs, dist, false, false); + Temporal *r = FN(t, gs, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), @@ -320,21 +320,21 @@ void GeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { Temporal *t = DecodeTemporalCopy(t_blob); int32 srid = tspatial_srid(t); GSERIALIZED *gs = GeometryToGSerialized(g_blob, srid); - Temporal *r = FN(gs, t, dist, false, false); + Temporal *r = FN(gs, t, dist); free(t); free(gs); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); }); } -template +template void TgeoTgeoDistTempExec(DataChunk &args, ExpressionState &, Vector &result) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t a, string_t b, double dist, ValidityMask &mask, idx_t idx) -> string_t { Temporal *t1 = DecodeTemporalCopy(a); Temporal *t2 = DecodeTemporalCopy(b); - Temporal *r = FN(t1, t2, dist, false, false); + Temporal *r = FN(t1, t2, dist); free(t1); free(t2); if (!r) { mask.SetInvalid(idx); return string_t(); } return TemporalToBlob(result, r); diff --git a/src/geo/tgeompoint.cpp b/src/geo/tgeompoint.cpp index 2bc5e6a0..5582bbda 100644 --- a/src/geo/tgeompoint.cpp +++ b/src/geo/tgeompoint.cpp @@ -2048,12 +2048,12 @@ void TgeoAsMVTGeomExec(DataChunk &args, ExpressionState &state, Vector &result) if (cc > 3) buffer = FlatVector::GetData(args.data[3])[row]; if (cc > 4) clip = FlatVector::GetData(args.data[4])[row]; - GSERIALIZED *geom = nullptr; - int64 *times = nullptr; - int count = 0; - bool found = tpoint_as_mvtgeom(t, bx, extent, buffer, clip, &geom, ×, &count); + MvtGeom mvt = tpoint_as_mvtgeom(t, bx, extent, buffer, clip); free(t); free(bx); - if (!found || !geom) { + GSERIALIZED *geom = mvt.geom; + int64 *times = mvt.times; + int count = mvt.count; + if (!geom) { out_validity.SetInvalid(row); times_entries[row] = list_entry_t{total_times, 0}; if (geom) free(geom); @@ -2232,12 +2232,18 @@ unique_ptr SpaceSplitInitCommon(ClientContext &context if (bind.has_torigin) { torigin = (TimestampTz) DuckDBToMeosTimestamp(bind.torigin).value; } - trajs = tgeo_space_time_split(temp, bind.xsize, bind.ysize, bind.zsize, - &mi, origin, torigin, bind.bitmatrix, true, - &bins, &tbins, &count); + SpaceTimeSplit sts = tgeo_space_time_split(temp, bind.xsize, bind.ysize, bind.zsize, + &mi, origin, torigin, bind.bitmatrix, true); + trajs = sts.fragments; + bins = sts.space_bins; + tbins = sts.time_bins; + count = sts.count; } else { - trajs = tgeo_space_split(temp, bind.xsize, bind.ysize, bind.zsize, - origin, bind.bitmatrix, true, &bins, &count); + SpaceSplit ss = tgeo_space_split(temp, bind.xsize, bind.ysize, bind.zsize, + origin, bind.bitmatrix, true); + trajs = ss.fragments; + bins = ss.bins; + count = ss.count; } free(temp); free(origin); diff --git a/src/geo/tgeompoint_functions.cpp b/src/geo/tgeompoint_functions.cpp index 9092e6e4..4f088600 100644 --- a/src/geo/tgeompoint_functions.cpp +++ b/src/geo/tgeompoint_functions.cpp @@ -455,7 +455,7 @@ void TgeompointFunctions::Tgeompoint_sequence_constructor(DataChunk &args, Expre auto arg_count = args.ColumnCount(); auto row_count = args.size(); meosType temptype = TemporalHelpers::GetTemptypeFromAlias(result.GetType().GetAlias().c_str()); - interpType interp = temptype_continuous(temptype) ? LINEAR : STEP; + interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP; bool lower_inc = true; bool upper_inc = true; @@ -1286,7 +1286,7 @@ void TgeompointFunctions::Tgeo_minus_geom(DataChunk &args, ExpressionState &stat throw InvalidInputException("Invalid geometry format: " + geometry_blob.GetString()); } - Temporal *ret = zspan ? tpoint_minus_geom(tgeom, gs, zspan) : tgeo_minus_geom(tgeom, gs); + Temporal *ret = zspan ? tpoint_minus_geom(tgeom, gs) : tgeo_minus_geom(tgeom, gs); free(tgeom); free(gs); if (!ret) { @@ -2419,7 +2419,7 @@ void TgeompointFunctions::Adwithin_tgeo_tgeo(DataChunk &args, ExpressionState &s ****************************************************/ void TgeompointFunctions::Tcontains_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { const idx_t count = args.size(); - auto eval = [&](string_t geometry_blob, string_t tgeom_blob, bool restr, bool at_value, ValidityMask &mask, + auto eval = [&](string_t geometry_blob, string_t tgeom_blob, ValidityMask &mask, idx_t idx) -> string_t { int32 srid = 0; GSERIALIZED *gs = GeometryToGSerialized(geometry_blob, srid); @@ -2438,7 +2438,7 @@ void TgeompointFunctions::Tcontains_geo_tgeo(DataChunk &args, ExpressionState &s throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tcontains_geo_tgeo(gs, tgeom, restr, at_value); + Temporal *ret = tcontains_geo_tgeo(gs, tgeom); free(tgeom); free(gs); if (!ret) { @@ -2456,14 +2456,14 @@ void TgeompointFunctions::Tcontains_geo_tgeo(DataChunk &args, ExpressionState &s BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, count, [&](string_t geometry_blob, string_t tgeom_blob, ValidityMask &mask, idx_t idx) -> string_t { - return eval(geometry_blob, tgeom_blob, false, false, mask, idx); + return eval(geometry_blob, tgeom_blob, mask, idx); }); } else if (args.ColumnCount() == 3) { TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, count, - [&](string_t geometry_blob, string_t tgeom_blob, bool at_value, ValidityMask &mask, + [&](string_t geometry_blob, string_t tgeom_blob, bool, ValidityMask &mask, idx_t idx) -> string_t { - return eval(geometry_blob, tgeom_blob, true, at_value, mask, idx); + return eval(geometry_blob, tgeom_blob, mask, idx); }); } else { throw InternalException("Tcontains_geo_tgeo: expected 2 or 3 arguments"); @@ -2474,12 +2474,6 @@ void TgeompointFunctions::Tcontains_geo_tgeo(DataChunk &args, ExpressionState &s } void TgeompointFunctions::Tdisjoint_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t geometry_blob, string_t tgeom_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2500,7 +2494,7 @@ void TgeompointFunctions::Tdisjoint_geo_tgeo(DataChunk &args, ExpressionState &s throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tdisjoint_geo_tgeo(gs, tgeom, restr, at_value); + Temporal *ret = tdisjoint_geo_tgeo(gs, tgeom); free(tgeom); free(gs); if (!ret) { @@ -2519,12 +2513,6 @@ void TgeompointFunctions::Tdisjoint_geo_tgeo(DataChunk &args, ExpressionState &s } void TgeompointFunctions::Tdisjoint_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t tgeom_blob, string_t geometry_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2545,7 +2533,7 @@ void TgeompointFunctions::Tdisjoint_tgeo_geo(DataChunk &args, ExpressionState &s throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tdisjoint_tgeo_geo(tgeom, gs, restr, at_value); + Temporal *ret = tdisjoint_tgeo_geo(tgeom, gs); free(tgeom); free(gs); if (!ret) { @@ -2564,12 +2552,6 @@ void TgeompointFunctions::Tdisjoint_tgeo_geo(DataChunk &args, ExpressionState &s } void TgeompointFunctions::Tdisjoint_tgeo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t tgeom1_blob, string_t tgeom2_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2594,7 +2576,7 @@ void TgeompointFunctions::Tdisjoint_tgeo_tgeo(DataChunk &args, ExpressionState & throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tdisjoint_tgeo_tgeo(tgeom1, tgeom2, restr, at_value); + Temporal *ret = tdisjoint_tgeo_tgeo(tgeom1, tgeom2); free(tgeom1); free(tgeom2); if (!ret) { @@ -2613,12 +2595,6 @@ void TgeompointFunctions::Tdisjoint_tgeo_tgeo(DataChunk &args, ExpressionState & } void TgeompointFunctions::Tintersects_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t geometry_blob, string_t tgeom_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2639,7 +2615,7 @@ void TgeompointFunctions::Tintersects_geo_tgeo(DataChunk &args, ExpressionState throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tintersects_geo_tgeo(gs, tgeom, restr, at_value); + Temporal *ret = tintersects_geo_tgeo(gs, tgeom); free(tgeom); free(gs); if (!ret) { @@ -2658,12 +2634,6 @@ void TgeompointFunctions::Tintersects_geo_tgeo(DataChunk &args, ExpressionState } void TgeompointFunctions::Tintersects_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t tgeom_blob, string_t geometry_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2684,7 +2654,7 @@ void TgeompointFunctions::Tintersects_tgeo_geo(DataChunk &args, ExpressionState throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tintersects_tgeo_geo(tgeom, gs, restr, at_value); + Temporal *ret = tintersects_tgeo_geo(tgeom, gs); free(tgeom); free(gs); if (!ret) { @@ -2703,12 +2673,6 @@ void TgeompointFunctions::Tintersects_tgeo_geo(DataChunk &args, ExpressionState } void TgeompointFunctions::Tintersects_tgeo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t tgeom1_blob, string_t tgeom2_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2733,7 +2697,7 @@ void TgeompointFunctions::Tintersects_tgeo_tgeo(DataChunk &args, ExpressionState throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tintersects_tgeo_tgeo(tgeom1, tgeom2, restr, at_value); + Temporal *ret = tintersects_tgeo_tgeo(tgeom1, tgeom2); free(tgeom1); free(tgeom2); if (!ret) { @@ -2752,12 +2716,6 @@ void TgeompointFunctions::Tintersects_tgeo_tgeo(DataChunk &args, ExpressionState } void TgeompointFunctions::Ttouches_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t geometry_blob, string_t tgeom_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2778,7 +2736,7 @@ void TgeompointFunctions::Ttouches_geo_tgeo(DataChunk &args, ExpressionState &st throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = ttouches_geo_tgeo(gs, tgeom, restr, at_value); + Temporal *ret = ttouches_geo_tgeo(gs, tgeom); free(tgeom); free(gs); if (!ret) { @@ -2797,12 +2755,6 @@ void TgeompointFunctions::Ttouches_geo_tgeo(DataChunk &args, ExpressionState &st } void TgeompointFunctions::Ttouches_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 2){ - at_value = args.data[2].GetValue(0).GetValue(); - restr = true; - } BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, args.size(), [&](string_t tgeom_blob, string_t geometry_blob, ValidityMask &mask, idx_t idx) -> string_t { @@ -2823,7 +2775,7 @@ void TgeompointFunctions::Ttouches_tgeo_geo(DataChunk &args, ExpressionState &st throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = ttouches_tgeo_geo(tgeom, gs, restr, at_value); + Temporal *ret = ttouches_tgeo_geo(tgeom, gs); free(tgeom); free(gs); if (!ret) { @@ -2842,12 +2794,6 @@ void TgeompointFunctions::Ttouches_tgeo_geo(DataChunk &args, ExpressionState &st } void TgeompointFunctions::Tdwithin_tgeo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 3) { - at_value = args.data[3].GetValue(0).GetValue(); - restr = true; - } TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t tgeom1_blob, string_t tgeom2_blob, double dist, ValidityMask &mask, idx_t idx) -> string_t { @@ -2871,7 +2817,7 @@ void TgeompointFunctions::Tdwithin_tgeo_tgeo(DataChunk &args, ExpressionState &s free(tgeom2_data_copy); throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tdwithin_tgeo_tgeo(tgeom1, tgeom2, dist, restr, at_value); + Temporal *ret = tdwithin_tgeo_tgeo(tgeom1, tgeom2, dist); if (!ret) { free(tgeom1); free(tgeom2); @@ -2892,12 +2838,6 @@ void TgeompointFunctions::Tdwithin_tgeo_tgeo(DataChunk &args, ExpressionState &s } void TgeompointFunctions::Tdwithin_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 3) { - at_value = args.data[3].GetValue(0).GetValue(); - restr = true; - } TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t tgeom_blob, string_t geometry_blob, double dist, ValidityMask &mask, idx_t idx) -> string_t { @@ -2918,7 +2858,7 @@ void TgeompointFunctions::Tdwithin_tgeo_geo(DataChunk &args, ExpressionState &st throw InvalidInputException("Invalid geometry format: " + geometry_blob.GetString()); } - Temporal *ret = tdwithin_tgeo_geo(tgeom, gs, dist, restr, at_value); + Temporal *ret = tdwithin_tgeo_geo(tgeom, gs, dist); free(tgeom); free(gs); if (!ret) { @@ -2937,12 +2877,6 @@ void TgeompointFunctions::Tdwithin_tgeo_geo(DataChunk &args, ExpressionState &st } void TgeompointFunctions::Tdwithin_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { - bool at_value = false; - bool restr = false; - if (args.ColumnCount() > 3) { - at_value = args.data[3].GetValue(0).GetValue(); - restr = true; - } TernaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], args.data[2], result, args.size(), [&](string_t geometry_blob, string_t tgeom_blob, double dist, ValidityMask &mask, idx_t idx) -> string_t { @@ -2963,7 +2897,7 @@ void TgeompointFunctions::Tdwithin_geo_tgeo(DataChunk &args, ExpressionState &st throw InvalidInputException("Invalid TGEOMPOINT data: null pointer"); } - Temporal *ret = tdwithin_geo_tgeo(gs, tgeom, dist, restr, at_value); + Temporal *ret = tdwithin_geo_tgeo(gs, tgeom, dist); free(tgeom); free(gs); if (!ret) { diff --git a/src/include/index/rtree_module.hpp b/src/include/index/rtree_module.hpp index 5cb45811..5e48e2ad 100644 --- a/src/include/index/rtree_module.hpp +++ b/src/include/index/rtree_module.hpp @@ -70,21 +70,21 @@ class TRTreeIndex : public BoundIndex { bool TryMatchDistanceFunction(const unique_ptr &expr, vector> &bindings) const; - meosType GetBboxType() const { return bbox_type_; } + MeosType GetBboxType() const { return bbox_type_; } size_t GetBboxSize() const { return bbox_size_; } private: case_insensitive_map_t options_; - + unique_ptr function_matcher; unique_ptr MakeFunctionMatcher() const; RTree *rtree_; void *boxes; - meosType bbox_type_; + MeosType bbox_type_; size_t bbox_size_; size_t current_size_ = 0; diff --git a/src/include/quadbin/raster_quadbin.hpp b/src/include/quadbin/raster_quadbin.hpp new file mode 100644 index 00000000..1eedd9ea --- /dev/null +++ b/src/include/quadbin/raster_quadbin.hpp @@ -0,0 +1,26 @@ +/* MobilityDuck binding for MEOS Raquet raster chip-sampling functions. + * + * Two MEOS kernel functions are exposed: + * rasterTileValueQuadbin — sample a Raquet band BLOB along a tgeompoint + * trajectory, keyed by QUADBIN cell; returns TFLOAT. + * trajectoryQuadbins — list the QUADBIN cells (at a zoom level) that a + * tgeompoint trajectory crosses; used as the + * WHERE-clause join key against a Raquet table. + */ + +#pragma once + +#include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/main/extension/extension_loader.hpp" + +namespace duckdb { + +struct RasterQuadbinFunctions { + static void Raster_tile_value_quadbin( + DataChunk &args, ExpressionState &state, Vector &result); + static void Trajectory_quadbins( + DataChunk &args, ExpressionState &state, Vector &result); + static void RegisterScalarFunctions(ExtensionLoader &loader); +}; + +} // namespace duckdb diff --git a/src/include/quadbin/tquadbin.hpp b/src/include/quadbin/tquadbin.hpp new file mode 100644 index 00000000..27f41822 --- /dev/null +++ b/src/include/quadbin/tquadbin.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "meos_wrapper_simple.hpp" +#include "duckdb/common/exception.hpp" +#include "duckdb/common/string_util.hpp" +#include "duckdb/function/scalar_function.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include + +namespace duckdb { + +/* QUADBIN is a 64-bit unsigned cell id (CARTO QUADBIN square-quadtree DGGS); + * surfaced as BIGINT (signed reinterpretation is safe — equality and ordering + * care only about the bit pattern). TQUADBIN is the temporal cell index, + * stored as a Temporal* blob (BLOB). Analogue of H3INDEX / TH3INDEX. */ +struct QuadbinTypes { + static LogicalType QUADBIN(); + static LogicalType TQUADBIN(); + + static void RegisterTypes(ExtensionLoader &loader); + static void RegisterCastFunctions(ExtensionLoader &loader); + static void RegisterScalarFunctions(ExtensionLoader &loader); +}; + +struct QuadbinFunctions { + /* In/out — static QUADBIN cell (BIGINT) */ + static bool Quadbin_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool Quadbin_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + + /* In/out — TQUADBIN temporal value */ + static bool Tquadbin_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool Tquadbin_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + + /* Static cell helpers */ + static void Quadbin_tile_to_cell(DataChunk &args, ExpressionState &state, Vector &result); + static void Quadbin_cell_to_tile_x(DataChunk &args, ExpressionState &state, Vector &result); + static void Quadbin_cell_to_tile_y(DataChunk &args, ExpressionState &state, Vector &result); + static void Quadbin_cell_to_tile_z(DataChunk &args, ExpressionState &state, Vector &result); + static void Quadbin_get_resolution(DataChunk &args, ExpressionState &state, Vector &result); + static void Quadbin_is_valid_cell(DataChunk &args, ExpressionState &state, Vector &result); + + /* Constructor */ + static void Tquadbin_make(DataChunk &args, ExpressionState &state, Vector &result); + + /* Accessors */ + static void Tquadbin_start_value(DataChunk &args, ExpressionState &state, Vector &result); + static void Tquadbin_end_value(DataChunk &args, ExpressionState &state, Vector &result); + static void Tquadbin_value_n(DataChunk &args, ExpressionState &state, Vector &result); + static void Tquadbin_values(DataChunk &args, ExpressionState &state, Vector &result); + static void Tquadbin_value_at_timestamptz(DataChunk &args, ExpressionState &state, Vector &result); + + /* Quadkey conversion */ + static void Tquadbin_cell_to_quadkey(DataChunk &args, ExpressionState &state, Vector &result); + + /* Casts to/from tbigint */ + static void Tbigint_to_tquadbin(DataChunk &args, ExpressionState &state, Vector &result); + static void Tquadbin_to_tbigint(DataChunk &args, ExpressionState &state, Vector &result); +}; + +} // namespace duckdb diff --git a/src/index/rtree_module.cpp b/src/index/rtree_module.cpp index d05e4be8..fbe95060 100644 --- a/src/index/rtree_module.cpp +++ b/src/index/rtree_module.cpp @@ -393,25 +393,22 @@ vector TRTreeIndex::Search(const void *query_box, RTreeSearchOp op) const return results; } - int count = 0; - int *ids = nullptr; - + MeosArray *ids = meos_array_create(sizeof(int)); + try { - ids = rtree_search(rtree_, op, query_box, &count); - - if (ids && count > 0) { + int count = rtree_search(rtree_, op, query_box, ids); + + if (count > 0) { results.reserve(count); for (int i = 0; i < count; i++) { - results.push_back(static_cast(ids[i])); + results.push_back(static_cast(*(int *) meos_array_get(ids, i))); } } } catch (...) { fprintf(stderr, "Exception during rtree_search\n"); } - - if (ids) { - free(ids); - } + + meos_array_destroy(ids); return results; } diff --git a/src/mobilityduck_extension.cpp b/src/mobilityduck_extension.cpp index 0eb7ebd4..7c4bf239 100644 --- a/src/mobilityduck_extension.cpp +++ b/src/mobilityduck_extension.cpp @@ -1,5 +1,6 @@ #define DUCKDB_EXTENSION_MAIN +#include #include "mobilityduck_extension.hpp" #include "temporal/spanset.hpp" #include "temporal/set.hpp" @@ -17,6 +18,8 @@ #include "geo/tgeography_ops.hpp" #include "geo/tgeogpoint.hpp" #include "geo/tgeogpoint_ops.hpp" +#include "quadbin/tquadbin.hpp" +#include "quadbin/raster_quadbin.hpp" #include "temporal/span.hpp" #include "temporal/span_aggregates.hpp" #include "temporal/temporal_aggregates.hpp" @@ -81,7 +84,7 @@ inline void MobilityduckOpenSSLVersionScalarFun(DataChunk &args, ExpressionState // MEOS does not expose a runtime version symbol, so the build-time pin // is the most precise version stamp the extension can report. #ifndef MOBILITYDUCK_MEOS_PIN -#define MOBILITYDUCK_MEOS_PIN "f11b7443e" +#define MOBILITYDUCK_MEOS_PIN "88e5d5b430" #endif inline std::string MobilityduckShortVersion() { @@ -227,12 +230,26 @@ static void LoadInternal(ExtensionLoader &loader) { // Single-timezone model: ensure DuckDB's session timezone matches the // MEOS timezone so bare TIMESTAMPTZ display agrees with MEOS composite - // type strings. Auto-load ICU (without it, the test framework keeps - // session timezone at UTC) and set the TimeZone option to Brussels. + // type strings. This needs ICU for the named "Europe/Brussels" zone. + // + // If ICU cannot be auto-loaded (no on-disk copy AND no network egress: + // CI docker images, edge/musl deployments, offline installs), degrade + // gracefully to the session default (UTC) instead of failing the whole + // extension load. Tests that assert Brussels display stage ICU locally + // via the Makefile's stage_icu. auto &db = loader.GetDatabaseInstance(); - ExtensionHelper::AutoLoadExtension(db, "icu"); - auto &config = DBConfig::GetConfig(db); - config.SetOptionByName("TimeZone", Value("Europe/Brussels")); + try { + ExtensionHelper::AutoLoadExtension(db, "icu"); + auto &config = DBConfig::GetConfig(db); + config.SetOptionByName("TimeZone", Value("Europe/Brussels")); + } catch (const std::exception &e) { + // ICU unavailable: leave the session timezone at its default. + // Temporal-type text I/O is unaffected; only bare TIMESTAMPTZ display + // falls back to UTC. + fprintf(stderr, + "mobilityduck: ICU not available (%s); session timezone left " + "at default instead of Europe/Brussels.\n", e.what()); + } // Register scalar function: mobilityduck_openssl_version @@ -326,6 +343,11 @@ static void LoadInternal(ExtensionLoader &loader) { SpatialSetType::RegisterCastFunctions(loader); SpatialSetType::RegisterScalarFunctions(loader); + QuadbinTypes::RegisterTypes(loader); + QuadbinTypes::RegisterCastFunctions(loader); + QuadbinTypes::RegisterScalarFunctions(loader); + RasterQuadbinFunctions::RegisterScalarFunctions(loader); + SpansetTypes::RegisterTypes(loader); SpansetTypes::RegisterCastFunctions(loader); SpansetTypes::RegisterScalarFunctions(loader); diff --git a/src/quadbin/raster_quadbin.cpp b/src/quadbin/raster_quadbin.cpp new file mode 100644 index 00000000..ec60e3b3 --- /dev/null +++ b/src/quadbin/raster_quadbin.cpp @@ -0,0 +1,218 @@ +/* MobilityDuck binding for MEOS Raquet raster chip-sampling functions. + * + * rasterTileValueQuadbin — sample a Raquet band BLOB along a tgeompoint + * trajectory, returning the sampled values as TFLOAT. + * trajectoryQuadbins — list the QUADBIN cells covered by a tgeompoint + * trajectory at a given zoom level, for Raquet table joins. + * + * DuckDB camelCase naming: raster_tile_value_quadbin → rasterTileValueQuadbin, + * trajectory_quadbins → trajectoryQuadbins. + */ + +#include "quadbin/raster_quadbin.hpp" + +#include "quadbin/tquadbin.hpp" +#include "geo/tgeompoint.hpp" +#include "temporal/temporal.hpp" +#include "mobilityduck/meos_exec_serial.hpp" + +#include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include "duckdb/function/scalar_function.hpp" + +extern "C" { + #include /* MeosPixType, raster_tile_value_quadbin, + trajectory_quadbins */ + #include /* temporal_mem_size */ +} + +#include +#include + +namespace duckdb { + +namespace { + +inline Temporal *BlobToTemp(string_t blob) { + size_t sz = blob.GetSize(); + uint8_t *copy = static_cast(malloc(sz)); + memcpy(copy, blob.GetData(), sz); + return reinterpret_cast(copy); +} + +inline string_t TempToBlob(Vector &result, Temporal *t) { + if (!t) return string_t(); + size_t sz = temporal_mem_size(t); + string_t out = StringVector::AddStringOrBlob( + result, string_t(reinterpret_cast(t), sz)); + free(t); + return out; +} + +/* Map a VARCHAR pixtype name to its MeosPixType enum value. + * Sets *ok=false on an unknown name. */ +static MeosPixType +str_to_pixtype(const char *s, size_t len, bool *ok) { + *ok = true; + if (len == 5 && memcmp(s, "UINT8", 5) == 0) return MEOS_PT_UINT8; + if (len == 5 && memcmp(s, "INT16", 5) == 0) return MEOS_PT_INT16; + if (len == 5 && memcmp(s, "INT32", 5) == 0) return MEOS_PT_INT32; + if (len == 7 && memcmp(s, "FLOAT32", 7) == 0) return MEOS_PT_FLOAT32; + if (len == 7 && memcmp(s, "FLOAT64", 7) == 0) return MEOS_PT_FLOAT64; + *ok = false; + return MEOS_PT_UINT8; +} + +} // namespace + +/* ========================================================================= + * rasterTileValueQuadbin + * ========================================================================= */ + +void RasterQuadbinFunctions::Raster_tile_value_quadbin( + DataChunk &args, ExpressionState &, Vector &result) +{ + /* args[0] pixels BLOB – raw row-major pixel bytes + * args[1] width INTEGER – tile width in pixels + * args[2] height INTEGER – tile height in pixels + * args[3] cell BIGINT(QUADBIN)– CARTO QUADBIN cell id + * args[4] pixtype VARCHAR – "UINT8"|"INT16"|"INT32"|"FLOAT32"|"FLOAT64" + * args[5] nodata DOUBLE – nodata sentinel value + * args[6] has_nodata BOOLEAN – enable nodata filtering + * args[7] traj BLOB(TGEOMPOINT) – trajectory, SRID 4326 + * Returns BLOB (TFLOAT) */ + const idx_t row_count = args.size(); + for (idx_t c = 0; c < args.ColumnCount(); ++c) + args.data[c].Flatten(row_count); + + auto px_data = FlatVector::GetData(args.data[0]); + auto width_data = FlatVector::GetData (args.data[1]); + auto height_data = FlatVector::GetData (args.data[2]); + auto cell_data = FlatVector::GetData (args.data[3]); + auto pixtype_data = FlatVector::GetData(args.data[4]); + auto nodata_data = FlatVector::GetData (args.data[5]); + auto hasnd_data = FlatVector::GetData (args.data[6]); + auto traj_data = FlatVector::GetData(args.data[7]); + + auto &out_validity = FlatVector::Validity(result); + auto out_data = FlatVector::GetData(result); + + for (idx_t i = 0; i < row_count; ++i) { + /* NULL propagation */ + bool any_null = false; + for (idx_t c = 0; c < args.ColumnCount(); ++c) { + if (!FlatVector::Validity(args.data[c]).RowIsValid(i)) { + any_null = true; + break; + } + } + if (any_null) { out_validity.SetInvalid(i); continue; } + + string_t pt = pixtype_data[i]; + bool pixtype_ok; + MeosPixType pixtype = str_to_pixtype(pt.GetData(), pt.GetSize(), &pixtype_ok); + if (!pixtype_ok) { out_validity.SetInvalid(i); continue; } + + const uint8_t *pixels = + reinterpret_cast(px_data[i].GetData()); + Temporal *traj = BlobToTemp(traj_data[i]); + + Temporal *res = raster_tile_value_quadbin( + pixels, + static_cast(width_data[i]), + static_cast(height_data[i]), + static_cast(cell_data[i]), + pixtype, + nodata_data[i], + hasnd_data[i], + traj); + + free(traj); + + if (!res) { + out_validity.SetInvalid(i); + continue; + } + out_data[i] = TempToBlob(result, res); + } +} + +/* ========================================================================= + * trajectoryQuadbins + * ========================================================================= */ + +void RasterQuadbinFunctions::Trajectory_quadbins( + DataChunk &args, ExpressionState &, Vector &result) +{ + /* args[0] traj BLOB (TGEOMPOINT) + * args[1] zoom INTEGER (0–26) + * Returns LIST (QUADBIN cell ids as signed BIGINT) */ + const idx_t row_count = args.size(); + for (idx_t c = 0; c < args.ColumnCount(); ++c) + args.data[c].Flatten(row_count); + + auto traj_data = FlatVector::GetData(args.data[0]); + auto zoom_data = FlatVector::GetData (args.data[1]); + + auto &out_validity = FlatVector::Validity(result); + auto list_entries = FlatVector::GetData(result); + idx_t total = 0; + + for (idx_t i = 0; i < row_count; ++i) { + if (!FlatVector::Validity(args.data[0]).RowIsValid(i) || + !FlatVector::Validity(args.data[1]).RowIsValid(i)) { + out_validity.SetInvalid(i); + list_entries[i] = list_entry_t{total, 0}; + continue; + } + + Temporal *traj = BlobToTemp(traj_data[i]); + int ncells = 0; + uint64_t *cells = trajectory_quadbins( + traj, static_cast(zoom_data[i]), &ncells); + free(traj); + + if (!cells || ncells <= 0) { + if (cells) free(cells); + list_entries[i] = list_entry_t{total, 0}; + continue; + } + + ListVector::Reserve(result, total + (idx_t) ncells); + ListVector::SetListSize(result, total + (idx_t) ncells); + list_entries[i] = list_entry_t{total, static_cast(ncells)}; + + auto child = FlatVector::GetData(ListVector::GetEntry(result)); + for (int j = 0; j < ncells; j++) + child[total + j] = static_cast(cells[j]); + total += (idx_t) ncells; + free(cells); + } +} + +/* ========================================================================= + * Registration + * ========================================================================= */ + +void RasterQuadbinFunctions::RegisterScalarFunctions(ExtensionLoader &loader) +{ + const auto QB = QuadbinTypes::QUADBIN(); + const auto TGMP = TgeompointType::TGEOMPOINT(); + const auto TF = TemporalTypes::TFLOAT(); + const auto I32 = LogicalType::INTEGER; + const auto DBL = LogicalType::DOUBLE; + const auto BOOL = LogicalType::BOOLEAN; + const auto V = LogicalType::VARCHAR; + + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "rasterTileValueQuadbin", + {LogicalType::BLOB, I32, I32, QB, V, DBL, BOOL, TGMP}, TF, + RasterQuadbinFunctions::Raster_tile_value_quadbin)); + + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "trajectoryQuadbins", + {TGMP, I32}, LogicalType::LIST(QB), + RasterQuadbinFunctions::Trajectory_quadbins)); +} + +} // namespace duckdb diff --git a/src/quadbin/tquadbin.cpp b/src/quadbin/tquadbin.cpp new file mode 100644 index 00000000..fb176c18 --- /dev/null +++ b/src/quadbin/tquadbin.cpp @@ -0,0 +1,456 @@ +/* MobilityDuck binding for the MEOS QUADBIN cell index types (quadbin + + * tquadbin). Wraps every implemented export from `meos_quadbin.h` so DuckDB + * SQL can call the full static-cell and temporal-cell surface. + * + * QUADBIN is surfaced as BIGINT (the 64-bit cell id reinterprets losslessly). + * TQUADBIN is the temporal cell index, stored as a Temporal* blob (BLOB). + * The analogue of H3INDEX / TH3INDEX for the square Web-Mercator DGGS. + */ + +#include "quadbin/tquadbin.hpp" +#include "temporal/temporal.hpp" +#include "tydef.hpp" +#include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include "duckdb/common/extension_type_info.hpp" +#include "duckdb/function/scalar_function.hpp" +#include "mobilityduck/meos_exec_serial.hpp" +#include "time_util.hpp" + +extern "C" { + #include + #include + #include +} + +#include +#include +#include +#include + +namespace duckdb { + +LogicalType QuadbinTypes::QUADBIN() { + LogicalType type = LogicalType::BIGINT; + type.SetAlias("QUADBIN"); + return type; +} + +LogicalType QuadbinTypes::TQUADBIN() { + auto type = LogicalType(LogicalTypeId::BLOB); + type.SetAlias("TQUADBIN"); + return type; +} + +void QuadbinTypes::RegisterTypes(ExtensionLoader &loader) { + loader.RegisterType("QUADBIN", QUADBIN()); + loader.RegisterType("TQUADBIN", TQUADBIN()); +} + +void QuadbinTypes::RegisterCastFunctions(ExtensionLoader &loader) { + loader.RegisterCastFunction(LogicalType::VARCHAR, QUADBIN(), + QuadbinFunctions::Quadbin_in_cast); + loader.RegisterCastFunction(QUADBIN(), LogicalType::VARCHAR, + QuadbinFunctions::Quadbin_out_cast); + loader.RegisterCastFunction(LogicalType::VARCHAR, TQUADBIN(), + QuadbinFunctions::Tquadbin_in_cast); + loader.RegisterCastFunction(TQUADBIN(), LogicalType::VARCHAR, + QuadbinFunctions::Tquadbin_out_cast); +} + +namespace { + +inline Temporal *BlobToTemp(string_t blob) { + size_t sz = blob.GetSize(); + uint8_t *copy = (uint8_t *) malloc(sz); + memcpy(copy, blob.GetData(), sz); + return reinterpret_cast(copy); +} + +inline string_t TempToBlob(Vector &result, Temporal *t) { + if (!t) return string_t(); + size_t sz = temporal_mem_size(t); + string_t out = StringVector::AddStringOrBlob( + result, string_t(reinterpret_cast(t), sz)); + free(t); + return out; +} + +} // namespace + +/* ===================================================================== + * In / out — static QUADBIN cell (BIGINT) + * + * QUADBIN cells are represented as decimal uint64 integers in CARTO's + * canonical form (e.g. 5193776270265024512). + * ===================================================================== */ + +bool QuadbinFunctions::Quadbin_in_cast( + Vector &source, Vector &result, idx_t count, CastParameters ¶meters) +{ + UnaryExecutor::Execute( + source, result, count, + [&](string_t s) -> int64_t { + std::string str(s.GetData(), s.GetSize()); + uint64_t v = std::stoull(str); + return static_cast(v); + }); + return true; +} + +bool QuadbinFunctions::Quadbin_out_cast( + Vector &source, Vector &result, idx_t count, CastParameters ¶meters) +{ + UnaryExecutor::Execute( + source, result, count, + [&](int64_t v) -> string_t { + std::string s = std::to_string(static_cast(v)); + return StringVector::AddString(result, s); + }); + return true; +} + +/* ===================================================================== + * In / out — TQUADBIN temporal value + * ===================================================================== */ + +bool QuadbinFunctions::Tquadbin_in_cast( + Vector &source, Vector &result, idx_t count, CastParameters ¶meters) +{ + UnaryExecutor::Execute( + source, result, count, + [&](string_t s) -> string_t { + std::string str(s.GetData(), s.GetSize()); + Temporal *t = tquadbin_in(str.c_str()); + return TempToBlob(result, t); + }); + return true; +} + +bool QuadbinFunctions::Tquadbin_out_cast( + Vector &source, Vector &result, idx_t count, CastParameters ¶meters) +{ + UnaryExecutor::Execute( + source, result, count, + [&](string_t blob) -> string_t { + Temporal *t = BlobToTemp(blob); + char *str = temporal_out(t, OUT_DEFAULT_DECIMAL_DIGITS); + free(t); + std::string copy(str); + free(str); + return StringVector::AddString(result, copy); + }); + return true; +} + +/* ===================================================================== + * Static cell helpers + * ===================================================================== */ + +void QuadbinFunctions::Quadbin_tile_to_cell( + DataChunk &args, ExpressionState &state, Vector &result) +{ + TernaryExecutor::Execute( + args.data[0], args.data[1], args.data[2], result, args.size(), + [](int32_t x, int32_t y, int32_t z) -> int64_t { + Quadbin cell = quadbin_tile_to_cell( + static_cast(x), + static_cast(y), + static_cast(z)); + return static_cast(cell); + }); +} + +/* quadbinCellToTileX/Y/Z: expose each tile coordinate as a separate scalar. + * DuckDB has no STRUCT type in the current MobilityDuck surface; three + * functions mirror the (x,y,z) MobilityDB SQL pattern. */ +void QuadbinFunctions::Quadbin_cell_to_tile_x( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [](int64_t v) -> int32_t { + uint32_t x, y, z; + quadbin_cell_to_tile(static_cast(v), &x, &y, &z); + return static_cast(x); + }); +} + +void QuadbinFunctions::Quadbin_cell_to_tile_y( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [](int64_t v) -> int32_t { + uint32_t x, y, z; + quadbin_cell_to_tile(static_cast(v), &x, &y, &z); + return static_cast(y); + }); +} + +void QuadbinFunctions::Quadbin_cell_to_tile_z( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [](int64_t v) -> int32_t { + uint32_t x, y, z; + quadbin_cell_to_tile(static_cast(v), &x, &y, &z); + return static_cast(z); + }); +} + +void QuadbinFunctions::Quadbin_get_resolution( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [](int64_t v) -> int32_t { + return static_cast( + quadbin_get_resolution(static_cast(v))); + }); +} + +void QuadbinFunctions::Quadbin_is_valid_cell( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [](int64_t v) -> bool { + return quadbin_is_valid_cell(static_cast(v)); + }); +} + +/* ===================================================================== + * Constructor — tquadbininst_make wrapped as `tquadbin(cell, t)` + * ===================================================================== */ + +void QuadbinFunctions::Tquadbin_make( + DataChunk &args, ExpressionState &state, Vector &result) +{ + BinaryExecutor::Execute( + args.data[0], args.data[1], result, args.size(), + [&](int64_t cell, timestamp_tz_t t) -> string_t { + TInstant *inst = tquadbininst_make( + static_cast(cell), ToMeosTimestamp(t)); + return TempToBlob(result, reinterpret_cast(inst)); + }); +} + +/* ===================================================================== + * Accessors + * ===================================================================== */ + +void QuadbinFunctions::Tquadbin_start_value( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t blob) -> int64_t { + Temporal *t = BlobToTemp(blob); + Quadbin v = tquadbin_start_value(t); + free(t); + return static_cast(v); + }); +} + +void QuadbinFunctions::Tquadbin_end_value( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t blob) -> int64_t { + Temporal *t = BlobToTemp(blob); + Quadbin v = tquadbin_end_value(t); + free(t); + return static_cast(v); + }); +} + +void QuadbinFunctions::Tquadbin_value_n( + DataChunk &args, ExpressionState &state, Vector &result) +{ + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [&](string_t blob, int32_t n, ValidityMask &mask, idx_t idx) -> int64_t { + Temporal *t = BlobToTemp(blob); + Quadbin v; + bool ok = tquadbin_value_n(t, n, &v); + free(t); + if (!ok) { mask.SetInvalid(idx); return 0; } + return static_cast(v); + }); +} + +void QuadbinFunctions::Tquadbin_values( + DataChunk &args, ExpressionState &state, Vector &result) +{ + /* Quadbin[] → LIST; cell ids returned as signed BIGINT. */ + auto &input = args.data[0]; + input.Flatten(args.size()); + auto in_data = FlatVector::GetData(input); + auto list_entries = FlatVector::GetData(result); + auto &out_validity = FlatVector::Validity(result); + idx_t total = 0; + + for (idx_t row = 0; row < args.size(); row++) { + if (!FlatVector::Validity(input).RowIsValid(row)) { + out_validity.SetInvalid(row); + list_entries[row] = list_entry_t{total, 0}; + continue; + } + Temporal *t = BlobToTemp(in_data[row]); + int n = 0; + Quadbin *vals = tquadbin_values(t, &n); + free(t); + if (!vals || n <= 0) { + if (vals) free(vals); + list_entries[row] = list_entry_t{total, 0}; + continue; + } + ListVector::Reserve(result, total + n); + ListVector::SetListSize(result, total + n); + list_entries[row] = list_entry_t{total, static_cast(n)}; + auto child = FlatVector::GetData(ListVector::GetEntry(result)); + for (int i = 0; i < n; i++) + child[total + i] = static_cast(vals[i]); + total += n; + free(vals); + } +} + +void QuadbinFunctions::Tquadbin_value_at_timestamptz( + DataChunk &args, ExpressionState &state, Vector &result) +{ + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [&](string_t blob, timestamp_tz_t ts, + ValidityMask &mask, idx_t idx) -> int64_t { + Temporal *t = BlobToTemp(blob); + Quadbin v; + bool ok = tquadbin_value_at_timestamptz( + t, ToMeosTimestamp(ts), true, &v); + free(t); + if (!ok) { mask.SetInvalid(idx); return 0; } + return static_cast(v); + }); +} + +/* ===================================================================== + * Quadkey conversion — tquadbin → tvarchar (base-4 slippy-tile string) + * ===================================================================== */ + +void QuadbinFunctions::Tquadbin_cell_to_quadkey( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::ExecuteWithNulls( + args.data[0], result, args.size(), + [&](string_t blob, ValidityMask &mask, idx_t idx) -> string_t { + Temporal *t = BlobToTemp(blob); + Temporal *r = tquadbin_cell_to_quadkey(t); + free(t); + if (!r) { mask.SetInvalid(idx); return string_t(); } + char *str = temporal_out(r, OUT_DEFAULT_DECIMAL_DIGITS); + free(r); + std::string copy(str); + free(str); + return StringVector::AddString(result, copy); + }); +} + +/* ===================================================================== + * Casts to/from tbigint + * ===================================================================== */ + +void QuadbinFunctions::Tbigint_to_tquadbin( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::ExecuteWithNulls( + args.data[0], result, args.size(), + [&](string_t blob, ValidityMask &mask, idx_t idx) -> string_t { + Temporal *t = BlobToTemp(blob); + Temporal *r = tbigint_to_tquadbin(t); + free(t); + if (!r) { mask.SetInvalid(idx); return string_t(); } + return TempToBlob(result, r); + }); +} + +void QuadbinFunctions::Tquadbin_to_tbigint( + DataChunk &args, ExpressionState &state, Vector &result) +{ + UnaryExecutor::ExecuteWithNulls( + args.data[0], result, args.size(), + [&](string_t blob, ValidityMask &mask, idx_t idx) -> string_t { + Temporal *t = BlobToTemp(blob); + Temporal *r = tquadbin_to_tbigint(t); + free(t); + if (!r) { mask.SetInvalid(idx); return string_t(); } + return TempToBlob(result, r); + }); +} + +/* ===================================================================== + * Registration + * ===================================================================== */ + +void QuadbinTypes::RegisterScalarFunctions(ExtensionLoader &loader) { + const auto QB = QuadbinTypes::QUADBIN(); + const auto TQB = QuadbinTypes::TQUADBIN(); + const auto I32 = LogicalType::INTEGER; + const auto B = LogicalType::BOOLEAN; + const auto V = LogicalType::VARCHAR; + const auto TS = LogicalType::TIMESTAMP_TZ; + + /* Static cell helpers */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinTileToCell", {I32, I32, I32}, QB, + QuadbinFunctions::Quadbin_tile_to_cell)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinCellToTileX", {QB}, I32, + QuadbinFunctions::Quadbin_cell_to_tile_x)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinCellToTileY", {QB}, I32, + QuadbinFunctions::Quadbin_cell_to_tile_y)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinCellToTileZ", {QB}, I32, + QuadbinFunctions::Quadbin_cell_to_tile_z)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinGetResolution", {QB}, I32, + QuadbinFunctions::Quadbin_get_resolution)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "quadbinIsValidCell", {QB}, B, + QuadbinFunctions::Quadbin_is_valid_cell)); + + /* Constructor */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tquadbin", {QB, TS}, TQB, + QuadbinFunctions::Tquadbin_make)); + + /* Accessors */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "startValue", {TQB}, QB, + QuadbinFunctions::Tquadbin_start_value)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "endValue", {TQB}, QB, + QuadbinFunctions::Tquadbin_end_value)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "valueN", {TQB, I32}, QB, + QuadbinFunctions::Tquadbin_value_n)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "values", {TQB}, LogicalType::LIST(QB), + QuadbinFunctions::Tquadbin_values)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "valueAtTimestamp", {TQB, TS}, QB, + QuadbinFunctions::Tquadbin_value_at_timestamptz)); + + /* Quadkey conversion */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "cellToQuadkey", {TQB}, V, + QuadbinFunctions::Tquadbin_cell_to_quadkey)); + + /* tbigint ↔ tquadbin: deferred until TemporalTypes::TBIGINT() is published, + * same as the th3index binding. */ +} + +} // namespace duckdb diff --git a/src/temporal/span_functions.cpp b/src/temporal/span_functions.cpp index 8c6f5bdb..966fdfb8 100644 --- a/src/temporal/span_functions.cpp +++ b/src/temporal/span_functions.cpp @@ -1529,13 +1529,14 @@ void SpanFunctions::Float_round(DataChunk &args, ExpressionState &state, Vector BinaryExecutor::Execute( args0, *args1, result, args.size(), [&](double_t float_value, int32_t precision) -> double_t { - return float_round(float_value, precision); + double factor = std::pow(10.0, (double)precision); + return std::round(float_value * factor) / factor; }); } else { UnaryExecutor::Execute( args0, result, args.size(), [&](double_t float_value) -> double_t { - return float_round(float_value, 0); + return std::round(float_value); }); } if (args.size() == 1) { diff --git a/src/temporal/temporal_functions.cpp b/src/temporal/temporal_functions.cpp index 7e5cc932..3324d51e 100644 --- a/src/temporal/temporal_functions.cpp +++ b/src/temporal/temporal_functions.cpp @@ -199,7 +199,7 @@ void TemporalFunctions::Tinstant_constructor_text(Vector &value, Vector &ts, Vec timestamp_tz_t meos_ts = DuckDBToMeosTimestamp(ts); std::string str = value.GetString(); - text *txt = cstring2text(str.c_str()); + text *txt = cstring_to_text(str.c_str()); TInstant *inst = ttextinst_make(txt, (TimestampTz)meos_ts.value); Temporal *temp = (Temporal*)inst; @@ -244,7 +244,7 @@ void TemporalFunctions::Tsequence_constructor(DataChunk &args, ExpressionState & auto &child_vec = ListVector::GetEntry(array_vec); meosType temptype = TemporalHelpers::GetTemptypeFromAlias(result.GetType().GetAlias().c_str()); - interpType interp = temptype_continuous(temptype) ? LINEAR : STEP; + interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP; bool lower_inc = true; bool upper_inc = true; @@ -446,7 +446,7 @@ void TemporalFunctions::Tsequence_from_base_tstzset(DataChunk &args, ExpressionS BinaryExecutor::Execute( args.data[0], args.data[1], result, count, [&](string_t value, string_t set_blob) { - text *txt = cstring2text(value.GetString().c_str()); + text *txt = cstring_to_text(value.GetString().c_str()); return Tsequence_from_base_tstzset_impl(PointerGetDatum(txt), set_blob, temptype, result); }); } else if (arg_type.id() == LogicalTypeId::BLOB) { @@ -516,7 +516,7 @@ void TemporalFunctions::Tsequence_from_base_tstzspan(DataChunk &args, Expression auto count = args.size(); const auto &arg_type = args.data[0].GetType(); meosType temptype = TemporalHelpers::GetTemptypeFromAlias(result.GetType().GetAlias().c_str()); - interpType interp = temptype_continuous(temptype) ? LINEAR : STEP; + interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP; if (args.ColumnCount() > 2) { auto &interp_child = args.data[2]; interp_child.Flatten(count); @@ -528,7 +528,7 @@ void TemporalFunctions::Tsequence_from_base_tstzspan(DataChunk &args, Expression BinaryExecutor::Execute( args.data[0], args.data[1], result, count, [&](string_t value, string_t span_blob) { - text *txt = cstring2text(value.GetString().c_str()); + text *txt = cstring_to_text(value.GetString().c_str()); return Tsequence_from_base_tstzspan_impl(PointerGetDatum(txt), span_blob, temptype, interp, result); }); } else if (arg_type.id() == LogicalTypeId::BLOB) { @@ -599,7 +599,7 @@ void TemporalFunctions::Tsequenceset_from_base_tstzspanset(DataChunk &args, Expr auto count = args.size(); const auto &arg_type = args.data[0].GetType(); meosType temptype = TemporalHelpers::GetTemptypeFromAlias(result.GetType().GetAlias().c_str()); - interpType interp = temptype_continuous(temptype) ? LINEAR : STEP; + interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP; if (args.ColumnCount() > 2) { auto &interp_child = args.data[2]; interp_child.Flatten(count); @@ -611,7 +611,7 @@ void TemporalFunctions::Tsequenceset_from_base_tstzspanset(DataChunk &args, Expr BinaryExecutor::Execute( args.data[0], args.data[1], result, count, [&](string_t value, string_t spanset_blob) { - text *txt = cstring2text(value.GetString().c_str()); + text *txt = cstring_to_text(value.GetString().c_str()); return Tsequenceset_from_base_tstzspanset_impl(PointerGetDatum(txt), spanset_blob, temptype, interp, result); }); } else if (arg_type.id() == LogicalTypeId::BLOB) { @@ -1335,7 +1335,7 @@ void TemporalFunctions::Temporal_value_n(DataChunk &args, ExpressionState &state return string_t(); } text *txt = DatumGetTextP(ret); - char *cstr = text2cstring(txt); + char *cstr = text_to_cstring(txt); return StringVector::AddString(result, cstr); } ); @@ -2417,7 +2417,7 @@ void TemporalFunctions::Temporal_set_interp(DataChunk &args, ExpressionState &st void TemporalFunctions::Temporal_append_tinstant(DataChunk &args, ExpressionState &state, Vector &result) { auto count = args.size(); meosType temptype = TemporalHelpers::GetTemptypeFromAlias(result.GetType().GetAlias().c_str()); - interpType interp = temptype_continuous(temptype) ? LINEAR : STEP; + interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP; if (args.ColumnCount() > 2) { auto &interp_child = args.data[2]; interp_child.Flatten(count); @@ -2798,7 +2798,7 @@ static void temporal_at_minus_values_dispatch(DataChunk &args, ExpressionState & BinaryExecutor::ExecuteWithNulls( args.data[0], args.data[1], result, count, [&](string_t temp_str, string_t value, ValidityMask &mask, idx_t idx) -> string_t { - text *txt = cstring2text(value.GetString().c_str()); + text *txt = cstring_to_text(value.GetString().c_str()); string_t stored = temporal_restrict_value_impl(temp_str, PointerGetDatum(txt), atfunc, result, mask, idx); return stored; }); @@ -3523,7 +3523,7 @@ void TemporalFunctions::Temporal_value_at_timestamptz(DataChunk &args, Expressio mask.SetInvalid(idx); return string_t(); } - char *cstr = text2cstring(value); + char *cstr = text_to_cstring(value); string_t stored = StringVector::AddString(result, cstr); return stored; }); @@ -4757,19 +4757,19 @@ void TemporalFunctions::Sub_tnumber_tnumber(DataChunk &args, ExpressionState &st } void TemporalFunctions::Mult_int_tint(DataChunk &args, ExpressionState &state, Vector &result) { - TemporalBinaryV1(args, result, [](int32_t i, Temporal *t) { return mult_int_tint(i, t); }); + TemporalBinaryV1(args, result, [](int32_t i, Temporal *t) { return mul_int_tint(i, t); }); } void TemporalFunctions::Mult_tint_int(DataChunk &args, ExpressionState &state, Vector &result) { - TemporalBinaryV(args, result, [](Temporal *t, int32_t i) { return mult_tint_int(t, i); }); + TemporalBinaryV(args, result, [](Temporal *t, int32_t i) { return mul_tint_int(t, i); }); } void TemporalFunctions::Mult_float_tfloat(DataChunk &args, ExpressionState &state, Vector &result) { - TemporalBinaryV1(args, result, [](double d, Temporal *t) { return mult_float_tfloat(d, t); }); + TemporalBinaryV1(args, result, [](double d, Temporal *t) { return mul_float_tfloat(d, t); }); } void TemporalFunctions::Mult_tfloat_float(DataChunk &args, ExpressionState &state, Vector &result) { - TemporalBinaryV(args, result, [](Temporal *t, double d) { return mult_tfloat_float(t, d); }); + TemporalBinaryV(args, result, [](Temporal *t, double d) { return mul_tfloat_float(t, d); }); } void TemporalFunctions::Mult_tnumber_tnumber(DataChunk &args, ExpressionState &state, Vector &result) { - TemporalBinaryTT(args, result, [](Temporal *a, Temporal *b) { return mult_tnumber_tnumber(a, b); }); + TemporalBinaryTT(args, result, [](Temporal *a, Temporal *b) { return mul_tnumber_tnumber(a, b); }); } void TemporalFunctions::Div_int_tint(DataChunk &args, ExpressionState &state, Vector &result) { @@ -5860,7 +5860,7 @@ void TemporalFunctions::Temporal_dump_common(DataChunk &args, Vector &result, me values.push_back(actual_value); } else if constexpr (std::is_same_v) { text *txt = DatumGetTextP(val); - char *actual_value = text2cstring(txt); + char *actual_value = text_to_cstring(txt); values.push_back(string_t(actual_value)); } diff --git a/test/sql/raster_quadbin.test b/test/sql/raster_quadbin.test new file mode 100644 index 00000000..062bc44f --- /dev/null +++ b/test/sql/raster_quadbin.test @@ -0,0 +1,50 @@ +require mobilityduck + +# ── trajectoryQuadbins ──────────────────────────────────────────────────────── + +# A single-instant trajectory at the origin returns exactly one zoom-0 cell. +query I +SELECT cardinality(trajectoryQuadbins(tgeompoint 'POINT(0 0)@2024-01-01', 0)); +---- +1 + +# That cell is the world tile at zoom 0. +query I +SELECT trajectoryQuadbins(tgeompoint 'POINT(0 0)@2024-01-01', 0)[1] + = quadbinTileToCell(0, 0, 0); +---- +true + +# NULL input propagates. +query I +SELECT trajectoryQuadbins(NULL::TGEOMPOINT, 0) IS NULL; +---- +true + +# ── rasterTileValueQuadbin ─────────────────────────────────────────────────── + +# 1×1 UINT8 tile, pixel value 42 (0x2a). POINT(0 0) in WGS84 maps to the +# center of the zoom-0 world tile → pixel (col=0, row=0) → value 42.0. +query I +SELECT startValue( + rasterTileValueQuadbin( + from_hex('2a'), -- 1-byte pixel buffer, value 42 + 1, 1, -- tile width × height in pixels + quadbinTileToCell(0,0,0), -- zoom-0 world cell + 'UINT8', 0.0, false, + tgeompoint 'POINT(0 0)@2024-01-01' + ) +) = 42.0; +---- +true + +# A trajectory entirely outside the tile bbox returns NULL. +query I +SELECT rasterTileValueQuadbin( + from_hex('2a'), 1, 1, + quadbinTileToCell(1, 0, 0), -- NW quadrant of zoom 1 + 'UINT8', 0.0, false, + tgeompoint 'POINT(90 -45)@2024-01-01' -- SE hemisphere → outside NW cell +) IS NULL; +---- +true diff --git a/test/sql/tfloat.test b/test/sql/tfloat.test index 80eaa7b0..0af560e7 100644 --- a/test/sql/tfloat.test +++ b/test/sql/tfloat.test @@ -209,12 +209,12 @@ SELECT update( query I SELECT segmentMinDuration(tfloat 'Interp=Step;{[1.5@2000-01-01, 2.5@2000-01-02, 1.5@2000-01-03],[3.5@2000-01-04, 3.5@2000-01-05]}', '1 day'); ---- -{[f@2000-01-01 00:00:00+01, f@2000-01-03 00:00:00+01), [f@2000-01-04 00:00:00+01, f@2000-01-05 00:00:00+01)} +{[f@2000-01-01 00:00:00+01, f@2000-01-03 00:00:00+01], [f@2000-01-04 00:00:00+01, f@2000-01-05 00:00:00+01]} -query I +query I SELECT segmentMaxDuration(tfloat '{[1.5@2000-01-01, 2.5@2000-01-02, 1.5@2000-01-03],[3.5@2000-01-04, 3.5@2000-01-05]}', '1 day'); ---- -{[f@2000-01-01 00:00:00+01, f@2000-01-03 00:00:00+01), [f@2000-01-04 00:00:00+01, f@2000-01-05 00:00:00+01)} +{[f@2000-01-01 00:00:00+01, f@2000-01-03 00:00:00+01], [f@2000-01-04 00:00:00+01, f@2000-01-05 00:00:00+01]} query I SELECT integral(tfloat '{[1.5@2000-01-01, 2.5@2000-01-02, 1.5@2000-01-03],[3.5@2000-01-04, 3.5@2000-01-05]}'); diff --git a/test/sql/tquadbin.test b/test/sql/tquadbin.test new file mode 100644 index 00000000..1ef18faa --- /dev/null +++ b/test/sql/tquadbin.test @@ -0,0 +1,35 @@ +require mobilityduck + +# ── static cell round-trip ────────────────────────────────────────────────── +query I +SELECT quadbinTileToCell(1, 0, 1); +---- +5194902170171867135 + +query I +SELECT quadbinGetResolution(quadbinTileToCell(2, 1, 2)); +---- +2 + +query I +SELECT quadbinIsValidCell(quadbinTileToCell(5, 3, 3)); +---- +true + +query III +SELECT quadbinCellToTileX(quadbinTileToCell(2, 1, 2)), + quadbinCellToTileY(quadbinTileToCell(2, 1, 2)), + quadbinCellToTileZ(quadbinTileToCell(2, 1, 2)); +---- +2 1 2 + +# ── tquadbin instant constructor + accessor ───────────────────────────────── +query I +SELECT startValue(tquadbin(quadbinTileToCell(1,0,1), TIMESTAMPTZ '2024-01-01')); +---- +5194902170171867135 + +query I +SELECT endValue(tquadbin(quadbinTileToCell(2,1,2), TIMESTAMPTZ '2024-06-01')); +---- +5199124294822526975 diff --git a/vcpkg_ports/meos/portfile.cmake b/vcpkg_ports/meos/portfile.cmake index 2a2a5614..f3a62b69 100644 --- a/vcpkg_ports/meos/portfile.cmake +++ b/vcpkg_ports/meos/portfile.cmake @@ -1,35 +1,249 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH - REPO MobilityDB/MobilityDB - REF f11b7443ee985dc1ffb778c325e62f0edaf255ec - SHA512 ae8589acc86016c601f9c3c157e94b35e6e8fc50d6194d26db510d51e65a6e751279a3ced258a6bb6e56a22e083993aaeab92f20b9d18d41c7a2c8c73b7dc9df + REPO estebanzimanyi/MobilityDB + REF 88e5d5b4306843a842e465e4dd6e59f5d4fb0390 + SHA512 a960002e1e56962e17b534cb12aa86e26cfc3fa5b7994b52ac0fd899e1aa487a1b7d09aa12c8a85646bef4ee1882f2988743bec14f912112ac929883f09d6877 ) -vcpkg_replace_string( - "${SOURCE_PATH}/postgres/utils/CMakeLists.txt" - "set_property(TARGET utils PROPERTY POSITION_INDEPENDENT_CODE ON)" - [=[ -set_property(TARGET utils PROPERTY POSITION_INDEPENDENT_CODE ON) -if(MEOS) - target_include_directories(utils PRIVATE "${CMAKE_SOURCE_DIR}/meos/include") +# json-c's FindJSON-C.cmake uses hardcoded system hints (/usr/lib, /usr/include) +# that miss vcpkg's installed layout. Resolve the library and include paths +# explicitly so they are pre-set as cache variables before FindJSON-C runs. +set(_meos_jsonc_lib_candidates + "${CURRENT_INSTALLED_DIR}/lib/libjson-c.so" + "${CURRENT_INSTALLED_DIR}/lib/libjson-c.a" + "${CURRENT_INSTALLED_DIR}/lib/libjson-c${CMAKE_SHARED_LIBRARY_SUFFIX}" + "${CURRENT_INSTALLED_DIR}/lib/libjson-c${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(_MEOS_JSONC_LIB "") +foreach(_cand IN LISTS _meos_jsonc_lib_candidates) + if(EXISTS "${_cand}") + set(_MEOS_JSONC_LIB "${_cand}") + break() + endif() +endforeach() +if(NOT _MEOS_JSONC_LIB) + message(FATAL_ERROR "MEOS port: cannot locate vcpkg-installed libjson-c under ${CURRENT_INSTALLED_DIR}/lib") endif() -]=] +# json-c headers install under include/json-c/; FindJSON-C.cmake searches for +# json.h with PATH_SUFFIXES json-c, so pass the parent include directory. +set(_MEOS_JSONC_INC "${CURRENT_INSTALLED_DIR}/include/json-c") +if(NOT EXISTS "${_MEOS_JSONC_INC}/json.h") + message(FATAL_ERROR "MEOS port: cannot locate vcpkg-installed json.h under ${CURRENT_INSTALLED_DIR}/include/json-c") +endif() + +# Upstream gap: `temporal_parse` in `meos/src/temporal/type_parser.c` routes any +# input that starts with '{' to the discrete-sequence parser, consuming the '{' as +# the outer sequence delimiter. For T_TJSONB, a bare instant like +# `{"k":1}@2000-01-01` also starts with '{' (the JSON object delimiter), so it is +# incorrectly dispatched to tdiscseq_parse which then tries to parse `"k":1}@...` +# as a temporal instant and fails with "Missing delimeter character '@'". +# +# Fix: after peeking inside the outer '{', distinguish the three cases: +# - next char is '[' or '(' → sequence set (existing behaviour) +# - next char is '{' → discrete sequence (first instant's value starts with '{') +# - anything else AND basetype == T_JSONB → JSON-object instant; restore and +# parse via tinstant_parse +# For non-T_JSONB types no observable behaviour change: their instant values never +# start with '{', so the second condition (!=T_JSONB) keeps them in tdiscseq_parse. +vcpkg_replace_string( + "${SOURCE_PATH}/meos/src/temporal/type_parser.c" + [=[ else if (**str == '{') + { + const char *bak = *str; + p_obrace(str); + p_whitespace(str); + if (**str == '[' || **str == '(') + { + *str = bak; + result = (Temporal *) tsequenceset_parse(str, temptype, interp); + } + else + { + *str = bak; + result = (Temporal *) tdiscseq_parse(str, temptype); + } + }]=] + [=[ else if (**str == '{') + { + const char *bak = *str; + p_obrace(str); + p_whitespace(str); + if (**str == '[' || **str == '(') + { + *str = bak; + result = (Temporal *) tsequenceset_parse(str, temptype, interp); + } + else if (**str == '{' || temptype_basetype(temptype) != T_JSONB) + { + /* Discrete sequence: either next token is another '{' (e.g. first + * instant's JSON-object value) or the base type never starts with '{' + * so the outer '{' is definitely the sequence delimiter. */ + *str = bak; + result = (Temporal *) tdiscseq_parse(str, temptype); + } + else + { + /* The outer '{' belongs to the base value itself (e.g. a JSON object). + * Restore and parse as a temporal instant. */ + *str = bak; + TInstant *inst = tinstant_parse(str, temptype, true); + if (! inst) + return NULL; + result = (Temporal *) inst; + } + }]=] +) + +# Upstream gap: `pgtypes/libpq/pqformat.h` contains a deprecated +# static-inline helper `pq_sendint` that calls `elog()`. In the +# standalone MEOS build the pgtypes shim does not declare `elog`, and +# GCC 14 (Ubuntu 24.04 runners) treats implicit-function-declaration +# as a hard error. Replace the call with `meos_error` — both symbols +# are in scope via the postgres.h → meos_error.h chain that pqformat.c +# already includes before pulling in pqformat.h. +vcpkg_replace_string( + "${SOURCE_PATH}/pgtypes/libpq/pqformat.h" + [=[elog(ERROR, "unsupported integer size %d", b);]=] + [=[meos_error(ERROR, MEOS_ERR_INTERNAL_ERROR, "unsupported integer size %d", b);]=] +) + +# Upstream gap: the MEOS=1 include path (`postgres_int_defs.h` → `pg_timestamp.h`) +# does not reach `pgtypes/utils/timestamp.h`, so the four Timestamp/TimestampTz +# Datum accessors are absent from pure MEOS=1 TUs (e.g. `meos/src/h3/th3index_boxops.c` +# calls `TimestampTzGetDatum`). GCC 14 treats implicit-function-declaration as a +# hard error. Adding the functions unconditionally to `pg_timestamp.h` causes +# redefinition errors in pgtypes TUs that explicitly include `utils/timestamp.h` +# (e.g. `pgtypes/common/stringinfo.c`), because both files define the same four +# static-inline functions. +# +# Fix — cross-guard the definitions so the header processed first wins: +# · `pg_timestamp.h` adds the four functions under `#ifndef TIMESTAMP_H` +# (the include-guard of `utils/timestamp.h`). +# · `utils/timestamp.h` wraps its four matching functions under +# `#ifndef __PG_TIMESTAMP_H__` (the include-guard of `pg_timestamp.h`). +vcpkg_replace_string( + "${SOURCE_PATH}/pgtypes/pg_timestamp.h" + [=[typedef int64 TimestampTz;]=] + [=[typedef int64 TimestampTz; +#ifndef TIMESTAMP_H +static inline Datum TimestampGetDatum(Timestamp X) { return Int64GetDatum(X); } +static inline Datum TimestampTzGetDatum(TimestampTz X) { return Int64GetDatum(X); } +static inline Timestamp DatumGetTimestamp(Datum X) { return (Timestamp) DatumGetInt64(X); } +static inline TimestampTz DatumGetTimestampTz(Datum X) { return (TimestampTz) DatumGetInt64(X); } +#endif]=] +) + +vcpkg_replace_string( + "${SOURCE_PATH}/pgtypes/utils/timestamp.h" + [=[static inline Timestamp +DatumGetTimestamp(Datum X) +{ + return (Timestamp) DatumGetInt64(X); +} + +static inline TimestampTz +DatumGetTimestampTz(Datum X) +{ + return (TimestampTz) DatumGetInt64(X); +} + +static inline Interval * +DatumGetIntervalP(Datum X) +{ + return (Interval *) DatumGetPointer(X); +} + +static inline Datum +TimestampGetDatum(Timestamp X) +{ + return Int64GetDatum(X); +} + +static inline Datum +TimestampTzGetDatum(TimestampTz X) +{ + return Int64GetDatum(X); +}]=] + [=[#ifndef __PG_TIMESTAMP_H__ +static inline Timestamp +DatumGetTimestamp(Datum X) +{ + return (Timestamp) DatumGetInt64(X); +} + +static inline TimestampTz +DatumGetTimestampTz(Datum X) +{ + return (TimestampTz) DatumGetInt64(X); +} +#endif + +static inline Interval * +DatumGetIntervalP(Datum X) +{ + return (Interval *) DatumGetPointer(X); +} + +#ifndef __PG_TIMESTAMP_H__ +static inline Datum +TimestampGetDatum(Timestamp X) +{ + return Int64GetDatum(X); +} + +static inline Datum +TimestampTzGetDatum(TimestampTz X) +{ + return Int64GetDatum(X); +} +#endif]=] +) + +# Upstream gap: `add_timestamptz_interval` and `add_date_int` were removed +# from meos.h in this pin but are still compiled into libmeos.so (pgtypes). +# Re-export them in postgres_ext_defs.in.h so the installed meos.h continues +# to declare them for binding consumers (temporal tile functions call them). +vcpkg_replace_string( + "${SOURCE_PATH}/meos/include/postgres_ext_defs.in.h" + [=[extern TimestampTz minus_timestamptz_interval(TimestampTz tstz, const Interval *interv);]=] + [=[extern TimestampTz add_timestamptz_interval(TimestampTz tstz, const Interval *interv); +extern TimestampTz minus_timestamptz_interval(TimestampTz tstz, const Interval *interv);]=] +) + +vcpkg_replace_string( + "${SOURCE_PATH}/meos/include/postgres_ext_defs.in.h" + [=[extern DateADT minus_date_int(DateADT date, int32 days);]=] + [=[extern DateADT add_date_int(DateADT date, int32 days); +extern DateADT minus_date_int(DateADT date, int32 days);]=] ) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS -DMEOS=ON + -DH3=OFF + -DJSON=ON + -DQUADBIN=ON + -DRASTER=ON + "-DJSON-C_LIBRARIES=${_MEOS_JSONC_LIB}" + "-DJSON-C_INCLUDE_DIRS=${_MEOS_JSONC_INC}" -DBUILD_SHARED_LIBS=ON + # Build only the MEOS library, not the MEOS C test binaries: those link + # the GEOS C++ API, which the arm64-linux vcpkg triplet does not carry. + -DBUILD_TESTING=OFF -DCMAKE_C_FLAGS="-Dsession_timezone=meos_session_timezone" -DCMAKE_CXX_FLAGS="-Dsession_timezone=meos_session_timezone" - ) -vcpkg_cmake_build(TARGET all) vcpkg_cmake_install() +# meos_tls.h is not listed in the upstream install() rules at this pin. +# It is included verbatim by the cmake-generated meos.h; copy it alongside +# the other installed headers. meos_json.h is installed automatically by +# cmake when JSON=ON (as the stripped export variant). +file(COPY "${SOURCE_PATH}/meos/include/meos_tls.h" + DESTINATION "${CURRENT_PACKAGES_DIR}/include") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/share/meos") file(WRITE "${CURRENT_PACKAGES_DIR}/share/meos/MEOSConfig.cmake" [=[ # Minimal imported target for MEOS