Skip to content

feat(core): support to_char format numeric values#1349

Merged
douenergy merged 3 commits intoCanner:mainfrom
goldmedal:feat/to-char-string
Oct 21, 2025
Merged

feat(core): support to_char format numeric values#1349
douenergy merged 3 commits intoCanner:mainfrom
goldmedal:feat/to-char-string

Conversation

@goldmedal
Copy link
Copy Markdown
Contributor

@goldmedal goldmedal commented Oct 14, 2025

follow up #1346

Summary by CodeRabbit

  • New Features

    • Added to_char SQL function for formatting date/time and numeric values with Chrono-format patterns. Supports multiple data types including dates, timestamps, durations, and decimal numbers.
  • Tests

    • Added comprehensive test coverage for to_char functionality with PostgreSQL and numeric formatting scenarios.

@github-actions github-actions bot added core ibis rust Pull requests that update Rust code python Pull requests that update Python code labels Oct 14, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

This pull request introduces a new to_char scalar UDF (User-Defined Function) to the Wren core system, implementing support for formatting date/time and numeric values. A macro-based infrastructure for lazy-initialized singleton UDF functions is added, alongside comprehensive tests validating the feature across multiple integration points.

Changes

Cohort / File(s) Summary
Test Coverage
ibis-server/tests/routers/v3/connector/postgres/test_query.py, wren-core/core/src/mdl/mod.rs
Adds test function for to_char numeric formatting at integration level (PostgreSQL connector) and MDL snapshot tests validating SQL transformation for date/time and numeric formatting cases
Macro Infrastructure
wren-core/core/src/mdl/function/macros.rs, wren-core/core/src/mdl/function/mod.rs
Introduces make_udf_function macro that generates thread-safe, lazily-initialized singleton Arc-wrapped DataFusion ScalarUDF instances; registers macro submodule
ToChar UDF Implementation & Registration
wren-core/core/src/mdl/function/scalar/to_char.rs, wren-core/core/src/mdl/function/scalar/mod.rs
Implements ToCharFunc struct with comprehensive type signature supporting date/time/numeric types; integrates into scalar function registry via macro invocation; adds module declaration and imports

Sequence Diagram

sequenceDiagram
    participant Test as Test/Query
    participant Engine as Wren Engine
    participant UDF as to_char UDF
    participant DataFusion as DataFusion

    Test->>Engine: SQL with to_char(column, pattern)
    Engine->>UDF: Resolve to_char function
    activate UDF
    Note over UDF: LazyLock<Arc<ScalarUDF>><br/>initialized on first call
    UDF-->>Engine: ScalarUDF instance
    deactivate UDF
    Engine->>DataFusion: Register & execute scalar UDF
    DataFusion->>DataFusion: invoke_with_args<br/>(type coercion & formatting)
    DataFusion-->>Engine: Formatted UTF8 result
    Engine-->>Test: Query result with formatted values
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Multiple interconnected files with new macro infrastructure and UDF implementation following established patterns. Complexity stems from macro mechanics, DataFusion type signature configuration, and cross-module integration; offset by consistent pattern reuse and comprehensive test validation.

Suggested reviewers

  • douenergy

Poem

🐰 A function takes form with to_char's gentle grace,
Formatting numbers and dates in their place,
With lazy singletons locked, thread-safe and true,
The macro framework makes UDFs anew! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat(core): support to_char format numeric values" is directly related to the main changes in the changeset. The pull request implements a comprehensive to_char UDF function that supports formatting multiple data types, including numeric values, with dedicated test coverage for numeric formatting in both the ibis-server and wren-core test suites. The title accurately summarizes the primary feature being added, is concise and clear, and would help teammates understand the key enhancement from scanning the commit history.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wren-core-py/src/context.rs (2)

344-361: Potential panic on NULLs and unwrap; make row decoding null‑safe

description and function_type may be NULL; calling value(row) and unwrap() can panic. Handle nulls and unknown function types gracefully.

Apply this diff:

