diff --git a/CMakeLists.txt b/CMakeLists.txt index ad30a13..2c9b2ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,8 @@ set(EXTENSION_SOURCES src/geo/tnpoint_in_out.cpp src/geo/tpose.cpp src/geo/tpose_in_out.cpp + src/rgeo/trgeometry.cpp + src/rgeo/trgeometry_in_out.cpp src/geo/tgeogpoint.cpp src/geo/tgeogpoint_in_out.cpp src/geo/tgeogpoint_ops.cpp diff --git a/Makefile b/Makefile index bc17d05..a948549 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,39 @@ 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 calls ExtensionHelper::AutoLoadExtension(db, "icu") so +# the timezone option is honoured. Autoload looks for the extension on disk +# at $HOME/.duckdb/extensions///icu.duckdb_extension +# and falls back to a hub download. That fails both inside the linux_amd64 +# test docker container (empty path, no network egress) and on the macOS +# osx_arm64 test runner (hub icu not reliably resolvable). We copy the +# icu.duckdb_extension that was built locally as part of this extension's +# build (declared in extension_config.cmake) into the expected path, +# matched to the DuckDB platform string, before running the unittester. +DUCKDB_VERSION_TAG := v1.4.4 + +define stage_icu + @if [ -f ./build/$(1)/extension/icu/icu.duckdb_extension ]; 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; \ + target=$$HOME/.duckdb/extensions/$(DUCKDB_VERSION_TAG)/$$platform; \ + mkdir -p "$$target" && cp -f ./build/$(1)/extension/icu/icu.duckdb_extension "$$target/" && \ + echo "Staged icu.duckdb_extension at $$target/"; \ + 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: - ./build/reldebug/$(TEST_PATH) "$(PROJ_DIR)test/*" \ No newline at end of file + $(call stage_icu,reldebug) + ./build/reldebug/$(TEST_PATH) "$(PROJ_DIR)test/*" diff --git a/src/include/rgeo/trgeometry.hpp b/src/include/rgeo/trgeometry.hpp new file mode 100644 index 0000000..125c41e --- /dev/null +++ b/src/include/rgeo/trgeometry.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#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 { + + +struct TRGeometryTypes { + static LogicalType TRGEOMETRY(); + static LogicalType GEOMETRY(); + static void RegisterTypes(ExtensionLoader &loader); + static void RegisterScalarFunctions(ExtensionLoader &loader); + static void RegisterCastFunctions(ExtensionLoader &loader); + static void RegisterScalarInOutFunctions(ExtensionLoader &loader); +}; + +struct TrgeometryFunctions { + static bool StringToTrgeometry(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool TrgeometryToString(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool WkbBlobToGeometry(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); +}; + + +} // namespace duckdb diff --git a/src/include/tydef.hpp b/src/include/tydef.hpp index b7b2810..b9860ca 100644 --- a/src/include/tydef.hpp +++ b/src/include/tydef.hpp @@ -11,11 +11,27 @@ extern "C" { #include } -// Forward-compat alias for the meosType → MeosType rename (MobilityDB -// pr785-sync-script). Vcpkg's MEOS exposes `MeosType`; existing -// MobilityDuck code still uses `meosType`. This alias bridges the two -// without touching every reference site. -using meosType = MeosType; +// MEOS naming history: `meosType` is the **pre-consolidation** spelling +// and `MeosType` is the **post-consolidation** target (the rename is +// part of the upstream consolidation sweep, not yet reached by the +// vcpkg pin). The current pin +// (`vcpkg_ports/meos/portfile.cmake` REF f11b7443ee98…) is still +// pre-consolidation and exposes `meosType` — see +// meos/include/temporal/meos_catalog.h, where line 121 declares +// `} meosType;`. MobilityDuck's source consistently uses +// `meosType` (verified via `grep -rn '\bmeosType\b' src/`), which +// matches the pin, so no alias is needed today. +// +// An earlier version of this file added `using meosType = MeosType;` +// as a forward-looking bridge for the eventual consolidation bump. +// That alias references `MeosType`, which the current pin does NOT +// yet expose, so it broke the build: +// "'MeosType' does not name a type; did you mean 'meosType'?". +// +// When the MEOS pin is bumped past the consolidation point, restore +// a bridge here (`using meosType = MeosType;` becomes valid then) or +// sweep the source `meosType → MeosType` in one PR — whichever the +// project prefers at that time. namespace duckdb { diff --git a/src/mobilityduck_extension.cpp b/src/mobilityduck_extension.cpp index ba74b65..caabf65 100644 --- a/src/mobilityduck_extension.cpp +++ b/src/mobilityduck_extension.cpp @@ -18,6 +18,7 @@ #include "geo/tcbuffer.hpp" #include "geo/tnpoint.hpp" #include "geo/tpose.hpp" +#include "rgeo/trgeometry.hpp" #include "geo/tgeogpoint.hpp" #include "geo/tgeogpoint_ops.hpp" #include "temporal/span.hpp" @@ -365,6 +366,11 @@ static void LoadInternal(ExtensionLoader &loader) { TPoseTypes::RegisterCastFunctions(loader); TPoseTypes::RegisterScalarInOutFunctions(loader); + TRGeometryTypes::RegisterScalarFunctions(loader); + TRGeometryTypes::RegisterTypes(loader); + TRGeometryTypes::RegisterCastFunctions(loader); + TRGeometryTypes::RegisterScalarInOutFunctions(loader); + TGeogpointType::RegisterScalarFunctions(loader); TGeogpointType::RegisterCastFunctions(loader); TGeogpointType::RegisterScalarInOutFunctions(loader); diff --git a/src/rgeo/trgeometry.cpp b/src/rgeo/trgeometry.cpp new file mode 100644 index 0000000..62d3384 --- /dev/null +++ b/src/rgeo/trgeometry.cpp @@ -0,0 +1,1477 @@ +#include "rgeo/trgeometry.hpp" +#include "geo/tgeompoint_functions.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include "duckdb/common/extension_type_info.hpp" +#include +#include +#include +#include "temporal/spanset.hpp" +#include "temporal/set.hpp" +#include "temporal/temporal_functions.hpp" +#include "geo/stbox.hpp" +#include "geo/geoset.hpp" +#include +#include "geo_util.hpp" +#include "spatial/spatial_types.hpp" +#include "mobilityduck/meos_exec_serial.hpp" + +extern "C" { + #include + #include + #include + #include + #include +} + +// The temporal rigid geometry module header meos_rgeo.h is deliberately +// NOT included. It is the only extended-type module header that declares +// an Interval-typed prototype (trgeometry_append_tinstant), and pulling +// the MEOS `struct Interval` into a scope that also has `duckdb::Interval` +// (from tydef.hpp) makes the unqualified `Interval` ambiguous and breaks +// the build. The few trgeometry_* symbols this port needs are declared +// locally instead (the same technique the sibling ports use for +// subtype-specific parsers); GSERIALIZED / Pose / MeosType stay reachable +// via meos_geo.h / meos_internal.h / meos_pose.h. Every name below is +// the post-uniformization trgeometry_* name exported from meos_rgeo.h. +extern "C" { + extern Temporal *trgeometry_in(const char *str); + extern TInstant *trgeometryinst_make(const GSERIALIZED *geom, + const Pose *pose, TimestampTz t); + extern Temporal *geo_tpose_to_trgeometry(const GSERIALIZED *gs, + const Temporal *temp); + extern GSERIALIZED *trgeometry_geom(const Temporal *temp); + extern GSERIALIZED *trgeometry_start_value(const Temporal *temp); + extern GSERIALIZED *trgeometry_end_value(const Temporal *temp); +} + + +namespace duckdb { + +LogicalType TRGeometryTypes::TRGEOMETRY() { + auto type = LogicalType(LogicalTypeId::BLOB); + type.SetAlias("TRGEOMETRY"); + return type; +} + +/* + * Constructors +*/ + +inline void Trgeometry_constructor(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_geom_str) -> string_t { + std::string input = input_geom_str.GetString(); + + // The temporal rigid geometry text form is a reference + // geometry, a ';' delimiter and a temporal pose, e.g. + // 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01'. + // The renamed trgeometry_in symbol is exported from + // meos_rgeo.h and is itself the canonical parser the + // MobilityDB SQL binds trgeometry_in / trgeometryFromText to. + Temporal *tinst = trgeometry_in(input.c_str()); + if (!tinst) { + throw InvalidInputException("Invalid TRGEOMETRY input: " + input); + } + + size_t data_size = temporal_mem_size(tinst); + + uint8_t *data_buffer = (uint8_t*)malloc(data_size); + if (!data_buffer) { + free(tinst); + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY data"); + } + + memcpy(data_buffer, tinst, data_size); + + string_t data_string_t(reinterpret_cast(data_buffer), data_size); + string_t stored_data = StringVector::AddStringOrBlob(result, data_string_t); + + free(data_buffer); + free(tinst); + + return stored_data; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Trgeometryinst_constructor(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &geom_vec = args.data[0]; + auto &pose_vec = args.data[1]; + auto &t_vec = args.data[2]; + + TernaryExecutor::Execute( + geom_vec, pose_vec, t_vec, result, count, + [&](string_t geom_blob, string_t pose_str, timestamp_tz_t t) -> string_t { + // The reference geometry arrives as a GEOMETRY blob. + GSERIALIZED *gs = GeometryToGSerialized(geom_blob, 0); + if (gs == NULL) { + throw InvalidInputException("Invalid geometry for TRGEOMETRY"); + } + + // The pose value type is a Pose (position + orientation): + // a 2D pose is a point plus a rotation angle, a 3D pose a + // point plus an orientation quaternion. It is parsed from + // its canonical text form, e.g. 'Pose(Point(1 1), 0.5)'. + std::string pose_value = pose_str.GetString(); + Pose *po = pose_in(pose_value.c_str()); + if (po == NULL) { + free(gs); + throw InvalidInputException("Invalid pose format: " + pose_value); + } + + timestamp_tz_t meos_timestamp = DuckDBToMeosTimestamp(t); + // The rigid-geometry instant constructor pairs the reference + // geometry with the moving pose at the given timestamp. + TInstant *inst = trgeometryinst_make(gs, po, + static_cast(meos_timestamp.value)); + + if (inst == NULL) { + free(po); + free(gs); + throw InvalidInputException("Failed to create TInstant"); + } + + size_t data_size = temporal_mem_size((Temporal*)inst); + + uint8_t *data_buffer = (uint8_t *)malloc(data_size); + + if (!data_buffer){ + free(inst); + free(po); + free(gs); + throw InvalidInputException("Failed to allocate memory to TRGEOMETRY data"); + } + memcpy(data_buffer, inst, data_size); + + string_t data_string_t(reinterpret_cast(data_buffer),data_size); + string_t stored_data = StringVector::AddStringOrBlob(result, data_string_t); + + free(data_buffer); + free(inst); + free(po); + free(gs); + + return stored_data; + + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Geo_tpose_to_trgeometry(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &geom_vec = args.data[0]; + auto &tpose_vec = args.data[1]; + + BinaryExecutor::Execute( + geom_vec, tpose_vec, result, count, + [&](string_t geom_blob, string_t tpose_blob)-> string_t{ + // The reference geometry arrives as a GEOMETRY blob and the + // motion as a serialized temporal pose; the rigid geometry + // is the geometry moved by that temporal pose. + GSERIALIZED *gs = GeometryToGSerialized(geom_blob, 0); + if (gs == NULL) { + throw InvalidInputException("Invalid geometry for TRGEOMETRY"); + } + + std::string tpose_input = tpose_blob.GetString(); + Temporal *tpose = reinterpret_cast(const_cast(tpose_input.c_str())); + if (!tpose) { + free(gs); + throw InvalidInputException("Invalid TPOSE data: null pointer"); + } + + Temporal *temp = geo_tpose_to_trgeometry(gs, tpose); + if (temp == NULL) { + free(gs); + throw InvalidInputException("Failed to create TRGEOMETRY"); + } + + size_t temp_size = temporal_mem_size(temp); + + uint8_t *temp_buffer = (uint8_t *)malloc(temp_size); + if (!temp_buffer) { + free(temp); + free(gs); + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY data"); + } + + memcpy(temp_buffer, temp, temp_size); + + string_t temp_string_t((char*) temp_buffer, temp_size); + string_t stored_data = StringVector::AddStringOrBlob(result, temp_string_t); + + free(temp_buffer); + free(temp); + free(gs); + + return stored_data; + + }); + + if (count == 1){ + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +TInstant **temparr_extract_rg(Vector &trgeom_arr_vec, list_entry_t list_entry, int *count) { + auto &child_vector = ListVector::GetEntry(trgeom_arr_vec); + auto list_size = list_entry.length; + auto list_offset = list_entry.offset; + + if (list_size == 0) { + *count = 0; + return nullptr; + } + + *count = list_size; + + TInstant **instants = (TInstant**)malloc(sizeof(TInstant*) * list_size); + if (!instants) { + *count = 0; + return nullptr; + } + + for (idx_t i = 0; i < list_size; i++) { + auto element_idx = list_offset + i; + string_t tgeom_blob = FlatVector::GetData(child_vector)[element_idx]; + + const uint8_t *data = reinterpret_cast(tgeom_blob.GetData()); + size_t data_size = tgeom_blob.GetSize(); + + if (data_size < sizeof(void*)) { + for (idx_t j = 0; j < i; j++) { + if (instants[j]) free(instants[j]); + } + free(instants); + *count = 0; + return nullptr; + } + + uint8_t *data_copy = (uint8_t*)malloc(data_size); + if (!data_copy) { + for (idx_t j = 0; j < i; j++) { + if (instants[j]) free(instants[j]); + } + free(instants); + *count = 0; + return nullptr; + } + memcpy(data_copy, data, data_size); + + Temporal *temp = reinterpret_cast(data_copy); + if (!temp) { + free(data_copy); + for (idx_t j = 0; j < i; j++) { + if (instants[j]) free(instants[j]); + } + free(instants); + *count = 0; + return nullptr; + } + + instants[i] = (TInstant*)temp; + } + + return instants; +} + +inline void Trgeometry_sequence_constructor(DataChunk &args, ExpressionState &state, Vector &result) { + // Default values + const char* default_interp = "linear"; + bool default_lower_inc = true; + bool default_upper_inc = true; + + auto count = args.size(); + auto arg_count = args.ColumnCount(); + + + auto &trgeom_arr_vec = args.data[0]; + trgeom_arr_vec.Flatten(count); + + Vector *interp_vec = nullptr; + Vector *lower_vec = nullptr; + Vector *upper_vec = nullptr; + + if (arg_count > 1) { + interp_vec = &args.data[1]; + interp_vec->Flatten(count); + } + if (arg_count > 2) { + lower_vec = &args.data[2]; + lower_vec->Flatten(count); + } + if (arg_count > 3) { + upper_vec = &args.data[3]; + upper_vec->Flatten(count); + } + + result.Flatten(count); + + auto trgeom_data = FlatVector::GetData(trgeom_arr_vec); + auto result_data = FlatVector::GetData(result); + + // Get validity masks + auto &trgeom_validity = FlatVector::Validity(trgeom_arr_vec); + auto &result_validity = FlatVector::Validity(result); + + for (idx_t i = 0; i < count; i++) { + if (!trgeom_validity.RowIsValid(i)) { + result_validity.SetInvalid(i); + continue; + } + + try { + list_entry_t list_entry = trgeom_data[i]; + + // Handle interp parameter with default + std::string interp_str = default_interp; + if (interp_vec) { + auto interp_data = FlatVector::GetData(*interp_vec); + auto &interp_validity = FlatVector::Validity(*interp_vec); + if (interp_validity.RowIsValid(i)) { + interp_str = interp_data[i].GetString(); + } + } + interpType interp = interptype_from_string(interp_str.c_str()); + + bool lower_inc = default_lower_inc; + bool upper_inc = default_upper_inc; + + if (lower_vec) { + auto lower_data = FlatVector::GetData(*lower_vec); + auto &lower_validity = FlatVector::Validity(*lower_vec); + if (lower_validity.RowIsValid(i)) { + lower_inc = lower_data[i]; + } + } + + if (upper_vec) { + auto upper_data = FlatVector::GetData(*upper_vec); + auto &upper_validity = FlatVector::Validity(*upper_vec); + if (upper_validity.RowIsValid(i)) { + upper_inc = upper_data[i]; + } + } + + // Extract array elements + int element_count; + TInstant **instants = temparr_extract_rg(trgeom_arr_vec, list_entry, &element_count); + + if (!instants || element_count == 0) { + result_validity.SetInvalid(i); + continue; + } + + TSequence *sequence_result = tsequence_make((TInstant **) instants, element_count, + lower_inc, upper_inc, interp, true); + + if (!sequence_result) { + for (int j = 0; j < element_count; j++) { + if (instants[j]) { + free(instants[j]); + } + } + free(instants); + result_validity.SetInvalid(i); + continue; + } + + size_t data_size = temporal_mem_size(reinterpret_cast(sequence_result)); + uint8_t *data_buffer = (uint8_t*)malloc(data_size); + if (!data_buffer) { + free(sequence_result); + for (int j = 0; j < element_count; j++) { + if (instants[j]) { + free(instants[j]); + } + } + free(instants); + result_validity.SetInvalid(i); + continue; + } + + memcpy(data_buffer, sequence_result, data_size); + + string_t data_string_t(reinterpret_cast(data_buffer), data_size); + result_data[i] = StringVector::AddStringOrBlob(result, data_string_t); + + free(data_buffer); + free(sequence_result); + for (int j = 0; j < element_count; j++) { + if (instants[j]) { + free(instants[j]); + } + } + free(instants); + + } catch (const std::exception& e) { + result_validity.SetInvalid(i); + } + } + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + + + +/* + * Conversions +*/ + +inline void Temporal_to_tstzspan(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + Span *timespan = temporal_to_tstzspan(temp); + + if (!timespan) { + throw InvalidInputException("Failed to extract timespan from TRGEOMETRY"); + } + + size_t span_size = sizeof(Span); + + uint8_t *span_buffer = (uint8_t*)malloc(span_size); + if (!span_buffer) { + free(timespan); + throw InvalidInputException("Failed to allocate memory for timespan data"); + } + + memcpy(span_buffer, timespan, span_size); + + string_t span_string_t(reinterpret_cast(span_buffer), span_size); + string_t stored_data = StringVector::AddStringOrBlob(result, span_string_t); + + free(span_buffer); + free(timespan); + + return stored_data; + } + ); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +/* + * Transformations +*/ + +inline void Temporal_to_tinstant(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + TInstant *inst = temporal_to_tinstant(temp); + if (!inst) { + throw InvalidInputException("Failed to convert TRGEOMETRY to TInstant"); + } + + size_t inst_size = temporal_mem_size((Temporal*)inst); + + uint8_t *inst_buffer = (uint8_t*)malloc(inst_size); + if (!inst_buffer) { + free(inst); + throw InvalidInputException("Failed to allocate memory for TInstant data"); + } + + memcpy(inst_buffer, inst, inst_size); + + string_t inst_string_t(reinterpret_cast(inst_buffer), inst_size); + string_t stored_data = StringVector::AddStringOrBlob(result, inst_string_t); + + free(inst_buffer); + free(inst); + + return stored_data; + } + ); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Temporal_set_interp(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom_vec = args.data[0]; + auto &interp_vec = args.data[1]; + + tgeom_vec.Flatten(count); + interp_vec.Flatten(count); + + BinaryExecutor::Execute( + tgeom_vec, interp_vec, result, count, + [&](string_t tgeom_str_t, string_t interp_str_t) -> string_t { + + std::string input = tgeom_str_t.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + std::string interp_str = interp_str_t.GetString(); + interpType new_interp = interptype_from_string(interp_str.c_str()); + + Temporal *result_temp = temporal_set_interp(temp, new_interp); + if (!result_temp) { + throw InvalidInputException("Failed to set interpolation"); + } + + // Serialize result back to binary + size_t result_size = temporal_mem_size(result_temp); + uint8_t *result_buffer = (uint8_t*)malloc(result_size); + if (!result_buffer) { + free(result_temp); + throw InvalidInputException("Failed to allocate memory for result"); + } + + memcpy(result_buffer, result_temp, result_size); + string_t result_string_t(reinterpret_cast(result_buffer), result_size); + string_t stored_data = StringVector::AddStringOrBlob(result, result_string_t); + + free(result_buffer); + free(result_temp); + + return stored_data; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Temporal_merge(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom1_vec = args.data[0]; + auto &tgeom2_vec = args.data[1]; + + tgeom1_vec.Flatten(count); + tgeom2_vec.Flatten(count); + + BinaryExecutor::Execute( + tgeom1_vec, tgeom2_vec, result, count, + [&](string_t tgeom1_str_t, string_t tgeom2_str_t) -> string_t { + std::string tgeom1 = tgeom1_str_t.GetString(); + + Temporal *temp1 = reinterpret_cast(const_cast(tgeom1.c_str())); + if (!temp1) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + std::string tgeom2 = tgeom2_str_t.GetString(); + + Temporal *temp2 = reinterpret_cast(const_cast(tgeom2.c_str())); + if (!temp2) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + Temporal *result_temp = temporal_merge(temp1, temp2); + if (!result_temp) { + throw InvalidInputException("Failed to merge temporal rigid geometries"); + } + + // Serialize result back to binary + size_t result_size = temporal_mem_size(result_temp); + uint8_t *result_buffer = (uint8_t*)malloc(result_size); + if (!result_buffer) { + free(result_temp); + throw InvalidInputException("Failed to allocate memory for result"); + } + + memcpy(result_buffer, result_temp, result_size); + string_t result_string_t(reinterpret_cast(result_buffer), result_size); + string_t stored_data = StringVector::AddStringOrBlob(result, result_string_t); + + free(result_buffer); + free(result_temp); + + return stored_data; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +/* + * Accessor Functions +*/ + +inline void Temporal_subtype(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom_vec = args.data[0]; + + tgeom_vec.Flatten(count); + + UnaryExecutor::Execute( + tgeom_vec, result, count, + [&](string_t tgeom_str_t) -> string_t { + std::string input = tgeom_str_t.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + const char *subtype_str = temporal_subtype(temp); + if (!subtype_str) { + throw InvalidInputException("Failed to get temporal subtype"); + } + + std::string result_str(subtype_str); + string_t stored_result = StringVector::AddString(result, result_str); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + + + +inline void Temporal_interp(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom_vec = args.data[0]; + + tgeom_vec.Flatten(count); + + UnaryExecutor::Execute( + tgeom_vec, result, count, + [&](string_t tgeom_str_t) -> string_t { + + std::string input = tgeom_str_t.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + + const char *interp_str = temporal_interp(temp); + if (!interp_str) { + throw InvalidInputException("Failed to get temporal interpolation"); + } + + std::string result_str(interp_str); + string_t stored_result = StringVector::AddString(result, result_str); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Temporal_mem_size(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom_vec = args.data[0]; + + tgeom_vec.Flatten(count); + + UnaryExecutor::Execute( + tgeom_vec, result, count, + [&](string_t tgeom_str_t) -> int32_t { + std::string input = tgeom_str_t.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + size_t mem_size = temporal_mem_size(temp); + + + return static_cast(mem_size); + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +// ---- Rigid-geometry value accessors ---- +// A temporal rigid geometry is a reference geometry rigidly moved over +// time by an embedded temporal pose, so its instant value type is a Pose +// (not a registered DuckDB type). getValue surfaces that pose in its +// canonical text form (`Pose(POINT(x y),theta)`), mirroring the asText +// output, exactly as the tpose port does. The reference geometry is +// surfaced by geometry(trgeometry) via trgeometry_geom, and the moved +// rigid-geometry snapshot at the bounding instants is surfaced by +// startValue / endValue via trgeometry_start_value / trgeometry_end_value +// (each a freshly allocated GSERIALIZED the caller owns), per the +// canonical MobilityDB SQL signatures. + +inline Pose *pose_from_instant_value(const TInstant *inst) { + Datum d = tinstant_value(inst); + return reinterpret_cast(d); +} + +inline void Tinstant_value(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + TInstant *tinst = reinterpret_cast(const_cast(input.c_str())); + + // tinstant_value returns a freshly allocated copy of the + // Pose value (datum_copy), which the caller owns. + Pose *po = pose_from_instant_value(tinst); + + char *str = pose_out(po, 15); + if (!str) { + free(po); + throw InvalidInputException("Failed to convert pose value to text"); + } + std::string output(str); + string_t stored_result = StringVector::AddString(result, output); + + free(str); + free(po); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + + +inline void Temporal_start_value(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + // trgeometry_start_value returns a freshly allocated + // GSERIALIZED of the rigid-geometry snapshot at the first + // instant (the reference geometry moved by the start pose). + GSERIALIZED *gs = trgeometry_start_value(temp); + if (!gs) { + throw InvalidInputException("Failed to extract start value from TRGEOMETRY"); + } + + string_t geometry_blob = GSerializedToGeometry(gs, state, result); + string_t stored_result = StringVector::AddStringOrBlob(result, geometry_blob); + + free(gs); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Temporal_end_value(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + // trgeometry_end_value returns a freshly allocated + // GSERIALIZED of the rigid-geometry snapshot at the last + // instant (the reference geometry moved by the end pose). + GSERIALIZED *gs = trgeometry_end_value(temp); + if (!gs) { + throw InvalidInputException("Failed to extract end value from TRGEOMETRY"); + } + + string_t geometry_blob = GSerializedToGeometry(gs, state, result); + string_t stored_result = StringVector::AddStringOrBlob(result, geometry_blob); + + free(gs); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Trgeometry_geom(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + // trgeometry_geom returns a freshly allocated GSERIALIZED + // copy of the reference geometry that is rigidly moved. + GSERIALIZED *gs = trgeometry_geom(temp); + if (!gs) { + throw InvalidInputException("Failed to extract reference geometry from TRGEOMETRY"); + } + + string_t geometry_blob = GSerializedToGeometry(gs, state, result); + string_t stored_result = StringVector::AddStringOrBlob(result, geometry_blob); + + free(gs); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Temporal_lower_inc(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal* temp = reinterpret_cast(const_cast(input.c_str())); + + bool lower_inc = temporal_lower_inc(temp); + + std::string result_str = lower_inc ? "true" : "false"; + string_t stored_result = StringVector::AddString(result, result_str); + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Temporal_upper_inc(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal* temp = reinterpret_cast(const_cast(input.c_str())); + + bool upper_inc = temporal_upper_inc(temp); + + std::string result_str = upper_inc ? "true" : "false"; + string_t stored_result = StringVector::AddString(result, result_str); + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Temporal_start_instant(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + TInstant *start_inst = temporal_start_instant(temp); + + if (!start_inst) { + throw InvalidInputException("Failed to get start_inst from temporal object"); + } + + size_t result_size = temporal_mem_size((Temporal*)start_inst); + if (result_size == 0) { + throw InvalidInputException("Invalid result size from temporal object"); + } + + uint8_t *result_buffer = (uint8_t*)malloc(result_size); + if (!result_buffer) { + free(start_inst); + throw InvalidInputException("Failed to allocate memory for result"); + } + + memcpy(result_buffer, start_inst, result_size); + string_t result_string_t(reinterpret_cast(result_buffer), result_size); + string_t stored_result = StringVector::AddStringOrBlob(result, result_string_t); + + free(result_buffer); + free(start_inst); + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Temporal_end_instant(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_vec = args.data[0]; + + UnaryExecutor::Execute( + input_vec, result, count, + [&](string_t input_str) -> string_t { + std::string input = input_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(input.c_str())); + + TInstant *end_inst = temporal_end_instant(temp); + + if (!end_inst) { + throw InvalidInputException("Failed to get end_inst from temporal object"); + } + + size_t result_size = temporal_mem_size((Temporal*)end_inst); + if (result_size == 0) { + throw InvalidInputException("Invalid result size from temporal object"); + } + + uint8_t *result_buffer = (uint8_t*)malloc(result_size); + if (!result_buffer) { + free(end_inst); + throw InvalidInputException("Failed to allocate memory for result"); + } + + memcpy(result_buffer, end_inst, result_size); + string_t result_string_t(reinterpret_cast(result_buffer), result_size); + string_t stored_result = StringVector::AddStringOrBlob(result, result_string_t); + + free(result_buffer); + free(end_inst); + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + + + +inline void Temporal_instant_n(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &tgeom_vec = args.data[0]; + auto &n_vec = args.data[1]; + + BinaryExecutor::Execute( + tgeom_vec, n_vec, result, count, + [&](string_t tgeom_str, int32_t n) -> string_t { + std::string tgeom = tgeom_str.GetString(); + + Temporal *temp = reinterpret_cast(const_cast(tgeom.c_str())); + + TInstant *inst_n = temporal_instant_n(temp, n); + if (!inst_n) { + throw InvalidInputException("Failed to get instant n from temporal object"); + } + + size_t result_size = temporal_mem_size((Temporal*)inst_n); + if (result_size == 0) { + throw InvalidInputException("Invalid result size from temporal object"); + } + + uint8_t *result_buffer = (uint8_t*)malloc(result_size); + if (!result_buffer) { + free(inst_n); + throw InvalidInputException("Failed to allocate memory for result"); + } + + memcpy(result_buffer, inst_n, result_size); + string_t result_string_t(reinterpret_cast(result_buffer), result_size); + string_t stored_result = StringVector::AddStringOrBlob(result, result_string_t); + + free(result_buffer); + free(inst_n); + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +inline void Tinstant_timestamptz(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_geom_str) -> timestamp_tz_t { + const uint8_t *data = reinterpret_cast(input_geom_str.GetData()); + size_t data_size = input_geom_str.GetSize(); + + if (data_size < sizeof(void*)) { + throw InvalidInputException("Invalid TRGEOMETRY data: insufficient size"); + } + + uint8_t *data_copy = (uint8_t*)malloc(data_size); + if (!data_copy) { + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY deserialization"); + } + memcpy(data_copy, data, data_size); + + TInstant *temp = reinterpret_cast(data_copy); + + if (!temp) { + free(data_copy); + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + TimestampTz meos_t = temp->t; + + timestamp_tz_t meos_timestamp{meos_t}; + timestamp_tz_t duckdb_t = MeosToDuckDBTimestamp(meos_timestamp); + + free(data_copy); + + return duckdb_t; + } + ); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +void TRGeometryTypes::RegisterScalarFunctions(ExtensionLoader &loader) { + + auto trgeometry_function = ScalarFunction( + "TRGEOMETRY", + {LogicalType::VARCHAR}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometry_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_function); + + auto trgeometry_inst_function = ScalarFunction( + "TRGEOMETRY", + {GeoTypes::GEOMETRY(), LogicalType::VARCHAR, LogicalType::TIMESTAMP_TZ}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometryinst_constructor); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_inst_function); + + auto trgeometry_from_tpose_function = ScalarFunction( + "TRGEOMETRY", + {GeoTypes::GEOMETRY(), LogicalType::VARCHAR}, + TRGeometryTypes::TRGEOMETRY(), + Geo_tpose_to_trgeometry + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_from_tpose_function); + + auto trgeometryseqarr_1param= ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeometryTypes::TRGEOMETRY())}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometry_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometryseqarr_1param); + + auto trgeometryseqarr_2params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeometryTypes::TRGEOMETRY()), LogicalType::VARCHAR}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometry_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometryseqarr_2params); + + auto trgeometryseqarr_3params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeometryTypes::TRGEOMETRY()), LogicalType::VARCHAR, LogicalType::BOOLEAN}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometry_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometryseqarr_3params); + + auto trgeometryseqarr_4params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeometryTypes::TRGEOMETRY()), LogicalType::VARCHAR, LogicalType::BOOLEAN, LogicalType::BOOLEAN}, + TRGeometryTypes::TRGEOMETRY(), + Trgeometry_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometryseqarr_4params); + + auto trgeometry_to_timespan_function = ScalarFunction( + "timeSpan", + {TRGeometryTypes::TRGEOMETRY()}, + SpanTypes::TSTZSPAN(), + Temporal_to_tstzspan); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_to_timespan_function); + + auto trgeometry_to_tinstant_function = ScalarFunction( + "trgeometryInst", + {TRGeometryTypes::TRGEOMETRY()}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_to_tinstant); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_to_tinstant_function); + + + auto setInterp_function = ScalarFunction( + "setInterp", + {TRGeometryTypes::TRGEOMETRY(), LogicalType::VARCHAR}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_set_interp + ); + duckdb::RegisterSerializedScalarFunction(loader, setInterp_function); + + + auto merge_function = ScalarFunction( + "merge", + {TRGeometryTypes::TRGEOMETRY(), TRGeometryTypes::TRGEOMETRY()}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_merge + ); + duckdb::RegisterSerializedScalarFunction(loader, merge_function); + + auto tempSubtype_function = ScalarFunction( + "tempSubtype", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Temporal_subtype + ); + duckdb::RegisterSerializedScalarFunction(loader, tempSubtype_function); + + auto interp_function = ScalarFunction( + "interp", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Temporal_interp + ); + duckdb::RegisterSerializedScalarFunction(loader, interp_function); + + auto memSize_function = ScalarFunction( + "memSize", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::INTEGER, + Temporal_mem_size + ); + duckdb::RegisterSerializedScalarFunction(loader, memSize_function); + + auto getValue_function = ScalarFunction( + "getValue", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Tinstant_value + ); + duckdb::RegisterSerializedScalarFunction(loader, getValue_function); + + + auto trgeometry_start_value_function = ScalarFunction( + "startValue", + {TRGeometryTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Temporal_start_value + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_start_value_function); + + auto trgeometry_end_value_function = ScalarFunction( + "endValue", + {TRGeometryTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Temporal_end_value + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_end_value_function); + + auto trgeometry_geom_function = ScalarFunction( + "geometry", + {TRGeometryTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Trgeometry_geom + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_geom_function); + + auto startInstant_function = ScalarFunction( + "startInstant", + {TRGeometryTypes::TRGEOMETRY()}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_start_instant + ); + duckdb::RegisterSerializedScalarFunction(loader, startInstant_function); + + auto endInstant_function = ScalarFunction( + "endInstant", + {TRGeometryTypes::TRGEOMETRY()}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_end_instant + ); + duckdb::RegisterSerializedScalarFunction(loader, endInstant_function); + + auto instantN_function = ScalarFunction( + "instantN", + {TRGeometryTypes::TRGEOMETRY(), LogicalType::INTEGER}, + TRGeometryTypes::TRGEOMETRY(), + Temporal_instant_n + ); + duckdb::RegisterSerializedScalarFunction(loader, instantN_function); + + + auto trgeometry_gettimestamptz_function = ScalarFunction( + "getTimestamp", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::TIMESTAMP_TZ, + Tinstant_timestamptz); + duckdb::RegisterSerializedScalarFunction(loader, trgeometry_gettimestamptz_function); + + + // =================================================================== + // Foundational trgeometry surface — accessors, time/value-restrict, + // modifiers, and comparison. The MEOS C functions delegated to here + // are subtype-agnostic (they take Temporal *), so we reuse the same + // generic handlers wired for tgeompoint in temporal_functions.cpp. + // =================================================================== + + const LogicalType TGEOM = TRGeometryTypes::TRGEOMETRY(); + const LogicalType TSTZ = LogicalType::TIMESTAMP_TZ; + const LogicalType IVAL = LogicalType::INTERVAL; + + // ---- Accessors ---- + loader.RegisterFunction(ScalarFunction( + "valueAtTimestamp", {TGEOM, TSTZ}, LogicalType::VARCHAR, + Tinstant_value)); + loader.RegisterFunction(ScalarFunction( + "getTime", {TGEOM}, SpansetTypes::tstzspanset(), + TemporalFunctions::Temporal_time)); + loader.RegisterFunction(ScalarFunction( + "duration", {TGEOM}, IVAL, + TemporalFunctions::Temporal_duration)); + loader.RegisterFunction(ScalarFunction( + "duration", {TGEOM, LogicalType::BOOLEAN}, IVAL, + TemporalFunctions::Temporal_duration)); + loader.RegisterFunction(ScalarFunction( + "lowerInc", {TGEOM}, LogicalType::BOOLEAN, + TemporalFunctions::Temporal_lower_inc)); + loader.RegisterFunction(ScalarFunction( + "upperInc", {TGEOM}, LogicalType::BOOLEAN, + TemporalFunctions::Temporal_upper_inc)); + loader.RegisterFunction(ScalarFunction( + "numInstants", {TGEOM}, LogicalType::INTEGER, + TemporalFunctions::Temporal_num_instants)); + loader.RegisterFunction(ScalarFunction( + "instants", {TGEOM}, LogicalType::LIST(TGEOM), + TemporalFunctions::Temporal_instants)); + loader.RegisterFunction(ScalarFunction( + "numSequences", {TGEOM}, LogicalType::INTEGER, + TemporalFunctions::Temporal_num_sequences)); + loader.RegisterFunction(ScalarFunction( + "sequences", {TGEOM}, LogicalType::LIST(TGEOM), + TemporalFunctions::Temporal_sequences)); + loader.RegisterFunction(ScalarFunction( + "startSequence", {TGEOM}, TGEOM, + TemporalFunctions::Temporal_start_sequence)); + loader.RegisterFunction(ScalarFunction( + "endSequence", {TGEOM}, TGEOM, + TemporalFunctions::Temporal_end_sequence)); + loader.RegisterFunction(ScalarFunction( + "sequenceN", {TGEOM, LogicalType::INTEGER}, TGEOM, + TemporalFunctions::Temporal_sequence_n)); + loader.RegisterFunction(ScalarFunction( + "numTimestamps", {TGEOM}, LogicalType::INTEGER, + TemporalFunctions::Temporal_num_timestamps)); + loader.RegisterFunction(ScalarFunction( + "timestamps", {TGEOM}, LogicalType::LIST(TSTZ), + TemporalFunctions::Temporal_timestamps)); + loader.RegisterFunction(ScalarFunction( + "startTimestamp", {TGEOM}, TSTZ, + TemporalFunctions::Temporal_start_timestamptz)); + loader.RegisterFunction(ScalarFunction( + "endTimestamp", {TGEOM}, TSTZ, + TemporalFunctions::Temporal_end_timestamptz)); + loader.RegisterFunction(ScalarFunction( + "timestampN", {TGEOM, LogicalType::INTEGER}, TSTZ, + TemporalFunctions::Temporal_timestamptz_n)); + loader.RegisterFunction(ScalarFunction( + "segments", {TGEOM}, LogicalType::LIST(TGEOM), + TemporalFunctions::Temporal_segments)); + + // ---- Time-domain restrict / minus ---- + for (const auto &t : std::vector>{ + {TSTZ, TemporalFunctions::Temporal_at_timestamptz}, + {SetTypes::tstzset(), TemporalFunctions::Temporal_at_tstzset}, + {SpanTypes::TSTZSPAN(), TemporalFunctions::Temporal_at_tstzspan}, + {SpansetTypes::tstzspanset(), TemporalFunctions::Temporal_at_tstzspanset}}) { + loader.RegisterFunction(ScalarFunction( + "atTime", {TGEOM, t.first}, TGEOM, t.second)); + } + for (const auto &t : std::vector>{ + {TSTZ, TemporalFunctions::Temporal_minus_timestamptz}, + {SetTypes::tstzset(), TemporalFunctions::Temporal_minus_tstzset}, + {SpanTypes::TSTZSPAN(), TemporalFunctions::Temporal_minus_tstzspan}, + {SpansetTypes::tstzspanset(), TemporalFunctions::Temporal_minus_tstzspanset}}) { + loader.RegisterFunction(ScalarFunction( + "minusTime", {TGEOM, t.first}, TGEOM, t.second)); + } + + // beforeTimestamp / afterTimestamp accept timestamptz + loader.RegisterFunction(ScalarFunction( + "beforeTimestamp", {TGEOM, TSTZ}, TGEOM, + TemporalFunctions::Temporal_before_timestamptz)); + loader.RegisterFunction(ScalarFunction( + "afterTimestamp", {TGEOM, TSTZ}, TGEOM, + TemporalFunctions::Temporal_after_timestamptz)); + + // ---- Modifiers (shift / scale / shiftScale / append / insert / update / + // delete) ---- + loader.RegisterFunction(ScalarFunction( + "shiftTime", {TGEOM, IVAL}, TGEOM, + TemporalFunctions::Temporal_shift_time)); + loader.RegisterFunction(ScalarFunction( + "scaleTime", {TGEOM, IVAL}, TGEOM, + TemporalFunctions::Temporal_scale_time)); + loader.RegisterFunction(ScalarFunction( + "shiftScaleTime", {TGEOM, IVAL, IVAL}, TGEOM, + TemporalFunctions::Temporal_shift_scale_time)); + loader.RegisterFunction(ScalarFunction( + "appendInstant", {TGEOM, TGEOM}, TGEOM, + TemporalFunctions::Temporal_append_tinstant)); + loader.RegisterFunction(ScalarFunction( + "appendSequence", {TGEOM, TGEOM}, TGEOM, + TemporalFunctions::Temporal_append_tsequence)); + loader.RegisterFunction(ScalarFunction( + "insert", {TGEOM, TGEOM}, TGEOM, + TemporalFunctions::Temporal_insert)); + loader.RegisterFunction(ScalarFunction( + "insert", {TGEOM, TGEOM, LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_insert)); + loader.RegisterFunction(ScalarFunction( + "update", {TGEOM, TGEOM}, TGEOM, + TemporalFunctions::Temporal_update)); + loader.RegisterFunction(ScalarFunction( + "update", {TGEOM, TGEOM, LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_update)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, TSTZ}, TGEOM, + TemporalFunctions::Temporal_delete_timestamptz)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, TSTZ, LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_delete_timestamptz)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SetTypes::tstzset()}, TGEOM, + TemporalFunctions::Temporal_delete_tstzset)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SetTypes::tstzset(), LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_delete_tstzset)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SpanTypes::TSTZSPAN()}, TGEOM, + TemporalFunctions::Temporal_delete_tstzspan)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SpanTypes::TSTZSPAN(), LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_delete_tstzspan)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SpansetTypes::tstzspanset()}, TGEOM, + TemporalFunctions::Temporal_delete_tstzspanset)); + loader.RegisterFunction(ScalarFunction( + "deleteTime", {TGEOM, SpansetTypes::tstzspanset(), LogicalType::BOOLEAN}, TGEOM, + TemporalFunctions::Temporal_delete_tstzspanset)); + + // ---- Comparison (named functions + operators) ---- + struct CmpEntry { + const char *name; + scalar_function_t fn; + }; + const std::vector named_cmps = { + {"temporal_eq", TemporalFunctions::Temporal_eq}, + {"temporal_ne", TemporalFunctions::Temporal_ne}, + {"temporal_lt", TemporalFunctions::Temporal_lt}, + {"temporal_le", TemporalFunctions::Temporal_le}, + {"temporal_gt", TemporalFunctions::Temporal_gt}, + {"temporal_ge", TemporalFunctions::Temporal_ge}, + }; + for (const auto &c : named_cmps) { + loader.RegisterFunction(ScalarFunction( + c.name, {TGEOM, TGEOM}, LogicalType::BOOLEAN, c.fn)); + } + loader.RegisterFunction(ScalarFunction( + "temporal_cmp", {TGEOM, TGEOM}, LogicalType::INTEGER, + TemporalFunctions::Temporal_cmp)); + + // Operator forms — mirror the registrations tgeometry.cpp does. + const std::vector op_cmps = { + {"=", TemporalFunctions::Temporal_eq}, + {"<>", TemporalFunctions::Temporal_ne}, + {"<", TemporalFunctions::Temporal_lt}, + {"<=", TemporalFunctions::Temporal_le}, + {">", TemporalFunctions::Temporal_gt}, + {">=", TemporalFunctions::Temporal_ge}, + }; + for (const auto &c : op_cmps) { + loader.RegisterFunction(ScalarFunction( + c.name, {TGEOM, TGEOM}, LogicalType::BOOLEAN, c.fn)); + } +} + +void TRGeometryTypes::RegisterTypes(ExtensionLoader &loader) { + loader.RegisterType( "TRGEOMETRY", TRGeometryTypes::TRGEOMETRY()); +} + + +} diff --git a/src/rgeo/trgeometry_in_out.cpp b/src/rgeo/trgeometry_in_out.cpp new file mode 100644 index 0000000..4f36b20 --- /dev/null +++ b/src/rgeo/trgeometry_in_out.cpp @@ -0,0 +1,325 @@ +#include "rgeo/trgeometry.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include "duckdb/common/extension_type_info.hpp" +#include +#include +#include +#include "mobilityduck/meos_exec_serial.hpp" + +extern "C" { + #include + #include + #include + #include + #include +} + +// meos_rgeo.h is intentionally not included (its trgeometry_append_tinstant +// prototype takes a MEOS Interval, which collides with duckdb::Interval). +// Declare only the trgeometry_* I/O symbols this translation unit needs; +// every name is the post-uniformization symbol exported from meos_rgeo.h. +extern "C" { + extern Temporal *trgeometry_in(const char *str); + extern Temporal *trgeometry_from_mfjson(const char *mfjson); + extern char *trgeometry_out(const Temporal *temp); +} + +namespace duckdb { + +inline void Tspatial_as_text(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_geom_str) -> string_t { + const uint8_t *data = reinterpret_cast(input_geom_str.GetData()); + size_t data_size = input_geom_str.GetSize(); + + if (data_size < sizeof(void*)) { + throw InvalidInputException("Invalid TRGEOMETRY data: insufficient size"); + } + + uint8_t *data_copy = (uint8_t*)malloc(data_size); + if (!data_copy) { + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY deserialization"); + } + memcpy(data_copy, data, data_size); + + Temporal *temp = reinterpret_cast(data_copy); + + if (!temp) { + free(data_copy); + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + // The temporal rigid geometry text form is the reference + // geometry, a ';' delimiter and the temporal pose; the + // type-specific trgeometry_out builds exactly that. + char *str = trgeometry_out(temp); + + if (!str) { + free(data_copy); + throw InvalidInputException("Failed to convert TRGEOMETRY to text"); + } + + std::string result_str(str); + string_t stored_result = StringVector::AddString(result, result_str); + + free(str); + free(data_copy); + + return stored_result; + } + ); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + +inline void Tspatial_as_ewkt(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto &input_geom_vec = args.data[0]; + + UnaryExecutor::Execute( + input_geom_vec, result, count, + [&](string_t input_geom_str) -> string_t { + + const uint8_t *data = reinterpret_cast(input_geom_str.GetData()); + size_t data_size = input_geom_str.GetSize(); + + if (data_size < sizeof(void*)) { + throw InvalidInputException("Invalid TRGEOMETRY data: insufficient size"); + } + + uint8_t *data_copy = (uint8_t*)malloc(data_size); + if (!data_copy) { + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY deserialization"); + } + memcpy(data_copy, data, data_size); + + Temporal *temp = reinterpret_cast(data_copy); + + if (!temp) { + free(data_copy); + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + char *ewkt = tspatial_as_ewkt(temp, 0); + + if (!ewkt) { + free(data_copy); + throw InvalidInputException("Failed to convert TRGEOMETRY to EWKT"); + } + + std::string result_str(ewkt); + string_t stored_result = StringVector::AddString(result, result_str); + + + free(ewkt); + free(data_copy); + + return stored_result; + } + ); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } +} + + +bool TrgeometryFunctions::StringToTrgeometry(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](string_t input_string) -> string_t { + std::string input_str = input_string.GetString(); + + Temporal *temp = trgeometry_in(input_str.c_str()); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY input: " + input_str); + } + + size_t data_size = temporal_mem_size(temp); + uint8_t *data_buffer = (uint8_t*)malloc(data_size); + if (!data_buffer) { + free(temp); + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY data"); + } + + memcpy(data_buffer, temp, data_size); + + string_t data_string_t(reinterpret_cast(data_buffer), data_size); + string_t stored_data = StringVector::AddStringOrBlob(result, data_string_t); + + free(data_buffer); + free(temp); + + return stored_data; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } + return true; +} + +bool TrgeometryFunctions::TrgeometryToString(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](string_t input_blob) -> string_t { + const uint8_t *data = reinterpret_cast(input_blob.GetData()); + size_t data_size = input_blob.GetSize(); + + if (data_size < sizeof(void*)) { + throw InvalidInputException("Invalid TRGEOMETRY data: insufficient size"); + } + + uint8_t *data_copy = (uint8_t*)malloc(data_size); + if (!data_copy) { + throw InvalidInputException("Failed to allocate memory for TRGEOMETRY deserialization"); + } + memcpy(data_copy, data, data_size); + + Temporal *temp = reinterpret_cast(data_copy); + if (!temp) { + free(data_copy); + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + char *str = trgeometry_out(temp); + if (!str) { + free(data_copy); + throw InvalidInputException("Failed to convert TRGEOMETRY to string"); + } + + std::string output(str); + string_t stored_result = StringVector::AddString(result, output); + + free(str); + free(data_copy); + + return stored_result; + }); + + if (count == 1) { + result.SetVectorType(VectorType::CONSTANT_VECTOR); + } + return true; +} + +// ---- Spatial-temporal parsers (Binary / HexWKB / MFJSON / Text) ---- +// Used to register the `trgeometryFrom*` overloads. +// `temporal_from_wkb` and `temporal_from_hexwkb` are subtype-agnostic; +// `trgeometry_in` and `trgeometry_from_mfjson` are the renamed, +// header-exported per-type entry points (post API-uniformization they +// are real linkable symbols, so this is a clean clone of the canonical +// template rather than a tspatial_parse work-around). The result is +// stored as a raw blob, the same format every other temporal type uses. + +inline string_t StoreTempAsBlob(Vector &result, Temporal *t) { + size_t sz = temporal_mem_size(t); + string_t stored = StringVector::AddStringOrBlob( + result, string_t(reinterpret_cast(t), sz)); + free(t); + return stored; +} + +inline void TspatialFromWkbExec(DataChunk &args, ExpressionState &, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t input) -> string_t { + if (input.GetSize() == 0) + throw InvalidInputException("fromBinary: empty WKB input"); + uint8_t *wkb = (uint8_t *)malloc(input.GetSize()); + if (!wkb) throw InternalException("fromBinary: malloc failed"); + memcpy(wkb, input.GetData(), input.GetSize()); + Temporal *t = temporal_from_wkb(wkb, input.GetSize()); + free(wkb); + if (!t) throw InvalidInputException("fromBinary: invalid MEOS-WKB"); + return StoreTempAsBlob(result, t); + }); +} + +inline void TspatialFromHexWkbExec(DataChunk &args, ExpressionState &, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t input) -> string_t { + std::string hex(input.GetData(), input.GetSize()); + Temporal *t = temporal_from_hexwkb(hex.c_str()); + if (!t) throw InvalidInputException( + "fromHexWKB: invalid hex-encoded MEOS-WKB"); + return StoreTempAsBlob(result, t); + }); +} + +inline void TrgeometryFromTextExec(DataChunk &args, ExpressionState &, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t input) -> string_t { + std::string s(input.GetData(), input.GetSize()); + Temporal *t = trgeometry_in(s.c_str()); + if (!t) throw InvalidInputException("from*: invalid input"); + return StoreTempAsBlob(result, t); + }); +} + +inline void TrgeometryFromMFJSONExec(DataChunk &args, ExpressionState &, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t input) -> string_t { + std::string s(input.GetData(), input.GetSize()); + // The renamed trgeometry_from_mfjson symbol is exported from + // meos_rgeo.h; it is the canonical per-type MF-JSON entry + // point the MobilityDB SQL binds trgeometryFromMFJSON to. + Temporal *t = trgeometry_from_mfjson(s.c_str()); + if (!t) throw InvalidInputException("fromMFJSON: invalid input"); + return StoreTempAsBlob(result, t); + }); +} + +void TRGeometryTypes::RegisterScalarInOutFunctions(ExtensionLoader &loader){ + auto TrgeometryAsText = ScalarFunction( + "asText", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Tspatial_as_text + ); + duckdb::RegisterSerializedScalarFunction(loader, TrgeometryAsText); + + auto TrgeometryAsEWKT = ScalarFunction( + "asEWKT", + {TRGeometryTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Tspatial_as_ewkt + ); + duckdb::RegisterSerializedScalarFunction(loader, TrgeometryAsEWKT); + + // ---- trgeometryFromBinary / FromEWKB (auto-detects format) ---- + const auto B = LogicalType::BLOB; + const auto V = LogicalType::VARCHAR; + const auto T = TRGeometryTypes::TRGEOMETRY(); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromBinary", {B}, T, TspatialFromWkbExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromEWKB", {B}, T, TspatialFromWkbExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromHexWKB", {V}, T, TspatialFromHexWkbExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromHexEWKB", {V}, T, TspatialFromHexWkbExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromMFJSON", {V}, T, TrgeometryFromMFJSONExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromText", {V}, T, TrgeometryFromTextExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromEWKT", {V}, T, TrgeometryFromTextExec)); +} + + +void TRGeometryTypes::RegisterCastFunctions(ExtensionLoader &loader) { + loader.RegisterCastFunction( LogicalType::VARCHAR, TRGeometryTypes::TRGEOMETRY(), TrgeometryFunctions::StringToTrgeometry); + loader.RegisterCastFunction( TRGeometryTypes::TRGEOMETRY(), LogicalType::VARCHAR, TrgeometryFunctions::TrgeometryToString); +} + +} diff --git a/test/sql/trgeometry.test b/test/sql/trgeometry.test new file mode 100644 index 0000000..6729b9f --- /dev/null +++ b/test/sql/trgeometry.test @@ -0,0 +1,199 @@ +# name: test/sql/trgeometry.test +# description: Core trgeometry type port — construction, text/EWKT/MFJSON +# I/O and basic accessors. A trgeometry value is a moving +# rigid geometry: a reference geometry rigidly rotated and +# translated over time by an embedded temporal pose, written +# as 'Geometry;Pose@timestamp'. +# group: [sql] + +require mobilityduck + +# Test trgeometry constructor with parentheses (instant) +query I +SELECT asText(trgeometry('Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01')); +---- +POLYGON((1 1,2 2,3 1,1 1));Pose(POINT(1 1),0.5)@2000-01-01 00:00:00+01 + +# Test trgeometry constructor without parentheses +query I +SELECT asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'); +---- +POLYGON((1 1,2 2,3 1,1 1));Pose(POINT(1 1),0.5)@2000-01-01 00:00:00+01 + +# Test asText with continuous sequence +query I +SELECT asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02, Pose(Point(1 1), 0.5)@2000-01-03]'); +---- +POLYGON((1 1,2 2,3 1,1 1));[Pose(POINT(1 1),0.2)@2000-01-01 00:00:00+01, Pose(POINT(1 1),0.4)@2000-01-02 00:00:00+01, Pose(POINT(1 1),0.5)@2000-01-03 00:00:00+01] + +# Test asText with discrete sequence +query I +SELECT asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));{Pose(Point(1 1), 0.3)@2000-01-01, Pose(Point(1 1), 0.5)@2000-01-02}'); +---- +POLYGON((1 1,2 2,3 1,1 1));{Pose(POINT(1 1),0.3)@2000-01-01 00:00:00+01, Pose(POINT(1 1),0.5)@2000-01-02 00:00:00+01} + +# Test asEWKT is non-null +query I +SELECT asEWKT(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01') IS NOT NULL; +---- +true + +# Test geometry/pose/timestamptz instant constructor round-trips +query I +SELECT trgeometry(geometry 'Polygon((1 1,2 2,3 1,1 1))', 'Pose(Point(1 1), 0.5)', timestamptz '2000-01-01') + = trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'; +---- +true + +# Test geometry/tpose constructor builds a moving rigid geometry that +# round-trips against the canonical text form +query I +SELECT trgeometry(geometry 'Polygon((1 1,2 2,3 1,1 1))', + tpose '[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]') + = trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]'; +---- +true + +# Test the reference geometry accessor +query I +SELECT ST_AsText(geometry(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01')); +---- +POLYGON ((1 1, 2 2, 3 1, 1 1)) + +# Test MFJSON round-trip is identity (format-agnostic) +query I +SELECT asText(trgeometryFromMFJSON(asMFJSON(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'))) + = asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'); +---- +true + +# Test MFJSON round-trip for a continuous sequence +query I +SELECT asText(trgeometryFromMFJSON(asMFJSON( + trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]'))) + = asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]'); +---- +true + +# Test trgeometryFromText constructor +query I +SELECT asText(trgeometryFromText('Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01')) IS NOT NULL; +---- +true + +# Test binary round-trip is identity +query I +SELECT asText(trgeometryFromBinary(asBinary(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'))) + = asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'); +---- +true + +# Test timeSpan function +query I +SELECT timeSpan(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-03]'); +---- +[2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01] + +# Test setInterp with discrete interpolation +query I +SELECT tempSubtype(setInterp(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]', 'discrete')); +---- +Sequence + +# Test merge function +query I +SELECT numInstants(merge( + trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.3)@2000-01-02]', + trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.3)@2000-01-02, Pose(Point(1 1), 0.5)@2000-01-03]')); +---- +3 + +# Test tempSubtype with instant +query I +SELECT tempSubtype(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'); +---- +Instant + +# Test tempSubtype with discrete sequence +query I +SELECT tempSubtype(trgeometry 'Polygon((1 1,2 2,3 1,1 1));{Pose(Point(1 1), 0.3)@2000-01-01, Pose(Point(1 1), 0.5)@2000-01-02}'); +---- +Sequence + +# Test tempSubtype with continuous sequence +query I +SELECT tempSubtype(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]'); +---- +Sequence + +# Test tempSubtype with sequence set +query I +SELECT tempSubtype(trgeometry 'Polygon((1 1,2 2,3 1,1 1));{[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02], [Pose(Point(2 2), 0.6)@2000-01-04, Pose(Point(2 2), 0.6)@2000-01-05]}'); +---- +SequenceSet + +# Test memSize is positive +query I +SELECT memSize(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01') > 0; +---- +true + +# Test interp accessor +query I +SELECT interp(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]'); +---- +Linear + +# Test getValue returns the pose value text +query I +SELECT getValue(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2000-01-01'); +---- +Pose(POINT(1 1),0.5) + +# Test startValue surfaces a non-null moved rigid-geometry snapshot +query I +SELECT startValue(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]') IS NOT NULL; +---- +true + +# Test endValue surfaces a non-null moved rigid-geometry snapshot +query I +SELECT endValue(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]') IS NOT NULL; +---- +true + +# Test startInstant +query I +SELECT asText(startInstant(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]')); +---- +POLYGON((1 1,2 2,3 1,1 1));Pose(POINT(1 1),0.2)@2000-01-01 00:00:00+01 + +# Test endInstant +query I +SELECT asText(endInstant(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]')); +---- +POLYGON((1 1,2 2,3 1,1 1));Pose(POINT(1 1),0.4)@2000-01-02 00:00:00+01 + +# Test instantN +query I +SELECT asText(instantN(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02]', 1)); +---- +POLYGON((1 1,2 2,3 1,1 1));Pose(POINT(1 1),0.2)@2000-01-01 00:00:00+01 + +# Test getTimestamp function +query I +SELECT getTimestamp(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1), 0.5)@2023-01-01 10:00:00+00'); +---- +2023-01-01 11:00:00+01 + +# Test numInstants generic accessor +query I +SELECT numInstants(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-02, Pose(Point(1 1), 0.5)@2000-01-03]'); +---- +3 + +# Test duration generic accessor +query I +SELECT duration(trgeometry 'Polygon((1 1,2 2,3 1,1 1));[Pose(Point(1 1), 0.2)@2000-01-01, Pose(Point(1 1), 0.4)@2000-01-03]'); +---- +2 days diff --git a/vcpkg_ports/meos/portfile.cmake b/vcpkg_ports/meos/portfile.cmake index 7fa4e5e..c804af5 100644 --- a/vcpkg_ports/meos/portfile.cmake +++ b/vcpkg_ports/meos/portfile.cmake @@ -1,24 +1,25 @@ -# TEMPORARY PROVISIONAL PIN. The core tcbuffer and tnpoint ports are -# clean clones that bind the per-type MF-JSON I/O: tcbufferFromMFJSON -# needs the tcbuffer MF-JSON support from MobilityDB PR #1051 -# (feat/tcbuffer-mfjson), and tnpointFromMFJSON needs the network-point -# MF-JSON support from MobilityDB PR #951 (split/tnpoint-mfjson-io, -# wired through the generic temporal_from_mfjson dispatch). Neither is -# on MobilityDB/MobilityDB master yet, so the pin points at an -# integration branch on the contributor fork that composes both PRs on -# top of MobilityDB master: estebanzimanyi/MobilityDB -# meos-mduck-ports-tcbuffer-tnpoint = cherry-pick #1051 -# (e624027f5b59f483476c8e5c26471f9e252a5e61) then #951 -# (800f80bfdc8dc4ce86516aacb85e48131bcf7454); the two only collide in -# disjoint #if CBUFFER vs #if NPOINT blocks of type_in.c / type_out.c -# and were union-merged with no logic change. This is provisional -# pending the #134 -> #145 MobilityDuck chain plus PRs #1051 and #951 -# merging into MobilityDB master. +# TEMPORARY PROVISIONAL PIN. The extended-type ports are clean clones +# that bind the per-type I/O: the tcbuffer / tnpoint MF-JSON support +# (MobilityDB PRs #1051 and #951), the tpose surface, and the renamed +# trgeometry_* C API (the MobilityDB API-uniformization PRs #1066 / +# #1067 / #1069 that promote trgeo_in / trgeo_from_mfjson / trgeo_out +# to exported trgeometry_in / trgeometry_from_mfjson / trgeometry_out) +# are not all on MobilityDB/MobilityDB master yet, so the pin points at +# the verified integration branch on the contributor fork that composes +# them on top of MobilityDB master: estebanzimanyi/MobilityDB +# meos-provisional-6type-base-session @ +# 3af4cb895c92446fa052c90a4ab22cf2c9e96c4a ("Rename the trgeometry +# distance and comparison functions for cross-type API uniformity"). +# This base exposes all six extended types' MEOS surface (tcbuffer and +# tnpoint MF-JSON, tpose, the renamed trgeometry_*, and the tpcpoint / +# tpcpatch base). This is provisional pending the #134 -> #145 +# MobilityDuck chain plus the MF-JSON and trgeometry-rename PRs merging +# into MobilityDB master. # -# Flip-to-merged-master recipe (apply once #1051 AND #951 are merged AND -# #145 has landed): set REPO back to MobilityDB/MobilityDB, set REF to -# the merged master commit that includes both MF-JSON changes, and -# recompute +# Flip-to-merged-master recipe (apply once the MF-JSON and the +# trgeometry-rename PRs are merged AND #145 has landed): set REPO back +# to MobilityDB/MobilityDB, set REF to the merged master commit that +# includes all of those changes, and recompute # curl -sL https://github.com/MobilityDB/MobilityDB/archive/.tar.gz | sha512sum # for SHA512. Then delete this comment block. The OPTIONS below (the #145 # CBUFFER/NPOINT/POSE/RGEO enablers) are unchanged by this pin and compose @@ -26,8 +27,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO estebanzimanyi/MobilityDB - REF 1a4f498b1689fdcbe77ae0bbaf8b53df1eb59554 - SHA512 da7c32c8f02b684dd8f68315ad1d128a59aa2027deae971b6fe94cba2de9303ca53b30676c98ba4e48a6721ab181007ce58ca4263ce2037554f171af6eca2f7a + REF 3af4cb895c92446fa052c90a4ab22cf2c9e96c4a + SHA512 381a7a50f587d66bb8b2dc19253cf78ca3b975ab23285a953b4367f118719a3b679a7a53a6f821ee1d1f5d2cb849d8aef822edfc69e8331ebfe5fb1685b6d6fc ) vcpkg_replace_string(