From 682403d13b66322177eb32c470c6ca415f460c1b Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 11 Jun 2026 00:34:37 +0200 Subject: [PATCH 1/2] feat(tfloat): bind sin/cos/tan as overloads on TFLOAT; bump MEOS pin to 59ba0ad59c Wire `tfloat_sin`, `tfloat_cos`, `tfloat_tan` from MEOS as `sin(tfloat)`, `cos(tfloat)`, `tan(tfloat)` DuckDB scalar overloads, following the same `TemporalUnary` pattern as `exp`/`ln`/`log10`. The pin advances from `278863520b` to `59ba0ad59c` (tag `ecosystem-pin-2026-06-11a`), which adds these three trig kernels and the `ensure_srid_reconcile` helper. --- src/include/temporal/temporal_functions.hpp | 3 ++ src/temporal/temporal.cpp | 3 ++ src/temporal/temporal_functions.cpp | 12 +++++ test/sql/parity/026c_tfloat_trig.test | 56 +++++++++++++++++++++ vcpkg_ports/meos/portfile.cmake | 4 +- 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 test/sql/parity/026c_tfloat_trig.test diff --git a/src/include/temporal/temporal_functions.hpp b/src/include/temporal/temporal_functions.hpp index bbb895c3..6a791493 100644 --- a/src/include/temporal/temporal_functions.hpp +++ b/src/include/temporal/temporal_functions.hpp @@ -232,6 +232,9 @@ struct TemporalFunctions { static void Tfloat_exp(DataChunk &args, ExpressionState &state, Vector &result); static void Tfloat_ln(DataChunk &args, ExpressionState &state, Vector &result); static void Tfloat_log10(DataChunk &args, ExpressionState &state, Vector &result); + static void Tfloat_sin(DataChunk &args, ExpressionState &state, Vector &result); + static void Tfloat_cos(DataChunk &args, ExpressionState &state, Vector &result); + static void Tfloat_tan(DataChunk &args, ExpressionState &state, Vector &result); // Temporal_derivative declared in the math-functions block below. static void Tfloat_degrees(DataChunk &args, ExpressionState &state, Vector &result); static void Tfloat_radians(DataChunk &args, ExpressionState &state, Vector &result); diff --git a/src/temporal/temporal.cpp b/src/temporal/temporal.cpp index 9abf2973..b5c59d9e 100644 --- a/src/temporal/temporal.cpp +++ b/src/temporal/temporal.cpp @@ -1367,6 +1367,9 @@ void TemporalTypes::RegisterScalarFunctions(ExtensionLoader &loader) { duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("exp", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_exp)); duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("ln", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_ln)); duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("log10", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_log10)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("sin", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_sin)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("cos", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_cos)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("tan", {TemporalTypes::TFLOAT()}, TemporalTypes::TFLOAT(), TemporalFunctions::Tfloat_tan)); // deltaValue / trend on tnumber duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("deltaValue", {TemporalTypes::TINT()}, TemporalTypes::TINT(), TemporalFunctions::Tnumber_delta_value)); diff --git a/src/temporal/temporal_functions.cpp b/src/temporal/temporal_functions.cpp index fc8a2b68..5b110e00 100644 --- a/src/temporal/temporal_functions.cpp +++ b/src/temporal/temporal_functions.cpp @@ -4816,6 +4816,18 @@ void TemporalFunctions::Tfloat_log10(DataChunk &args, ExpressionState &state, Ve TemporalUnary(args, result, [](Temporal *t) { return tfloat_log10(t); }); } +void TemporalFunctions::Tfloat_sin(DataChunk &args, ExpressionState &state, Vector &result) { + TemporalUnary(args, result, [](Temporal *t) { return tfloat_sin(t); }); +} + +void TemporalFunctions::Tfloat_cos(DataChunk &args, ExpressionState &state, Vector &result) { + TemporalUnary(args, result, [](Temporal *t) { return tfloat_cos(t); }); +} + +void TemporalFunctions::Tfloat_tan(DataChunk &args, ExpressionState &state, Vector &result) { + TemporalUnary(args, result, [](Temporal *t) { return tfloat_tan(t); }); +} + namespace { template diff --git a/test/sql/parity/026c_tfloat_trig.test b/test/sql/parity/026c_tfloat_trig.test new file mode 100644 index 00000000..1ede88d3 --- /dev/null +++ b/test/sql/parity/026c_tfloat_trig.test @@ -0,0 +1,56 @@ +# name: test/sql/parity/026c_tfloat_trig.test +# description: Trigonometric lifts for tfloat — sin, cos, tan. +# group: [sql] + +require mobilityduck + +# Instant: sin(0) = 0 +query I +SELECT sin(tfloat '[0@2000-01-01]'); +---- +[0@2000-01-01 00:00:00+01] + +# Instant: cos(0) = 1 +query I +SELECT cos(tfloat '[0@2000-01-01]'); +---- +[1@2000-01-01 00:00:00+01] + +# Instant: tan(0) = 0 +query I +SELECT tan(tfloat '[0@2000-01-01]'); +---- +[0@2000-01-01 00:00:00+01] + +# Sequence endpoints: sin lifted over [0, pi/2] starts at 0 and ends at 1 +query I +SELECT round(startValue(sin(tfloat '[0@2000-01-01, 1.5707963267948966@2000-01-02]')), 6); +---- +0.0 + +query I +SELECT round(endValue(sin(tfloat '[0@2000-01-01, 1.5707963267948966@2000-01-02]')), 6); +---- +1.0 + +# Sequence endpoints: cos lifted over [0, pi/2] starts at 1 and ends at 0 +query I +SELECT round(startValue(cos(tfloat '[0@2000-01-01, 1.5707963267948966@2000-01-02]')), 6); +---- +1.0 + +query I +SELECT round(endValue(cos(tfloat '[0@2000-01-01, 1.5707963267948966@2000-01-02]')), 6); +---- +0.0 + +# Sequence endpoints: tan lifted over [0, pi/4] starts at 0 and ends at 1 +query I +SELECT round(startValue(tan(tfloat '[0@2000-01-01, 0.7853981633974483@2000-01-02]')), 6); +---- +0.0 + +query I +SELECT round(endValue(tan(tfloat '[0@2000-01-01, 0.7853981633974483@2000-01-02]')), 6); +---- +1.0 diff --git a/vcpkg_ports/meos/portfile.cmake b/vcpkg_ports/meos/portfile.cmake index fd0da052..738c9f17 100644 --- a/vcpkg_ports/meos/portfile.cmake +++ b/vcpkg_ports/meos/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO estebanzimanyi/MobilityDB - REF 278863520b000b735cc361beab88387174909ed7 - SHA512 2e617cac4bfed919eee8bd73e35f9b3da8ffd7a51f68104a9497c0d3339dbb1af4a2ec6feab3e8fffb880481badf86c352ad1efb39cab533de19d2c1192a8e5b + REF 59ba0ad59cb93db6fa46929394e475b7851c00be + SHA512 e6a4a1578e5760326a596248865ccb487850120ca423f5675d1f014e9146de4f407c699afa63a102d8ba4ef1f895946d206ee4ab2f870dc4508ec2ca3d771869 ) vcpkg_replace_string( From 52541e709f9ae92f87bee00355476722ac784f92 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sun, 7 Jun 2026 12:27:36 +0200 Subject: [PATCH 2/2] Register asMFJSON for the number, text and bool temporal types asMFJSON(tint/tfloat/tbool/ttext[, options[, flags[, maxdecimaldigits]]]) wraps the type-agnostic temporal_as_mfjson, completing the MFJSON round-trip alongside the existing tintFromMFJSON/tfloatFromMFJSON/tboolFromMFJSON/ ttextFromMFJSON parsers and matching the MobilityDB surface. --- src/temporal/temporal.cpp | 33 +++++++++++++++++++++++++++ test/sql/asmfjson_temporal.test | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 test/sql/asmfjson_temporal.test diff --git a/src/temporal/temporal.cpp b/src/temporal/temporal.cpp index b5c59d9e..eef3b58e 100644 --- a/src/temporal/temporal.cpp +++ b/src/temporal/temporal.cpp @@ -2117,6 +2117,30 @@ void TemporalScalarFromMfjsonExec(DataChunk &args, ExpressionState &, Vector &re }); } +// asMFJSON(temp[, options[, flags[, maxdecimaldigits]]]) for the number/text/bool +// temporal types, deriving the same Temporal_as_mfjson surface MobilityDB exposes. +// options != 0 requests the bounding box; srs is not applicable to non-spatial temporals. +void TemporalScalarAsMfjsonExec(DataChunk &args, ExpressionState &, Vector &result) { + const idx_t n = args.size(); + const idx_t cc = args.ColumnCount(); + for (idx_t i = 0; i < cc; i++) args.data[i].Flatten(n); + auto in = FlatVector::GetData(args.data[0]); + auto out = FlatVector::GetData(result); + auto &outv = FlatVector::Validity(result); + for (idx_t row = 0; row < n; row++) { + if (!FlatVector::Validity(args.data[0]).RowIsValid(row)) { outv.SetInvalid(row); continue; } + Temporal *t = ScalarBlobToTemp(in[row]); + bool with_bbox = (cc > 1) ? FlatVector::GetData(args.data[1])[row] != 0 : false; + int flags = (cc > 2) ? FlatVector::GetData(args.data[2])[row] : 0; + int precision = (cc > 3) ? FlatVector::GetData(args.data[3])[row] : 15; + char *json = temporal_as_mfjson(t, with_bbox, flags, precision, nullptr); + free(t); + if (!json) { outv.SetInvalid(row); continue; } + out[row] = StringVector::AddString(result, json); + free(json); + } +} + } // anonymous namespace void TemporalTypes::RegisterWkbFunctions(ExtensionLoader &loader) { @@ -2155,7 +2179,16 @@ void TemporalTypes::RegisterWkbFunctions(ExtensionLoader &loader) { duckdb::RegisterSerializedScalarFunction( loader, ScalarFunction(e.mfj_name, {V}, e.type, e.mfj_exec)); + // asMFJSON(temp[, options[, flags]]) — completes the MFJSON round-trip. + const auto I = LogicalType::INTEGER; + loader.RegisterFunction(ScalarFunction("asMFJSON", {e.type}, V, TemporalScalarAsMfjsonExec)); + loader.RegisterFunction(ScalarFunction("asMFJSON", {e.type, I}, V, TemporalScalarAsMfjsonExec)); + loader.RegisterFunction(ScalarFunction("asMFJSON", {e.type, I, I}, V, TemporalScalarAsMfjsonExec)); } + // tfloat additionally takes maxdecimaldigits, matching the MobilityDB surface. + loader.RegisterFunction(ScalarFunction("asMFJSON", + {TFLOAT(), LogicalType::INTEGER, LogicalType::INTEGER, LogicalType::INTEGER}, + V, TemporalScalarAsMfjsonExec)); } namespace { diff --git a/test/sql/asmfjson_temporal.test b/test/sql/asmfjson_temporal.test new file mode 100644 index 00000000..ed7ea69a --- /dev/null +++ b/test/sql/asmfjson_temporal.test @@ -0,0 +1,40 @@ +# name: test/sql/asmfjson_temporal.test +# description: asMFJSON for the number/text/bool temporal types (MFJSON output round-trip) +# group: [mobilityduck] + +require mobilityduck + +# Each non-spatial temporal type emits its MovingX MFJSON type +query I +SELECT asMFJSON(tfloat '{1@2000-01-01, 2@2000-01-02}') LIKE '%"type":"MovingFloat"%'; +---- +true + +query I +SELECT asMFJSON(tint '[3@2000-01-01, 4@2000-01-02]') LIKE '%"type":"MovingInteger"%'; +---- +true + +query I +SELECT asMFJSON(ttext '[abc@2000-01-01]') LIKE '%"type":"MovingText"%'; +---- +true + +query I +SELECT asMFJSON(tbool '[true@2000-01-01]') LIKE '%"type":"MovingBoolean"%'; +---- +true + +# tfloat takes the optional options/flags/maxdecimaldigits arguments +query I +SELECT length(asMFJSON(tfloat '{1.23456789@2000-01-01, 2@2000-01-02}', 0, 0, 3)) > 0; +---- +true + +# round-trips back through tfloatFromMFJSON: parsing the emitted MFJSON and +# re-emitting reproduces the original MFJSON +query I +SELECT asMFJSON(tfloatFromMFJSON(asMFJSON(tfloat '{1@2000-01-01, 2@2000-01-02, 3@2000-01-03}'))) + = asMFJSON(tfloat '{1@2000-01-01, 2@2000-01-02, 3@2000-01-03}'); +---- +true