@@
-        for batch in batches {
-            let name_array = batch.column(0).as_string::<i32>();
-            let function_type_array = batch.column(1).as_string::<i32>();
-            let description_array = batch.column(2).as_string::<i32>();
-
-            for row in 0..batch.num_rows() {
-                let name = name_array.value(row).to_string();
-                let description = description_array.value(row).to_string();
-                let function_type = function_type_array.value(row).to_string();
-
-                functions.push(RemoteFunctionDto {
-                    name,
-                    description: Some(description),
-                    function_type: FunctionType::from_str(&function_type).unwrap(),
-                });
-            }
-        }
+        for batch in batches {
+            let name_array = batch.column(0).as_string::<i32>();
+            let function_type_array = batch.column(1).as_string::<i32>();
+            let description_array = batch.column(2).as_string::<i32>();
+
+            for row in 0..batch.num_rows() {
+                // name is mandatory; skip if NULL
+                if !name_array.is_valid(row) {
+                    continue;
+                }
+                let name = name_array.value(row).to_string();
+
+                // description is optional
+                let description = if description_array.is_valid(row) {
+                    Some(description_array.value(row).to_string())
+                } else {
+                    None
+                };
+
+                // function_type may be NULL or unknown; default to Scalar or skip unknowns
+                let function_type_str = if function_type_array.is_valid(row) {
+                    function_type_array.value(row).to_string()
+                } else {
+                    "scalar".to_string()
+                };
+                let function_type = match FunctionType::from_str(&function_type_str) {
+                    Ok(ft) => ft,
+                    Err(_) => continue, // skip unrecognized types
+                };
+
+                functions.push(RemoteFunctionDto {
+                    name,
+                    description,
+                    function_type,
+                });
+            }
+        }

327-342: Use function_name instead of routine_name in SQL query
DataFusion 50’s information_schema.routines defines function_name (not routine_name), so update the SELECT clause in wren-core-py/src/context.rs to use r.function_name.

🧹 Nitpick comments (10)
wren-core/core/src/mdl/function/scalar/to_char.rs (1)

29-40: Review signature precision for Decimal types.

Lines 38-39 specify exact Decimal128(38, 10) and Decimal256(38, 10) signatures. These fixed precision/scale values may not match the actual decimal types in user data, potentially causing type coercion failures. Consider using TypeSignature::Coercible for decimal types as well, or document why these specific precision/scale values are required.

wren-core/core/src/mdl/function/scalar/mod.rs (1)

145-191: Unify nested function paths for readability (optional).

Mixed use of datafusion::functions_nested::... and bare module names (string::, range::, etc.). Prefer one style consistently (since functions_nested::* is imported). This is purely cosmetic.

Apply this diff to normalize the fully-qualified occurrences:

-        datafusion::functions_nested::concat::array_append_udf(),
-        datafusion::functions_nested::concat::array_prepend_udf(),
-        datafusion::functions_nested::concat::array_concat_udf(),
+        concat::array_append_udf(),
+        concat::array_prepend_udf(),
+        concat::array_concat_udf(),
@@
-        datafusion::functions_nested::repeat::array_repeat_udf(),
+        repeat::array_repeat_udf(),
@@
-        datafusion::functions_nested::reverse::array_reverse_udf(),
+        reverse::array_reverse_udf(),
@@
-        datafusion::functions_nested::replace::array_replace_n_udf(),
-        datafusion::functions_nested::replace::array_replace_all_udf(),
-        datafusion::functions_nested::replace::array_replace_udf(),
+        replace::array_replace_n_udf(),
+        replace::array_replace_all_udf(),
+        replace::array_replace_udf(),

Also applies to: 152-155, 172-175, 183-185

wren-core/core/src/mdl/function/macros.rs (3)

11-19: Confirm MSRV for std::sync::LazyLock or switch to once_cell::sync::Lazy.

LazyLock requires a relatively recent Rust (>=1.70). If your MSRV is lower or aligned to DataFusion consumers, prefer once_cell::sync::Lazy to avoid toolchain friction.

If needed, apply this diff:

