Skip to content

[Variant] Align cast logic related to utf8 with arrow-cast kernel - #10114

Open
klion26 wants to merge 11 commits into
apache:mainfrom
klion26:variant-cast-utf8
Open

klion26 wants to merge 11 commits into
apache:mainfrom
klion26:variant-cast-utf8

Conversation

@klion26

@klion26 klion26 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

What changes are included in this PR?

  • Align the variant to arrow logic(utf8 related) with arrow-cast kernel
  • make some function public in arrow-rs

Are these changes tested?

Are there any user-facing changes?

@github-actions github-actions Bot added arrow Changes to the arrow crate parquet-variant parquet-variant* crates labels Jun 11, 2026
@klion26
klion26 marked this pull request as draft June 11, 2026 10:36
@klion26
klion26 force-pushed the variant-cast-utf8 branch from ba2dcbd to 408a83f Compare June 12, 2026 09:57
@sdf-jkl

sdf-jkl commented Aug 8, 2026

Copy link
Copy Markdown
Member

Now that #10145 is fixed we can go back to this!

@klion26

klion26 commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Rebased on the latest main branch, there are too many changes, I need to have a self review, will make this ready when self review done.

@klion26
klion26 force-pushed the variant-cast-utf8 branch 3 times, most recently from 7a55774 to d2e09e8 Compare August 24, 2026 06:47
@klion26
klion26 marked this pull request as ready for review August 24, 2026 06:48

@klion26 klion26 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@sdf-jkl This is ready for review now, the ci failed because of #10708 , will rebase the master after #10814 has been merged

use std::iter::zip;

