diff --git a/docs/mo-exact-decimal-import.md b/docs/mo-exact-decimal-import.md index b4d5148660..40259fea18 100644 --- a/docs/mo-exact-decimal-import.md +++ b/docs/mo-exact-decimal-import.md @@ -18,6 +18,9 @@ The first type owner is the validated import descriptor. Ordinary DuckDB decimal inference cannot substitute a narrower carrier. Decimal64/128 use tagged BIGINT/HUGEINT DuckDB carriers; Decimal256 uses the signed-high/unsigned low limb STRUCT introduced by #25. Sirius retains a distinct MO decimal type. +The scoped importer marks canonical exact-decimal carriers as opaque when +assigning SQL root names. Decimal256's private limbs consume no additional +headings; ordinary SQL STRUCT fields retain their usual flattened names. The private alias records the descriptor and is validated against the complete physical carrier on conversion. It is internal binding metadata, not a new diff --git a/src/numeric/decimal_import.cpp b/src/numeric/decimal_import.cpp index 20028c4d49..c2b05e6a02 100644 --- a/src/numeric/decimal_import.cpp +++ b/src/numeric/decimal_import.cpp @@ -63,6 +63,11 @@ class importer final : public duckdb::SubstraitExtensionHandler { public: explicit importer(std::vector anchors) : anchors_(std::move(anchors)) {} + bool IsOpaqueType(duckdb::LogicalType const& type) const override + { + auto exact = from_duckdb_type(type); + return exact && exact->is_mo_decimal(); + } bool Handles(duckdb::SubstraitExtensionIdentity const& identity) const override { return identity.urn == extension_uri; diff --git a/substrait b/substrait index 95d9ce8d78..99c7ca3b6f 160000 --- a/substrait +++ b/substrait @@ -1 +1 @@ -Subproject commit 95d9ce8d78490db3991ab6145653716aa3ec42c9 +Subproject commit 99c7ca3b6f8f3159239e119ed2982d42f98c4690 diff --git a/test/cpp/embedding/test_decimal_import.cpp b/test/cpp/embedding/test_decimal_import.cpp index dee0756989..d706604a16 100644 --- a/test/cpp/embedding/test_decimal_import.cpp +++ b/test/cpp/embedding/test_decimal_import.cpp @@ -10,11 +10,40 @@ #include "numeric/exact_decimal.hpp" #include +#include #include #include using namespace sirius; +TEST_CASE("MO opaque carriers preserve following SQL root columns", "[decimal_import]") +{ + decimal_fixture::importer fixture; + for (uint16_t bits : {64, 128, 256}) { + mo_decimal::decimal_type input{bits, 9, 2}, output{bits, 15, 2}; + auto plan = decimal_fixture::plan( + "mo_decimal_cast", decimal_fixture::type(output), {decimal_fixture::literal(input, 125)}); + auto root = plan.mutable_relations(0)->mutable_root(); + root->add_names("after"); + auto project = root->mutable_input()->mutable_project(); + project->mutable_common()->mutable_emit()->add_output_mapping(2); + project->add_expressions()->mutable_literal()->set_i64(42); + auto bytes = plan.SerializeAsString(); + auto handler = mo_decimal::make_import_handler(bytes); + CHECK(handler->IsOpaqueType(to_duckdb(logical_type::make_mo_decimal(output, false)))); + CHECK_FALSE( + handler->IsOpaqueType(duckdb::LogicalType::STRUCT({{"v", duckdb::LogicalType::BIGINT}}))); + duckdb::SubstraitToDuckDB converter(fixture.connection.context, bytes, false, false, handler); + auto relation = converter.TransformPlan(); + auto const& columns = relation->Columns(); + REQUIRE(columns.size() == 2); + CHECK(columns[0].GetName() == "exact"); + CHECK(columns[1].GetName() == "after"); + CHECK(from_duckdb(columns[0].GetType()) == logical_type::make_mo_decimal(output, false)); + CHECK(columns[1].GetType() == duckdb::LogicalType::BIGINT); + } +} + TEST_CASE("MO import carriers retain width precision scale and nullability", "[decimal_import]") { for (uint16_t bits : {64, 128, 256}) {