-            static INSTANCE: std::sync::LazyLock<
-                std::sync::Arc<datafusion::logical_expr::ScalarUDF>,
-            > = std::sync::LazyLock::new(|| {
-                std::sync::Arc::new(datafusion::logical_expr::ScalarUDF::new_from_impl(
-                    <$UDF>::new(),
-                ))
-            });
+            static INSTANCE: once_cell::sync::Lazy<
+                std::sync::Arc<datafusion::logical_expr::ScalarUDF>,
+            > = once_cell::sync::Lazy::new(|| {
+                std::sync::Arc::new(datafusion::logical_expr::ScalarUDF::new_from_impl(
+                    <$UDF>::new(),
+                ))
+            });

Note: add once_cell as a dependency if not already present.


8-9: Fix doc link target.

Use the same path as the code (datafusion::logical_expr::ScalarUDF) to avoid broken intra-doc links.

-        #[doc = concat!("Return a [`ScalarUDF`](datafusion_expr::ScalarUDF) implementation of ", stringify!($NAME))]
+        #[doc = concat!("Return a [`ScalarUDF`](datafusion::logical_expr::ScalarUDF) implementation of ", stringify!($NAME))]

5-7: Consider not exporting the macro globally.

If this macro is only used within this crate, avoid #[macro_export] to prevent leaking it into downstream crates. Use path-qualified invocation instead.

