Skip to main content

datafusion_spark/function/string/
format_string.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Write;
19use std::sync::Arc;
20
21use core::num::FpCategory;
22
23use arrow::{
24    array::{Array, ArrayRef, LargeStringArray, StringArray, StringViewArray},
25    datatypes::{DataType, Field, FieldRef},
26};
27use bigdecimal::{
28    BigDecimal, ToPrimitive,
29    num_bigint::{BigInt, Sign},
30};
31use chrono::{DateTime, Datelike, Timelike, Utc};
32use datafusion_common::{
33    DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, plan_err,
34};
35use datafusion_expr::{
36    ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature,
37    TypeSignature, Volatility,
38};
39
40/// Spark-compatible `format_string` expression
41/// <https://spark.apache.org/docs/latest/api/sql/index.html#format_string>
42#[derive(Debug, PartialEq, Eq, Hash)]
43pub struct FormatStringFunc {
44    signature: Signature,
45    aliases: Vec<String>,
46}
47
48impl Default for FormatStringFunc {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl FormatStringFunc {
55    pub fn new() -> Self {
56        Self {
57            signature: Signature::new(TypeSignature::VariadicAny, Volatility::Immutable),
58            aliases: vec![String::from("printf")],
59        }
60    }
61}
62
63impl ScalarUDFImpl for FormatStringFunc {
64    fn name(&self) -> &str {
65        "format_string"
66    }
67
68    fn aliases(&self) -> &[String] {
69        &self.aliases
70    }
71
72    fn signature(&self) -> &Signature {
73        &self.signature
74    }
75
76    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
77        datafusion_common::internal_err!(
78            "return_type should not be called, use return_field_from_args instead"
79        )
80    }
81
82    fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
83        match args.arg_fields[0].data_type() {
84            DataType::Null => {
85                Ok(Arc::new(Field::new("format_string", DataType::Utf8, true)))
86            }
87            DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => {
88                Ok(Arc::clone(&args.arg_fields[0]))
89            }
90            _ => exec_err!(
91                "format_string expects the first argument to be Utf8, LargeUtf8 or Utf8View, got {} instead",
92                args.arg_fields[0].data_type()
93            ),
94        }
95    }
96
97    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
98        let len = args.args.iter().find_map(|arg| match arg {
99            ColumnarValue::Scalar(_) => None,
100            ColumnarValue::Array(a) => Some(a.len()),
101        });
102        let is_scalar = len.is_none();
103        let data_types = args.args[1..]
104            .iter()
105            .map(|arg| arg.data_type())
106            .collect::<Vec<_>>();
107        let fmt_type = args.args[0].data_type();
108
109        match &args.args[0] {
110            ColumnarValue::Scalar(ScalarValue::Null) => {
111                Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
112            }
113            ColumnarValue::Scalar(ScalarValue::Utf8(None)) => {
114                Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
115            }
116            ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)) => {
117                Ok(ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)))
118            }
119            ColumnarValue::Scalar(ScalarValue::Utf8View(None)) => {
120                Ok(ColumnarValue::Scalar(ScalarValue::Utf8View(None)))
121            }
122            ColumnarValue::Scalar(ScalarValue::Utf8(Some(fmt)))
123            | ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(fmt)))
124            | ColumnarValue::Scalar(ScalarValue::Utf8View(Some(fmt))) => {
125                let formatter = Formatter::parse(fmt, &data_types)?;
126                let mut result = Vec::with_capacity(len.unwrap_or(1));
127                for i in 0..len.unwrap_or(1) {
128                    let scalars = args.args[1..]
129                        .iter()
130                        .map(|arg| try_to_scalar(arg.clone(), i))
131                        .collect::<Result<Vec<_>>>()?;
132                    let formatted = formatter.format(&scalars)?;
133                    result.push(formatted);
134                }
135                if is_scalar {
136                    let scalar_result = result.pop().unwrap();
137                    match fmt_type {
138                        DataType::Utf8 => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
139                            Some(scalar_result),
140                        ))),
141                        DataType::LargeUtf8 => Ok(ColumnarValue::Scalar(
142                            ScalarValue::LargeUtf8(Some(scalar_result)),
143                        )),
144                        DataType::Utf8View => Ok(ColumnarValue::Scalar(
145                            ScalarValue::Utf8View(Some(scalar_result)),
146                        )),
147                        _ => unreachable!(),
148                    }
149                } else {
150                    let array: ArrayRef = match fmt_type {
151                        DataType::Utf8 => Arc::new(StringArray::from(result)),
152                        DataType::LargeUtf8 => Arc::new(LargeStringArray::from(result)),
153                        DataType::Utf8View => Arc::new(StringViewArray::from(result)),
154                        _ => unreachable!(),
155                    };
156                    Ok(ColumnarValue::Array(array))
157                }
158            }
159            ColumnarValue::Array(fmts) => {
160                let mut result = Vec::with_capacity(len.unwrap());
161                for i in 0..len.unwrap() {
162                    let fmt = ScalarValue::try_from_array(fmts, i)?;
163                    match fmt.try_as_str() {
164                        Some(Some(fmt)) => {
165                            let formatter = Formatter::parse(fmt, &data_types)?;
166                            let scalars = args.args[1..]
167                                .iter()
168                                .map(|arg| try_to_scalar(arg.clone(), i))
169                                .collect::<Result<Vec<_>>>()?;
170                            let formatted = formatter.format(&scalars)?;
171                            result.push(Some(formatted));
172                        }
173                        Some(None) => {
174                            result.push(None);
175                        }
176                        _ => unreachable!(),
177                    }
178                }
179                let array: ArrayRef = match fmt_type {
180                    DataType::Utf8 => Arc::new(StringArray::from(result)),
181                    DataType::LargeUtf8 => Arc::new(LargeStringArray::from(result)),
182                    DataType::Utf8View => Arc::new(StringViewArray::from(result)),
183                    _ => unreachable!(),
184                };
185                Ok(ColumnarValue::Array(array))
186            }
187            _ => exec_err!(
188                "The format_string function expects the first argument to be a string"
189            ),
190        }
191    }
192}
193
194fn try_to_scalar(arg: ColumnarValue, index: usize) -> Result<ScalarValue> {
195    match arg {
196        ColumnarValue::Scalar(scalar) => Ok(scalar),
197        ColumnarValue::Array(array) => ScalarValue::try_from_array(&array, index),
198    }
199}
200
201/// Compatible with `java.util.Formatter`
202#[derive(Debug)]
203pub struct Formatter<'a> {
204    pub elements: Vec<FormatElement<'a>>,
205    pub arg_num: usize,
206}
207
208impl<'a> Formatter<'a> {
209    pub fn new(elements: Vec<FormatElement<'a>>) -> Self {
210        let arg_num = elements
211            .iter()
212            .map(|element| match element {
213                FormatElement::Format(spec) => spec.argument_index,
214                _ => 0,
215            })
216            .max()
217            .unwrap_or(0);
218        Self { elements, arg_num }
219    }
220
221    /// Parses a printf-style format string into a Formatter with validation.
222    ///
223    /// This method implements a comprehensive parser for Java `java.util.Formatter` syntax,
224    /// processing the format string character by character to identify and validate format
225    /// specifiers against the provided argument types.
226    ///
227    /// # Arguments
228    ///
229    /// * `fmt` - The format string containing literal text and format specifiers
230    /// * `arg_types` - Array of DataFusion DataTypes corresponding to the arguments
231    ///
232    /// # Parsing Process
233    ///
234    /// The parser operates in several phases:
235    ///
236    /// 1. **String Scanning**: Iterates through the format string looking for '%' characters
237    ///    that mark the beginning of format specifiers or special sequences.
238    ///
239    /// 2. **Special Sequence Handling**: Processes escape sequences:
240    ///    - `%%` becomes a literal '%' character
241    ///    - `%n` becomes a newline character
242    ///    - `%<` indicates reuse of the previous argument with a new format specifier
243    ///
244    /// 3. **Argument Index Resolution**: Determines which argument each format specifier refers to:
245    ///    - Sequential indexing: arguments are consumed in order (1, 2, 3, ...)
246    ///    - Positional indexing: explicit argument position using `%n$` syntax
247    ///    - Previous argument reuse: `%<` references the last used argument
248    ///
249    /// 4. **Format Specifier Parsing**: For each format specifier, extracts:
250    ///    - Flags (-, +, space, #, 0, ',', '(')
251    ///    - Width specification (minimum field width)
252    ///    - Precision specification (decimal places or maximum characters)
253    ///    - Conversion type (d, s, f, x, etc.)
254    ///
255    /// 5. **Type Validation**: Verifies that each format specifier's conversion type
256    ///    is compatible with the corresponding argument's DataType. For example:
257    ///    - Integer conversions (%d, %x, %o) require integer DataTypes
258    ///    - String conversions (%s, %S) accept any DataType
259    ///    - Float conversions (%f, %e, %g) require numeric DataTypes
260    ///
261    /// 6. **Element Construction**: Creates FormatElement instances for:
262    ///    - Verbatim text sections (copied directly to output)
263    ///    - Validated format specifiers with their parsed parameters
264    ///
265    /// # Internal State Management
266    ///
267    /// The parser maintains several state variables:
268    /// - `argument_index`: Tracks the current sequential argument position
269    /// - `prev`: Remembers the last used argument index for `%<` references
270    /// - `res`: Accumulates the parsed FormatElement instances
271    /// - `rem`: Points to the remaining unparsed portion of the format string
272    ///
273    /// # Validation and Error Handling
274    ///
275    /// The parser performs extensive validation including:
276    /// - Argument index bounds checking against the provided arg_types array
277    /// - Format specifier syntax validation
278    /// - Type compatibility verification between conversion types and DataTypes
279    /// - Detection of malformed numeric parameters and invalid flag combinations
280    ///
281    /// # Returns
282    ///
283    /// Returns a Formatter containing the parsed elements and the maximum argument
284    /// index encountered, enabling efficient argument validation during formatting.
285    pub fn parse(fmt: &'a str, arg_types: &[DataType]) -> Result<Self> {
286        // find the first %
287        let mut res = Vec::new();
288
289        let mut rem = fmt;
290        let mut argument_index = 0;
291
292        let mut prev: Option<usize> = None;
293
294        while !rem.is_empty() {
295            if let Some((verbatim_prefix, rest)) = rem.split_once('%') {
296                if !verbatim_prefix.is_empty() {
297                    res.push(FormatElement::Verbatim(verbatim_prefix));
298                }
299                if let Some(rest) = rest.strip_prefix('%') {
300                    res.push(FormatElement::Verbatim("%"));
301                    rem = rest;
302                    continue;
303                }
304                if let Some(rest) = rest.strip_prefix('n') {
305                    res.push(FormatElement::Verbatim("\n"));
306                    rem = rest;
307                    continue;
308                }
309                if let Some(rest) = rest.strip_prefix('<') {
310                    // %< means reuse the previous argument
311                    let Some(p) = prev else {
312                        return exec_err!("No previous argument to reference");
313                    };
314                    let (spec, rest) =
315                        take_conversion_specifier(rest, p, &arg_types[p - 1])?;
316                    res.push(FormatElement::Format(spec));
317                    rem = rest;
318                    continue;
319                }
320
321                let (current_argument_index, rest2) = take_numeric_param(rest, false);
322                let (current_argument_index, rest) =
323                    match (current_argument_index, rest2.starts_with('$')) {
324                        (NumericParam::Literal(index), true) => {
325                            (index as usize, &rest2[1..])
326                        }
327                        (NumericParam::FromArgument, true) => {
328                            return exec_err!("Invalid numeric parameter");
329                        }
330                        (_, false) => {
331                            argument_index += 1;
332                            (argument_index, rest)
333                        }
334                    };
335                if current_argument_index == 0 || current_argument_index > arg_types.len()
336                {
337                    return exec_err!(
338                        "Argument index {} is out of bounds",
339                        current_argument_index
340                    );
341                }
342
343                let (spec, rest) = take_conversion_specifier(
344                    rest,
345                    current_argument_index,
346                    &arg_types[current_argument_index - 1],
347                )
348                .map_err(|e| exec_datafusion_err!("{:?}, format string: {:?}", e, fmt))?;
349                res.push(FormatElement::Format(spec));
350                prev = Some(spec.argument_index);
351                rem = rest;
352            } else {
353                res.push(FormatElement::Verbatim(rem));
354                break;
355            }
356        }
357
358        Ok(Self::new(res))
359    }
360
361    pub fn format(&self, args: &[ScalarValue]) -> Result<String> {
362        if args.len() < self.arg_num {
363            return exec_err!(
364                "Expected at least {} arguments, got {}",
365                self.arg_num,
366                args.len()
367            );
368        }
369        let mut string = String::new();
370        for element in &self.elements {
371            match element {
372                FormatElement::Verbatim(text) => {
373                    string.push_str(text);
374                }
375                FormatElement::Format(spec) => {
376                    spec.format(&mut string, &args[spec.argument_index - 1])?;
377                }
378            }
379        }
380        Ok(string)
381    }
382}
383
384#[derive(Debug)]
385pub enum FormatElement<'a> {
386    /// Some characters that are copied to the output as-is
387    Verbatim(&'a str),
388    /// A format specifier
389    Format(ConversionSpecifier),
390}
391
392/// Parsed printf conversion specifier
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
394pub struct ConversionSpecifier {
395    pub argument_index: usize,
396    /// flag `#`: use `0x`, etc?
397    pub alt_form: bool,
398    /// flag `0`: left-pad with zeros?
399    pub zero_pad: bool,
400    /// flag `-`: left-adjust (pad with spaces on the right)
401    pub left_adj: bool,
402    /// flag `' '` (space): indicate sign with a space?
403    pub space_sign: bool,
404    /// flag `+`: Always show sign? (for signed numbers)
405    pub force_sign: bool,
406    /// flag `,`: include locale-specific grouping separators
407    pub grouping_separator: bool,
408    /// flag `(`: enclose negative numbers in parentheses
409    pub negative_in_parentheses: bool,
410    /// field width
411    pub width: NumericParam,
412    /// floating point field precision
413    pub precision: NumericParam,
414    /// data type
415    pub conversion_type: ConversionType,
416}
417
418/// Width / precision parameter
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum NumericParam {
421    /// The literal width
422    Literal(i32),
423    /// Get the width from the previous argument
424    FromArgument,
425}
426
427/// Printf data type
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429pub enum ConversionType {
430    /// `B`
431    BooleanUpper,
432    /// `b`
433    BooleanLower,
434    /// Not implemented yet. Can be implemented after <https://github.com/apache/datafusion/pull/17093> is merged
435    /// `h`
436    HexHashLower,
437    /// `H`
438    HexHashUpper,
439    /// `d`
440    DecInt,
441    /// `o`
442    OctInt,
443    /// `x`
444    HexIntLower,
445    /// `X`
446    HexIntUpper,
447    /// `e`
448    SciFloatLower,
449    /// `E`
450    SciFloatUpper,
451    /// `f`
452    DecFloatLower,
453    /// `g`
454    CompactFloatLower,
455    /// `G`
456    CompactFloatUpper,
457    /// `a`
458    HexFloatLower,
459    /// `A`
460    HexFloatUpper,
461    /// `t`
462    TimeLower(TimeFormat),
463    /// `T`
464    TimeUpper(TimeFormat),
465    /// `c`
466    CharLower,
467    /// `C`
468    CharUpper,
469    /// `s`
470    StringLower,
471    /// `S`
472    StringUpper,
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub enum TimeFormat {
477    // Hour of the day for the 24-hour clock,
478    // formatted as two digits with a leading zero as necessary i.e. 00 - 23. 00 corresponds to midnight.
479    HUpper,
480    // Hour for the 12-hour clock,
481    // formatted as two digits with a leading zero as necessary, i.e. 01 - 12. 01 corresponds to one o'clock (either morning or afternoon).
482    IUpper,
483    // Hour of the day for the 24-hour clock,
484    // i.e. 0 - 23. 0 corresponds to midnight.
485    KLower,
486    // Hour for the 12-hour clock,
487    // i.e. 1 - 12. 1 corresponds to one o'clock (either morning or afternoon).
488    LLower,
489    // Minute within the hour formatted as two digits with a leading zero as necessary, i.e. 00 - 59.
490    MUpper,
491    // Seconds within the minute, formatted as two digits with a leading zero as necessary,
492    // i.e. 00 - 60 ("60" is a special value required to support leap seconds).
493    SUpper,
494    // Millisecond within the second formatted as three digits with leading zeros as necessary, i.e. 000 - 999.
495    LUpper,
496    // Nanosecond within the second, formatted as nine digits with leading zeros as necessary,
497    // i.e. 000000000 - 999999999. The precision of this value is limited by the resolution of the underlying operating system or hardware.
498    NUpper,
499    // Locale-specific morning or afternoon marker in lower case, e.g."am" or "pm".
500    // Use of the conversion prefix 'T' forces this output to upper case. (Note that 'p' produces lower-case output.
501    // This is different from GNU date and POSIX strftime(3c) which produce upper-case output.)
502    PLower,
503    // RFC 822 style numeric time zone offset from GMT,
504    // e.g. -0800. This value will be adjusted as necessary for Daylight Saving Time.
505    // For long, Long, and Date the time zone used is the default time zone for this instance of the Java virtual machine.
506    ZLower,
507    // A string representing the abbreviation for the time zone. This value will be adjusted as necessary for Daylight Saving Time.
508    // For long, Long, and Date the time zone used is the default time zone for this instance of the Java virtual machine.
509    // The Formatter's locale will supersede the locale of the argument (if any).
510    ZUpper,
511    // Seconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC,
512    // i.e. Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000.
513    SLower,
514    // Milliseconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC,
515    // i.e. Long.MIN_VALUE to Long.MAX_VALUE. The precision of this value is limited by the resolution of the underlying operating system or hardware.
516    QUpper,
517    // Locale-specific full month name, e.g. "January", "February".
518    BUpper,
519    // Locale-specific abbreviated month name, e.g. "Jan", "Feb".
520    BLower,
521    // Locale-specific full weekday name, e.g. "Monday", "Tuesday".
522    AUpper,
523    // Locale-specific abbreviated weekday name, e.g. "Mon", "Tue".
524    ALower,
525    // Four-digit year divided by 100, formatted as two digits with leading zero as necessary, i.e. 00 - 99
526    CUpper,
527    // Year, formatted to at least four digits with leading zeros as necessary, e.g. 0092 equals 92 CE for the Gregorian calendar.
528    YUpper,
529    // Last two digits of the year, formatted with leading zeros as necessary, i.e. 00 - 99.
530    YLower,
531    // Day of year, formatted as three digits with leading zeros as necessary, e.g. 001 - 366 for the Gregorian calendar. 001 corresponds to the first day of the year.
532    JLower,
533    // Month, formatted as two digits with leading zeros as necessary, i.e. 01 - 13, where "01" is the first month of the year and ("13" is a special value required to support lunar calendars).
534    MLower,
535    // Day of month, formatted as two digits with leading zeros as necessary, i.e. 01 - 31, where "01" is the first day of the month.
536    DLower,
537    // Day of month, formatted as two digits, i.e. 1 - 31 where "1" is the first day of the month.
538    ELower,
539    // Time formatted for the 24-hour clock as "%tH:%tM"
540    RUpper,
541    // Time formatted for the 24-hour clock as "%tH:%tM:%tS"
542    TUpper,
543    // Time formatted for the 12-hour clock as "%tI:%tM:%tS %Tp". The location of the morning or afternoon marker ('%Tp') may be locale-dependent.
544    RLower,
545    // Date formatted as "%tm/%td/%ty"
546    DUpper,
547    // ISO 8601 complete date formatted as "%tY-%tm-%td"
548    FUpper,
549    // Date and time formatted as "%ta %tb %td %tT %tZ %tY", e.g. "Sun Jul 20 16:17:00 EDT 1969"
550    CLower,
551}
552
553impl TryFrom<char> for TimeFormat {
554    type Error = DataFusionError;
555    fn try_from(value: char) -> Result<Self, Self::Error> {
556        match value {
557            'H' => Ok(TimeFormat::HUpper),
558            'I' => Ok(TimeFormat::IUpper),
559            'k' => Ok(TimeFormat::KLower),
560            'l' => Ok(TimeFormat::LLower),
561            'M' => Ok(TimeFormat::MUpper),
562            'S' => Ok(TimeFormat::SUpper),
563            'L' => Ok(TimeFormat::LUpper),
564            'N' => Ok(TimeFormat::NUpper),
565            'p' => Ok(TimeFormat::PLower),
566            'z' => Ok(TimeFormat::ZLower),
567            'Z' => Ok(TimeFormat::ZUpper),
568            's' => Ok(TimeFormat::SLower),
569            'Q' => Ok(TimeFormat::QUpper),
570            'B' => Ok(TimeFormat::BUpper),
571            'b' | 'h' => Ok(TimeFormat::BLower),
572            'A' => Ok(TimeFormat::AUpper),
573            'a' => Ok(TimeFormat::ALower),
574            'C' => Ok(TimeFormat::CUpper),
575            'Y' => Ok(TimeFormat::YUpper),
576            'y' => Ok(TimeFormat::YLower),
577            'j' => Ok(TimeFormat::JLower),
578            'm' => Ok(TimeFormat::MLower),
579            'd' => Ok(TimeFormat::DLower),
580            'e' => Ok(TimeFormat::ELower),
581            'R' => Ok(TimeFormat::RUpper),
582            'T' => Ok(TimeFormat::TUpper),
583            'r' => Ok(TimeFormat::RLower),
584            'D' => Ok(TimeFormat::DUpper),
585            'F' => Ok(TimeFormat::FUpper),
586            'c' => Ok(TimeFormat::CLower),
587            _ => exec_err!("Invalid time format: {}", value),
588        }
589    }
590}
591
592impl ConversionType {
593    pub fn validate(&self, arg_type: &DataType) -> Result<()> {
594        match self {
595            ConversionType::BooleanLower | ConversionType::BooleanUpper
596                if *arg_type != DataType::Boolean =>
597            {
598                return exec_err!(
599                    "Invalid argument type for boolean conversion: {:?}",
600                    arg_type
601                );
602            }
603            ConversionType::CharLower | ConversionType::CharUpper
604                if !matches!(
605                    arg_type,
606                    DataType::Int8
607                        | DataType::UInt8
608                        | DataType::Int16
609                        | DataType::UInt16
610                        | DataType::Int32
611                        | DataType::UInt32
612                        | DataType::Int64
613                        | DataType::UInt64
614                ) =>
615            {
616                return exec_err!(
617                    "Invalid argument type for char conversion: {:?}",
618                    arg_type
619                );
620            }
621            ConversionType::DecInt
622            | ConversionType::OctInt
623            | ConversionType::HexIntLower
624            | ConversionType::HexIntUpper
625                if !arg_type.is_integer() =>
626            {
627                return exec_err!(
628                    "Invalid argument type for integer conversion: {:?}",
629                    arg_type
630                );
631            }
632            ConversionType::SciFloatLower
633            | ConversionType::SciFloatUpper
634            | ConversionType::DecFloatLower
635            | ConversionType::CompactFloatLower
636            | ConversionType::CompactFloatUpper
637            | ConversionType::HexFloatLower
638            | ConversionType::HexFloatUpper
639                if !arg_type.is_numeric() =>
640            {
641                return exec_err!(
642                    "Invalid argument type for float conversion: {:?}",
643                    arg_type
644                );
645            }
646            ConversionType::TimeLower(_) | ConversionType::TimeUpper(_)
647                if !arg_type.is_temporal() =>
648            {
649                return exec_err!(
650                    "Invalid argument type for time conversion: {:?}",
651                    arg_type
652                );
653            }
654            _ => {}
655        }
656        Ok(())
657    }
658
659    fn supports_integer(&self) -> bool {
660        matches!(
661            self,
662            ConversionType::DecInt
663                | ConversionType::HexIntLower
664                | ConversionType::HexIntUpper
665                | ConversionType::OctInt
666                | ConversionType::CharLower
667                | ConversionType::CharUpper
668                | ConversionType::StringLower
669                | ConversionType::StringUpper
670        )
671    }
672
673    fn supports_float(&self) -> bool {
674        matches!(
675            self,
676            ConversionType::DecFloatLower
677                | ConversionType::SciFloatLower
678                | ConversionType::SciFloatUpper
679                | ConversionType::CompactFloatLower
680                | ConversionType::CompactFloatUpper
681                | ConversionType::StringLower
682                | ConversionType::StringUpper
683                | ConversionType::HexFloatLower
684                | ConversionType::HexFloatUpper
685        )
686    }
687
688    fn supports_decimal(&self) -> bool {
689        matches!(
690            self,
691            ConversionType::DecFloatLower
692                | ConversionType::SciFloatLower
693                | ConversionType::SciFloatUpper
694                | ConversionType::CompactFloatLower
695                | ConversionType::CompactFloatUpper
696                | ConversionType::StringLower
697                | ConversionType::StringUpper
698        )
699    }
700
701    fn supports_time(&self) -> bool {
702        matches!(
703            self,
704            ConversionType::TimeLower(_)
705                | ConversionType::TimeUpper(_)
706                | ConversionType::StringLower
707                | ConversionType::StringUpper
708        )
709    }
710
711    fn is_upper(&self) -> bool {
712        matches!(
713            self,
714            ConversionType::BooleanUpper
715                | ConversionType::HexHashUpper
716                | ConversionType::HexIntUpper
717                | ConversionType::SciFloatUpper
718                | ConversionType::CompactFloatUpper
719                | ConversionType::HexFloatUpper
720                | ConversionType::TimeUpper(_)
721                | ConversionType::CharUpper
722                | ConversionType::StringUpper
723        )
724    }
725}
726
727fn take_conversion_specifier<'a>(
728    mut s: &'a str,
729    argument_index: usize,
730    arg_type: &DataType,
731) -> Result<(ConversionSpecifier, &'a str)> {
732    let mut spec = ConversionSpecifier {
733        argument_index,
734        alt_form: false,
735        zero_pad: false,
736        left_adj: false,
737        space_sign: false,
738        force_sign: false,
739        grouping_separator: false,
740        negative_in_parentheses: false,
741        width: NumericParam::Literal(0),
742        precision: NumericParam::FromArgument, // Placeholder - must not be returned!
743        // ignore length modifier
744        conversion_type: ConversionType::DecInt,
745    };
746
747    // parse flags
748    loop {
749        match s.chars().next() {
750            Some('#') => {
751                spec.alt_form = true;
752            }
753            Some('0') => {
754                if spec.left_adj {
755                    return exec_err!("Invalid flag combination: '0' and '-'");
756                }
757                spec.zero_pad = true;
758            }
759            Some('-') => {
760                spec.left_adj = true;
761            }
762            Some(' ') => {
763                if spec.force_sign {
764                    return exec_err!("Invalid flag combination: '+' and ' '");
765                }
766                spec.space_sign = true;
767            }
768            Some('+') => {
769                if spec.space_sign {
770                    return exec_err!("Invalid flag combination: '+' and ' '");
771                }
772                spec.force_sign = true;
773            }
774            Some(',') => {
775                spec.grouping_separator = true;
776            }
777            Some('(') => {
778                spec.negative_in_parentheses = true;
779            }
780            _ => {
781                break;
782            }
783        }
784        s = &s[1..];
785    }
786    // parse width
787    let (w, mut s) = take_numeric_param(s, false);
788    spec.width = w;
789    // parse precision
790    if matches!(s.chars().next(), Some('.')) {
791        s = &s[1..];
792        let (p, s2) = take_numeric_param(s, true);
793        spec.precision = p;
794        s = s2;
795    }
796    let mut chars = s.chars();
797    let mut offset = 1;
798    // parse conversion type
799    spec.conversion_type = match chars.next() {
800        Some('b') => ConversionType::BooleanLower,
801        Some('B') => ConversionType::BooleanUpper,
802        Some('h') => ConversionType::HexHashLower,
803        Some('H') => ConversionType::HexHashUpper,
804        Some('s') => ConversionType::StringLower,
805        Some('S') => ConversionType::StringUpper,
806        Some('c') => ConversionType::CharLower,
807        Some('C') => ConversionType::CharUpper,
808        Some('d') => ConversionType::DecInt,
809        Some('o') => ConversionType::OctInt,
810        Some('x') => ConversionType::HexIntLower,
811        Some('X') => ConversionType::HexIntUpper,
812        Some('e') => ConversionType::SciFloatLower,
813        Some('E') => ConversionType::SciFloatUpper,
814        Some('f') => ConversionType::DecFloatLower,
815        Some('g') => ConversionType::CompactFloatLower,
816        Some('G') => ConversionType::CompactFloatUpper,
817        Some('a') => ConversionType::HexFloatLower,
818        Some('A') => ConversionType::HexFloatUpper,
819        Some('t') => {
820            let Some(chr) = chars.next() else {
821                return exec_err!("Invalid time format: {}", s);
822            };
823            offset += 1;
824            ConversionType::TimeLower(chr.try_into()?)
825        }
826        Some('T') => {
827            let Some(chr) = chars.next() else {
828                return exec_err!("Invalid time format: {}", s);
829            };
830            offset += 1;
831            ConversionType::TimeUpper(chr.try_into()?)
832        }
833        chr => {
834            return plan_err!("Invalid conversion type: {:?}", chr);
835        }
836    };
837
838    spec.conversion_type.validate(arg_type)?;
839    Ok((spec, &s[offset..]))
840}
841
842fn take_numeric_param(s: &str, zero: bool) -> (NumericParam, &str) {
843    match s.chars().next() {
844        Some(digit) if (if zero { '0'..='9' } else { '1'..='9' }).contains(&digit) => {
845            let mut s = s;
846            let mut w = 0;
847            loop {
848                match s.chars().next() {
849                    Some(digit) if digit.is_ascii_digit() => {
850                        w = 10 * w + (digit as i32 - '0' as i32);
851                    }
852                    _ => {
853                        break;
854                    }
855                }
856                s = &s[1..];
857            }
858            (NumericParam::Literal(w), s)
859        }
860        _ => (NumericParam::FromArgument, s),
861    }
862}
863
864/// Convert a `u32` to a [`char`] for the `%c` conversion. Returns an error if
865/// the value is not a valid Unicode scalar value (i.e. is in the surrogate
866/// range `0xD800..=0xDFFF` or above `0x10FFFF`).
867fn codepoint_to_char(value: u32) -> Result<char> {
868    char::from_u32(value).ok_or_else(|| {
869        exec_datafusion_err!("invalid Unicode scalar value for %c: {value:#x}")
870    })
871}
872
873/// Convert a signed integer to a [`char`] for the `%c` conversion. Returns an
874/// error if the value is negative or is not a valid Unicode scalar value (i.e.
875/// is in the surrogate range `0xD800..=0xDFFF` or above `0x10FFFF`).
876fn signed_to_char(value: i64) -> Result<char> {
877    let codepoint = u32::try_from(value).map_err(|_| {
878        exec_datafusion_err!("invalid Unicode scalar value for %c: {value}")
879    })?;
880    codepoint_to_char(codepoint)
881}
882
883/// Convert an unsigned integer to a [`char`] for the `%c` conversion. Returns
884/// an error if the value does not fit in a `u32` or is not a valid Unicode
885/// scalar value (i.e. is in the surrogate range `0xD800..=0xDFFF` or above
886/// `0x10FFFF`).
887fn unsigned_to_char(value: u64) -> Result<char> {
888    let codepoint = u32::try_from(value).map_err(|_| {
889        exec_datafusion_err!("invalid Unicode scalar value for %c: {value:#x}")
890    })?;
891    codepoint_to_char(codepoint)
892}
893
894/// Formatting operations that differ between signed and unsigned integer
895/// primitives. Signed values format as decimal for `%d` / `%s` / `%c`, but use
896/// their original bit width for `%x` / `%o` via `unsigned_bits`.
897trait IntegerFormatValue {
898    fn unsigned_bits(self) -> u64;
899
900    fn to_char(self) -> Result<char>;
901
902    fn format_decimal(
903        self,
904        spec: &ConversionSpecifier,
905        writer: &mut String,
906    ) -> Result<()>;
907
908    fn decimal_string(self) -> String;
909}
910
911macro_rules! signed_integer_value {
912    ($source:ty, $unsigned:ty) => {
913        impl IntegerFormatValue for $source {
914            fn unsigned_bits(self) -> u64 {
915                (self as $unsigned) as u64
916            }
917
918            fn to_char(self) -> Result<char> {
919                signed_to_char(self as i64)
920            }
921
922            fn format_decimal(
923                self,
924                spec: &ConversionSpecifier,
925                writer: &mut String,
926            ) -> Result<()> {
927                spec.format_signed(writer, self as i64)
928            }
929
930            fn decimal_string(self) -> String {
931                self.to_string()
932            }
933        }
934    };
935}
936
937signed_integer_value!(i8, u8);
938signed_integer_value!(i16, u16);
939signed_integer_value!(i32, u32);
940signed_integer_value!(i64, u64);
941
942macro_rules! unsigned_integer_value {
943    ($source:ty) => {
944        impl IntegerFormatValue for $source {
945            fn unsigned_bits(self) -> u64 {
946                self as u64
947            }
948
949            fn to_char(self) -> Result<char> {
950                unsigned_to_char(self as u64)
951            }
952
953            fn format_decimal(
954                self,
955                spec: &ConversionSpecifier,
956                writer: &mut String,
957            ) -> Result<()> {
958                spec.format_unsigned(writer, self as u64)
959            }
960
961            fn decimal_string(self) -> String {
962                self.to_string()
963            }
964        }
965    };
966}
967
968unsigned_integer_value!(u8);
969unsigned_integer_value!(u16);
970unsigned_integer_value!(u32);
971unsigned_integer_value!(u64);
972
973impl ConversionSpecifier {
974    /// Validates that the grouping separator flag is not used with scientific
975    /// notation conversions, matching Java/Spark behavior which throws
976    /// `FormatFlagsConversionMismatchException` for `%,e` / `%,E`.
977    fn validate_grouping_separator(&self) -> Result<()> {
978        if self.grouping_separator
979            && matches!(
980                self.conversion_type,
981                ConversionType::SciFloatLower | ConversionType::SciFloatUpper
982            )
983        {
984            return exec_err!(
985                "Grouping separator ',' flag is not compatible with scientific notation conversion '{}'",
986                if self.conversion_type == ConversionType::SciFloatUpper {
987                    'E'
988                } else {
989                    'e'
990                }
991            );
992        }
993        Ok(())
994    }
995
996    pub fn format(&self, string: &mut String, value: &ScalarValue) -> Result<()> {
997        match value {
998            ScalarValue::Boolean(value) => match self.conversion_type {
999                ConversionType::StringLower | ConversionType::StringUpper => {
1000                    self.format_string(string, &value.unwrap_or(false).to_string())
1001                }
1002
1003                _ => self.format_boolean(string, value),
1004            },
1005            ScalarValue::Int8(value) => self.format_integer(string, value, "Int8"),
1006            ScalarValue::Int16(value) => self.format_integer(string, value, "Int16"),
1007            ScalarValue::Int32(value) => self.format_integer(string, value, "Int32"),
1008            ScalarValue::Int64(value) => self.format_integer(string, value, "Int64"),
1009            ScalarValue::UInt8(value) => self.format_integer(string, value, "UInt8"),
1010            ScalarValue::UInt16(value) => self.format_integer(string, value, "UInt16"),
1011            ScalarValue::UInt32(value) => self.format_integer(string, value, "UInt32"),
1012            ScalarValue::UInt64(value) => self.format_integer(string, value, "UInt64"),
1013            ScalarValue::Float16(value) => match (self.conversion_type, value) {
1014                (
1015                    ConversionType::DecFloatLower
1016                    | ConversionType::SciFloatLower
1017                    | ConversionType::SciFloatUpper
1018                    | ConversionType::CompactFloatLower
1019                    | ConversionType::CompactFloatUpper,
1020                    Some(value),
1021                ) => self.format_float(string, value.to_f64().unwrap()),
1022                (
1023                    ConversionType::StringLower | ConversionType::StringUpper,
1024                    Some(value),
1025                ) => self.format_string(string, &value.to_f32().unwrap().spark_string()),
1026                (
1027                    ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
1028                    Some(value),
1029                ) => self.format_hex_float(string, value.to_f64().unwrap()),
1030                (t, None) if t.supports_float() => self.format_string(string, "null"),
1031                _ => {
1032                    exec_err!(
1033                        "Invalid conversion type: {:?} for Float16",
1034                        self.conversion_type
1035                    )
1036                }
1037            },
1038            ScalarValue::Float32(value) => match (self.conversion_type, value) {
1039                (
1040                    ConversionType::DecFloatLower
1041                    | ConversionType::SciFloatLower
1042                    | ConversionType::SciFloatUpper
1043                    | ConversionType::CompactFloatLower
1044                    | ConversionType::CompactFloatUpper,
1045                    Some(value),
1046                ) => self.format_float(string, *value as f64),
1047                (
1048                    ConversionType::StringLower | ConversionType::StringUpper,
1049                    Some(value),
1050                ) => self.format_string(string, &value.spark_string()),
1051                (
1052                    ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
1053                    Some(value),
1054                ) => self.format_hex_float(string, *value as f64),
1055                (t, None) if t.supports_float() => self.format_string(string, "null"),
1056                _ => {
1057                    exec_err!(
1058                        "Invalid conversion type: {:?} for Float32",
1059                        self.conversion_type
1060                    )
1061                }
1062            },
1063            ScalarValue::Float64(value) => match (self.conversion_type, value) {
1064                (
1065                    ConversionType::DecFloatLower
1066                    | ConversionType::SciFloatLower
1067                    | ConversionType::SciFloatUpper
1068                    | ConversionType::CompactFloatLower
1069                    | ConversionType::CompactFloatUpper,
1070                    Some(value),
1071                ) => self.format_float(string, *value),
1072                (
1073                    ConversionType::StringLower | ConversionType::StringUpper,
1074                    Some(value),
1075                ) => self.format_string(string, &value.spark_string()),
1076                (
1077                    ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
1078                    Some(value),
1079                ) => self.format_hex_float(string, *value),
1080                (t, None) if t.supports_float() => self.format_string(string, "null"),
1081                _ => {
1082                    exec_err!(
1083                        "Invalid conversion type: {:?} for Float64",
1084                        self.conversion_type
1085                    )
1086                }
1087            },
1088            ScalarValue::Utf8(value) => {
1089                let value: &str = match value {
1090                    Some(value) => value.as_str(),
1091                    None => "null",
1092                };
1093                if matches!(
1094                    self.conversion_type,
1095                    ConversionType::StringLower | ConversionType::StringUpper
1096                ) {
1097                    self.format_string(string, value)
1098                } else {
1099                    exec_err!(
1100                        "Invalid conversion type: {:?} for Utf8",
1101                        self.conversion_type
1102                    )
1103                }
1104            }
1105            ScalarValue::LargeUtf8(value) => {
1106                let value: &str = match value {
1107                    Some(value) => value.as_str(),
1108                    None => "null",
1109                };
1110                if matches!(
1111                    self.conversion_type,
1112                    ConversionType::StringLower | ConversionType::StringUpper
1113                ) {
1114                    self.format_string(string, value)
1115                } else {
1116                    exec_err!(
1117                        "Invalid conversion type: {:?} for LargeUtf8",
1118                        self.conversion_type
1119                    )
1120                }
1121            }
1122            ScalarValue::Utf8View(value) => {
1123                let value: &str = match value {
1124                    Some(value) => value.as_str(),
1125                    None => "null",
1126                };
1127                self.format_string(string, value)
1128            }
1129            ScalarValue::Decimal128(value, _, scale) => {
1130                match (self.conversion_type, value) {
1131                    (
1132                        ConversionType::DecFloatLower
1133                        | ConversionType::SciFloatLower
1134                        | ConversionType::SciFloatUpper
1135                        | ConversionType::CompactFloatLower
1136                        | ConversionType::CompactFloatUpper,
1137                        Some(value),
1138                    ) => self.format_decimal(string, &value.to_string(), *scale as i64),
1139                    (
1140                        ConversionType::StringLower | ConversionType::StringUpper,
1141                        Some(value),
1142                    ) => self.format_string(string, &value.to_string()),
1143                    (t, None) if t.supports_decimal() => {
1144                        self.format_string(string, "null")
1145                    }
1146
1147                    _ => {
1148                        exec_err!(
1149                            "Invalid conversion type: {:?} for Decimal128",
1150                            self.conversion_type
1151                        )
1152                    }
1153                }
1154            }
1155            ScalarValue::Decimal256(value, _, scale) => {
1156                match (self.conversion_type, value) {
1157                    (
1158                        ConversionType::DecFloatLower
1159                        | ConversionType::SciFloatLower
1160                        | ConversionType::SciFloatUpper
1161                        | ConversionType::CompactFloatLower
1162                        | ConversionType::CompactFloatUpper,
1163                        Some(value),
1164                    ) => self.format_decimal(string, &value.to_string(), *scale as i64),
1165                    (
1166                        ConversionType::StringLower | ConversionType::StringUpper,
1167                        Some(value),
1168                    ) => self.format_string(string, &value.to_string()),
1169                    (t, None) if t.supports_decimal() => {
1170                        self.format_string(string, "null")
1171                    }
1172
1173                    _ => {
1174                        exec_err!(
1175                            "Invalid conversion type: {:?} for Decimal256",
1176                            self.conversion_type
1177                        )
1178                    }
1179                }
1180            }
1181
1182            ScalarValue::Time32Second(value) => match (self.conversion_type, value) {
1183                (
1184                    ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1185                    Some(value),
1186                ) => self.format_time(string, *value as i64 * 1000000000, &None),
1187                (
1188                    ConversionType::StringLower | ConversionType::StringUpper,
1189                    Some(value),
1190                ) => self.format_string(string, &value.to_string()),
1191                (t, None) if t.supports_time() => self.format_string(string, "null"),
1192                _ => {
1193                    exec_err!(
1194                        "Invalid conversion type: {:?} for Time32Second",
1195                        self.conversion_type
1196                    )
1197                }
1198            },
1199            ScalarValue::Time32Millisecond(value) => {
1200                match (self.conversion_type, value) {
1201                    (
1202                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1203                        Some(value),
1204                    ) => self.format_time(string, *value as i64 * 1000000, &None),
1205                    (
1206                        ConversionType::StringLower | ConversionType::StringUpper,
1207                        Some(value),
1208                    ) => self.format_string(string, &value.to_string()),
1209                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1210                    _ => {
1211                        exec_err!(
1212                            "Invalid conversion type: {:?} for Time32Millisecond",
1213                            self.conversion_type
1214                        )
1215                    }
1216                }
1217            }
1218            ScalarValue::Time64Microsecond(value) => {
1219                match (self.conversion_type, value) {
1220                    (
1221                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1222                        Some(value),
1223                    ) => self.format_time(string, *value * 1000, &None),
1224                    (
1225                        ConversionType::StringLower | ConversionType::StringUpper,
1226                        Some(value),
1227                    ) => self.format_string(string, &value.to_string()),
1228                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1229                    _ => {
1230                        exec_err!(
1231                            "Invalid conversion type: {:?} for Time64Microsecond",
1232                            self.conversion_type
1233                        )
1234                    }
1235                }
1236            }
1237            ScalarValue::Time64Nanosecond(value) => match (self.conversion_type, value) {
1238                (
1239                    ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1240                    Some(value),
1241                ) => self.format_time(string, *value, &None),
1242                (
1243                    ConversionType::StringLower | ConversionType::StringUpper,
1244                    Some(value),
1245                ) => self.format_string(string, &value.to_string()),
1246                (t, None) if t.supports_time() => self.format_string(string, "null"),
1247                _ => {
1248                    exec_err!(
1249                        "Invalid conversion type: {:?} for Time64Nanosecond",
1250                        self.conversion_type
1251                    )
1252                }
1253            },
1254            ScalarValue::TimestampSecond(value, zone) => {
1255                match (self.conversion_type, value) {
1256                    (
1257                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1258                        Some(value),
1259                    ) => self.format_time(string, value * 1000000000, zone),
1260                    (
1261                        ConversionType::StringLower | ConversionType::StringUpper,
1262                        Some(value),
1263                    ) => self.format_string(string, &value.to_string()),
1264                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1265                    _ => {
1266                        exec_err!(
1267                            "Invalid conversion type: {:?} for TimestampSecond",
1268                            self.conversion_type
1269                        )
1270                    }
1271                }
1272            }
1273            ScalarValue::TimestampMillisecond(value, zone) => {
1274                match (self.conversion_type, value) {
1275                    (
1276                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1277                        Some(value),
1278                    ) => self.format_time(string, *value * 1000000, zone),
1279                    (
1280                        ConversionType::StringLower | ConversionType::StringUpper,
1281                        Some(value),
1282                    ) => self.format_string(string, &value.to_string()),
1283
1284                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1285                    _ => {
1286                        exec_err!(
1287                            "Invalid conversion type: {:?} for TimestampMillisecond",
1288                            self.conversion_type
1289                        )
1290                    }
1291                }
1292            }
1293            ScalarValue::TimestampMicrosecond(value, zone) => {
1294                match (self.conversion_type, value) {
1295                    (
1296                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1297                        Some(value),
1298                    ) => self.format_time(string, value * 1000, zone),
1299                    (
1300                        ConversionType::StringLower | ConversionType::StringUpper,
1301                        Some(value),
1302                    ) => self.format_string(string, &value.to_string()),
1303                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1304                    _ => {
1305                        exec_err!(
1306                            "Invalid conversion type: {:?} for timestampmicrosecond",
1307                            self.conversion_type
1308                        )
1309                    }
1310                }
1311            }
1312
1313            ScalarValue::TimestampNanosecond(value, zone) => {
1314                match (self.conversion_type, value) {
1315                    (
1316                        ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1317                        Some(value),
1318                    ) => self.format_time(string, *value, zone),
1319                    (
1320                        ConversionType::StringLower | ConversionType::StringUpper,
1321                        Some(value),
1322                    ) => self.format_string(string, &value.to_string()),
1323                    (t, None) if t.supports_time() => self.format_string(string, "null"),
1324                    _ => {
1325                        exec_err!(
1326                            "Invalid conversion type: {:?} for TimestampNanosecond",
1327                            self.conversion_type
1328                        )
1329                    }
1330                }
1331            }
1332            ScalarValue::Date32(value) => match (self.conversion_type, value) {
1333                (
1334                    ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1335                    Some(value),
1336                ) => self.format_date(string, *value as i64),
1337                (
1338                    ConversionType::StringLower | ConversionType::StringUpper,
1339                    Some(value),
1340                ) => self.format_string(string, &value.to_string()),
1341                (t, None) if t.supports_time() => self.format_string(string, "null"),
1342                _ => {
1343                    exec_err!(
1344                        "Invalid conversion type: {:?} for Date32",
1345                        self.conversion_type
1346                    )
1347                }
1348            },
1349            ScalarValue::Date64(value) => match (self.conversion_type, value) {
1350                (
1351                    ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
1352                    Some(value),
1353                ) => self.format_date(string, *value),
1354                (
1355                    ConversionType::StringLower | ConversionType::StringUpper,
1356                    Some(value),
1357                ) => self.format_string(string, &value.to_string()),
1358                (t, None) if t.supports_time() => self.format_string(string, "null"),
1359                _ => {
1360                    exec_err!(
1361                        "Invalid conversion type: {:?} for Date64",
1362                        self.conversion_type
1363                    )
1364                }
1365            },
1366            ScalarValue::Null => {
1367                let value = "null".to_string();
1368                self.format_string(string, &value)
1369            }
1370            _ => exec_err!("Invalid scalar value: {value}"),
1371        }
1372    }
1373
1374    fn format_integer<T>(
1375        &self,
1376        writer: &mut String,
1377        value: &Option<T>,
1378        type_name: &str,
1379    ) -> Result<()>
1380    where
1381        T: Copy + IntegerFormatValue,
1382    {
1383        let Some(value) = *value else {
1384            return if self.conversion_type.supports_integer() {
1385                self.format_string(writer, "null")
1386            } else {
1387                self.invalid_integer_conversion(type_name)
1388            };
1389        };
1390
1391        match self.conversion_type {
1392            ConversionType::DecInt => value.format_decimal(self, writer),
1393            ConversionType::HexIntLower
1394            | ConversionType::HexIntUpper
1395            | ConversionType::OctInt => {
1396                self.format_unsigned(writer, value.unsigned_bits())
1397            }
1398            ConversionType::CharLower | ConversionType::CharUpper => {
1399                self.format_char(writer, value.to_char()?)
1400            }
1401            ConversionType::StringLower | ConversionType::StringUpper => {
1402                self.format_string(writer, &value.decimal_string())
1403            }
1404            _ => self.invalid_integer_conversion(type_name),
1405        }
1406    }
1407
1408    fn invalid_integer_conversion<T>(&self, type_name: &str) -> Result<T> {
1409        exec_err!(
1410            "Invalid conversion type: {:?} for {}",
1411            self.conversion_type,
1412            type_name
1413        )
1414    }
1415
1416    fn format_hex_float(&self, writer: &mut String, value: f64) -> Result<()> {
1417        // Handle special cases first
1418        let (sign, raw_exponent, mantissa) = value.to_parts();
1419        let is_subnormal = raw_exponent == 0;
1420
1421        let precision = match self.precision {
1422            NumericParam::FromArgument => None,
1423            NumericParam::Literal(p) => Some(p),
1424        };
1425
1426        // Determine if we need to normalize subnormal numbers
1427        // Only normalize when precision is specified and less than full mantissa width
1428        let mantissa_hex_digits = f64::MANTISSA_BITS.div_ceil(4); // 13 for f64
1429        let should_normalize = is_subnormal
1430            && precision.is_some()
1431            && precision.unwrap() < mantissa_hex_digits as i32;
1432
1433        let (value, raw_exponent, mantissa) = if should_normalize {
1434            let value = value * f64::SCALEUP;
1435            let (_, raw_exponent, mantissa) = value.to_parts();
1436            (value, raw_exponent, mantissa)
1437        } else {
1438            (value, raw_exponent, mantissa)
1439        };
1440
1441        let mut temp = String::new();
1442
1443        let sign_char = if sign {
1444            "-"
1445        } else if self.force_sign {
1446            "+"
1447        } else if self.space_sign {
1448            " "
1449        } else {
1450            ""
1451        };
1452        match value.category() {
1453            FpCategory::Nan => {
1454                write!(&mut temp, "NaN")?;
1455            }
1456            FpCategory::Infinite => {
1457                write!(&mut temp, "{sign_char}Infinity")?;
1458            }
1459            FpCategory::Zero => {
1460                write!(&mut temp, "{sign_char}0x0.0p0")?;
1461            }
1462            _ => {
1463                let bias = i32::from(f64::EXPONENT_BIAS);
1464                // Calculate actual exponent
1465                // For subnormal numbers, the exponent is 1 - bias (not 0 - bias)
1466                let exponent = if is_subnormal && !should_normalize {
1467                    1 - bias
1468                } else {
1469                    raw_exponent as i32 - bias
1470                };
1471
1472                // Handle precision for rounding
1473                let final_mantissa = if let Some(p) = precision {
1474                    if p == 0 {
1475                        // For precision 0, we still need at least 1 hex digit
1476                        // Round to the nearest integer mantissa value
1477                        let shift_distance = f64::MANTISSA_BITS as i32 - 4; // Keep 1 hex digit (4 bits)
1478                        let shifted = mantissa >> shift_distance;
1479                        let rounding_bits = mantissa & ((1u64 << shift_distance) - 1);
1480                        let round_bit = 1u64 << (shift_distance - 1);
1481
1482                        // Round to nearest, ties to even
1483                        if rounding_bits > round_bit
1484                            || (rounding_bits == round_bit && (shifted & 1) != 0)
1485                        {
1486                            (shifted + 1) << shift_distance
1487                        } else {
1488                            shifted << shift_distance
1489                        }
1490                    } else {
1491                        // Apply rounding based on precision
1492                        let precision_bits = p * 4; // Each hex digit is 4 bits
1493                        let keep_bits = f64::MANTISSA_BITS as i32;
1494                        let shift_distance = keep_bits - precision_bits;
1495
1496                        if shift_distance > 0 {
1497                            let shifted = mantissa >> shift_distance;
1498                            let rounding_bits = mantissa & ((1u64 << shift_distance) - 1);
1499                            let round_bit = 1u64 << (shift_distance - 1);
1500
1501                            // Round to nearest, ties to even
1502                            if rounding_bits > round_bit
1503                                || (rounding_bits == round_bit && (shifted & 1) != 0)
1504                            {
1505                                (shifted + 1) << shift_distance
1506                            } else {
1507                                shifted << shift_distance
1508                            }
1509                        } else {
1510                            mantissa
1511                        }
1512                    }
1513                } else {
1514                    mantissa
1515                };
1516
1517                if is_subnormal && !should_normalize {
1518                    // Original subnormal format: 0x0.xxxp-1022
1519                    if precision.is_some() {
1520                        // precision >= 13, show as subnormal
1521                        let full_hex = format!(
1522                            "{:0width$x}",
1523                            final_mantissa,
1524                            width = mantissa_hex_digits as usize
1525                        );
1526                        write!(&mut temp, "{sign_char}0x0.{full_hex}p{exponent}")?;
1527                    } else {
1528                        // No precision specified, show full subnormal
1529                        let hex_digits = format!(
1530                            "{:0width$x}",
1531                            final_mantissa,
1532                            width = mantissa_hex_digits as usize
1533                        );
1534                        write!(&mut temp, "{sign_char}0x0.{hex_digits}p{exponent}")?;
1535                    }
1536                } else {
1537                    // Normal format or normalized subnormal: 0x1.xxxpN
1538                    if let Some(p) = precision {
1539                        let p = if p == 0 { 1 } else { p };
1540                        let hex_digits = format!("{final_mantissa:x}");
1541                        let formatted_digits = if p as usize >= hex_digits.len() {
1542                            // Pad with zeros to match precision
1543                            format!("{:0<width$}", hex_digits, width = p as usize)
1544                        } else {
1545                            hex_digits[..p as usize].to_string()
1546                        };
1547                        write!(
1548                            &mut temp,
1549                            "{sign_char}0x1.{formatted_digits}p{exponent}"
1550                        )?;
1551                    } else {
1552                        // Default: show all significant digits
1553                        let mut hex_digits = format!("{final_mantissa:x}");
1554                        hex_digits = trim_trailing_0s_hex(&hex_digits).to_owned();
1555                        if hex_digits.is_empty() {
1556                            write!(&mut temp, "{sign_char}0x1.0p{exponent}")?;
1557                        } else {
1558                            write!(&mut temp, "{sign_char}0x1.{hex_digits}p{exponent}")?;
1559                        }
1560                    }
1561                }
1562                if should_normalize {
1563                    let (prefix, exp) = temp.split_once('p').unwrap();
1564                    let iexp = exp.parse::<i32>().unwrap() - f64::SCALEUP_POWER as i32;
1565                    temp = format!("{prefix}p{iexp}");
1566                }
1567            }
1568        };
1569
1570        if self.conversion_type.is_upper() {
1571            temp = temp.to_ascii_uppercase();
1572        }
1573
1574        let NumericParam::Literal(width) = self.width else {
1575            writer.push_str(&temp);
1576            return Ok(());
1577        };
1578        if self.left_adj {
1579            writer.push_str(&temp);
1580            for _ in temp.len()..width as usize {
1581                writer.push(' ');
1582            }
1583        } else if self.zero_pad && value.is_finite() {
1584            let delimiter = if self.conversion_type.is_upper() {
1585                "0X"
1586            } else {
1587                "0x"
1588            };
1589            let (prefix, suffix) = temp.split_once(delimiter).unwrap();
1590            writer.push_str(prefix);
1591            writer.push_str(delimiter);
1592            for _ in temp.len()..width as usize {
1593                writer.push('0');
1594            }
1595            writer.push_str(suffix);
1596        } else {
1597            while temp.len() < width as usize {
1598                temp = " ".to_owned() + &temp;
1599            }
1600            writer.push_str(&temp);
1601        };
1602        Ok(())
1603    }
1604
1605    fn format_char(&self, writer: &mut String, value: char) -> Result<()> {
1606        let upper = self.conversion_type.is_upper();
1607        match self.conversion_type {
1608            ConversionType::CharLower | ConversionType::CharUpper => {
1609                let NumericParam::Literal(width) = self.width else {
1610                    if upper {
1611                        writer.push(value.to_ascii_uppercase());
1612                    } else {
1613                        writer.push(value);
1614                    }
1615                    return Ok(());
1616                };
1617
1618                let start_len = writer.len();
1619                if self.left_adj {
1620                    if upper {
1621                        writer.push(value.to_ascii_uppercase());
1622                    } else {
1623                        writer.push(value);
1624                    }
1625                    while writer.len() - start_len < width as usize {
1626                        writer.push(' ');
1627                    }
1628                } else {
1629                    while writer.len() - start_len + value.len_utf8() < width as usize {
1630                        writer.push(' ');
1631                    }
1632                    if upper {
1633                        writer.push(value.to_ascii_uppercase());
1634                    } else {
1635                        writer.push(value);
1636                    }
1637                }
1638                Ok(())
1639            }
1640            _ => exec_err!(
1641                "Invalid conversion type: {:?} for char",
1642                self.conversion_type
1643            ),
1644        }
1645    }
1646
1647    fn format_boolean(&self, writer: &mut String, value: &Option<bool>) -> Result<()> {
1648        let value = value.unwrap_or(false);
1649
1650        let formatted = match self.conversion_type {
1651            ConversionType::BooleanUpper => {
1652                if value {
1653                    "TRUE"
1654                } else {
1655                    "FALSE"
1656                }
1657            }
1658            ConversionType::BooleanLower => {
1659                if value {
1660                    "true"
1661                } else {
1662                    "false"
1663                }
1664            }
1665            _ => {
1666                return exec_err!(
1667                    "Invalid conversion type: {:?} for boolean array",
1668                    self.conversion_type
1669                );
1670            }
1671        };
1672        self.format_str(writer, formatted)
1673    }
1674
1675    fn format_float(&self, writer: &mut String, value: f64) -> Result<()> {
1676        self.validate_grouping_separator()?;
1677
1678        let mut prefix = String::new();
1679        let mut suffix = String::new();
1680        let mut number = String::new();
1681        let upper = self.conversion_type.is_upper();
1682
1683        // set up the sign
1684        if value.is_sign_negative() {
1685            if self.negative_in_parentheses {
1686                prefix.push('(');
1687                suffix.push(')');
1688            } else {
1689                prefix.push('-');
1690            }
1691        } else if self.space_sign {
1692            prefix.push(' ');
1693        } else if self.force_sign {
1694            prefix.push('+');
1695        }
1696
1697        if value.is_finite() {
1698            let mut use_scientific = false;
1699            let mut strip_trailing_0s = false;
1700            let mut abs = value.abs();
1701            let mut exponent = abs.log10().floor() as i32;
1702            let mut precision = match self.precision {
1703                NumericParam::Literal(p) => p,
1704                _ => 6,
1705            };
1706            match self.conversion_type {
1707                ConversionType::DecFloatLower => {
1708                    // default
1709                }
1710                ConversionType::SciFloatLower => {
1711                    use_scientific = true;
1712                }
1713                ConversionType::SciFloatUpper => {
1714                    use_scientific = true;
1715                }
1716                ConversionType::CompactFloatLower | ConversionType::CompactFloatUpper => {
1717                    strip_trailing_0s = true;
1718                    if precision == 0 {
1719                        precision = 1;
1720                    }
1721                    // exponent signifies significant digits - we must round now
1722                    // to (re)calculate the exponent
1723                    let rounding_factor =
1724                        10.0_f64.powf((precision - 1 - exponent) as f64);
1725                    let rounded_fixed = (abs * rounding_factor).round();
1726                    abs = rounded_fixed / rounding_factor;
1727                    exponent = abs.log10().floor() as i32;
1728                    if exponent < -4 || exponent >= precision {
1729                        use_scientific = true;
1730                        precision -= 1;
1731                    } else {
1732                        // precision specifies the number of significant digits
1733                        precision -= 1 + exponent;
1734                    }
1735                }
1736                _ => {
1737                    return exec_err!(
1738                        "Invalid conversion type: {:?} for float",
1739                        self.conversion_type
1740                    );
1741                }
1742            }
1743
1744            if use_scientific {
1745                // Manual scientific notation formatting for uppercase E
1746                let mantissa = abs / 10.0_f64.powf(exponent as f64);
1747                let exp_char = if upper { 'E' } else { 'e' };
1748                number = format!("{mantissa:.prec$}", prec = precision as usize);
1749                if strip_trailing_0s {
1750                    number = trim_trailing_0s(&number).to_owned();
1751                }
1752                number = format!("{number}{exp_char}{exponent:+03}");
1753            } else {
1754                number = format!("{abs:.prec$}", prec = precision as usize);
1755                if strip_trailing_0s {
1756                    number = trim_trailing_0s(&number).to_owned();
1757                }
1758                if self.grouping_separator {
1759                    number = insert_thousands_separator(&number);
1760                }
1761            }
1762            if self.alt_form && !number.contains('.') {
1763                number += ".";
1764            }
1765        } else {
1766            // not finite
1767            match self.conversion_type {
1768                ConversionType::DecFloatLower
1769                | ConversionType::SciFloatLower
1770                | ConversionType::CompactFloatLower => {
1771                    if value.is_infinite() {
1772                        number.push_str("Infinity")
1773                    } else {
1774                        number.push_str("NaN")
1775                    }
1776                }
1777                ConversionType::SciFloatUpper | ConversionType::CompactFloatUpper => {
1778                    if value.is_infinite() {
1779                        number.push_str("INFINITY")
1780                    } else {
1781                        number.push_str("NAN")
1782                    }
1783                }
1784                _ => {
1785                    return exec_err!(
1786                        "Invalid conversion type: {:?} for float",
1787                        self.conversion_type
1788                    );
1789                }
1790            }
1791        }
1792
1793        self.write_numeric_parts(writer, prefix, &number, &suffix, value.is_finite());
1794        Ok(())
1795    }
1796
1797    fn format_signed(&self, writer: &mut String, value: i64) -> Result<()> {
1798        let negative = value < 0;
1799        let abs_val = value.abs();
1800
1801        let (sign_prefix, sign_suffix) = if negative && self.negative_in_parentheses {
1802            ("(".to_owned(), ")".to_owned())
1803        } else if negative {
1804            ("-".to_owned(), "".to_owned())
1805        } else if self.force_sign {
1806            ("+".to_owned(), "".to_owned())
1807        } else if self.space_sign {
1808            (" ".to_owned(), "".to_owned())
1809        } else {
1810            ("".to_owned(), "".to_owned())
1811        };
1812
1813        let mut mod_spec = *self;
1814        mod_spec.width = match self.width {
1815            NumericParam::Literal(w) => NumericParam::Literal(
1816                w - sign_prefix.len() as i32 - sign_suffix.len() as i32,
1817            ),
1818            _ => NumericParam::FromArgument,
1819        };
1820        let mut formatted = String::new();
1821        mod_spec.format_unsigned(&mut formatted, abs_val as u64)?;
1822        // put the sign a after any leading spaces
1823        let mut actual_number = &formatted[0..];
1824        let mut leading_spaces = &formatted[0..0];
1825        if let Some(first_non_space) = formatted.find(|c| c != ' ') {
1826            actual_number = &formatted[first_non_space..];
1827            leading_spaces = &formatted[0..first_non_space];
1828        }
1829        write!(
1830            writer,
1831            "{}{}{}{}",
1832            leading_spaces.to_owned(),
1833            sign_prefix,
1834            actual_number,
1835            sign_suffix
1836        )
1837        .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
1838        Ok(())
1839    }
1840
1841    fn format_unsigned(&self, writer: &mut String, value: u64) -> Result<()> {
1842        let mut s = String::new();
1843        let mut alt_prefix = "";
1844        match self.conversion_type {
1845            ConversionType::DecInt => {
1846                let num_str = format!("{value}");
1847                s = if self.grouping_separator {
1848                    insert_thousands_separator(&num_str)
1849                } else {
1850                    num_str
1851                };
1852            }
1853            ConversionType::HexIntLower => {
1854                alt_prefix = "0x";
1855                write!(&mut s, "{value:x}")
1856                    .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
1857            }
1858            ConversionType::HexIntUpper => {
1859                alt_prefix = "0X";
1860                write!(&mut s, "{value:X}")
1861                    .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
1862            }
1863            ConversionType::OctInt => {
1864                alt_prefix = "0";
1865                write!(&mut s, "{value:o}")
1866                    .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
1867            }
1868            _ => {
1869                return exec_err!(
1870                    "Invalid conversion type: {:?} for u64",
1871                    self.conversion_type
1872                );
1873            }
1874        }
1875        let mut prefix = if self.alt_form {
1876            alt_prefix.to_owned()
1877        } else {
1878            String::new()
1879        };
1880
1881        let formatted = if let NumericParam::Literal(width) = self.width {
1882            if self.left_adj {
1883                let mut num_str = prefix + &s;
1884                while num_str.len() < width as usize {
1885                    num_str.push(' ');
1886                }
1887                num_str
1888            } else if self.zero_pad {
1889                while prefix.len() + s.len() < width as usize {
1890                    prefix.push('0');
1891                }
1892                prefix + &s
1893            } else {
1894                let mut num_str = prefix + &s;
1895                while num_str.len() < width as usize {
1896                    num_str = " ".to_owned() + &num_str;
1897                }
1898                num_str
1899            }
1900        } else {
1901            prefix + &s
1902        };
1903        write!(writer, "{formatted}")
1904            .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
1905        Ok(())
1906    }
1907
1908    fn format_str(&self, writer: &mut String, value: &str) -> Result<()> {
1909        // Take care of precision, putting the truncated string in `content`
1910        let precision: usize = match self.precision {
1911            NumericParam::Literal(p) => p,
1912            _ => i32::MAX,
1913        }
1914        .try_into()
1915        .unwrap_or_default();
1916        let content_len = {
1917            let mut content_len = precision.min(value.len());
1918            while !value.is_char_boundary(content_len) {
1919                content_len -= 1;
1920            }
1921            content_len
1922        };
1923        let content = &value[..content_len];
1924
1925        // Pad to width if needed, putting the padded string in `s`
1926
1927        if let NumericParam::Literal(width) = self.width {
1928            let start_len = writer.len();
1929            if self.left_adj {
1930                writer.push_str(content);
1931                while writer.len() - start_len < width as usize {
1932                    writer.push(' ');
1933                }
1934            } else {
1935                while writer.len() - start_len + content.len() < width as usize {
1936                    writer.push(' ');
1937                }
1938                writer.push_str(content);
1939            }
1940        } else {
1941            writer.push_str(content);
1942        }
1943        Ok(())
1944    }
1945
1946    fn format_string(&self, writer: &mut String, value: &str) -> Result<()> {
1947        if self.conversion_type.is_upper() {
1948            let upper = value.to_ascii_uppercase();
1949            self.format_str(writer, &upper)
1950        } else {
1951            self.format_str(writer, value)
1952        }
1953    }
1954
1955    fn format_decimal(&self, writer: &mut String, value: &str, scale: i64) -> Result<()> {
1956        self.validate_grouping_separator()?;
1957
1958        let mut prefix = String::new();
1959        let mut suffix = String::new();
1960        let upper = self.conversion_type.is_upper();
1961
1962        // Parse as BigDecimal
1963        let decimal = value
1964            .parse::<BigInt>()
1965            .map_err(|e| exec_datafusion_err!("Failed to parse decimal: {}", e))?;
1966        let decimal = BigDecimal::from_bigint(decimal, scale);
1967
1968        // Handle sign
1969        let is_negative = decimal.sign() == Sign::Minus;
1970        let abs_decimal = decimal.abs();
1971
1972        if is_negative {
1973            if self.negative_in_parentheses {
1974                prefix.push('(');
1975                suffix.push(')');
1976            } else {
1977                prefix.push('-');
1978            }
1979        } else if self.space_sign {
1980            prefix.push(' ');
1981        } else if self.force_sign {
1982            prefix.push('+');
1983        }
1984
1985        let exp_symb = if upper { 'E' } else { 'e' };
1986        let mut strip_trailing_0s = false;
1987
1988        // Get precision setting
1989        let mut precision = match self.precision {
1990            NumericParam::Literal(p) => p,
1991            _ => 6,
1992        };
1993
1994        let number = match self.conversion_type {
1995            ConversionType::DecFloatLower => {
1996                // Format as fixed-point decimal
1997                let mut n = self.format_decimal_fixed(
1998                    &abs_decimal,
1999                    precision,
2000                    strip_trailing_0s,
2001                )?;
2002                if self.grouping_separator {
2003                    n = insert_thousands_separator(&n);
2004                }
2005                n
2006            }
2007            ConversionType::SciFloatLower => self.format_decimal_scientific(
2008                &abs_decimal,
2009                precision,
2010                'e',
2011                strip_trailing_0s,
2012            )?,
2013            ConversionType::SciFloatUpper => self.format_decimal_scientific(
2014                &abs_decimal,
2015                precision,
2016                'E',
2017                strip_trailing_0s,
2018            )?,
2019            ConversionType::CompactFloatLower | ConversionType::CompactFloatUpper => {
2020                strip_trailing_0s = true;
2021                if precision == 0 {
2022                    precision = 1;
2023                }
2024                // Determine if we should use scientific notation
2025                let log10_val = abs_decimal.to_f64().map(|f| f.log10()).unwrap_or(0.0);
2026                if log10_val < -4.0 || log10_val >= precision as f64 {
2027                    self.format_decimal_scientific(
2028                        &abs_decimal,
2029                        precision - 1,
2030                        exp_symb,
2031                        strip_trailing_0s,
2032                    )?
2033                } else {
2034                    let mut n = self.format_decimal_fixed(
2035                        &abs_decimal,
2036                        precision - 1 - log10_val.floor() as i32,
2037                        strip_trailing_0s,
2038                    )?;
2039                    if self.grouping_separator {
2040                        n = insert_thousands_separator(&n);
2041                    }
2042                    n
2043                }
2044            }
2045            _ => {
2046                return exec_err!(
2047                    "Invalid conversion type: {:?} for decimal",
2048                    self.conversion_type
2049                );
2050            }
2051        };
2052
2053        self.write_numeric_parts(writer, prefix, &number, &suffix, true);
2054        Ok(())
2055    }
2056
2057    fn format_decimal_fixed(
2058        &self,
2059        decimal: &BigDecimal,
2060        precision: i32,
2061        strip_trailing_0s: bool,
2062    ) -> Result<String> {
2063        if precision <= 0 {
2064            Ok(decimal.round(0).to_string())
2065        } else {
2066            // Use BigDecimal's with_scale method for precise decimal formatting
2067            let scaled = decimal.round(precision as i64);
2068            let mut number = scaled.to_string();
2069            if strip_trailing_0s {
2070                number = trim_trailing_0s(&number).to_owned();
2071            }
2072            Ok(number)
2073        }
2074    }
2075
2076    fn format_decimal_scientific(
2077        &self,
2078        decimal: &BigDecimal,
2079        precision: i32,
2080        exp_char: char,
2081        strip_trailing_0s: bool,
2082    ) -> Result<String> {
2083        // Convert to f64 for scientific notation (may lose precision for very large numbers)
2084        let float_val = decimal.to_f64().unwrap_or(0.0);
2085        if float_val == 0.0 {
2086            return Ok(format!("0{exp_char}+00"));
2087        }
2088
2089        let abs_val = float_val.abs();
2090        let exponent = abs_val.log10().floor() as i32;
2091        let mantissa = abs_val / 10.0_f64.powf(exponent as f64);
2092
2093        let mut number = if precision <= 0 {
2094            format!("{mantissa:.0}")
2095        } else {
2096            format!("{mantissa:.prec$}", prec = precision as usize)
2097        };
2098
2099        if strip_trailing_0s {
2100            number = trim_trailing_0s(&number).to_owned();
2101        }
2102
2103        Ok(format!("{number}{exp_char}{exponent:+03}"))
2104    }
2105
2106    fn format_time(
2107        &self,
2108        writer: &mut String,
2109        timestamp_nanos: i64,
2110        timezone: &Option<Arc<str>>,
2111    ) -> Result<()> {
2112        let upper = self.conversion_type.is_upper();
2113        match &self.conversion_type {
2114            ConversionType::TimeLower(time_format)
2115            | ConversionType::TimeUpper(time_format) => {
2116                let formatted =
2117                    self.format_time_component(timestamp_nanos, *time_format, timezone)?;
2118                let result = if upper {
2119                    formatted.to_uppercase()
2120                } else {
2121                    formatted
2122                };
2123                write!(writer, "{result}")
2124                    .map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
2125                Ok(())
2126            }
2127            _ => exec_err!(
2128                "Invalid conversion type for time: {:?}",
2129                self.conversion_type
2130            ),
2131        }
2132    }
2133
2134    fn format_date(&self, writer: &mut String, date_days: i64) -> Result<()> {
2135        // Convert days since epoch to timestamp in nanoseconds
2136        let timestamp_nanos = date_days * 24 * 60 * 60 * 1_000_000_000;
2137        self.format_time(writer, timestamp_nanos, &None)
2138    }
2139
2140    fn format_time_component(
2141        &self,
2142        timestamp_nanos: i64,
2143        time_format: TimeFormat,
2144        _timezone: &Option<Arc<str>>,
2145    ) -> Result<String> {
2146        // Convert nanoseconds to seconds and nanoseconds remainder
2147        let secs = timestamp_nanos / 1_000_000_000;
2148        let nanos = (timestamp_nanos % 1_000_000_000) as u32;
2149
2150        // Create DateTime from timestamp
2151        let dt = DateTime::<Utc>::from_timestamp(secs, nanos).ok_or_else(|| {
2152            exec_datafusion_err!("Invalid timestamp: {}", timestamp_nanos)
2153        })?;
2154
2155        match time_format {
2156            TimeFormat::HUpper => Ok(format!("{:02}", dt.hour())),
2157            TimeFormat::IUpper => {
2158                let hour_12 = match dt.hour12() {
2159                    (true, h) => h,  // PM
2160                    (false, h) => h, // AM
2161                };
2162                Ok(format!("{hour_12:02}"))
2163            }
2164            TimeFormat::KLower => Ok(format!("{}", dt.hour())),
2165            TimeFormat::LLower => {
2166                let hour_12 = match dt.hour12() {
2167                    (true, h) => h,  // PM
2168                    (false, h) => h, // AM
2169                };
2170                Ok(format!("{hour_12}"))
2171            }
2172            TimeFormat::MUpper => Ok(format!("{:02}", dt.minute())),
2173            TimeFormat::SUpper => Ok(format!("{:02}", dt.second())),
2174            TimeFormat::LUpper => Ok(format!("{:03}", dt.timestamp_millis() % 1000)),
2175            TimeFormat::NUpper => Ok(format!("{:09}", dt.nanosecond())),
2176            TimeFormat::PLower => {
2177                let (is_pm, _) = dt.hour12();
2178                Ok(if is_pm {
2179                    "pm".to_string()
2180                } else {
2181                    "am".to_string()
2182                })
2183            }
2184            TimeFormat::ZLower => Ok("+0000".to_string()), // UTC timezone offset
2185            TimeFormat::ZUpper => Ok("UTC".to_string()),   // UTC timezone name
2186            TimeFormat::SLower => Ok(format!("{}", dt.timestamp())),
2187            TimeFormat::QUpper => Ok(format!("{}", dt.timestamp_millis())),
2188            TimeFormat::BUpper => Ok(dt.format("%B").to_string()), // Full month name
2189            TimeFormat::BLower => Ok(dt.format("%b").to_string()), // Abbreviated month name
2190            TimeFormat::AUpper => Ok(dt.format("%A").to_string()), // Full weekday name
2191            TimeFormat::ALower => Ok(dt.format("%a").to_string()), // Abbreviated weekday name
2192            TimeFormat::CUpper => Ok(format!("{:02}", dt.year() / 100)),
2193            TimeFormat::YUpper => Ok(format!("{:04}", dt.year())),
2194            TimeFormat::YLower => Ok(format!("{:02}", dt.year() % 100)),
2195            TimeFormat::JLower => Ok(format!("{:03}", dt.ordinal())), // Day of year
2196            TimeFormat::MLower => Ok(format!("{:02}", dt.month())),
2197            TimeFormat::DLower => Ok(format!("{:02}", dt.day())),
2198            TimeFormat::ELower => Ok(format!("{}", dt.day())),
2199            TimeFormat::RUpper => Ok(dt.format("%H:%M").to_string()),
2200            TimeFormat::TUpper => Ok(dt.format("%H:%M:%S").to_string()),
2201            TimeFormat::RLower => {
2202                let (is_pm, hour_12) = dt.hour12();
2203                let am_pm = if is_pm { "PM" } else { "AM" };
2204                Ok(format!(
2205                    "{:02}:{:02}:{:02} {}",
2206                    hour_12,
2207                    dt.minute(),
2208                    dt.second(),
2209                    am_pm
2210                ))
2211            }
2212            TimeFormat::DUpper => Ok(dt.format("%m/%d/%y").to_string()),
2213            TimeFormat::FUpper => Ok(dt.format("%Y-%m-%d").to_string()),
2214            TimeFormat::CLower => Ok(dt.format("%a %b %d %H:%M:%S UTC %Y").to_string()),
2215        }
2216    }
2217
2218    fn write_numeric_parts(
2219        &self,
2220        writer: &mut String,
2221        mut prefix: String,
2222        number: &str,
2223        suffix: &str,
2224        zero_pad_allowed: bool,
2225    ) {
2226        // Handle padding
2227        let NumericParam::Literal(width) = self.width else {
2228            writer.push_str(&prefix);
2229            writer.push_str(number);
2230            writer.push_str(suffix);
2231            return;
2232        };
2233
2234        if self.left_adj {
2235            let mut full_num = prefix + number + suffix;
2236            while full_num.len() < width as usize {
2237                full_num.push(' ');
2238            }
2239            writer.push_str(&full_num);
2240        } else if self.zero_pad && zero_pad_allowed {
2241            while prefix.len() + number.len() + suffix.len() < width as usize {
2242                prefix.push('0');
2243            }
2244            writer.push_str(&prefix);
2245            writer.push_str(number);
2246            writer.push_str(suffix);
2247        } else {
2248            let mut full_num = prefix + number + suffix;
2249            while full_num.len() < width as usize {
2250                full_num = " ".to_owned() + &full_num;
2251            }
2252            writer.push_str(&full_num);
2253        }
2254    }
2255}
2256
2257trait FloatFormattable: std::fmt::Display {
2258    fn category(&self) -> FpCategory;
2259
2260    fn spark_string(&self) -> String {
2261        match self.category() {
2262            FpCategory::Nan => "NaN".to_string(),
2263            FpCategory::Infinite => {
2264                if self.negative() {
2265                    "-Infinity".to_string()
2266                } else {
2267                    "Infinity".to_string()
2268                }
2269            }
2270            _ => self.to_string(),
2271        }
2272    }
2273    fn negative(&self) -> bool;
2274}
2275
2276impl FloatFormattable for f32 {
2277    fn category(&self) -> FpCategory {
2278        self.classify()
2279    }
2280
2281    fn negative(&self) -> bool {
2282        self.is_sign_negative()
2283    }
2284}
2285
2286impl FloatFormattable for f64 {
2287    fn category(&self) -> FpCategory {
2288        self.classify()
2289    }
2290
2291    fn negative(&self) -> bool {
2292        self.is_sign_negative()
2293    }
2294}
2295
2296trait FloatBits: FloatFormattable {
2297    const MANTISSA_BITS: u8;
2298    const EXPONENT_BIAS: u16;
2299    const SCALEUP_POWER: u8;
2300    const SCALEUP: Self;
2301
2302    fn to_parts(&self) -> (bool, u16, u64);
2303}
2304
2305impl FloatBits for f64 {
2306    const MANTISSA_BITS: u8 = 52;
2307    const EXPONENT_BIAS: u16 = 1023;
2308    const SCALEUP_POWER: u8 = 54;
2309    const SCALEUP: f64 = (1_i64 << Self::SCALEUP_POWER) as f64;
2310
2311    fn to_parts(&self) -> (bool, u16, u64) {
2312        let bits = self.to_bits();
2313        let sign: bool = (bits >> 63) == 1;
2314        let exponent = ((bits >> 52) & 0x7FF) as u16;
2315        let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
2316        (sign, exponent, mantissa)
2317    }
2318}
2319
2320/// Inserts thousands separators (`,`) into the integer part of a numeric string.
2321/// For example, `"1234567.89"` becomes `"1,234,567.89"`.
2322fn insert_thousands_separator(number: &str) -> String {
2323    let (int_part, frac_part) = match number.find('.') {
2324        Some(pos) => (&number[..pos], &number[pos..]),
2325        None => (number, ""),
2326    };
2327    let mut result = String::with_capacity(number.len() + number.len() / 3);
2328    for (i, c) in int_part.char_indices() {
2329        if i > 0 && (int_part.len() - i) % 3 == 0 {
2330            result.push(',');
2331        }
2332        result.push(c);
2333    }
2334    result.push_str(frac_part);
2335    result
2336}
2337
2338fn trim_trailing_0s(number: &str) -> &str {
2339    if number.contains('.') {
2340        for (i, c) in number.chars().rev().enumerate() {
2341            if c != '0' {
2342                return &number[..number.len() - i];
2343            }
2344        }
2345    }
2346    number
2347}
2348
2349fn trim_trailing_0s_hex(number: &str) -> &str {
2350    for (i, c) in number.chars().rev().enumerate() {
2351        if c != '0' {
2352            return &number[..number.len() - i];
2353        }
2354    }
2355    number
2356}
2357
2358#[cfg(test)]
2359mod tests {
2360    use super::*;
2361    use crate::function::utils::test::test_scalar_function;
2362    use arrow::array::StringArray;
2363    use arrow::datatypes::{DataType::Utf8, i256};
2364
2365    #[test]
2366    fn test_format_string_nullability() -> Result<()> {
2367        let func = FormatStringFunc::new();
2368        let nullable_format: FieldRef = Arc::new(Field::new("fmt", Utf8, true));
2369
2370        let out_nullable = func.return_field_from_args(ReturnFieldArgs {
2371            arg_fields: &[nullable_format],
2372            scalar_arguments: &[None],
2373        })?;
2374
2375        assert!(
2376            out_nullable.is_nullable(),
2377            "format_string(fmt, ...) should be nullable when fmt is nullable"
2378        );
2379        let non_nullable_format: FieldRef = Arc::new(Field::new("fmt", Utf8, false));
2380
2381        let out_non_nullable = func.return_field_from_args(ReturnFieldArgs {
2382            arg_fields: &[non_nullable_format],
2383            scalar_arguments: &[None],
2384        })?;
2385
2386        assert!(
2387            !out_non_nullable.is_nullable(),
2388            "format_string(fmt, ...) should NOT be nullable when fmt is NOT nullable"
2389        );
2390
2391        Ok(())
2392    }
2393
2394    #[test]
2395    fn test_format_char_invalid_codepoint_errors() {
2396        use arrow::datatypes::Field;
2397        use datafusion_common::config::ConfigOptions;
2398
2399        let func = FormatStringFunc::new();
2400        // Spark/Java reject any negative integer or any value outside
2401        // `0..=0x10FFFF` (and the surrogate range) regardless of integer
2402        // width, so all of these inputs must surface a SQL error rather than
2403        // panicking or silently reinterpreting the bits as unsigned.
2404        let cases: Vec<(&str, ScalarValue)> = vec![
2405            ("Int8(-1)", ScalarValue::Int8(Some(-1))),
2406            ("Int16(-1)", ScalarValue::Int16(Some(-1))),
2407            ("Int16(-10000)", ScalarValue::Int16(Some(-10000))),
2408            ("Int32(-1)", ScalarValue::Int32(Some(-1))),
2409            ("Int32(0x110000)", ScalarValue::Int32(Some(0x110000))),
2410            ("Int64(0x1FFFFFFFF)", ScalarValue::Int64(Some(0x1FFFFFFFF))),
2411            ("Int64(-1)", ScalarValue::Int64(Some(-1))),
2412            ("UInt16(0xD800)", ScalarValue::UInt16(Some(0xD800))),
2413            ("UInt32(0x110000)", ScalarValue::UInt32(Some(0x110000))),
2414            (
2415                "UInt64(0x1_0000_0000)",
2416                ScalarValue::UInt64(Some(0x1_0000_0000)),
2417            ),
2418        ];
2419
2420        for (label, value) in cases {
2421            let fmt = ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string())));
2422            let arg_data_type = value.data_type();
2423            let arg = ColumnarValue::Scalar(value);
2424            let arg_fields = vec![
2425                Arc::new(Field::new("fmt", Utf8, false)),
2426                Arc::new(Field::new("v", arg_data_type, false)),
2427            ];
2428            let res = func.invoke_with_args(ScalarFunctionArgs {
2429                args: vec![fmt, arg],
2430                number_rows: 1,
2431                arg_fields,
2432                return_field: Arc::new(Field::new("o", Utf8, false)),
2433                config_options: Arc::new(ConfigOptions::default()),
2434            });
2435            assert!(
2436                res.is_err(),
2437                "format_string('[%c]', {label}) should error, got Ok"
2438            );
2439            let err = res.unwrap_err().to_string();
2440            assert!(
2441                err.contains("invalid Unicode scalar value for %c"),
2442                "unexpected error for {label}: {err}"
2443            );
2444        }
2445    }
2446
2447    #[test]
2448    fn test_format_char_valid_codepoint_succeeds() {
2449        test_scalar_function!(
2450            FormatStringFunc::new(),
2451            vec![
2452                ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
2453                ColumnarValue::Scalar(ScalarValue::Int32(Some(0x1F680))),
2454            ],
2455            Ok(Some("[\u{1F680}]")),
2456            &str,
2457            Utf8,
2458            StringArray
2459        );
2460        test_scalar_function!(
2461            FormatStringFunc::new(),
2462            vec![
2463                ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
2464                ColumnarValue::Scalar(ScalarValue::UInt32(Some(0x10FFFF))),
2465            ],
2466            Ok(Some("[\u{10FFFF}]")),
2467            &str,
2468            Utf8,
2469            StringArray
2470        );
2471        test_scalar_function!(
2472            FormatStringFunc::new(),
2473            vec![
2474                ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
2475                ColumnarValue::Scalar(ScalarValue::Int16(Some(65))),
2476            ],
2477            Ok(Some("[A]")),
2478            &str,
2479            Utf8,
2480            StringArray
2481        );
2482        // Int8 / UInt8 can never produce an invalid codepoint for non-negative
2483        // values, but they must still flow through the validating helper.
2484        test_scalar_function!(
2485            FormatStringFunc::new(),
2486            vec![
2487                ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
2488                ColumnarValue::Scalar(ScalarValue::Int8(Some(97))),
2489            ],
2490            Ok(Some("[a]")),
2491            &str,
2492            Utf8,
2493            StringArray
2494        );
2495        test_scalar_function!(
2496            FormatStringFunc::new(),
2497            vec![
2498                ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
2499                ColumnarValue::Scalar(ScalarValue::UInt8(Some(255))),
2500            ],
2501            Ok(Some("[\u{00FF}]")),
2502            &str,
2503            Utf8,
2504            StringArray
2505        );
2506    }
2507
2508    #[test]
2509    fn test_integer_formatting_across_widths() -> Result<()> {
2510        let cases = [
2511            (
2512                ScalarValue::Int8(Some(-1)),
2513                "%d|%x|%o|%s",
2514                4,
2515                "-1|ff|377|-1",
2516            ),
2517            (
2518                ScalarValue::Int16(Some(-1)),
2519                "%d|%x|%o|%s",
2520                4,
2521                "-1|ffff|177777|-1",
2522            ),
2523            (
2524                ScalarValue::Int32(Some(-1)),
2525                "%d|%x|%o|%s",
2526                4,
2527                "-1|ffffffff|37777777777|-1",
2528            ),
2529            (
2530                ScalarValue::Int64(Some(-1)),
2531                "%d|%x|%o|%s",
2532                4,
2533                "-1|ffffffffffffffff|1777777777777777777777|-1",
2534            ),
2535            (
2536                ScalarValue::UInt8(Some(255)),
2537                "%d|%x|%o|%s|%c",
2538                5,
2539                "255|ff|377|255|ÿ",
2540            ),
2541            (
2542                ScalarValue::UInt16(Some(65535)),
2543                "%d|%x|%o|%s",
2544                4,
2545                "65535|ffff|177777|65535",
2546            ),
2547            (
2548                ScalarValue::UInt32(Some(u32::MAX)),
2549                "%d|%x|%o|%s",
2550                4,
2551                "4294967295|ffffffff|37777777777|4294967295",
2552            ),
2553            (
2554                ScalarValue::UInt64(Some(u64::MAX)),
2555                "%d|%x|%o|%s",
2556                4,
2557                "18446744073709551615|ffffffffffffffff|1777777777777777777777|18446744073709551615",
2558            ),
2559            (
2560                ScalarValue::Int32(None),
2561                "%d|%x|%o|%s|%c",
2562                5,
2563                "null|null|null|null|null",
2564            ),
2565        ];
2566
2567        for (value, fmt, arg_count, expected) in cases {
2568            let data_types = vec![value.data_type(); arg_count];
2569            let formatter = Formatter::parse(fmt, &data_types)?;
2570            let args = vec![value; arg_count];
2571            assert_eq!(formatter.format(&args)?, expected, "{fmt}");
2572        }
2573        Ok(())
2574    }
2575
2576    #[test]
2577    fn test_insert_thousands_separator() {
2578        assert_eq!(insert_thousands_separator("1234567.89"), "1,234,567.89");
2579        assert_eq!(insert_thousands_separator("123.45"), "123.45");
2580        assert_eq!(insert_thousands_separator("1234"), "1,234");
2581        assert_eq!(insert_thousands_separator("12"), "12");
2582        assert_eq!(insert_thousands_separator("0.5"), "0.5");
2583        assert_eq!(
2584            insert_thousands_separator("1234567890.1234"),
2585            "1,234,567,890.1234"
2586        );
2587        assert_eq!(insert_thousands_separator("1000"), "1,000");
2588        assert_eq!(insert_thousands_separator("100"), "100");
2589    }
2590
2591    #[test]
2592    fn test_grouping_separator_float() -> Result<()> {
2593        test_scalar_function!(
2594            FormatStringFunc::new(),
2595            vec![
2596                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.2f".to_string()))),
2597                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2598            ],
2599            Ok(Some("1,234,567.89")),
2600            &str,
2601            Utf8,
2602            StringArray
2603        );
2604        Ok(())
2605    }
2606
2607    #[test]
2608    fn test_grouping_separator_decimal() -> Result<()> {
2609        test_scalar_function!(
2610            FormatStringFunc::new(),
2611            vec![
2612                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.2f".to_string()))),
2613                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2614            ],
2615            Ok(Some("1,234,567.89")),
2616            &str,
2617            Utf8,
2618            StringArray
2619        );
2620        Ok(())
2621    }
2622
2623    #[test]
2624    fn test_grouping_separator_scientific_float() -> Result<()> {
2625        // %,e — Java/Spark reject grouping separator with scientific notation
2626        test_scalar_function!(
2627            FormatStringFunc::new(),
2628            vec![
2629                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,e".to_string()))),
2630                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2631            ],
2632            Err(DataFusionError::Execution(
2633                "Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
2634            )),
2635            &str,
2636            Utf8,
2637            StringArray
2638        );
2639        // %,E — uppercase scientific also rejected
2640        test_scalar_function!(
2641            FormatStringFunc::new(),
2642            vec![
2643                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,E".to_string()))),
2644                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2645            ],
2646            Err(DataFusionError::Execution(
2647                "Grouping separator ',' flag is not compatible with scientific notation conversion 'E'".to_string(),
2648            )),
2649            &str,
2650            Utf8,
2651            StringArray
2652        );
2653        // %,.0e — precision 0 scientific with grouping also rejected
2654        test_scalar_function!(
2655            FormatStringFunc::new(),
2656            vec![
2657                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0e".to_string()))),
2658                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2659            ],
2660            Err(DataFusionError::Execution(
2661                "Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
2662            )),
2663            &str,
2664            Utf8,
2665            StringArray
2666        );
2667        Ok(())
2668    }
2669
2670    #[test]
2671    fn test_grouping_separator_compact_float() -> Result<()> {
2672        // %,g with large number — triggers scientific, no commas
2673        test_scalar_function!(
2674            FormatStringFunc::new(),
2675            vec![
2676                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
2677                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2678            ],
2679            Ok(Some("1.23457e+06")),
2680            &str,
2681            Utf8,
2682            StringArray
2683        );
2684        // %,g with small number — triggers fixed-point, commas in integer part
2685        test_scalar_function!(
2686            FormatStringFunc::new(),
2687            vec![
2688                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
2689                ColumnarValue::Scalar(ScalarValue::Float64(Some(12345.6))),
2690            ],
2691            Ok(Some("12,345.6")),
2692            &str,
2693            Utf8,
2694            StringArray
2695        );
2696        // %,.0g — precision 0 compact with grouping (large number, scientific)
2697        test_scalar_function!(
2698            FormatStringFunc::new(),
2699            vec![
2700                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0g".to_string()))),
2701                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2702            ],
2703            Ok(Some("1e+06")),
2704            &str,
2705            Utf8,
2706            StringArray
2707        );
2708        // %,G — uppercase compact
2709        test_scalar_function!(
2710            FormatStringFunc::new(),
2711            vec![
2712                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,G".to_string()))),
2713                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2714            ],
2715            Ok(Some("1.23457E+06")),
2716            &str,
2717            Utf8,
2718            StringArray
2719        );
2720        Ok(())
2721    }
2722
2723    #[test]
2724    fn test_grouping_separator_scientific_decimal() -> Result<()> {
2725        // %,e on decimal — Java/Spark reject grouping separator with scientific notation
2726        test_scalar_function!(
2727            FormatStringFunc::new(),
2728            vec![
2729                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,e".to_string()))),
2730                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2731            ],
2732            Err(DataFusionError::Execution(
2733                "Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
2734            )),
2735            &str,
2736            Utf8,
2737            StringArray
2738        );
2739        // %,.0e on decimal — also rejected
2740        test_scalar_function!(
2741            FormatStringFunc::new(),
2742            vec![
2743                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0e".to_string()))),
2744                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2745            ],
2746            Err(DataFusionError::Execution(
2747                "Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
2748            )),
2749            &str,
2750            Utf8,
2751            StringArray
2752        );
2753        Ok(())
2754    }
2755
2756    #[test]
2757    fn test_grouping_separator_compact_decimal() -> Result<()> {
2758        // %,g on decimal — large number triggers scientific, no commas
2759        test_scalar_function!(
2760            FormatStringFunc::new(),
2761            vec![
2762                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
2763                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2764            ],
2765            Ok(Some("1.23457e+06")),
2766            &str,
2767            Utf8,
2768            StringArray
2769        );
2770        // %,g on decimal — small number triggers fixed-point, commas expected
2771        test_scalar_function!(
2772            FormatStringFunc::new(),
2773            vec![
2774                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
2775                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(1234560), 10, 2)),
2776            ],
2777            Ok(Some("12,345.6")),
2778            &str,
2779            Utf8,
2780            StringArray
2781        );
2782        // %,.0g on decimal — precision 0 compact with grouping (scientific)
2783        test_scalar_function!(
2784            FormatStringFunc::new(),
2785            vec![
2786                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0g".to_string()))),
2787                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2788            ],
2789            Ok(Some("1e+06")),
2790            &str,
2791            Utf8,
2792            StringArray
2793        );
2794        Ok(())
2795    }
2796
2797    #[test]
2798    fn test_grouping_separator_width_sign_float() -> Result<()> {
2799        // %0,15.2f — zero-pad + grouping + width
2800        test_scalar_function!(
2801            FormatStringFunc::new(),
2802            vec![
2803                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%0,15.2f".to_string()))),
2804                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2805            ],
2806            Ok(Some("0001,234,567.89")),
2807            &str,
2808            Utf8,
2809            StringArray
2810        );
2811        // %+,15.2f — force-sign + grouping + width (space-padded)
2812        test_scalar_function!(
2813            FormatStringFunc::new(),
2814            vec![
2815                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+,15.2f".to_string()))),
2816                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2817            ],
2818            Ok(Some("  +1,234,567.89")),
2819            &str,
2820            Utf8,
2821            StringArray
2822        );
2823        // %-,15.2f — left-adjust + grouping + width
2824        test_scalar_function!(
2825            FormatStringFunc::new(),
2826            vec![
2827                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%-,15.2f".to_string()))),
2828                ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
2829            ],
2830            Ok(Some("1,234,567.89   ")),
2831            &str,
2832            Utf8,
2833            StringArray
2834        );
2835        Ok(())
2836    }
2837
2838    #[test]
2839    fn test_grouping_separator_width_sign_decimal() -> Result<()> {
2840        // %0,15.2f — zero-pad + grouping + width on decimal
2841        test_scalar_function!(
2842            FormatStringFunc::new(),
2843            vec![
2844                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%0,15.2f".to_string()))),
2845                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2846            ],
2847            Ok(Some("0001,234,567.89")),
2848            &str,
2849            Utf8,
2850            StringArray
2851        );
2852        // %+,15.2f — force-sign + grouping + width on decimal
2853        test_scalar_function!(
2854            FormatStringFunc::new(),
2855            vec![
2856                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+,15.2f".to_string()))),
2857                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
2858            ],
2859            Ok(Some("  +1,234,567.89")),
2860            &str,
2861            Utf8,
2862            StringArray
2863        );
2864        Ok(())
2865    }
2866
2867    #[test]
2868    fn test_grouping_separator_parentheses_float() -> Result<()> {
2869        // %(,15.2f with negative — parentheses + grouping + width
2870        // Java: String.format("%(,15.2f", -1234.5) → "     (1,234.50)"
2871        test_scalar_function!(
2872            FormatStringFunc::new(),
2873            vec![
2874                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,15.2f".to_string()))),
2875                ColumnarValue::Scalar(ScalarValue::Float64(Some(-1234.5))),
2876            ],
2877            Ok(Some("     (1,234.50)")),
2878            &str,
2879            Utf8,
2880            StringArray
2881        );
2882        Ok(())
2883    }
2884
2885    #[test]
2886    fn test_grouping_separator_parentheses_decimal() -> Result<()> {
2887        test_scalar_function!(
2888            FormatStringFunc::new(),
2889            vec![
2890                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,.2f".to_string()))),
2891                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(-123450), 10, 2)),
2892            ],
2893            Ok(Some("(1,234.50)")),
2894            &str,
2895            Utf8,
2896            StringArray
2897        );
2898
2899        test_scalar_function!(
2900            FormatStringFunc::new(),
2901            vec![
2902                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,.2f".to_string()))),
2903                ColumnarValue::Scalar(ScalarValue::Decimal256(
2904                    Some(i256::from(-123450)),
2905                    10,
2906                    2,
2907                )),
2908            ],
2909            Ok(Some("(1,234.50)")),
2910            &str,
2911            Utf8,
2912            StringArray
2913        );
2914
2915        // Java: String.format("%(,15.2f", -1234.5) → "     (1,234.50)"
2916        test_scalar_function!(
2917            FormatStringFunc::new(),
2918            vec![
2919                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,15.2f".to_string()))),
2920                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(-123450), 10, 2)),
2921            ],
2922            Ok(Some("     (1,234.50)")),
2923            &str,
2924            Utf8,
2925            StringArray
2926        );
2927        Ok(())
2928    }
2929
2930    #[test]
2931    fn test_grouping_separator_ignore_zero_padding_for_float_nan() -> Result<()> {
2932        test_scalar_function!(
2933            FormatStringFunc::new(),
2934            vec![
2935                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%010.2f".to_string()))),
2936                ColumnarValue::Scalar(ScalarValue::Float64(Some(f64::NAN))),
2937            ],
2938            Ok(Some("       NaN")),
2939            &str,
2940            Utf8,
2941            StringArray
2942        );
2943        Ok(())
2944    }
2945
2946    #[test]
2947    fn test_grouping_separator_ignore_zero_padding_for_float_inf() -> Result<()> {
2948        test_scalar_function!(
2949            FormatStringFunc::new(),
2950            vec![
2951                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%010.2f".to_string()))),
2952                ColumnarValue::Scalar(ScalarValue::Float64(Some(f64::INFINITY))),
2953            ],
2954            Ok(Some("  Infinity")),
2955            &str,
2956            Utf8,
2957            StringArray
2958        );
2959        Ok(())
2960    }
2961
2962    #[test]
2963    fn test_grouping_separator_parentheses_zero_padding_decimal() -> Result<()> {
2964        test_scalar_function!(
2965            FormatStringFunc::new(),
2966            vec![
2967                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(0,15.2f".to_string()))),
2968                ColumnarValue::Scalar(ScalarValue::Decimal128(Some(-123450), 2, 2)),
2969            ],
2970            Ok(Some("(000001,234.50)")),
2971            &str,
2972            Utf8,
2973            StringArray
2974        );
2975        Ok(())
2976    }
2977}