From 6c7fcbe3af4d613c136480b306d9c80f9204a3ac Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sun, 17 May 2026 14:30:13 +0200 Subject: [PATCH 1/2] Add the core trgeo type port Port the temporal rigid geometry type into MobilityDuck by cloning the tpose core port and substituting trgeo. The trgeometry value is a moving rigid geometry: a reference geometry rotated and translated over time by an embedded temporal pose. MEOS exposes no linkable trgeo_in or trgeo_from_mfjson symbol (both are inline #if MEOS wrappers absent from the installed meos_rgeo.h), so text and EWKT input route through the subtype-agnostic tspatial_parse with T_TRGEOMETRY (the exact path trgeo_in itself delegates to and the canonical MobilityDB SQL binds trgeometryFromText and trgeometry_in to), MF-JSON input routes through the generic temporal_from_mfjson with T_TRGEOMETRY, and the value is serialized back with the type-specific trgeo_out. The geom accessor surfaces the reference geometry via trgeo_geom and startValue and endValue surface the rigid-geometry snapshot via trgeo_start_value and trgeo_end_value. The cloned instant-array helper is suffixed temparr_extract_rg to avoid an ODR clash with the tpose, tnpoint and tcbuffer siblings in the same extension library. Generic subtype- agnostic accessors, time-domain restrictions, modifiers and comparison reuse the shared temporal handlers. Core scope only, no operators layer, mirroring the tcbuffer, tnpoint and tpose core ports. The trgeo MF-JSON and SQL surface is already on MobilityDB master so no MEOS prerequisite or pin change is needed; the provisional MEOS pin is inherited unchanged from the parent. Stacks on the core tpose type port (#151). --- CMakeLists.txt | 2 + src/geo/trgeo.cpp | 1278 ++++++++++++++++++++++++++++++++ src/geo/trgeo_in_out.cpp | 335 +++++++++ src/include/geo/trgeo.hpp | 29 + src/mobilityduck_extension.cpp | 6 + test/sql/trgeo.test | 211 ++++++ 6 files changed, 1861 insertions(+) create mode 100644 src/geo/trgeo.cpp create mode 100644 src/geo/trgeo_in_out.cpp create mode 100644 src/include/geo/trgeo.hpp create mode 100644 test/sql/trgeo.test diff --git a/CMakeLists.txt b/CMakeLists.txt index ad30a13..2794a06 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/geo/trgeo.cpp + src/geo/trgeo_in_out.cpp src/geo/tgeogpoint.cpp src/geo/tgeogpoint_in_out.cpp src/geo/tgeogpoint_ops.cpp diff --git a/src/geo/trgeo.cpp b/src/geo/trgeo.cpp new file mode 100644 index 0000000..a55e3cf --- /dev/null +++ b/src/geo/trgeo.cpp @@ -0,0 +1,1278 @@ +#include "geo/trgeo.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 text/EWKT parser. trgeo_in and + // trgeo_from_mfjson are MEOS inline (#if MEOS) wrappers with no + // linkable symbol and are not declared in the installed + // meos_rgeo.h, so this port routes through the subtype-agnostic + // spatial parser tspatial_parse (a real exported symbol that + // trgeo_in itself delegates to with T_TRGEOMETRY), exactly the way + // the canonical MobilityDB SQL binds trgeometryFromText / + // trgeometry_in. Declared locally because the internal + // geo/tspatial_parser.h is not part of the installed MEOS headers. + extern Temporal *tspatial_parse(const char **str, MeosType temptype); +} + + +namespace duckdb { + +LogicalType TRGeoTypes::TRGEOMETRY() { + auto type = LogicalType(LogicalTypeId::BLOB); + type.SetAlias("TRGEOMETRY"); + return type; +} + +static inline Temporal *Trgeo_parse_text(const std::string &str) { + const char *ptr = str.c_str(); + return tspatial_parse(&ptr, T_TRGEOMETRY); +} + +/* + * Constructors +*/ + +inline void Trgeo_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(); + + Temporal *tinst = Trgeo_parse_text(input); + 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); + } +} + +TInstant **temparr_extract_rg(Vector &trgeo_arr_vec, list_entry_t list_entry, int *count) { + auto &child_vector = ListVector::GetEntry(trgeo_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 Trgeo_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 &trgeo_arr_vec = args.data[0]; + trgeo_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 trgeo_data = FlatVector::GetData(trgeo_arr_vec); + auto result_data = FlatVector::GetData(result); + + // Get validity masks + auto &trgeo_validity = FlatVector::Validity(trgeo_arr_vec); + auto &result_validity = FlatVector::Validity(result); + + for (idx_t i = 0; i < count; i++) { + if (!trgeo_validity.RowIsValid(i)) { + result_validity.SetInvalid(i); + continue; + } + + try { + list_entry_t list_entry = trgeo_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(trgeo_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 ---- +// The trgeometry value is a moving rigid geometry: a reference geometry +// rotated/translated over time by an embedded temporal pose. It is not a +// registered DuckDB type. geom(trgeometry) returns the reference geometry +// via trgeo_geom; startValue / endValue return the geometry snapshot at +// the first / last instant via trgeo_start_value / trgeo_end_value (both +// header-declared, returning a freshly allocated GSERIALIZED the caller +// owns), surfaced as a GEOMETRY blob, mirroring how the tpose port +// surfaces its value accessors. + +inline void Trgeo_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())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + // trgeo_geom returns a freshly allocated GSERIALIZED copy of + // the reference geometry, which the caller owns. + GSERIALIZED *gs = trgeo_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_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())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + // trgeo_start_value returns a freshly allocated GSERIALIZED + // copy of the rigid-geometry snapshot at the first instant. + GSERIALIZED *gs = trgeo_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())); + if (!temp) { + throw InvalidInputException("Invalid TRGEOMETRY data: null pointer"); + } + + // trgeo_end_value returns a freshly allocated GSERIALIZED + // copy of the rigid-geometry snapshot at the last instant. + GSERIALIZED *gs = trgeo_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 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 TRGeoTypes::RegisterScalarFunctions(ExtensionLoader &loader) { + + auto trgeo_function = ScalarFunction( + "TRGEOMETRY", + {LogicalType::VARCHAR}, + TRGeoTypes::TRGEOMETRY(), + Trgeo_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_function); + + auto trgeoseqarr_1param= ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeoTypes::TRGEOMETRY())}, + TRGeoTypes::TRGEOMETRY(), + Trgeo_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeoseqarr_1param); + + auto trgeoseqarr_2params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeoTypes::TRGEOMETRY()), LogicalType::VARCHAR}, + TRGeoTypes::TRGEOMETRY(), + Trgeo_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeoseqarr_2params); + + auto trgeoseqarr_3params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeoTypes::TRGEOMETRY()), LogicalType::VARCHAR, LogicalType::BOOLEAN}, + TRGeoTypes::TRGEOMETRY(), + Trgeo_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeoseqarr_3params); + + auto trgeoseqarr_4params = ScalarFunction( + "trgeometrySeq", + {LogicalType::LIST(TRGeoTypes::TRGEOMETRY()), LogicalType::VARCHAR, LogicalType::BOOLEAN, LogicalType::BOOLEAN}, + TRGeoTypes::TRGEOMETRY(), + Trgeo_sequence_constructor + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeoseqarr_4params); + + auto trgeo_to_timespan_function = ScalarFunction( + "timeSpan", + {TRGeoTypes::TRGEOMETRY()}, + SpanTypes::TSTZSPAN(), + Temporal_to_tstzspan); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_to_timespan_function); + + auto trgeo_to_tinstant_function = ScalarFunction( + "trgeometryInst", + {TRGeoTypes::TRGEOMETRY()}, + TRGeoTypes::TRGEOMETRY(), + Temporal_to_tinstant); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_to_tinstant_function); + + + auto setInterp_function = ScalarFunction( + "setInterp", + {TRGeoTypes::TRGEOMETRY(), LogicalType::VARCHAR}, + TRGeoTypes::TRGEOMETRY(), + Temporal_set_interp + ); + duckdb::RegisterSerializedScalarFunction(loader, setInterp_function); + + + auto merge_function = ScalarFunction( + "merge", + {TRGeoTypes::TRGEOMETRY(), TRGeoTypes::TRGEOMETRY()}, + TRGeoTypes::TRGEOMETRY(), + Temporal_merge + ); + duckdb::RegisterSerializedScalarFunction(loader, merge_function); + + auto tempSubtype_function = ScalarFunction( + "tempSubtype", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Temporal_subtype + ); + duckdb::RegisterSerializedScalarFunction(loader, tempSubtype_function); + + auto interp_function = ScalarFunction( + "interp", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Temporal_interp + ); + duckdb::RegisterSerializedScalarFunction(loader, interp_function); + + auto memSize_function = ScalarFunction( + "memSize", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::INTEGER, + Temporal_mem_size + ); + duckdb::RegisterSerializedScalarFunction(loader, memSize_function); + + auto trgeo_geom_function = ScalarFunction( + "geom", + {TRGeoTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Trgeo_geom + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_geom_function); + + auto trgeo_start_value_function = ScalarFunction( + "startValue", + {TRGeoTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Temporal_start_value + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_start_value_function); + + auto trgeo_end_value_function = ScalarFunction( + "endValue", + {TRGeoTypes::TRGEOMETRY()}, + GeoTypes::GEOMETRY(), + Temporal_end_value + ); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_end_value_function); + + auto startInstant_function = ScalarFunction( + "startInstant", + {TRGeoTypes::TRGEOMETRY()}, + TRGeoTypes::TRGEOMETRY(), + Temporal_start_instant + ); + duckdb::RegisterSerializedScalarFunction(loader, startInstant_function); + + auto endInstant_function = ScalarFunction( + "endInstant", + {TRGeoTypes::TRGEOMETRY()}, + TRGeoTypes::TRGEOMETRY(), + Temporal_end_instant + ); + duckdb::RegisterSerializedScalarFunction(loader, endInstant_function); + + auto instantN_function = ScalarFunction( + "instantN", + {TRGeoTypes::TRGEOMETRY(), LogicalType::INTEGER}, + TRGeoTypes::TRGEOMETRY(), + Temporal_instant_n + ); + duckdb::RegisterSerializedScalarFunction(loader, instantN_function); + + + auto trgeo_gettimestamptz_function = ScalarFunction( + "getTimestamp", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::TIMESTAMP_TZ, + Tinstant_timestamptz); + duckdb::RegisterSerializedScalarFunction(loader, trgeo_gettimestamptz_function); + + + // =================================================================== + // Foundational trgeo 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 = TRGeoTypes::TRGEOMETRY(); + const LogicalType TSTZ = LogicalType::TIMESTAMP_TZ; + const LogicalType IVAL = LogicalType::INTERVAL; + + // ---- Accessors ---- + 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 TRGeoTypes::RegisterTypes(ExtensionLoader &loader) { + loader.RegisterType( "TRGEOMETRY", TRGeoTypes::TRGEOMETRY()); +} + + +} diff --git a/src/geo/trgeo_in_out.cpp b/src/geo/trgeo_in_out.cpp new file mode 100644 index 0000000..4a208b0 --- /dev/null +++ b/src/geo/trgeo_in_out.cpp @@ -0,0 +1,335 @@ +#include "geo/trgeo.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 + // See geo/trgeo.cpp: trgeo_in is a MEOS inline (#if MEOS) wrapper + // with no linkable symbol and is not declared in the installed + // meos_rgeo.h. This port routes text/EWKT input through the + // subtype-agnostic spatial parser tspatial_parse with T_TRGEOMETRY + // (the exact path trgeo_in itself delegates to and the canonical + // MobilityDB SQL binds trgeometryFromText / trgeometry_in to). + extern Temporal *tspatial_parse(const char **str, MeosType temptype); +} + +namespace duckdb { + +static inline Temporal *Trgeo_parse_in(const std::string &str) { + const char *ptr = str.c_str(); + return tspatial_parse(&ptr, T_TRGEOMETRY); +} + +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"); + } + + char *str = tspatial_as_text(temp, 0); + + 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 TrgeoFunctions::StringToTrgeo(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 = Trgeo_parse_in(input_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 TrgeoFunctions::TrgeoToString(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"); + } + + // The trgeometry value is a moving rigid geometry; trgeo_out + // is the type-specific serializer that renders the reference + // geometry plus the embedded temporal pose. The generic + // temporal_out does not understand the rigid value model. + char *str = trgeo_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; +// the trgeometry text/EWKT parser routes through tspatial_parse with +// T_TRGEOMETRY. The temporal-rigid-geometry MF-JSON support already +// lives on MobilityDB master (MovingRigidGeometry typestring + dispatch), +// so no preceding MEOS parity PR is needed; MEOS does not, however, +// expose a header-declared `trgeo_from_mfjson(const char *)` symbol (it +// is an `#if MEOS` inline that calls `temporal_from_mfjson(mfjson, +// T_TRGEOMETRY)`). This port therefore routes through that +// subtype-agnostic dispatch directly, exactly as the canonical +// MobilityDB SQL binds `trgeometryFromMFJSON` to the generic +// Temporal_from_mfjson handler. 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 TrgeoFromTextExec(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 = Trgeo_parse_in(s); + if (!t) throw InvalidInputException("from*: invalid input"); + return StoreTempAsBlob(result, t); + }); +} + +inline void TrgeoFromMFJSONExec(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()); + // trgeo exposes no header-declared *_from_mfjson symbol; + // route through the generic dispatch with the T_TRGEOMETRY + // temporal type, the same path the canonical MobilityDB SQL + // binds trgeometryFromMFJSON to. + Temporal *t = temporal_from_mfjson(s.c_str(), T_TRGEOMETRY); + if (!t) throw InvalidInputException("fromMFJSON: invalid input"); + return StoreTempAsBlob(result, t); + }); +} + +void TRGeoTypes::RegisterScalarInOutFunctions(ExtensionLoader &loader){ + auto TrgeoAsText = ScalarFunction( + "asText", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Tspatial_as_text + ); + duckdb::RegisterSerializedScalarFunction(loader, TrgeoAsText); + + auto TrgeoAsEWKT = ScalarFunction( + "asEWKT", + {TRGeoTypes::TRGEOMETRY()}, + LogicalType::VARCHAR, + Tspatial_as_ewkt + ); + duckdb::RegisterSerializedScalarFunction(loader, TrgeoAsEWKT); + + // ---- trgeometryFromBinary / FromEWKB (auto-detects format) ---- + const auto B = LogicalType::BLOB; + const auto V = LogicalType::VARCHAR; + const auto T = TRGeoTypes::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, TrgeoFromMFJSONExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromText", {V}, T, TrgeoFromTextExec)); + duckdb::RegisterSerializedScalarFunction(loader, + ScalarFunction("trgeometryFromEWKT", {V}, T, TrgeoFromTextExec)); +} + + +void TRGeoTypes::RegisterCastFunctions(ExtensionLoader &loader) { + loader.RegisterCastFunction( LogicalType::VARCHAR, TRGeoTypes::TRGEOMETRY(), TrgeoFunctions::StringToTrgeo); + loader.RegisterCastFunction( TRGeoTypes::TRGEOMETRY(), LogicalType::VARCHAR, TrgeoFunctions::TrgeoToString); +} + +} diff --git a/src/include/geo/trgeo.hpp b/src/include/geo/trgeo.hpp new file mode 100644 index 0000000..8c72e36 --- /dev/null +++ b/src/include/geo/trgeo.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 TRGeoTypes { + 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 TrgeoFunctions { + static bool StringToTrgeo(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool TrgeoToString(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/mobilityduck_extension.cpp b/src/mobilityduck_extension.cpp index ba74b65..ac489dc 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 "geo/trgeo.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); + TRGeoTypes::RegisterScalarFunctions(loader); + TRGeoTypes::RegisterTypes(loader); + TRGeoTypes::RegisterCastFunctions(loader); + TRGeoTypes::RegisterScalarInOutFunctions(loader); + TGeogpointType::RegisterScalarFunctions(loader); TGeogpointType::RegisterCastFunctions(loader); TGeogpointType::RegisterScalarInOutFunctions(loader); diff --git a/test/sql/trgeo.test b/test/sql/trgeo.test new file mode 100644 index 0000000..774c5ce --- /dev/null +++ b/test/sql/trgeo.test @@ -0,0 +1,211 @@ +# name: test/sql/trgeo.test +# description: Core trgeo type port — construction, text/EWKT/MFJSON I/O +# and basic accessors. The trgeometry value is a moving rigid +# geometry: a reference geometry rotated and translated over +# time by an embedded temporal pose. +# group: [sql] + +require mobilityduck + +# Test trgeometry constructor with parentheses +query I +SELECT asText(trgeometry('Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01')) IS NOT NULL; +---- +true + +# 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') IS NOT NULL; +---- +true + +# Test asText round-trips through the type cast (text -> trgeometry -> text) +query I +SELECT asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01') + = asText(trgeometryFromText('Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01')); +---- +true + +# 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]') IS NOT NULL; +---- +true + +# 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}') IS NOT NULL; +---- +true + +# 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 MFJSON / FromMFJSON 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 2),0.5)@2000-01-01'))) + = asText(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 2),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 2),0.5)@2000-01-01, Pose(Point(3 4),0.5)@2000-01-02]'))) + = asText(trgeometry '[Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 2),0.5)@2000-01-01, Pose(Point(3 4),0.5)@2000-01-02]'); +---- +true + +# Test trgeometryFromText constructor is non-null +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 HexWKB round-trip is identity +query I +SELECT asText(trgeometryFromHexWKB(asHexWKB(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 geom returns the reference geometry (non-null, the rigid body) +query I +SELECT ST_AsText(geom(trgeometry 'Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01')) IS NOT NULL; +---- +true + +# Test startValue returns the rigid-geometry snapshot at the first instant +query I +SELECT ST_AsText(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 returns the rigid-geometry snapshot at the last instant +query I +SELECT ST_AsText(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]')) IS NOT NULL; +---- +true + +# 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]')) IS NOT NULL; +---- +true + +# 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)) IS NOT NULL; +---- +true + +# 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 + +# Test typed-literal INSERT round-trip (avoids the sequential VARCHAR-cast path) +statement ok +CREATE TABLE trgeo_tbl (rg trgeometry); + +statement ok +INSERT INTO trgeo_tbl VALUES ('Polygon((1 1,2 2,3 1,1 1));Pose(Point(1 1),0.5)@2000-01-01'::trgeometry); + +statement ok +INSERT INTO trgeo_tbl VALUES ('[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]'::trgeometry); + +query I +SELECT count(*) FROM trgeo_tbl WHERE asText(rg) IS NOT NULL; +---- +2 + +query I +SELECT numInstants(rg) FROM trgeo_tbl ORDER BY numInstants(rg); +---- +1 +2 From 2da2b5b7123c91a8d261bb5e0765647db6e6934d Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sun, 17 May 2026 14:51:47 +0200 Subject: [PATCH 2/2] Drop meos_rgeo.h include to fix the Interval ambiguity meos_rgeo.h is the only extended-type module header that declares an Interval-typed prototype (trgeo_append_tinstant). Including it brings the MEOS typedef struct Interval into the same namespace scope as duckdb::Interval (from tydef.hpp), so the unqualified Interval reference becomes ambiguous and the arm64 build of trgeo.cpp failed at the Build extension step. The pose, npoint and cbuffer module headers have no Interval-typed signature, which is why the sibling ports compile. This port only needs trgeo_out, trgeo_geom, trgeo_start_value and trgeo_end_value, so declare them locally as extern prototypes (the same approach already used for tspatial_parse) and stop including meos_rgeo.h. GSERIALIZED and MeosType remain reachable through meos_geo.h and meos_internal.h. --- src/geo/trgeo.cpp | 12 +++++++++++- src/geo/trgeo_in_out.cpp | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/geo/trgeo.cpp b/src/geo/trgeo.cpp index a55e3cf..15f1177 100644 --- a/src/geo/trgeo.cpp +++ b/src/geo/trgeo.cpp @@ -20,7 +20,17 @@ extern "C" { #include #include #include - #include + // meos_rgeo.h is deliberately NOT included: it is the only extended + // module header that declares an Interval-typed prototype + // (trgeo_append_tinstant), and at this namespace scope the MEOS + // `typedef struct Interval Interval` (from meos.h) collides with + // `duckdb::Interval` (from tydef.hpp), making the reference + // ambiguous. This port only needs four trgeo symbols, declared + // locally as extern prototypes instead. + extern char *trgeo_out(const Temporal *temp); + extern GSERIALIZED *trgeo_geom(const Temporal *temp); + extern GSERIALIZED *trgeo_start_value(const Temporal *temp); + extern GSERIALIZED *trgeo_end_value(const Temporal *temp); // The temporal rigid geometry text/EWKT parser. trgeo_in and // trgeo_from_mfjson are MEOS inline (#if MEOS) wrappers with no // linkable symbol and are not declared in the installed diff --git a/src/geo/trgeo_in_out.cpp b/src/geo/trgeo_in_out.cpp index 4a208b0..a713e44 100644 --- a/src/geo/trgeo_in_out.cpp +++ b/src/geo/trgeo_in_out.cpp @@ -11,7 +11,11 @@ extern "C" { #include #include #include - #include + // meos_rgeo.h is deliberately NOT included: its Interval-typed + // trgeo_append_tinstant prototype makes `Interval` ambiguous against + // duckdb::Interval at this namespace scope (see geo/trgeo.cpp). Only + // trgeo_out is needed here; declared locally. + extern char *trgeo_out(const Temporal *temp); // See geo/trgeo.cpp: trgeo_in is a MEOS inline (#if MEOS) wrapper // with no linkable symbol and is not declared in the installed // meos_rgeo.h. This port routes text/EWKT input through the