From f896b95701176dfc1b208ff0e2e43e1ecdd1abd7 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sat, 16 May 2026 08:14:16 +0200 Subject: [PATCH 1/3] Initialize MEOS per worker thread MEOS keeps the session timezone, errno, PROJ context and RNGs in thread-local storage and requires every thread that calls into it to run meos_initialize() before its first call. The extension only did this once on the load thread, so DuckDB TaskScheduler workers ran scalar, cast and aggregate bodies with a NULL session_timezone and segfaulted in pg_next_dst_boundary on the first timestamp parse. A thread-local guard now runs the per-thread init (and re-installs the process-global error handler, which meos_initialize() resets to the exit-on-error default) at the scalar exec wrapper and through a cast registration trampoline covering every cast entry point. --- src/geo/geoset.cpp | 4 +-- src/geo/stbox.cpp | 14 ++++---- src/geo/tgeogpoint.cpp | 8 ++--- src/geo/tgeogpoint_in_out.cpp | 5 +-- src/geo/tgeography_in_out.cpp | 4 +-- src/geo/tgeometry_in_out.cpp | 4 +-- src/geo/tgeompoint.cpp | 8 ++--- src/include/mobilityduck/meos_exec_serial.hpp | 32 +++++++++++++++++ src/include/mobilityduck/meos_thread.hpp | 34 +++++++++++++++++++ src/temporal/set.cpp | 14 ++++---- src/temporal/span.cpp | 30 ++++++++-------- src/temporal/spanset.cpp | 20 +++++------ src/temporal/tbox.cpp | 34 +++++++++---------- src/temporal/temporal.cpp | 16 ++++----- 14 files changed, 147 insertions(+), 80 deletions(-) create mode 100644 src/include/mobilityduck/meos_thread.hpp diff --git a/src/geo/geoset.cpp b/src/geo/geoset.cpp index 842045dd..1e19887e 100644 --- a/src/geo/geoset.cpp +++ b/src/geo/geoset.cpp @@ -36,12 +36,12 @@ void SpatialSetType::RegisterTypes(ExtensionLoader &loader){ } void SpatialSetType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, SpatialSetType::geomset(), SpatialSetFunctions::Text_to_geoset ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, SpatialSetType::geogset(), SpatialSetFunctions::Text_to_geoset diff --git a/src/geo/stbox.cpp b/src/geo/stbox.cpp index 8038f09b..7de72be5 100644 --- a/src/geo/stbox.cpp +++ b/src/geo/stbox.cpp @@ -27,43 +27,43 @@ void StboxType::RegisterType(ExtensionLoader &loader) { } void StboxType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, STBOX(), StboxFunctions::Stbox_in_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, STBOX(), LogicalType::VARCHAR, StboxFunctions::Stbox_out ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, GeoTypes::GEOMETRY(), STBOX(), StboxFunctions::Geo_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::TIMESTAMP_TZ, STBOX(), StboxFunctions::Timestamptz_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::tstzset(), STBOX(), StboxFunctions::Tstzset_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::TSTZSPAN(), STBOX(), StboxFunctions::Tstzspan_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::tstzspanset(), STBOX(), StboxFunctions::Tstzspanset_to_stbox_cast diff --git a/src/geo/tgeogpoint.cpp b/src/geo/tgeogpoint.cpp index 42e068e1..e811afde 100644 --- a/src/geo/tgeogpoint.cpp +++ b/src/geo/tgeogpoint.cpp @@ -43,25 +43,25 @@ void TgeogpointType::RegisterType(ExtensionLoader &loader) { } void TgeogpointType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TGEOGPOINT(), TgeogpointFunctions::Tpoint_in ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOGPOINT(), LogicalType::VARCHAR, TemporalFunctions::Temporal_out ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOGPOINT(), StboxType::STBOX(), TgeompointFunctions::Tspatial_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOGPOINT(), SpanTypes::TSTZSPAN(), TgeompointFunctions::Temporal_to_tstzspan_cast diff --git a/src/geo/tgeogpoint_in_out.cpp b/src/geo/tgeogpoint_in_out.cpp index 63645c33..f0efb6c5 100644 --- a/src/geo/tgeogpoint_in_out.cpp +++ b/src/geo/tgeogpoint_in_out.cpp @@ -2,6 +2,7 @@ #include "geo/tgeogpoint_functions.hpp" #include "duckdb/main/extension/extension_loader.hpp" #include "duckdb/common/extension_type_info.hpp" +#include "mobilityduck/meos_exec_serial.hpp" #include #include #include @@ -215,8 +216,8 @@ void TGeogpointType::RegisterScalarInOutFunctions(ExtensionLoader &loader){ void TGeogpointType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( LogicalType::VARCHAR, TGeogpointType::TGEOGPOINT(), TgeogpointFunctions::StringToTgeogpoint); - loader.RegisterCastFunction( TGeogpointType::TGEOGPOINT(), LogicalType::VARCHAR, TgeogpointFunctions::TgeogpointToString); + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TGeogpointType::TGEOGPOINT(), TgeogpointFunctions::StringToTgeogpoint); + RegisterMeosCastFunction(loader, TGeogpointType::TGEOGPOINT(), LogicalType::VARCHAR, TgeogpointFunctions::TgeogpointToString); } } diff --git a/src/geo/tgeography_in_out.cpp b/src/geo/tgeography_in_out.cpp index 3d5fac17..4aedbd31 100644 --- a/src/geo/tgeography_in_out.cpp +++ b/src/geo/tgeography_in_out.cpp @@ -288,8 +288,8 @@ void TGeographyTypes::RegisterScalarInOutFunctions(ExtensionLoader &loader){ void TGeographyTypes::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( LogicalType::VARCHAR, TGeographyTypes::TGEOGRAPHY(), TgeographyFunctions::StringToTgeography); - loader.RegisterCastFunction( TGeographyTypes::TGEOGRAPHY(), LogicalType::VARCHAR, TgeographyFunctions::TgeographyToString); + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TGeographyTypes::TGEOGRAPHY(), TgeographyFunctions::StringToTgeography); + RegisterMeosCastFunction(loader, TGeographyTypes::TGEOGRAPHY(), LogicalType::VARCHAR, TgeographyFunctions::TgeographyToString); } } diff --git a/src/geo/tgeometry_in_out.cpp b/src/geo/tgeometry_in_out.cpp index 7c8d5a87..5b88aec2 100644 --- a/src/geo/tgeometry_in_out.cpp +++ b/src/geo/tgeometry_in_out.cpp @@ -292,8 +292,8 @@ void TGeometryTypes::RegisterScalarInOutFunctions(ExtensionLoader &loader){ void TGeometryTypes::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( LogicalType::VARCHAR, TGeometryTypes::TGEOMETRY(), TgeometryFunctions::StringToTgeometry); - loader.RegisterCastFunction( TGeometryTypes::TGEOMETRY(), LogicalType::VARCHAR, TgeometryFunctions::TgeometryToString); + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TGeometryTypes::TGEOMETRY(), TgeometryFunctions::StringToTgeometry); + RegisterMeosCastFunction(loader, TGeometryTypes::TGEOMETRY(), LogicalType::VARCHAR, TgeometryFunctions::TgeometryToString); } } diff --git a/src/geo/tgeompoint.cpp b/src/geo/tgeompoint.cpp index 4f99e60f..9bfdf2aa 100644 --- a/src/geo/tgeompoint.cpp +++ b/src/geo/tgeompoint.cpp @@ -34,25 +34,25 @@ void TgeompointType::RegisterType(ExtensionLoader &loader) { } void TgeompointType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TGEOMPOINT(), TgeompointFunctions::Tpoint_in ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOMPOINT(), LogicalType::VARCHAR, TemporalFunctions::Temporal_out ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOMPOINT(), StboxType::STBOX(), TgeompointFunctions::Tspatial_to_stbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TGEOMPOINT(), SpanTypes::TSTZSPAN(), TgeompointFunctions::Temporal_to_tstzspan_cast diff --git a/src/include/mobilityduck/meos_exec_serial.hpp b/src/include/mobilityduck/meos_exec_serial.hpp index 5e3ba782..e12d9c5c 100644 --- a/src/include/mobilityduck/meos_exec_serial.hpp +++ b/src/include/mobilityduck/meos_exec_serial.hpp @@ -2,8 +2,10 @@ #include +#include "duckdb/function/cast/default_casts.hpp" #include "duckdb/function/scalar_function.hpp" #include "duckdb/main/extension/extension_loader.hpp" +#include "mobilityduck/meos_thread.hpp" namespace duckdb { @@ -26,6 +28,7 @@ inline ScalarFunction WrapScalarFunctionWithMeosExecMutex(ScalarFunction sf) { scalar_function_t orig = std::move(sf.function); sf.function = [orig = std::move(orig)](DataChunk &args, ExpressionState &state, Vector &result) { std::lock_guard guard(MeosSerializedExecMutex()); + EnsureMeosThreadInitialized(); orig(args, state, result); }; return sf; @@ -35,4 +38,33 @@ inline void RegisterSerializedScalarFunction(ExtensionLoader &loader, ScalarFunc loader.RegisterFunction(WrapScalarFunctionWithMeosExecMutex(std::move(sf))); } +/** + * Cast functions are a separate registration path from scalar functions and + * have no shared execution wrapper, yet they call MEOS just the same (e.g. the + * VARCHAR -> tgeompoint parse). The original function pointer is stashed in + * the bound cast data and reached through a trampoline that runs the + * per-thread MEOS init before delegating. MobilityDuck cast functions do not + * use cast_data themselves, so forwarding it untouched is safe. + */ +struct MeosCastData : BoundCastData { + explicit MeosCastData(cast_function_t orig_p) : orig(orig_p) { + } + cast_function_t orig; + unique_ptr Copy() const override { + return make_uniq(orig); + } +}; + +inline bool MeosCastTrampoline(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + EnsureMeosThreadInitialized(); + auto &data = parameters.cast_data->Cast(); + return data.orig(source, result, count, parameters); +} + +inline void RegisterMeosCastFunction(ExtensionLoader &loader, const LogicalType &source, const LogicalType &target, + cast_function_t function, int64_t implicit_cast_cost = -1) { + loader.RegisterCastFunction(source, target, BoundCastInfo(MeosCastTrampoline, make_uniq(function)), + implicit_cast_cost); +} + } // namespace duckdb diff --git a/src/include/mobilityduck/meos_thread.hpp b/src/include/mobilityduck/meos_thread.hpp new file mode 100644 index 00000000..e23eef3d --- /dev/null +++ b/src/include/mobilityduck/meos_thread.hpp @@ -0,0 +1,34 @@ +#pragma once + +extern "C" { +#include +} + +// Defined in mobilityduck_extension.cpp. Converts MEOS errors into DuckDB +// exceptions instead of the process-exiting default handler. +extern "C" void MobilityduckMeosErrorHandler(int errlevel, int errcode, const char *errmsg); + +namespace duckdb { + +// MEOS keeps the session timezone, errno, PROJ context and the RNGs in +// thread-local storage; each thread that calls MEOS must initialise it +// before its first call (see meos.h, "Multithreading"). DuckDB runs +// scalar, cast and aggregate bodies on TaskScheduler worker threads, so a +// one-shot init on the load thread leaves workers with a NULL +// session_timezone and pg_next_dst_boundary segfaults on the first +// timestamp parse. This runs the per-thread init exactly once per thread. +// +// meos_initialize() resets the process-global error handler to the +// exit-on-error default, so MobilityduckMeosErrorHandler is re-installed +// here; the store is an idempotent atomic write of the same pointer. +inline void EnsureMeosThreadInitialized() { + static thread_local const bool meos_thread_ready = []() { + meos_initialize(); + meos_initialize_error_handler(&MobilityduckMeosErrorHandler); + meos_initialize_timezone("Europe/Brussels"); + return true; + }(); + (void) meos_thread_ready; +} + +} // namespace duckdb diff --git a/src/temporal/set.cpp b/src/temporal/set.cpp index bf185b90..f1c26bd3 100644 --- a/src/temporal/set.cpp +++ b/src/temporal/set.cpp @@ -93,43 +93,43 @@ LogicalType SetTypeMapping::GetChildType(const LogicalType &type) { // Register all cast functions void SetTypes::RegisterCastFunctions(ExtensionLoader &loader) { for (const auto &set_type : SetTypes::AllTypes()) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, set_type, LogicalType::VARCHAR, SetFunctions::Set_to_text ); // Blob to text - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, set_type, SetFunctions::Text_to_set ); // text to blob auto base_type = SetTypeMapping::GetChildType(set_type); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, base_type, set_type, SetFunctions::Value_to_set_cast // set from base type ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::intset(), SetTypes::floatset(), SetFunctions::Intset_to_floatset_cast // intset -> floatset ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::floatset(), SetTypes::intset(), SetFunctions::Floatset_to_intset_cast // floatset --> intset ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::dateset(), SetTypes::tstzset(), SetFunctions::Dateset_to_tstzset_cast // dateset -> tstzset ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::tstzset(), SetTypes::dateset(), SetFunctions::Tstzset_to_dateset_cast // tstz -> dateset diff --git a/src/temporal/span.cpp b/src/temporal/span.cpp index b874625d..48dce1ff 100644 --- a/src/temporal/span.cpp +++ b/src/temporal/span.cpp @@ -89,68 +89,68 @@ LogicalType SpanTypeMapping::GetChildType(const LogicalType &type) { // Register all cast functions void SpanTypes::RegisterCastFunctions(ExtensionLoader &loader) { for (const auto &span_type : SpanTypes::AllTypes()) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, span_type, LogicalType::VARCHAR, SpanFunctions::Span_to_text ); // Blob to text - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, span_type, SpanFunctions::Text_to_span ); // text to blob - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::INTSPAN(), SpanTypes::FLOATSPAN(), SpanFunctions::Intspan_to_floatspan_cast // intspan -> floatspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::FLOATSPAN(), SpanTypes::INTSPAN(), SpanFunctions::Floatspan_to_intspan_cast // floatspan -> intspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::DATESPAN(), SpanTypes::TSTZSPAN(), SpanFunctions::Datespan_to_tstzspan_cast // datespan -> tstzspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::TSTZSPAN(), SpanTypes::DATESPAN(), SpanFunctions::Tstzspan_to_datespan_cast // tstzspan -> datespan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::intset(), SpanTypes::INTSPAN(), SpanFunctions::Set_to_span_cast // intset -> intspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::bigintset(), SpanTypes::BIGINTSPAN(), SpanFunctions::Set_to_span_cast // bigintset -> bigintspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::floatset(), SpanTypes::FLOATSPAN(), SpanFunctions::Set_to_span_cast // floatset -> floatspan ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::tstzset(), SpanTypes::TSTZSPAN(), SpanFunctions::Set_to_span_cast // tstzset -> tstzspan ); // Scalar value -> span casts - loader.RegisterCastFunction(LogicalType::INTEGER, SpanTypes::INTSPAN(), SpanFunctions::Value_to_span_cast); - loader.RegisterCastFunction(LogicalType::BIGINT, SpanTypes::BIGINTSPAN(), SpanFunctions::Value_to_span_cast); - loader.RegisterCastFunction(LogicalType::DOUBLE, SpanTypes::FLOATSPAN(), SpanFunctions::Value_to_span_cast); - loader.RegisterCastFunction(LogicalType::DATE, SpanTypes::DATESPAN(), SpanFunctions::Value_to_span_cast); - loader.RegisterCastFunction(LogicalType::TIMESTAMP_TZ, SpanTypes::TSTZSPAN(), SpanFunctions::Value_to_span_cast); + RegisterMeosCastFunction(loader, LogicalType::INTEGER, SpanTypes::INTSPAN(), SpanFunctions::Value_to_span_cast); + RegisterMeosCastFunction(loader, LogicalType::BIGINT, SpanTypes::BIGINTSPAN(), SpanFunctions::Value_to_span_cast); + RegisterMeosCastFunction(loader, LogicalType::DOUBLE, SpanTypes::FLOATSPAN(), SpanFunctions::Value_to_span_cast); + RegisterMeosCastFunction(loader, LogicalType::DATE, SpanTypes::DATESPAN(), SpanFunctions::Value_to_span_cast); + RegisterMeosCastFunction(loader, LogicalType::TIMESTAMP_TZ, SpanTypes::TSTZSPAN(), SpanFunctions::Value_to_span_cast); } } diff --git a/src/temporal/spanset.cpp b/src/temporal/spanset.cpp index 5c38c8e4..2c2c0e66 100644 --- a/src/temporal/spanset.cpp +++ b/src/temporal/spanset.cpp @@ -103,62 +103,62 @@ LogicalType SpansetTypeMapping::GetBaseType(const LogicalType &type) { // --- Register Cast --- void SpansetTypes::RegisterCastFunctions(ExtensionLoader &loader) { for (const auto &spanset_type : SpansetTypes::AllTypes()) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, spanset_type, LogicalType::VARCHAR, SpansetFunctions::Spanset_to_text ); // Blob to text - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, spanset_type, SpansetFunctions::Text_to_spanset ); // text to blob auto base_type = SpansetTypeMapping::GetBaseType(spanset_type); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, base_type, spanset_type, SpansetFunctions::Value_to_spanset_cast ); auto set_type = SpansetTypeMapping::GetSetType(spanset_type); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, set_type, spanset_type, SpansetFunctions::Set_to_spanset_cast ); auto child_type = SpansetTypeMapping::GetChildType(spanset_type); // span - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, child_type, spanset_type, SpansetFunctions::Span_to_spanset_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, spanset_type, child_type, SpansetFunctions::Spanset_to_span_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::intspanset(), SpansetTypes::floatspanset(), SpansetFunctions::Intspanset_to_floatspanset_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::floatspanset(), SpansetTypes::intspanset(), SpansetFunctions::Floatspanset_to_intspanset_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::datespanset(), SpansetTypes::tstzspanset(), SpansetFunctions::Datespanset_to_tstzspanset_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::tstzspanset(), SpansetTypes::datespanset(), SpansetFunctions::Tstzspanset_to_datespanset_cast diff --git a/src/temporal/tbox.cpp b/src/temporal/tbox.cpp index 4e342038..f093cc33 100644 --- a/src/temporal/tbox.cpp +++ b/src/temporal/tbox.cpp @@ -27,103 +27,103 @@ void TboxType::RegisterType(ExtensionLoader &loader) { } void TboxType::RegisterCastFunctions(ExtensionLoader &loader) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, TBOX(), TboxFunctions::Tbox_in ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TBOX(), LogicalType::VARCHAR, TboxFunctions::Tbox_out ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::INTEGER, TBOX(), TboxFunctions::Number_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::DOUBLE, TBOX(), TboxFunctions::Number_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::TIMESTAMP_TZ, TBOX(), TboxFunctions::Timestamptz_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::intset(), TBOX(), TboxFunctions::Set_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::floatset(), TBOX(), TboxFunctions::Set_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SetTypes::tstzset(), TBOX(), TboxFunctions::Set_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::INTSPAN(), TBOX(), TboxFunctions::Span_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::FLOATSPAN(), TBOX(), TboxFunctions::Span_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpanTypes::TSTZSPAN(), TBOX(), TboxFunctions::Span_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TBOX(), SpanTypes::INTSPAN(), TboxFunctions::Tbox_to_intspan_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TBOX(), SpanTypes::FLOATSPAN(), TboxFunctions::Tbox_to_floatspan_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TBOX(), SpanTypes::TSTZSPAN(), TboxFunctions::Tbox_to_tstzspan_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::intspanset(), TBOX(), TboxFunctions::Spanset_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::floatspanset(), TBOX(), TboxFunctions::Spanset_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, SpansetTypes::tstzspanset(), TBOX(), TboxFunctions::Spanset_to_tbox_cast diff --git a/src/temporal/temporal.cpp b/src/temporal/temporal.cpp index 7a64b498..87e36a9d 100644 --- a/src/temporal/temporal.cpp +++ b/src/temporal/temporal.cpp @@ -69,13 +69,13 @@ LogicalType TemporalTypes::GetBaseTypeFromAlias(const char *alias) { void TemporalTypes::RegisterCastFunctions(ExtensionLoader &loader) { for (auto &type : TemporalTypes::AllTypes()) { - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::VARCHAR, type, TemporalFunctions::Temporal_in ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, type, LogicalType::VARCHAR, TemporalFunctions::Temporal_out @@ -90,37 +90,37 @@ void TemporalTypes::RegisterCastFunctions(ExtensionLoader &loader) { // ); // } - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, LogicalType::BLOB, SpansetTypes::tstzspanset(), TemporalFunctions::Blob_to_tstzspanset ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TemporalTypes::TBOOL(), TemporalTypes::TINT(), TemporalFunctions::Tbool_to_tint_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TemporalTypes::TINT(), TemporalTypes::TFLOAT(), TemporalFunctions::Tint_to_tfloat_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TemporalTypes::TFLOAT(), TemporalTypes::TINT(), TemporalFunctions::Tfloat_to_tint_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TemporalTypes::TINT(), TboxType::TBOX(), TemporalFunctions::Tnumber_to_tbox_cast ); - loader.RegisterCastFunction( + RegisterMeosCastFunction(loader, TemporalTypes::TFLOAT(), TboxType::TBOX(), TemporalFunctions::Tnumber_to_tbox_cast From 2be1582e94e8b6a958a36e19e7baa5a1121bbb3b Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Wed, 20 May 2026 11:55:01 +0200 Subject: [PATCH 2/3] fix: drop premature `using meosType = MeosType;` alias in tydef.hpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `meosType` (lower-case) is the **pre-consolidation** MEOS type name; `MeosType` (upper-case) is the **post-consolidation** target that the upstream rename sweep has not yet reached. The current vcpkg pin (`vcpkg_ports/meos/portfile.cmake` REF f11b7443ee98…) is still pre-consolidation: `meos/include/temporal/meos_catalog.h` line 121 declares the typedef as `} meosType;` and every MEOS API uses the lower-case spelling. MobilityDuck's source code consistently uses `meosType` to match — `grep -rn '\bMeosType\b' src/` finds the name only on the alias line and its comment, nowhere else. c8cad6d added `using meosType = MeosType;` as a forward-looking bridge for the eventual consolidation bump. That bridge points at `MeosType`, which the current pin does NOT yet expose, so it breaks every PR's Linux arm64 build with: /duckdb_build_dir/src/include/tydef.hpp:18:18: error: ‘MeosType’ does not name a type; did you mean ‘meosType’? The fix is to drop the premature alias and replace the misleading comment with one that documents the pre/post-consolidation distinction and the resume path for the next pin bump — at that point a reviewer can either restore the bridge (this time it'll be valid because `MeosType` will exist) or sweep the MobilityDuck source from `meosType` to `MeosType` in a single PR. Unblocks every in-flight PR's Linux arm64 build: #126, #130, #149, #158, #159, #160, plus the entire `feat/*_port_core` extended-type stack (#148/#150/#151/#153/#155/#156). --- src/include/tydef.hpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/include/tydef.hpp b/src/include/tydef.hpp index b7b28109..b9860ca0 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 { From 690008560b6e8aec864e9ac7fabbba525df5ec1e Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sat, 16 May 2026 11:04:41 +0200 Subject: [PATCH 3/3] Stage icu for the macOS osx_arm64 test path too The stage_icu helper mapped only the Linux uname values, so on the macOS arm64 test runner uname -m returned "arm64" and the icu extension was copied to .duckdb/extensions/v1.4.4/arm64 instead of .../osx_arm64, where DuckDB's autoload looks. The hub fallback is not reliably resolvable on that runner, so the osx_arm64 Test step failed to load the extension. Map the OS and architecture to the DuckDB platform string (linux_amd64, linux_arm64, osx_amd64, osx_arm64) so the locally built icu is staged at the path autoload expects on every tested platform; the Linux mapping is unchanged. Co-Authored-By: Claude Opus 4.7 --- Makefile | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ab8cf2ee..a9485498 100644 --- a/Makefile +++ b/Makefile @@ -15,16 +15,23 @@ include extension-ci-tools/makefiles/duckdb_extension.Makefile # 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. Inside the linux_amd64 test docker -# container that path is empty and there is no network egress, so the -# autoload fails. 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 before running the unittester. +# 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 \ - platform=$$(uname -m | sed 's/x86_64/linux_amd64/;s/aarch64/linux_arm64/'); \ + 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/"; \