#[test]
fn test_compatible_cast_logic_with_cast_kernel() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Some cast logic was implemented directly here, add a test to cover it.

) {
Ok(Some(v)) => {
self.builder.append_value(v);
let $builder = &mut self.builder;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Add this because that variant_to_string return String now, but the builder.append_value receives a &str

@klion26
klion26 force-pushed the variant-cast-utf8 branch 3 times, most recently from 9f05bca to 829e8cf Compare August 26, 2026 11:36
Comment thread arrow-cast/src/display.rs

fn write_timestamp(
/// Writes a timestamp value to the output using the given representation.
pub fn write_timestamp(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

im not sure about adding this to our public api; we could probably get away with leaving it public and just copying the code required into the variant crate? especially as they seem to use default format + either no timezone or the default utc timezone

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

realized there was a typo here; i meant get away with leaving it private*

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed; I meant to make the logic aligned with arrow-cast, even if we change the logic in arrow-cast later, but we have a test to cover this, and the logic is simple; copied to parquet-variant crate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we keep this private then?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we need to update it to pub for now, previously, we'll call this function with different timezone/format now.

Comment thread arrow-cast/src/parse.rs Outdated
/// let expected_d4 = NaiveDate::from_ymd_opt(2026, 6, 10).unwrap();
/// assert_eq!(expected_d4, d4);
/// ```
pub fn parse_date(string: &str) -> Option<NaiveDate> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this a leftover change? cant seem to find where this now being used in the diff

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This case just wants to show the supported format of this function; it is not used in the current change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so it doesnt need to be made public?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, this belongs to a future pr, leftover when rebasing and splitting the original pr. changed.

// boolean -> string
let boolean_array = BooleanArray::from(vec![Some(true), Some(false)]);
let cast_array = cast(&boolean_array, &DataType::Utf8).unwrap();
let boolean_utf8_array = cast_array.as_any().downcast_ref::<StringArray>().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
let boolean_utf8_array = cast_array.as_any().downcast_ref::<StringArray>().unwrap();
let boolean_utf8_array = cast_array.as_string::<i32>();

can use the downcasters here to make it more succinct: https://docs.rs/arrow/latest/arrow/array/trait.AsArray.html

@klion26

klion26 commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@Jefffrey Thanks for the review, I've addressed the comments, please take another look when you're free.

Comment thread arrow-cast/src/display.rs

fn write_timestamp(
/// Writes a timestamp value to the output using the given representation.
pub fn write_timestamp(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

realized there was a typo here; i meant get away with leaving it private*

Comment thread arrow-cast/src/parse.rs Outdated
/// let expected_d4 = NaiveDate::from_ymd_opt(2026, 6, 10).unwrap();
/// assert_eq!(expected_d4, d4);
/// ```
pub fn parse_date(string: &str) -> Option<NaiveDate> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so it doesnt need to be made public?

cast(&timestamp_nano_arrow_array, &DataType::Utf8).unwrap();
let timestamp_nano_cast_utf8_array = timestamp_nano_arrow_cast_array
.as_any()
.downcast_ref::<StringArray>()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fyi can replace more usages of this downcast pattern with just as_string::<i32>() in more of these tests, etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed

@github-actions github-actions Bot removed arrow Changes to the arrow crate arrow-cast labels Sep 1, 2026

@klion26 klion26 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@Jefffrey I've updated the code, please take another look when you're free, thanks.

@Jefffrey Jefffrey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should be good once ci is green

@sdf-jkl

sdf-jkl commented Sep 1, 2026

Copy link
Copy Markdown
Member

I'll take a look today

@sdf-jkl sdf-jkl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @klion26, left some comments that need to be addressed before merging.

fyi @Jefffrey

Variant::Int16(i) => Some(lexical_to_string(*i)),
Variant::Int32(i) => Some(lexical_to_string(*i)),
Variant::Int64(i) => Some(lexical_to_string(*i)),
Variant::Float(f) => Some(lexical_to_string(*f)),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

arrow-cast's ArrayFormatter uses ryu for float

($($t:ty),+) => {
$(impl<'a> DisplayIndex for &'a PrimitiveArray<$t>
{
fn write(&self, idx: usize, f: &mut dyn Write) -> FormatResult {
let value = self.value(idx);
let mut buffer = ryu::Buffer::new();
f.write_str(buffer.format(value))?;
Ok(())
}
})+
};
}

the difference:

  #[test]
  fn reproduce_float_string_difference() {
      let value = f32::from_bits(0xd378_62a7);

      // What #10114 currently produces
      let variant_output = lexical_to_string(value);

      // What arrow-cast produces
      let array = Float32Array::from(vec![value]);
      let casted = cast(&array, &DataType::Utf8).unwrap();
      let arrow_output = casted.as_string::<i32>().value(0);

      assert_eq!(variant_output, "-1.066807e12");
      assert_eq!(arrow_output, "-1066807000000.0");

      // Fails:
      assert_eq!(variant_output, arrow_output);
  }

}

// convert a variant to an owned string.
pub(crate) fn variant_to_string(variant: &Variant<'_, '_>) -> Option<String> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should add FormatOptions parameter to pick how to write Date/Time/Timestamps to a string. It shouldn't be just default like it is in write_utc_timestamp_with_default_format and Date/Time arms.

let _ = write!(
ret_str,
", {}",
variant_to_string(&item).unwrap_or_default()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

By calling variant_to_string recursively we drop the Variant::Object values inside a list.

arrow-cast doesn't support Objects -> String, but supports them inside a List via ArrayFormatter 🤷 (separate issue maybe?)

regardless of correctness of the Object support above, variant-cast to String shouldn't lose data.

  #[test]
  fn reproduce_list_of_objects_utf8_difference() {
      // Build the Variant value: [{"x": 1}]
      let mut variant_builder = VariantBuilder::new();
      let mut variant_list = variant_builder.new_list();

      variant_list
          .new_object()
          .with_field("x", 1_i32)
          .finish();

      variant_list.finish();

      let (metadata, value) = variant_builder.finish();
      let variant = Variant::new(&metadata, &value);

      // Current #10114 result
      let variant_output = variant_to_string(&variant).unwrap();

      // Build the equivalent Arrow List<Struct<x: Int32>>
      let fields = vec![Field::new("x", DataType::Int32, true)];
      let struct_builder = StructBuilder::from_fields(fields, 1);
      let mut arrow_list = ListBuilder::new(struct_builder);

      let struct_builder = arrow_list.values();
      struct_builder
          .field_builder::<Int32Builder>(0)
          .unwrap()
          .append_value(1);
      struct_builder.append(true);
      arrow_list.append(true);

      let arrow_list = arrow_list.finish();

      // Arrow List<Struct> → Utf8 result
      let casted = cast(&arrow_list, &DataType::Utf8).unwrap();
      let arrow_output = casted.as_string::<i32>().value(0);

      assert_eq!(variant_output, "[]");
      assert_eq!(arrow_output, "[{x: 1}]");

      // Fails
      assert_eq!(variant_output, arrow_output);
  }

@klion26

klion26 commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

@sdf-jkl Thank for the review, filed a new issue to track the Variant::Object in Variant::List case,

I've update the code, and will run some benchmark to see if there are any performance regression in arrow-cast, will ping you for another review when ready.

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing variant-cast-utf8 (e73c88f) to aee9535 (merge-base) diff

Run configuration
run benchmark cast_kernels
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                              main                                   variant-cast-utf8
-----                                                              ----                                   -----------------
"cast decimal128 to float64"                                       1.00     27.2±0.03µs        ? ?/sec    1.00     27.1±0.02µs        ? ?/sec
"cast decimal128 to int64"                                         1.00     48.0±0.44µs        ? ?/sec    1.00     48.0±0.44µs        ? ?/sec
"cast decimal128 to int8"                                          1.00     62.4±2.23µs        ? ?/sec    1.00     62.1±2.33µs        ? ?/sec
"cast decimal256 to float64"                                       1.00     56.4±0.03µs        ? ?/sec    1.00     56.6±0.05µs        ? ?/sec
"cast decimal256 to int64"                                         1.00    137.7±0.66µs        ? ?/sec    1.00    137.6±0.74µs        ? ?/sec
"cast float64 to decimal128(32, 3)"                                1.00     27.7±0.02µs        ? ?/sec    1.01     28.1±0.03µs        ? ?/sec
"cast invalid float64 to to decimal128(32, 3)"                     1.00     10.7±0.01µs        ? ?/sec    1.00     10.7±0.01µs        ? ?/sec
"cast string to decimal128(38, 3)"                                 1.00     85.5±0.35µs        ? ?/sec    1.00     85.6±0.41µs        ? ?/sec
"cast string to decimal256(76, 3)"                                 1.02    137.1±0.69µs        ? ?/sec    1.00    134.7±0.52µs        ? ?/sec
cast binary dict to string view (sparse)                           1.17     52.8±3.94µs        ? ?/sec    1.00     45.3±0.13µs        ? ?/sec
cast binary to dict high cardinality                               1.00    222.9±0.43µs        ? ?/sec    1.00    222.4±0.16µs        ? ?/sec
cast binary to dict high cardinality no nulls                      1.00    164.4±0.32µs        ? ?/sec    1.01    165.4±0.44µs        ? ?/sec
cast binary to dict low cardinality                                1.00    191.3±0.13µs        ? ?/sec    1.00    190.9±0.10µs        ? ?/sec
cast binary to dict low cardinality no nulls                       1.00    132.5±0.25µs        ? ?/sec    1.01    133.4±0.37µs        ? ?/sec
cast binary to dict medium cardinality                             1.00    192.9±0.10µs        ? ?/sec    1.00    192.7±0.09µs        ? ?/sec
cast binary to dict medium cardinality no nulls                    1.00    134.3±0.22µs        ? ?/sec    1.01    135.0±0.40µs        ? ?/sec
cast binary view to string                                         1.04     69.4±1.87µs        ? ?/sec    1.00     66.8±0.63µs        ? ?/sec
cast binary view to string view                                    1.08     67.5±0.58µs        ? ?/sec    1.00     62.5±1.21µs        ? ?/sec
cast binary view to wide string                                    1.00     67.4±0.57µs        ? ?/sec    1.00     67.4±0.60µs        ? ?/sec
cast date to string                                                1.00    500.3±7.68µs        ? ?/sec    1.01    504.0±7.42µs        ? ?/sec
cast date32 to date64 512                                          1.00    330.1±0.25ns        ? ?/sec    1.01    334.5±2.58ns        ? ?/sec
cast date64 to date32 512                                          1.02    832.8±1.90ns        ? ?/sec    1.00    813.0±2.18ns        ? ?/sec
cast decimal128 to decimal128 512                                  1.00      6.9±0.01µs        ? ?/sec    1.00      6.9±0.01µs        ? ?/sec
cast decimal128 to decimal128 512 lower precision                  1.00     18.0±0.71µs        ? ?/sec    1.00     17.9±0.15µs        ? ?/sec
cast decimal128 to decimal128 512 with lower scale (infallible)    1.00     45.5±0.04µs        ? ?/sec    1.00     45.6±0.04µs        ? ?/sec
cast decimal128 to decimal128 512 with same scale                  1.02     76.9±3.25ns        ? ?/sec    1.00     75.3±2.85ns        ? ?/sec
cast decimal128 to decimal256 512                                  1.00     26.3±0.09µs        ? ?/sec    1.00     26.2±0.02µs        ? ?/sec
cast decimal256 to decimal128 512                                  1.00    303.2±0.55µs        ? ?/sec    1.00    303.2±0.29µs        ? ?/sec
cast decimal256 to decimal256 512                                  1.00     99.1±0.37µs        ? ?/sec    1.01    100.1±0.27µs        ? ?/sec
cast decimal256 to decimal256 512 with same scale                  1.01     77.0±1.27ns        ? ?/sec    1.00     75.9±2.14ns        ? ?/sec
cast dict to string view                                           1.00     10.5±0.01µs        ? ?/sec    1.01     10.6±0.01µs        ? ?/sec
cast dict to string view (sparse)                                  1.00      5.2±0.06µs        ? ?/sec    1.01      5.3±0.10µs        ? ?/sec
cast f32 to string 512                                             1.00     11.9±0.08µs        ? ?/sec    1.00     11.9±0.06µs        ? ?/sec
cast f64 to string 512                                             1.00     15.2±0.04µs        ? ?/sec    1.01     15.4±0.03µs        ? ?/sec
cast float32 to int32 512                                          1.00    596.1±0.95ns        ? ?/sec    1.02    610.9±2.18ns        ? ?/sec
cast float64 to float32 512                                        1.03    622.6±1.58ns        ? ?/sec    1.00    605.1±2.17ns        ? ?/sec
cast float64 to uint64 512                                         1.00    619.7±0.52ns        ? ?/sec    1.00    621.3±1.89ns        ? ?/sec
cast i64 to string 512                                             1.00      8.7±0.06µs        ? ?/sec    1.02      8.8±0.03µs        ? ?/sec
cast int32 to float32 512                                          1.00    598.6±0.81ns        ? ?/sec    1.04    621.4±2.15ns        ? ?/sec
cast int32 to float64 512                                          1.00    692.1±0.63ns        ? ?/sec    1.01    699.8±2.57ns        ? ?/sec
cast int32 to int32 512                                            1.01    163.4±1.42ns        ? ?/sec    1.00    162.2±1.83ns        ? ?/sec
cast int32 to int64 512                                            1.00    600.6±1.01ns        ? ?/sec    1.02    612.3±1.89ns        ? ?/sec
cast int32 to uint32 512                                           1.00    713.3±1.05ns        ? ?/sec    1.00    716.5±1.69ns        ? ?/sec
cast int64 to decimal32(9, -1) 512                                 1.00      3.4±0.01µs        ? ?/sec    1.00      3.4±0.00µs        ? ?/sec
cast int64 to decimal32(9, 0) 512                                  1.00   1246.6±5.10ns        ? ?/sec    1.02   1265.6±3.55ns        ? ?/sec
cast int64 to int32 512                                            1.00    815.0±1.58ns        ? ?/sec    1.00    812.3±1.52ns        ? ?/sec
cast micro timestamp to string                                     1.00  1195.8±10.18µs        ? ?/sec    1.00  1198.4±11.96µs        ? ?/sec
cast micro timestamp with timezone to string                       1.00    873.7±1.18µs        ? ?/sec    1.00    869.4±1.36µs        ? ?/sec
cast nested dict to dict                                           1.01      4.5±0.01µs        ? ?/sec    1.00      4.5±0.01µs        ? ?/sec
cast no runs of int32s to ree<int32>                               1.03     57.3±0.61µs        ? ?/sec    1.00     55.6±0.60µs        ? ?/sec
cast runs of 10 string to ree<int32>                               1.00      8.1±0.05µs        ? ?/sec    1.02      8.3±0.05µs        ? ?/sec
cast runs of 1000 int32s to ree<int32>                             1.00      3.0±0.00µs        ? ?/sec    1.00      2.9±0.01µs        ? ?/sec
cast string single run to ree<int32>                               1.00     26.8±0.01µs        ? ?/sec    1.00     26.8±0.02µs        ? ?/sec
cast string to binary view 512                                     1.00      2.0±0.00µs        ? ?/sec    1.04      2.1±0.00µs        ? ?/sec
cast string view to binary view                                    1.00     88.3±0.70ns        ? ?/sec    1.00     88.3±1.20ns        ? ?/sec
cast string view to dict                                           1.01    155.3±0.24µs        ? ?/sec    1.00    154.3±0.35µs        ? ?/sec
cast string view to string                                         1.08     45.9±7.60µs        ? ?/sec    1.00     42.6±0.89µs        ? ?/sec
cast string view to wide string                                    1.10     46.8±9.94µs        ? ?/sec    1.00     42.4±0.85µs        ? ?/sec
cast time32s to time32ms 512                                       1.01    821.7±1.19ns        ? ?/sec    1.00    811.3±1.82ns        ? ?/sec
cast time32s to time64us 512                                       1.01    336.6±0.26ns        ? ?/sec    1.00    334.7±0.92ns        ? ?/sec
cast time64 to string                                              1.00   552.4±11.32µs        ? ?/sec    1.01   559.8±13.95µs        ? ?/sec
cast time64ns to time32s 512                                       1.01      2.3±0.00µs        ? ?/sec    1.00      2.3±0.00µs        ? ?/sec
cast timestamp_ms to i64 512                                       1.04    254.2±6.21ns        ? ?/sec    1.00    245.1±2.37ns        ? ?/sec
cast timestamp_ms to timestamp_ns 512                              1.00   1226.5±3.43ns        ? ?/sec    1.00   1228.5±3.77ns        ? ?/sec
cast timestamp_ns to timestamp_s 512                               1.01    162.4±1.92ns        ? ?/sec    1.00    161.1±2.40ns        ? ?/sec
cast utf8 to date32 512                                            1.01      6.6±0.04µs        ? ?/sec    1.00      6.6±0.03µs        ? ?/sec
cast utf8 to date64 512                                            1.00     31.8±0.17µs        ? ?/sec    1.00     31.8±0.09µs        ? ?/sec
cast utf8 to f32                                                   1.01      5.6±0.02µs        ? ?/sec    1.00      5.5±0.02µs        ? ?/sec
cast utf8 to i32                                                   1.00      5.3±0.08µs        ? ?/sec    1.00      5.3±0.04µs        ? ?/sec
cast wide string to binary view 512                                1.00      3.7±0.08µs        ? ?/sec    1.00      3.7±0.07µs        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 700.2s
Peak memory 18.8 MiB
Avg memory 17.4 MiB
CPU user 697.5s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 700.2s
Peak memory 18.8 MiB
Avg memory 17.4 MiB
CPU user 697.8s
CPU sys 0.1s
Peak spill 0 B

File an issue against this benchmark runner

@sdf-jkl sdf-jkl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hey @klion26. The bench results are good on the cast kernel, but show regressions on the Variant one.

variant_get_utf8_from_date                                                                        1.00    570.2±5.55µs        ? ?/sec    2.03   1159.8±8.32µs        ? ?/sec
variant_get_utf8_from_decimal                                                                     1.00    476.4±5.35µs        ? ?/sec    2.16   1029.4±5.10µs        ? ?/sec
variant_get_utf8_from_float                                                                       1.00    449.0±2.24µs        ? ?/sec    1.92    864.3±1.24µs        ? ?/sec
variant_get_utf8_from_int                                                                         1.00    449.7±2.07µs        ? ?/sec    1.65    741.3±1.33µs        ? ?/sec
variant_get_utf8_from_list                                                                        1.00   657.8±15.06µs        ? ?/sec    3.24      2.1±0.02ms        ? ?/sec
variant_get_utf8_from_map_in_list                                                                 1.00   697.9±20.52µs        ? ?/sec    5.75      4.0±0.01ms        ? ?/sec
variant_get_utf8_from_time                                                                        1.00    780.9±2.99µs        ? ?/sec    1.82   1424.1±7.84µs        ? ?/sec
variant_get_utf8_from_timestamp_with_timezone                                                     1.00    571.3±3.64µs        ? ?/sec    2.56  1462.8±14.26µs        ? ?/sec
variant_get_utf8_from_timestamp_without_timezone                                                  1.00    572.0±4.01µs        ? ?/sec    3.05   1744.1±4.25µs        ? ?/sec
variant_get_utf8_from_unshredded_string                                                           1.00    495.8±2.01µs        ? ?/sec    1.42    705.0±4.03µs        ? ?/sec

I think it's coming from the extra allocations that came from using String instead of &str

Comment thread arrow-cast/src/display.rs

fn write_timestamp(
/// Writes a timestamp value to the output using the given representation.
pub fn write_timestamp(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we keep this private then?

if matches!(variant, Variant::Object(_)) {
return None;
}
let mut s = String::new();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

here

Comment on lines +751 to +767
out.push_str(formats.null());
true
}
Variant::String(s) => {
out.push_str(s);
true
}
Variant::ShortString(s) => {
out.push_str(s);
true
}
Variant::BooleanTrue => {
out.push_str("true");
true
}
Variant::BooleanFalse => {
out.push_str("false");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

here

|capacity| -> B { B::with_capacity(capacity) },
|value; temporal_formats: formats| variant_to_string(value, formats),
type_name: B::type_name(),
append_value: |builder, v| builder.append_value(&v)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

here

@klion26

klion26 commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

I'll take a look at the variant benchmark and get back

@klion26

klion26 commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

run benchmark variant_kernels

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5808563163-2673-bc95z 6.12.94+ #1 SMP Wed Aug 19 07:47:20 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing variant-cast-utf8 (906d699) to 891711e (merge-base) diff

Run configuration
run benchmark variant_kernels

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench variant_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing variant-cast-utf8 (906d699) to 891711e (merge-base) diff

Run configuration
run benchmark variant_kernels
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                                             main                                   variant-cast-utf8
-----                                                                                             ----                                   -----------------
batch_json_string_to_variant json_list 8k string                                                  1.01     19.5±0.16ms        ? ?/sec    1.00     19.3±0.09ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(100 fields) random_json(2436 bytes per document)    1.01    147.8±0.92ms        ? ?/sec    1.00    146.3±0.21ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(200 fields) random_json(4871 bytes per document)    1.01    307.3±1.14ms        ? ?/sec    1.00    305.6±1.29ms        ? ?/sec
batch_json_string_to_variant random_json(2633 bytes per document)                                 1.00    228.6±3.38ms        ? ?/sec    1.02    232.8±1.18ms        ? ?/sec
batch_json_string_to_variant repeated_struct 8k string                                            1.00      6.2±0.09ms        ? ?/sec    1.01      6.3±0.03ms        ? ?/sec
shred_variant_partial_object_8k_rows                                                              1.01     20.8±0.09ms        ? ?/sec    1.00     20.7±0.12ms        ? ?/sec
shred_variant_unmatched_object_8k_rows                                                            1.00     26.3±0.17ms        ? ?/sec    1.00     26.3±0.15ms        ? ?/sec
variant_array_builder_build_262k_small_values                                                     1.09      2.7±0.10ms        ? ?/sec    1.00      2.5±0.03ms        ? ?/sec
variant_get_binary_from_string                                                                    1.00    480.0±0.84µs        ? ?/sec    1.09   521.5±12.66µs        ? ?/sec
variant_get_binary_from_unshredded_binary                                                         1.00    462.8±3.23µs        ? ?/sec    1.08   500.5±26.74µs        ? ?/sec
variant_get_primitive                                                                             1.00    564.2±4.85ns        ? ?/sec    1.00    566.1±8.54ns        ? ?/sec
variant_get_shredded_utf8                                                                         1.02    728.4±6.57ns        ? ?/sec    1.00    717.3±4.12ns        ? ?/sec
variant_get_unshredded_object_path_262k_rows                                                      1.00     44.9±0.40ms        ? ?/sec    1.02     45.7±0.79ms        ? ?/sec
variant_get_utf8_from_date                                                                        1.00    572.3±5.11µs        ? ?/sec    2.05   1174.9±6.34µs        ? ?/sec
variant_get_utf8_from_decimal                                                                     1.00    474.3±6.06µs        ? ?/sec    2.19  1037.9±25.85µs        ? ?/sec
variant_get_utf8_from_float                                                                       1.00    448.3±1.78µs        ? ?/sec    1.97   884.8±32.06µs        ? ?/sec
variant_get_utf8_from_int                                                                         1.00    447.4±1.37µs        ? ?/sec    1.69   755.5±28.26µs        ? ?/sec
variant_get_utf8_from_list                                                                        1.00   643.7±10.68µs        ? ?/sec    3.31      2.1±0.04ms        ? ?/sec
variant_get_utf8_from_map_in_list                                                                 1.00   670.0±11.79µs        ? ?/sec    6.02      4.0±0.02ms        ? ?/sec
variant_get_utf8_from_time                                                                        1.00    775.2±1.95µs        ? ?/sec    1.86   1444.9±5.51µs        ? ?/sec
variant_get_utf8_from_timestamp_with_timezone                                                     1.00    571.8±3.06µs        ? ?/sec    2.54   1452.1±8.91µs        ? ?/sec
variant_get_utf8_from_timestamp_without_timezone                                                  1.00    572.1±3.16µs        ? ?/sec    3.03   1733.7±3.84µs        ? ?/sec
variant_get_utf8_from_unshredded_string                                                           1.00    494.4±1.51µs        ? ?/sec    1.07   529.1±15.08µs        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 330.1s
Peak memory 445.8 MiB
Avg memory 245.8 MiB
CPU user 319.7s
CPU sys 5.5s
Peak spill 0 B

branch

Metric Value
Wall time 325.1s
Peak memory 445.9 MiB
Avg memory 245.7 MiB
CPU user 316.1s
CPU sys 5.3s
Peak spill 0 B

File an issue against this benchmark runner

@klion26

klion26 commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

run benchmark variant_kernels

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5811627620-2685-n4bfg 6.12.94+ #1 SMP Wed Aug 19 07:47:20 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing variant-cast-utf8 (0db7289) to 891711e (merge-base) diff

Run configuration
run benchmark variant_kernels

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench variant_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing variant-cast-utf8 (0db7289) to 891711e (merge-base) diff

Run configuration
run benchmark variant_kernels
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                                             main                                   variant-cast-utf8
-----                                                                                             ----                                   -----------------
batch_json_string_to_variant json_list 8k string                                                  1.00     19.5±0.14ms        ? ?/sec    1.00     19.5±0.12ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(100 fields) random_json(2436 bytes per document)    1.01    147.5±0.77ms        ? ?/sec    1.00    146.6±0.22ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(200 fields) random_json(4871 bytes per document)    1.01    308.3±0.89ms        ? ?/sec    1.00    306.1±0.61ms        ? ?/sec
batch_json_string_to_variant random_json(2633 bytes per document)                                 1.00    229.8±3.36ms        ? ?/sec    1.01    232.9±0.81ms        ? ?/sec
batch_json_string_to_variant repeated_struct 8k string                                            1.00      6.1±0.04ms        ? ?/sec    1.02      6.3±0.04ms        ? ?/sec
shred_variant_partial_object_8k_rows                                                              1.00     20.9±0.10ms        ? ?/sec    1.00     20.8±0.14ms        ? ?/sec
shred_variant_unmatched_object_8k_rows                                                            1.00     26.2±0.19ms        ? ?/sec    1.01     26.5±0.19ms        ? ?/sec
variant_array_builder_build_262k_small_values                                                     1.02      2.7±0.03ms        ? ?/sec    1.00      2.6±0.04ms        ? ?/sec
variant_get_binary_from_string                                                                    1.00    480.6±0.63µs        ? ?/sec    1.07    514.7±7.87µs        ? ?/sec
variant_get_binary_from_unshredded_binary                                                         1.00    463.8±2.89µs        ? ?/sec    1.05    484.9±7.62µs        ? ?/sec
variant_get_primitive                                                                             1.00    566.4±3.94ns        ? ?/sec    1.00    564.6±4.44ns        ? ?/sec
variant_get_shredded_utf8                                                                         1.00    720.7±4.30ns        ? ?/sec    1.00    723.4±6.21ns        ? ?/sec
variant_get_unshredded_object_path_262k_rows                                                      1.00     45.1±0.48ms        ? ?/sec    1.00     45.2±0.33ms        ? ?/sec
variant_get_utf8_from_date                                                                        1.00    572.4±5.17µs        ? ?/sec    2.05   1171.2±5.78µs        ? ?/sec
variant_get_utf8_from_decimal                                                                     1.00    474.9±6.14µs        ? ?/sec    2.14   1014.9±7.21µs        ? ?/sec
variant_get_utf8_from_float                                                                       1.00    447.6±1.18µs        ? ?/sec    1.91    854.2±1.47µs        ? ?/sec
variant_get_utf8_from_int                                                                         1.00    447.9±1.10µs        ? ?/sec    1.64    734.9±1.38µs        ? ?/sec
variant_get_utf8_from_list                                                                        1.00    647.1±9.12µs        ? ?/sec    3.29      2.1±0.01ms        ? ?/sec
variant_get_utf8_from_map_in_list                                                                 1.00   671.5±10.27µs        ? ?/sec    6.02      4.0±0.02ms        ? ?/sec
variant_get_utf8_from_time                                                                        1.00    775.8±2.31µs        ? ?/sec    1.85   1435.1±3.52µs        ? ?/sec
variant_get_utf8_from_timestamp_with_timezone                                                     1.00    572.3±2.87µs        ? ?/sec    2.54   1452.8±2.52µs        ? ?/sec
variant_get_utf8_from_timestamp_without_timezone                                                  1.00    571.6±2.69µs        ? ?/sec    3.05   1740.4±1.83µs        ? ?/sec
variant_get_utf8_from_unshredded_string                                                           1.00    495.0±1.37µs        ? ?/sec    1.01    500.1±6.58µs        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 330.1s
Peak memory 445.8 MiB
Avg memory 246.2 MiB
CPU user 320.8s
CPU sys 5.4s
Peak spill 0 B

branch

Metric Value
Wall time 325.1s
Peak memory 445.9 MiB
Avg memory 247.3 MiB
CPU user 317.9s
CPU sys 5.5s
Peak spill 0 B

File an issue against this benchmark runner

@klion26

klion26 commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

@sdf-jkl Please take another look when you're free, I think the perf is ok now (variant_get_utf8_from_unshredded_string), differences for other perf cases are that before they will return None immediately but now will cast to string.

@sdf-jkl sdf-jkl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @klion26 left some suggestions.

Comment on lines +797 to +803
let value_str = d.integer().to_string();
out.push_str(&format_decimal_str(
&value_str,
value_str.len(),
d.scale() as _,
));
true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use write_decimal here and in the Decimal8/Decimal16 arms? It was added in #11002 and is already available in this PR's base through arrow::datatypes. It formats the integer using a stack buffer and writes directly into out, avoiding the two temporary heap strings from integer().to_string() and format_decimal_str() for every decimal value.

Suggested change
let value_str = d.integer().to_string();
out.push_str(&format_decimal_str(
&value_str,
value_str.len(),
d.scale() as _,
));
true
arrow::datatypes::write_decimal(out, d.integer(), d.scale() as _).is_ok()

This snippet fits the current bool return type. If we adopt the separate Result suggestion, propagate the write result with ? instead of .is_ok().

Comment on lines +208 to +211
pub(crate) enum VariantToStringArrowRowBuilder<'a, B: StringLikeArrayBuilder> {
Get(VariantToStringGetArrowBuilder<'a, B>),
Shred(VariantToStringShredArrowBuilder<'a, B>),
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we follow the existing Boolean/numeric/timestamp/decimal builder pattern here: keep one string builder and one binary builder, each taking shred: bool, and drop the separate Get/Shred implementations and forwarding enums?

For binary, the shared builder can select value.as_u8_slice() when shredding and variant_to_binary(value) otherwise. For strings, both modes can share the existing String/ShortString fast path; other values should only go through string formatting when shred is false.

This preserves strict shredding behavior while removing the extra builder types, forwarding methods, and repeated constructor branches for the six string/binary array types.

Comment on lines +1454 to +1456
fn append_cast_value(&mut self, value: &Variant<'_, '_>) -> Result<bool> {
match variant_cast_with_options(value, self.cast_options, |value| {
variant_to_string(value, &self.formats)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A perf suggestion, can bench before and after.

We can have a reusable scratch: String on the string builder. variant_to_string currently creates a fresh String for each formatted value, which is then copied into the Arrow builder and dropped.

Calling scratch.clear() before each conversion, writing through write_variant_to_string, and appending scratch.as_str() on success would reuse the capacity across rows. The String/ShortString fast path can stay as-is. We would need to make the writer pub(crate) and preserve the wrapper's rejection of top-level Null/Object values (and the strict shredding guard if the builders are consolidated).

Comment on lines +860 to +865
Variant::Binary(v) => match std::str::from_utf8(v) {
Ok(s) => {
out.push_str(s);
true
}
Err(_) => false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use Arrow's hex formatting for binary values nested in collections? A list containing b"ab" and [0xff, 0x00] currently becomes [ab, ], whereas Arrow produces [6162, ff00]. Please add a comparison test for this case.

Comment on lines +884 to +887
if let Some(item) = iter.next()
&& !write_variant_to_string(&item, formats, out)
{
out.push_str(formats.null());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we return Result from the recursive formatter and use ?, rather than reducing formatting errors to bool and replacing failed children with null text? A date inside a list with %Y-%m-%d %H currently succeeds even with safe: false; the same format on a top-level date returns NULL with safe: true, while Arrow errors in both cases. Keeping formatting failures distinct from unsupported casts would fix both cases and remove the repeated true/.is_ok() branches. Please cover both cases with regression tests.

Comment on lines +720 to +723
pub(crate) fn variant_to_string<'v>(
variant: &Variant<'_, 'v>,
formats: &TemporalFormats<'_>,
) -> Option<Cow<'v, str>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we adopt the scratch-buffer suggestion, could we remove this allocating wrapper and its Cow machinery? The builder can use write_variant_to_string directly, retaining the string fast path and top-level Null/Object checks. The comparison tests could exercise the public conversion path instead.

For top-level Binary in Get mode, std::str::from_utf8(bytes) can also supply a borrowed &str directly to the builder, avoiding the extra copy through scratch. Invalid UTF-8 should retain safe/strict handling, shredding should still reject binary as a string, and nested binary should use Arrow's hex formatting.

Comment on lines +1305 to +1312
#[derive(Default)]
pub(crate) struct TemporalFormats<'a> {
date: CompiledTimeFormat<'a>,
time: CompiledTimeFormat<'a>,
timestamp: CompiledTimeFormat<'a>,
timestamp_tz: CompiledTimeFormat<'a>,
null: &'a str,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional cleanup: could we move TemporalFormats beside the formatting functions in type_conversion.rs? That would remove the reverse dependency from the conversion module to the builder module and let the formatter access these fields directly, without the five forwarding getters.

Comment on lines +1011 to +1012
#[test]
fn test_compatible_cast_logic_with_cast_kernel() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional test cleanup: could we factor the repeated comparison setup into a helper that runs cast_to_variant -> variant_get and compares against Arrow casting? Reusing it for Utf8/LargeUtf8/Utf8View, nulls, and safe/strict failures would shorten these tests while covering the builder path as well as the formatter.

Comment thread arrow-cast/src/display.rs Outdated
) -> FormatResult {
match &format.0 {
CompiledTimeFormatInner::Custom(items) => {
write!(f, "{}", self.format_with_items(items.0.iter()))?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use self.format_with_items(items.0.iter()).write_to(f)? here, and the equivalent in the custom timestamp branches? Chrono's Display implementation allocates an intermediate String even when the destination buffer is reused; write_to writes directly to it. It can leave partial output on failure, so we should propagate the error and cover invalid formats, as discussed in the error-handling comment.

(struct $name:ident<$lifetime:lifetime $(, $generic:ident: $bound:path )?>
|$array_param:ident $(, $field:ident: $field_type:ty)?| -> $builder_name:ident $(< $array_type:ty >)? { $init_expr: expr },
|$value: ident $(, $shred: ident)?| $value_transform:expr,
|$value: ident $(, $shred: ident)? $(; temporal_formats: $formats:ident)?| $value_transform:expr,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we remove the temporal_formats macro parameter and its generated field, initialization, and local binding? No invocation uses it now that the string builder is handwritten and owns its formats directly.

@klion26

klion26 commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Will take a look at the following days

@github-actions github-actions Bot added the arrow-kernels Crates that transform arrow data (e.g. compute, cast) label Oct 19, 2026

@klion26 klion26 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@sdf-jkl Please take a look at this when you're free, I've addressed all the comments, thanks.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrow Changes to the arrow crate arrow-cast arrow-kernels Crates that transform arrow data (e.g. compute, cast) parquet-variant parquet-variant* crates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align cast logic betwee variant_get and cast kernel for utf8 type

5 participants