-#[macro_export]
-macro_rules! make_udf_function {
+// keep crate-local, invoke as `crate::mdl::function::macros::make_udf_function!`
+macro_rules! make_udf_function {
wren-core/core/src/mdl/function/aggregate/mod.rs (1)

11-51: Optionally cache the registry to avoid repeated Vec allocation.

If aggregate_functions() is called frequently, consider a Lazy<Vec<Arc<AggregateUDF>>> and return a clone.

Example:

use once_cell::sync::Lazy;

static AGG_FUNCS: Lazy<Vec<Arc<AggregateUDF>>> = Lazy::new(|| vec![
    // ...
]);

pub fn aggregate_functions() -> Vec<Arc<AggregateUDF>> {
    AGG_FUNCS.clone()
}
wren-core/sqllogictest/src/test_context.rs (2)

81-89: Simplify Result handling; avoid .ok().unwrap().

Use direct unwrap() on the Result.

-                let ctx = apply_wren_on_ctx(
+                let ctx = apply_wren_on_ctx(
                     &ctx,
                     mdl.clone(),
                     SessionPropertiesRef::default(),
                     Mode::LocalRuntime,
                 )
-                .await
-                .ok()
-                .unwrap();
+                .await
+                .unwrap();

314-321: Use SessionPropertiesRef::default() for consistency.

Minor readability improvement over Arc::new(HashMap::new()).

-    let ctx = apply_wren_on_ctx(
+    let ctx = apply_wren_on_ctx(
         ctx,
         Arc::clone(&analyzed_mdl),
-        Arc::new(HashMap::new()),
+        SessionPropertiesRef::default(),
         Mode::LocalRuntime,
     )

Also applies to: 550-556

wren-core/core/src/mdl/function/mod.rs (1)

8-11: Avoid wildcard re-exports for API stability

pub use remote_function::* exports the entire module surface and can cause churn. Prefer exporting only needed symbols to keep a stable public API.

wren-core/wren-example/examples/to-many-calculation.rs (1)

20-26: Be explicit about CSV headers to avoid defaults drift

Set has_header(true) explicitly for robustness across DataFusion versions.

-    ctx.register_csv(
+    ctx.register_csv(
         "orders",
         "sqllogictest/tests/resources/ecommerce/orders.csv",
-        CsvReadOptions::new(),
+        CsvReadOptions::new().has_header(true),
     )
@@
-    ctx.register_csv(
+    ctx.register_csv(
         "customers",
         "sqllogictest/tests/resources/ecommerce/customers.csv",
-        CsvReadOptions::new(),
+        CsvReadOptions::new().has_header(true),
     )
@@
-    ctx.register_csv(
+    ctx.register_csv(
         "order_items",
         "sqllogictest/tests/resources/ecommerce/order_items.csv",
-        CsvReadOptions::new(),
+        CsvReadOptions::new().has_header(true),
     )

Also applies to: 36-41, 51-56

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52d0150 and bd19884.

📒 Files selected for processing (13)
  • ibis-server/tests/routers/v3/connector/postgres/test_query.py (1 hunks)
  • wren-core-py/src/context.rs (6 hunks)
  • wren-core/core/src/mdl/context.rs (1 hunks)
  • wren-core/core/src/mdl/function/aggregate/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/macros.rs (1 hunks)
  • wren-core/core/src/mdl/function/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/scalar/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/scalar/to_char.rs (1 hunks)
  • wren-core/core/src/mdl/function/table/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/window/mod.rs (1 hunks)
  • wren-core/core/src/mdl/mod.rs (81 hunks)
  • wren-core/sqllogictest/src/test_context.rs (5 hunks)
  • wren-core/wren-example/examples/to-many-calculation.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
ibis-server/tests/routers/v3/connector/postgres/test_query.py (3)
ibis-server/tests/conftest.py (1)
  • client (18-23)
ibis-server/tests/routers/v3/connector/postgres/test_functions.py (1)
  • manifest_str (35-36)
ibis-server/tests/routers/v3/connector/postgres/conftest.py (1)
  • connection_info (54-61)
wren-core/core/src/mdl/function/mod.rs (4)
wren-core/core/src/mdl/function/aggregate/mod.rs (1)
  • aggregate_functions (11-51)
wren-core/core/src/mdl/function/scalar/mod.rs (1)
  • scalar_functions (18-192)
wren-core/core/src/mdl/function/table/mod.rs (1)
  • table_functions (9-11)
wren-core/core/src/mdl/function/window/mod.rs (1)
  • window_functions (5-19)
wren-core-py/src/context.rs (3)
wren-core/core/src/mdl/mod.rs (4)
  • mdl (197-232)
  • create_wren_ctx (367-382)
  • new (128-183)
  • new (553-555)
wren-core/core/src/mdl/context.rs (2)
  • apply_wren_on_ctx (46-117)
  • new (336-366)
wren-core-py/src/remote_functions.rs (2)
  • from (65-99)
  • from (103-139)
wren-core/core/src/mdl/function/macros.rs (1)
wren-core/core/src/mdl/function/scalar/to_char.rs (1)
  • new (27-50)
wren-core/core/src/mdl/mod.rs (6)
wren-core/core/src/mdl/context.rs (3)
  • apply_wren_on_ctx (46-117)
  • new (336-366)
  • properties (93-99)
wren-core-py/src/context.rs (4)
  • new (81-203)
  • remote_functions (88-91)
  • register_remote_function (282-301)
  • default (62-70)
wren-core/core/src/mdl/function/scalar/mod.rs (1)
  • scalar_functions (18-192)
wren-core/core/src/mdl/function/aggregate/mod.rs (1)
  • aggregate_functions (11-51)
wren-core/core/src/mdl/function/window/mod.rs (1)
  • window_functions (5-19)
wren-core/core/src/mdl/function/table/mod.rs (1)
  • table_functions (9-11)
wren-core/wren-example/examples/to-many-calculation.rs (2)
wren-core/core/src/mdl/mod.rs (2)
  • mdl (197-232)
  • create_wren_ctx (367-382)
wren-core/core/src/mdl/context.rs (1)
  • apply_wren_on_ctx (46-117)
wren-core/sqllogictest/src/test_context.rs (2)
wren-core/core/src/mdl/mod.rs (2)
  • mdl (197-232)
  • create_wren_ctx (367-382)
wren-core/core/src/mdl/context.rs (1)
  • apply_wren_on_ctx (46-117)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: cargo check
  • GitHub Check: ci
🔇 Additional comments (7)
wren-core/core/src/mdl/function/table/mod.rs (1)

1-11: LGTM!

Clean implementation of the table function registry. Both generate_series() and range() are standard DataFusion table functions.

wren-core/core/src/mdl/function/window/mod.rs (1)

1-19: LGTM!

Comprehensive window function registry covering all standard DataFusion window functions.

wren-core/core/src/mdl/mod.rs (1)

366-382: LGTM!

Well-structured helper function that centralizes SessionContext creation with all Wren MDL function registries (scalar, aggregate, window, table). This promotes consistency and reduces duplication across the codebase.

wren-core/core/src/mdl/context.rs (1)

46-51: API rename/addition looks good.

apply_wren_on_ctx with Mode threads analyze/optimize behavior cleanly and preserves prior configuration handling.

wren-core/sqllogictest/src/test_context.rs (1)

66-67: Good switch to create_wren_ctx.

Using create_wren_ctx(Some(config)) aligns tests with the new registration flow.

wren-core-py/src/context.rs (1)

35-35: API migration to create_wren_ctx/apply_wren_on_ctx looks correct

Import, context creation, and both Unparse/LocalRuntime applications are wired properly; get_available_functions uses exec_ctx as expected.

Also applies to: 93-99, 172-188, 229-230

wren-core/wren-example/examples/to-many-calculation.rs (1)

79-85: Context application flow LGTM

create_wren_ctx then apply_wren_on_ctx with Mode::LocalRuntime is correct; tables registered before apply are preserved via state cloning.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
wren-core/core/src/mdl/mod.rs (1)

3735-3775: Test will fail: to_char implementation is incomplete.

This test verifies that to_char calls for both date/time formatting (line 3762) and numeric formatting (line 3768) are correctly transformed into SQL. However, as noted in a previous review, the actual to_char implementation in wren-core/core/src/mdl/function/scalar/to_char.rs still returns not_impl_err!, so any attempt to execute this function will fail at runtime.

The test only validates SQL transformation, not execution correctness. To make this test meaningful, you must first complete the to_char implementation to handle both temporal and numeric formatting before this test can pass end-to-end execution.

Based on learnings

wren-core/core/src/mdl/function/scalar/to_char.rs (2)

45-50: Document numeric-format behaviour too.

Docs still describe only Chrono/strftime formats even though the signature accepts integers/floats/decimals. Please add the PostgreSQL-style numeric format description and examples so users know how numerics are formatted.


72-74: Stub still blocks use.

invoke_with_args returns not_impl_err!, so every to_char(...) call fails. Implement formatting for the supported value types (dates/times/durations + numerics) instead of returning not_impl_err!.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd19884 and 64a3ecf.

📒 Files selected for processing (2)
  • wren-core/core/src/mdl/function/scalar/to_char.rs (1 hunks)
  • wren-core/core/src/mdl/mod.rs (81 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
wren-core/core/src/mdl/mod.rs (7)
wren-core/core/src/mdl/context.rs (3)
  • apply_wren_on_ctx (46-117)
  • new (336-366)
  • properties (93-99)
wren-core/core/src/mdl/function/scalar/to_char.rs (2)
  • new (29-52)
  • default (23-25)
wren-core-py/src/context.rs (4)
  • new (81-203)
  • remote_functions (88-91)
  • register_remote_function (282-301)
  • default (62-70)
wren-core/core/src/mdl/function/scalar/mod.rs (1)
  • scalar_functions (18-192)
wren-core/core/src/mdl/function/aggregate/mod.rs (1)
  • aggregate_functions (11-51)
wren-core/core/src/mdl/function/window/mod.rs (1)
  • window_functions (5-19)
wren-core/core/src/mdl/function/table/mod.rs (1)
  • table_functions (9-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: check Cargo.toml formatting
  • GitHub Check: cargo test (arm64)
  • GitHub Check: cargo test (amd64)
  • GitHub Check: clippy
  • GitHub Check: ci
  • GitHub Check: test
🔇 Additional comments (2)
wren-core/core/src/mdl/mod.rs (2)

366-382: LGTM! Centralized function registration.

The new create_wren_ctx function centralizes SessionContext creation with all default function registrations. The builder pattern is used appropriately, and the optional config parameter provides flexibility.


393-393: LGTM! Consistent API refactoring.

All test functions have been consistently updated to use the new create_wren_ctx(None) helper instead of direct SessionContext instantiation, and calls have been updated to use the renamed apply_wren_on_ctx API. The refactoring is thorough and consistent.

Also applies to: 480-480, 647-647, 680-680, 712-712, 723-723, 737-737, 757-757, 778-778, 837-837, 876-876, 890-890, 903-903, 917-917, 946-946, 962-962, 980-980, 1005-1005, 1016-1016, 1028-1028, 1047-1047, 1078-1078, 1093-1093, 1113-1113, 1128-1128, 1147-1147, 1169-1169, 1211-1211, 1252-1252, 1283-1283, 1309-1309, 1343-1343, 1362-1362, 1391-1391, 1409-1409, 1460-1460, 1487-1487, 1502-1502, 1516-1516, 1531-1531, 1547-1547, 1579-1579, 1678-1678, 1697-1697, 1728-1728, 1744-1744, 1782-1782, 1817-1817, 1852-1852, 1914-1914, 1965-1965, 1989-1989, 2201-2201, 2563-2563, 2626-2626, 2666-2666, 2714-2714, 2811-2811, 2884-2884, 2926-2926, 3034-3034, 3186-3186, 3224-3224, 3254-3254, 3315-3315, 3374-3374, 3458-3458, 3495-3495, 3525-3525, 3568-3568, 3631-3631, 3665-3665, 3684-3684, 3737-3737

@goldmedal goldmedal marked this pull request as draft October 14, 2025 09:03
@goldmedal goldmedal force-pushed the feat/to-char-string branch from 64a3ecf to 9bdc268 Compare October 17, 2025 07:16
@goldmedal goldmedal marked this pull request as ready for review October 17, 2025 10:12
@goldmedal goldmedal requested a review from douenergy October 17, 2025 10:12
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
wren-core/core/src/mdl/function/scalar/to_char.rs (3)

40-41: Accept any Decimal precision/scale, not just (38,10).

Hard-coding Decimal128/256 to (38,10) rejects valid types like DECIMAL(12,2). Use a Decimal class signature so any decimal coerces with a Utf8 format string.

-                TypeSignature::Exact(vec![DataType::Decimal128(38, 10), DataType::Utf8]),
-                TypeSignature::Exact(vec![DataType::Decimal256(38, 10), DataType::Utf8]),
+                TypeSignature::Coercible(vec![
+                    Coercion::new_exact(TypeSignatureClass::Decimal),
+                    Coercion::new_exact(TypeSignatureClass::Native(logical_string()))
+                ]),

If TypeSignatureClass::Decimal isn’t available in your version, reply and I’ll adapt this to the project’s decimal matching pattern.


45-50: Docs: mention numeric formatting and examples.

Signature supports integers/floats/decimals but docs only cover Chrono formats. Clarify that:

  • Chrono patterns apply to date/time/timestamp/duration
  • PostgreSQL-style patterns apply to numerics (9/0 placeholders, decimal point, rounding, optional FM modifier)
    Add 2–3 examples.
-                "Returns a string representation of a date, time, timestamp or duration based on a [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).",
-                "to_char(expression, format)")
-                .with_argument("expression", "The date, time, timestamp or duration to format.")
-                .with_argument("format", "The [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) string to use for formatting.")
+                "Format values as text. For date/time types, use [Chrono strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html). For numeric types, use PostgreSQL-style patterns (e.g., 9/0 for digit placeholders, '.' for decimal point, optional FM).",
+                "to_char(expression, format)")
+                .with_argument("expression", "Value to format (date/time/timestamp/duration or integer/float/decimal).")
+                .with_argument("format", "Chrono format for temporal values; PostgreSQL-style pattern for numerics (e.g., '9999', '0000.00', 'FM999.99').")
+                .with_example("to_char(1234, '9999') -> '1234'")
+                .with_example("to_char(1234.567, '0000.00') -> '1234.57'")
+                .with_example(\"to_char(timestamp '2024-01-01 23:59:59', '%Y-%m-%d %H:%M:%S') -> '2024-01-01 23:59:59'\")

72-74: Implement invoke_with_args (blocking).

Function currently returns not_impl_err; all new tests will fail. Implement formatting for:

  • date/time/timestamp/duration using Chrono patterns
  • integer/float/decimal using PostgreSQL-style numeric patterns ('9', '0', '.', grouping, rounding for scale)
    Return Utf8. Handle NULLs, arity/type checks, and errors without panics. I can provide a minimal numeric formatter to pass current tests ('9999', '0000.00') if helpful.
ibis-server/tests/routers/v3/connector/postgres/test_query.py (1)

1284-1301: Tests will fail: to_char UDF still returns not_impl_err.

The new assertions depend on a working to_char, but wren-core/core/src/mdl/function/scalar/to_char.rs::invoke_with_args still returns not_impl_err. Either implement it or skip these tests until it’s ready.

🧹 Nitpick comments (1)
wren-core/core/src/mdl/function/macros.rs (1)

8-8: Fix doc link to match the actual type path.

The code uses datafusion::logical_expr::ScalarUDF; the doc link points to datafusion_expr::ScalarUDF, which may break rustdoc unless that crate is a dependency. Point the link at the used path.

-        #[doc = concat!("Return a [`ScalarUDF`](datafusion_expr::ScalarUDF) implementation of ", stringify!($NAME))]
+        #[doc = concat!("Return a [`ScalarUDF`](datafusion::logical_expr::ScalarUDF) implementation of ", stringify!($NAME))]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64a3ecf and 9bdc268.

📒 Files selected for processing (6)
  • ibis-server/tests/routers/v3/connector/postgres/test_query.py (1 hunks)
  • wren-core/core/src/mdl/function/macros.rs (1 hunks)
  • wren-core/core/src/mdl/function/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/scalar/mod.rs (1 hunks)
  • wren-core/core/src/mdl/function/scalar/to_char.rs (1 hunks)
  • wren-core/core/src/mdl/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • wren-core/core/src/mdl/mod.rs
  • wren-core/core/src/mdl/function/scalar/mod.rs
🧰 Additional context used
🧬 Code graph analysis (2)
ibis-server/tests/routers/v3/connector/postgres/test_query.py (3)
ibis-server/tests/conftest.py (1)
  • client (18-23)
ibis-server/tests/routers/v3/connector/postgres/test_functions.py (1)
  • manifest_str (35-36)
ibis-server/tests/routers/v3/connector/postgres/conftest.py (1)
  • connection_info (54-61)
wren-core/core/src/mdl/function/macros.rs (1)
wren-core/core/src/mdl/function/scalar/to_char.rs (1)
  • new (29-52)
🔇 Additional comments (2)
wren-core/core/src/mdl/function/mod.rs (1)

2-2: LGTM: macros module inclusion is fine.

ibis-server/tests/routers/v3/connector/postgres/test_query.py (1)

1296-1297: Remove leading spaces from test assertions; PostgreSQL does not add them for these inputs.

The web search confirms that PostgreSQL's to_char(1234, '9999') returns '1234' and to_char(1234.567, '0000.00') returns '1234.57' without leading spaces. While by default output is right-aligned with leading spaces, this padding applies only when the value requires right-alignment within the template width. Since both test values fit exactly within their format templates, no leading spaces are added.

Update lines 1296–1297:

-    assert result["data"][0][0] == " 1234"
-    assert result["data"][0][1] == " 1234.57"
+    assert result["data"][0][0] == "1234"
+    assert result["data"][0][1] == "1234.57"

Likely an incorrect or invalid review comment.

@douenergy douenergy merged commit 489b952 into Canner:main Oct 21, 2025
11 of 12 checks passed
nhaluc1005 pushed a commit to nhaluc1005/text2sql-practice that referenced this pull request Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core ibis python Pull requests that update Python code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants