use std::fmt::Write;
use std::sync::Arc;
use core::num::FpCategory;
use arrow::{
array::{Array, ArrayRef, LargeStringArray, StringArray, StringViewArray},
datatypes::{DataType, Field, FieldRef},
};
use bigdecimal::{
BigDecimal, ToPrimitive,
num_bigint::{BigInt, Sign},
};
use chrono::{DateTime, Datelike, Timelike, Utc};
use datafusion_common::{
DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, plan_err,
};
use datafusion_expr::{
ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature,
TypeSignature, Volatility,
};
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct FormatStringFunc {
signature: Signature,
aliases: Vec<String>,
}
impl Default for FormatStringFunc {
fn default() -> Self {
Self::new()
}
}
impl FormatStringFunc {
pub fn new() -> Self {
Self {
signature: Signature::new(TypeSignature::VariadicAny, Volatility::Immutable),
aliases: vec![String::from("printf")],
}
}
}
impl ScalarUDFImpl for FormatStringFunc {
fn name(&self) -> &str {
"format_string"
}
fn aliases(&self) -> &[String] {
&self.aliases
}
fn signature(&self) -> &Signature {
&self.signature
}
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
datafusion_common::internal_err!(
"return_type should not be called, use return_field_from_args instead"
)
}
fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
match args.arg_fields[0].data_type() {
DataType::Null => {
Ok(Arc::new(Field::new("format_string", DataType::Utf8, true)))
}
DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => {
Ok(Arc::clone(&args.arg_fields[0]))
}
_ => exec_err!(
"format_string expects the first argument to be Utf8, LargeUtf8 or Utf8View, got {} instead",
args.arg_fields[0].data_type()
),
}
}
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
let len = args.args.iter().find_map(|arg| match arg {
ColumnarValue::Scalar(_) => None,
ColumnarValue::Array(a) => Some(a.len()),
});
let is_scalar = len.is_none();
let data_types = args.args[1..]
.iter()
.map(|arg| arg.data_type())
.collect::<Vec<_>>();
let fmt_type = args.args[0].data_type();
match &args.args[0] {
ColumnarValue::Scalar(ScalarValue::Null) => {
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
}
ColumnarValue::Scalar(ScalarValue::Utf8(None)) => {
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
}
ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)) => {
Ok(ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)))
}
ColumnarValue::Scalar(ScalarValue::Utf8View(None)) => {
Ok(ColumnarValue::Scalar(ScalarValue::Utf8View(None)))
}
ColumnarValue::Scalar(ScalarValue::Utf8(Some(fmt)))
| ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(fmt)))
| ColumnarValue::Scalar(ScalarValue::Utf8View(Some(fmt))) => {
let formatter = Formatter::parse(fmt, &data_types)?;
let mut result = Vec::with_capacity(len.unwrap_or(1));
for i in 0..len.unwrap_or(1) {
let scalars = args.args[1..]
.iter()
.map(|arg| try_to_scalar(arg.clone(), i))
.collect::<Result<Vec<_>>>()?;
let formatted = formatter.format(&scalars)?;
result.push(formatted);
}
if is_scalar {
let scalar_result = result.pop().unwrap();
match fmt_type {
DataType::Utf8 => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
Some(scalar_result),
))),
DataType::LargeUtf8 => Ok(ColumnarValue::Scalar(
ScalarValue::LargeUtf8(Some(scalar_result)),
)),
DataType::Utf8View => Ok(ColumnarValue::Scalar(
ScalarValue::Utf8View(Some(scalar_result)),
)),
_ => unreachable!(),
}
} else {
let array: ArrayRef = match fmt_type {
DataType::Utf8 => Arc::new(StringArray::from(result)),
DataType::LargeUtf8 => Arc::new(LargeStringArray::from(result)),
DataType::Utf8View => Arc::new(StringViewArray::from(result)),
_ => unreachable!(),
};
Ok(ColumnarValue::Array(array))
}
}
ColumnarValue::Array(fmts) => {
let mut result = Vec::with_capacity(len.unwrap());
for i in 0..len.unwrap() {
let fmt = ScalarValue::try_from_array(fmts, i)?;
match fmt.try_as_str() {
Some(Some(fmt)) => {
let formatter = Formatter::parse(fmt, &data_types)?;
let scalars = args.args[1..]
.iter()
.map(|arg| try_to_scalar(arg.clone(), i))
.collect::<Result<Vec<_>>>()?;
let formatted = formatter.format(&scalars)?;
result.push(Some(formatted));
}
Some(None) => {
result.push(None);
}
_ => unreachable!(),
}
}
let array: ArrayRef = match fmt_type {
DataType::Utf8 => Arc::new(StringArray::from(result)),
DataType::LargeUtf8 => Arc::new(LargeStringArray::from(result)),
DataType::Utf8View => Arc::new(StringViewArray::from(result)),
_ => unreachable!(),
};
Ok(ColumnarValue::Array(array))
}
_ => exec_err!(
"The format_string function expects the first argument to be a string"
),
}
}
}
fn try_to_scalar(arg: ColumnarValue, index: usize) -> Result<ScalarValue> {
match arg {
ColumnarValue::Scalar(scalar) => Ok(scalar),
ColumnarValue::Array(array) => ScalarValue::try_from_array(&array, index),
}
}
#[derive(Debug)]
pub struct Formatter<'a> {
pub elements: Vec<FormatElement<'a>>,
pub arg_num: usize,
}
impl<'a> Formatter<'a> {
pub fn new(elements: Vec<FormatElement<'a>>) -> Self {
let arg_num = elements
.iter()
.map(|element| match element {
FormatElement::Format(spec) => spec.argument_index,
_ => 0,
})
.max()
.unwrap_or(0);
Self { elements, arg_num }
}
pub fn parse(fmt: &'a str, arg_types: &[DataType]) -> Result<Self> {
let mut res = Vec::new();
let mut rem = fmt;
let mut argument_index = 0;
let mut prev: Option<usize> = None;
while !rem.is_empty() {
if let Some((verbatim_prefix, rest)) = rem.split_once('%') {
if !verbatim_prefix.is_empty() {
res.push(FormatElement::Verbatim(verbatim_prefix));
}
if let Some(rest) = rest.strip_prefix('%') {
res.push(FormatElement::Verbatim("%"));
rem = rest;
continue;
}
if let Some(rest) = rest.strip_prefix('n') {
res.push(FormatElement::Verbatim("\n"));
rem = rest;
continue;
}
if let Some(rest) = rest.strip_prefix('<') {
let Some(p) = prev else {
return exec_err!("No previous argument to reference");
};
let (spec, rest) =
take_conversion_specifier(rest, p, &arg_types[p - 1])?;
res.push(FormatElement::Format(spec));
rem = rest;
continue;
}
let (current_argument_index, rest2) = take_numeric_param(rest, false);
let (current_argument_index, rest) =
match (current_argument_index, rest2.starts_with('$')) {
(NumericParam::Literal(index), true) => {
(index as usize, &rest2[1..])
}
(NumericParam::FromArgument, true) => {
return exec_err!("Invalid numeric parameter");
}
(_, false) => {
argument_index += 1;
(argument_index, rest)
}
};
if current_argument_index == 0 || current_argument_index > arg_types.len()
{
return exec_err!(
"Argument index {} is out of bounds",
current_argument_index
);
}
let (spec, rest) = take_conversion_specifier(
rest,
current_argument_index,
&arg_types[current_argument_index - 1],
)
.map_err(|e| exec_datafusion_err!("{:?}, format string: {:?}", e, fmt))?;
res.push(FormatElement::Format(spec));
prev = Some(spec.argument_index);
rem = rest;
} else {
res.push(FormatElement::Verbatim(rem));
break;
}
}
Ok(Self::new(res))
}
pub fn format(&self, args: &[ScalarValue]) -> Result<String> {
if args.len() < self.arg_num {
return exec_err!(
"Expected at least {} arguments, got {}",
self.arg_num,
args.len()
);
}
let mut string = String::new();
for element in &self.elements {
match element {
FormatElement::Verbatim(text) => {
string.push_str(text);
}
FormatElement::Format(spec) => {
spec.format(&mut string, &args[spec.argument_index - 1])?;
}
}
}
Ok(string)
}
}
#[derive(Debug)]
pub enum FormatElement<'a> {
Verbatim(&'a str),
Format(ConversionSpecifier),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConversionSpecifier {
pub argument_index: usize,
pub alt_form: bool,
pub zero_pad: bool,
pub left_adj: bool,
pub space_sign: bool,
pub force_sign: bool,
pub grouping_separator: bool,
pub negative_in_parentheses: bool,
pub width: NumericParam,
pub precision: NumericParam,
pub conversion_type: ConversionType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericParam {
Literal(i32),
FromArgument,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionType {
BooleanUpper,
BooleanLower,
HexHashLower,
HexHashUpper,
DecInt,
OctInt,
HexIntLower,
HexIntUpper,
SciFloatLower,
SciFloatUpper,
DecFloatLower,
CompactFloatLower,
CompactFloatUpper,
HexFloatLower,
HexFloatUpper,
TimeLower(TimeFormat),
TimeUpper(TimeFormat),
CharLower,
CharUpper,
StringLower,
StringUpper,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeFormat {
HUpper,
IUpper,
KLower,
LLower,
MUpper,
SUpper,
LUpper,
NUpper,
PLower,
ZLower,
ZUpper,
SLower,
QUpper,
BUpper,
BLower,
AUpper,
ALower,
CUpper,
YUpper,
YLower,
JLower,
MLower,
DLower,
ELower,
RUpper,
TUpper,
RLower,
DUpper,
FUpper,
CLower,
}
impl TryFrom<char> for TimeFormat {
type Error = DataFusionError;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'H' => Ok(TimeFormat::HUpper),
'I' => Ok(TimeFormat::IUpper),
'k' => Ok(TimeFormat::KLower),
'l' => Ok(TimeFormat::LLower),
'M' => Ok(TimeFormat::MUpper),
'S' => Ok(TimeFormat::SUpper),
'L' => Ok(TimeFormat::LUpper),
'N' => Ok(TimeFormat::NUpper),
'p' => Ok(TimeFormat::PLower),
'z' => Ok(TimeFormat::ZLower),
'Z' => Ok(TimeFormat::ZUpper),
's' => Ok(TimeFormat::SLower),
'Q' => Ok(TimeFormat::QUpper),
'B' => Ok(TimeFormat::BUpper),
'b' | 'h' => Ok(TimeFormat::BLower),
'A' => Ok(TimeFormat::AUpper),
'a' => Ok(TimeFormat::ALower),
'C' => Ok(TimeFormat::CUpper),
'Y' => Ok(TimeFormat::YUpper),
'y' => Ok(TimeFormat::YLower),
'j' => Ok(TimeFormat::JLower),
'm' => Ok(TimeFormat::MLower),
'd' => Ok(TimeFormat::DLower),
'e' => Ok(TimeFormat::ELower),
'R' => Ok(TimeFormat::RUpper),
'T' => Ok(TimeFormat::TUpper),
'r' => Ok(TimeFormat::RLower),
'D' => Ok(TimeFormat::DUpper),
'F' => Ok(TimeFormat::FUpper),
'c' => Ok(TimeFormat::CLower),
_ => exec_err!("Invalid time format: {}", value),
}
}
}
impl ConversionType {
pub fn validate(&self, arg_type: &DataType) -> Result<()> {
match self {
ConversionType::BooleanLower | ConversionType::BooleanUpper
if *arg_type != DataType::Boolean =>
{
return exec_err!(
"Invalid argument type for boolean conversion: {:?}",
arg_type
);
}
ConversionType::CharLower | ConversionType::CharUpper
if !matches!(
arg_type,
DataType::Int8
| DataType::UInt8
| DataType::Int16
| DataType::UInt16
| DataType::Int32
| DataType::UInt32
| DataType::Int64
| DataType::UInt64
) =>
{
return exec_err!(
"Invalid argument type for char conversion: {:?}",
arg_type
);
}
ConversionType::DecInt
| ConversionType::OctInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
if !arg_type.is_integer() =>
{
return exec_err!(
"Invalid argument type for integer conversion: {:?}",
arg_type
);
}
ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::DecFloatLower
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper
| ConversionType::HexFloatLower
| ConversionType::HexFloatUpper
if !arg_type.is_numeric() =>
{
return exec_err!(
"Invalid argument type for float conversion: {:?}",
arg_type
);
}
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_)
if !arg_type.is_temporal() =>
{
return exec_err!(
"Invalid argument type for time conversion: {:?}",
arg_type
);
}
_ => {}
}
Ok(())
}
fn supports_integer(&self) -> bool {
matches!(
self,
ConversionType::DecInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt
| ConversionType::CharLower
| ConversionType::CharUpper
| ConversionType::StringLower
| ConversionType::StringUpper
)
}
fn supports_float(&self) -> bool {
matches!(
self,
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper
| ConversionType::StringLower
| ConversionType::StringUpper
| ConversionType::HexFloatLower
| ConversionType::HexFloatUpper
)
}
fn supports_decimal(&self) -> bool {
matches!(
self,
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper
| ConversionType::StringLower
| ConversionType::StringUpper
)
}
fn supports_time(&self) -> bool {
matches!(
self,
ConversionType::TimeLower(_)
| ConversionType::TimeUpper(_)
| ConversionType::StringLower
| ConversionType::StringUpper
)
}
fn is_upper(&self) -> bool {
matches!(
self,
ConversionType::BooleanUpper
| ConversionType::HexHashUpper
| ConversionType::HexIntUpper
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatUpper
| ConversionType::HexFloatUpper
| ConversionType::TimeUpper(_)
| ConversionType::CharUpper
| ConversionType::StringUpper
)
}
}
fn take_conversion_specifier<'a>(
mut s: &'a str,
argument_index: usize,
arg_type: &DataType,
) -> Result<(ConversionSpecifier, &'a str)> {
let mut spec = ConversionSpecifier {
argument_index,
alt_form: false,
zero_pad: false,
left_adj: false,
space_sign: false,
force_sign: false,
grouping_separator: false,
negative_in_parentheses: false,
width: NumericParam::Literal(0),
precision: NumericParam::FromArgument, conversion_type: ConversionType::DecInt,
};
loop {
match s.chars().next() {
Some('#') => {
spec.alt_form = true;
}
Some('0') => {
if spec.left_adj {
return exec_err!("Invalid flag combination: '0' and '-'");
}
spec.zero_pad = true;
}
Some('-') => {
spec.left_adj = true;
}
Some(' ') => {
if spec.force_sign {
return exec_err!("Invalid flag combination: '+' and ' '");
}
spec.space_sign = true;
}
Some('+') => {
if spec.space_sign {
return exec_err!("Invalid flag combination: '+' and ' '");
}
spec.force_sign = true;
}
Some(',') => {
spec.grouping_separator = true;
}
Some('(') => {
spec.negative_in_parentheses = true;
}
_ => {
break;
}
}
s = &s[1..];
}
let (w, mut s) = take_numeric_param(s, false);
spec.width = w;
if matches!(s.chars().next(), Some('.')) {
s = &s[1..];
let (p, s2) = take_numeric_param(s, true);
spec.precision = p;
s = s2;
}
let mut chars = s.chars();
let mut offset = 1;
spec.conversion_type = match chars.next() {
Some('b') => ConversionType::BooleanLower,
Some('B') => ConversionType::BooleanUpper,
Some('h') => ConversionType::HexHashLower,
Some('H') => ConversionType::HexHashUpper,
Some('s') => ConversionType::StringLower,
Some('S') => ConversionType::StringUpper,
Some('c') => ConversionType::CharLower,
Some('C') => ConversionType::CharUpper,
Some('d') => ConversionType::DecInt,
Some('o') => ConversionType::OctInt,
Some('x') => ConversionType::HexIntLower,
Some('X') => ConversionType::HexIntUpper,
Some('e') => ConversionType::SciFloatLower,
Some('E') => ConversionType::SciFloatUpper,
Some('f') => ConversionType::DecFloatLower,
Some('g') => ConversionType::CompactFloatLower,
Some('G') => ConversionType::CompactFloatUpper,
Some('a') => ConversionType::HexFloatLower,
Some('A') => ConversionType::HexFloatUpper,
Some('t') => {
let Some(chr) = chars.next() else {
return exec_err!("Invalid time format: {}", s);
};
offset += 1;
ConversionType::TimeLower(chr.try_into()?)
}
Some('T') => {
let Some(chr) = chars.next() else {
return exec_err!("Invalid time format: {}", s);
};
offset += 1;
ConversionType::TimeUpper(chr.try_into()?)
}
chr => {
return plan_err!("Invalid conversion type: {:?}", chr);
}
};
spec.conversion_type.validate(arg_type)?;
Ok((spec, &s[offset..]))
}
fn take_numeric_param(s: &str, zero: bool) -> (NumericParam, &str) {
match s.chars().next() {
Some(digit) if (if zero { '0'..='9' } else { '1'..='9' }).contains(&digit) => {
let mut s = s;
let mut w = 0;
loop {
match s.chars().next() {
Some(digit) if digit.is_ascii_digit() => {
w = 10 * w + (digit as i32 - '0' as i32);
}
_ => {
break;
}
}
s = &s[1..];
}
(NumericParam::Literal(w), s)
}
_ => (NumericParam::FromArgument, s),
}
}
fn codepoint_to_char(value: u32) -> Result<char> {
char::from_u32(value).ok_or_else(|| {
exec_datafusion_err!("invalid Unicode scalar value for %c: {value:#x}")
})
}
fn signed_to_char(value: i64) -> Result<char> {
let codepoint = u32::try_from(value).map_err(|_| {
exec_datafusion_err!("invalid Unicode scalar value for %c: {value}")
})?;
codepoint_to_char(codepoint)
}
fn unsigned_to_char(value: u64) -> Result<char> {
let codepoint = u32::try_from(value).map_err(|_| {
exec_datafusion_err!("invalid Unicode scalar value for %c: {value:#x}")
})?;
codepoint_to_char(codepoint)
}
fn integer_scalar_to_char(scalar: &ScalarValue) -> Result<char> {
match scalar {
ScalarValue::Int8(Some(value)) => signed_to_char(*value as i64),
ScalarValue::Int16(Some(value)) => signed_to_char(*value as i64),
ScalarValue::Int32(Some(value)) => signed_to_char(*value as i64),
ScalarValue::Int64(Some(value)) => signed_to_char(*value),
ScalarValue::UInt8(Some(value)) => unsigned_to_char(*value as u64),
ScalarValue::UInt16(Some(value)) => unsigned_to_char(*value as u64),
ScalarValue::UInt32(Some(value)) => unsigned_to_char(*value as u64),
ScalarValue::UInt64(Some(value)) => unsigned_to_char(*value),
_ => datafusion_common::internal_err!(
"integer_scalar_to_char expects a non-null integer scalar, got {scalar:?}"
),
}
}
impl ConversionSpecifier {
fn validate_grouping_separator(&self) -> Result<()> {
if self.grouping_separator
&& matches!(
self.conversion_type,
ConversionType::SciFloatLower | ConversionType::SciFloatUpper
)
{
return exec_err!(
"Grouping separator ',' flag is not compatible with scientific notation conversion '{}'",
if self.conversion_type == ConversionType::SciFloatUpper {
'E'
} else {
'e'
}
);
}
Ok(())
}
pub fn format(&self, string: &mut String, value: &ScalarValue) -> Result<()> {
match value {
ScalarValue::Boolean(value) => match self.conversion_type {
ConversionType::StringLower | ConversionType::StringUpper => {
self.format_string(string, &value.unwrap_or(false).to_string())
}
_ => self.format_boolean(string, value),
},
ScalarValue::Int8(Some(_))
| ScalarValue::Int16(Some(_))
| ScalarValue::Int32(Some(_))
| ScalarValue::Int64(Some(_))
| ScalarValue::UInt8(Some(_))
| ScalarValue::UInt16(Some(_))
| ScalarValue::UInt32(Some(_))
| ScalarValue::UInt64(Some(_))
if matches!(
self.conversion_type,
ConversionType::CharLower | ConversionType::CharUpper
) =>
{
self.format_char(string, integer_scalar_to_char(value)?)
}
ScalarValue::Int8(value) => match (self.conversion_type, value) {
(ConversionType::DecInt, Some(value)) => {
self.format_signed(string, *value as i64)
}
(
ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, (*value as u8) as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Int8",
self.conversion_type
)
}
},
ScalarValue::Int16(value) => match (self.conversion_type, value) {
(ConversionType::DecInt, Some(value)) => {
self.format_signed(string, *value as i64)
}
(
ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, (*value as u16) as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Int16",
self.conversion_type
)
}
},
ScalarValue::Int32(value) => match (self.conversion_type, value) {
(ConversionType::DecInt, Some(value)) => {
self.format_signed(string, *value as i64)
}
(
ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, (*value as u32) as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Int32",
self.conversion_type
)
}
},
ScalarValue::Int64(value) => match (self.conversion_type, value) {
(ConversionType::DecInt, Some(value)) => {
self.format_signed(string, *value)
}
(
ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, *value as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Int64",
self.conversion_type
)
}
},
ScalarValue::UInt8(value) => match (self.conversion_type, value) {
(
ConversionType::DecInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, *value as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for UInt8",
self.conversion_type
)
}
},
ScalarValue::UInt16(value) => match (self.conversion_type, value) {
(
ConversionType::DecInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, *value as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for UInt16",
self.conversion_type
)
}
},
ScalarValue::UInt32(value) => match (self.conversion_type, value) {
(
ConversionType::DecInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, *value as u64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for UInt32",
self.conversion_type
)
}
},
ScalarValue::UInt64(value) => match (self.conversion_type, value) {
(
ConversionType::DecInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::OctInt,
Some(value),
) => self.format_unsigned(string, *value),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_integer() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for UInt64",
self.conversion_type
)
}
},
ScalarValue::Float16(value) => match (self.conversion_type, value) {
(
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper,
Some(value),
) => self.format_float(string, value.to_f64().unwrap()),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_f32().unwrap().spark_string()),
(
ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
Some(value),
) => self.format_hex_float(string, value.to_f64().unwrap()),
(t, None) if t.supports_float() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Float16",
self.conversion_type
)
}
},
ScalarValue::Float32(value) => match (self.conversion_type, value) {
(
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper,
Some(value),
) => self.format_float(string, *value as f64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.spark_string()),
(
ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
Some(value),
) => self.format_hex_float(string, *value as f64),
(t, None) if t.supports_float() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Float32",
self.conversion_type
)
}
},
ScalarValue::Float64(value) => match (self.conversion_type, value) {
(
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper,
Some(value),
) => self.format_float(string, *value),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.spark_string()),
(
ConversionType::HexFloatLower | ConversionType::HexFloatUpper,
Some(value),
) => self.format_hex_float(string, *value),
(t, None) if t.supports_float() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Float64",
self.conversion_type
)
}
},
ScalarValue::Utf8(value) => {
let value: &str = match value {
Some(value) => value.as_str(),
None => "null",
};
if matches!(
self.conversion_type,
ConversionType::StringLower | ConversionType::StringUpper
) {
self.format_string(string, value)
} else {
exec_err!(
"Invalid conversion type: {:?} for Utf8",
self.conversion_type
)
}
}
ScalarValue::LargeUtf8(value) => {
let value: &str = match value {
Some(value) => value.as_str(),
None => "null",
};
if matches!(
self.conversion_type,
ConversionType::StringLower | ConversionType::StringUpper
) {
self.format_string(string, value)
} else {
exec_err!(
"Invalid conversion type: {:?} for LargeUtf8",
self.conversion_type
)
}
}
ScalarValue::Utf8View(value) => {
let value: &str = match value {
Some(value) => value.as_str(),
None => "null",
};
self.format_string(string, value)
}
ScalarValue::Decimal128(value, _, scale) => {
match (self.conversion_type, value) {
(
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper,
Some(value),
) => self.format_decimal(string, &value.to_string(), *scale as i64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_decimal() => {
self.format_string(string, "null")
}
_ => {
exec_err!(
"Invalid conversion type: {:?} for Decimal128",
self.conversion_type
)
}
}
}
ScalarValue::Decimal256(value, _, scale) => {
match (self.conversion_type, value) {
(
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper,
Some(value),
) => self.format_decimal(string, &value.to_string(), *scale as i64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_decimal() => {
self.format_string(string, "null")
}
_ => {
exec_err!(
"Invalid conversion type: {:?} for Decimal256",
self.conversion_type
)
}
}
}
ScalarValue::Time32Second(value) => match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value as i64 * 1000000000, &None),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Time32Second",
self.conversion_type
)
}
},
ScalarValue::Time32Millisecond(value) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value as i64 * 1000000, &None),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Time32Millisecond",
self.conversion_type
)
}
}
}
ScalarValue::Time64Microsecond(value) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value * 1000, &None),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Time64Microsecond",
self.conversion_type
)
}
}
}
ScalarValue::Time64Nanosecond(value) => match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value, &None),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Time64Nanosecond",
self.conversion_type
)
}
},
ScalarValue::TimestampSecond(value, zone) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, value * 1000000000, zone),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for TimestampSecond",
self.conversion_type
)
}
}
}
ScalarValue::TimestampMillisecond(value, zone) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value * 1000000, zone),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for TimestampMillisecond",
self.conversion_type
)
}
}
}
ScalarValue::TimestampMicrosecond(value, zone) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, value * 1000, zone),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for timestampmicrosecond",
self.conversion_type
)
}
}
}
ScalarValue::TimestampNanosecond(value, zone) => {
match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_time(string, *value, zone),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for TimestampNanosecond",
self.conversion_type
)
}
}
}
ScalarValue::Date32(value) => match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_date(string, *value as i64),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Date32",
self.conversion_type
)
}
},
ScalarValue::Date64(value) => match (self.conversion_type, value) {
(
ConversionType::TimeLower(_) | ConversionType::TimeUpper(_),
Some(value),
) => self.format_date(string, *value),
(
ConversionType::StringLower | ConversionType::StringUpper,
Some(value),
) => self.format_string(string, &value.to_string()),
(t, None) if t.supports_time() => self.format_string(string, "null"),
_ => {
exec_err!(
"Invalid conversion type: {:?} for Date64",
self.conversion_type
)
}
},
ScalarValue::Null => {
let value = "null".to_string();
self.format_string(string, &value)
}
_ => exec_err!("Invalid scalar value: {value}"),
}
}
fn format_hex_float(&self, writer: &mut String, value: f64) -> Result<()> {
let (sign, raw_exponent, mantissa) = value.to_parts();
let is_subnormal = raw_exponent == 0;
let precision = match self.precision {
NumericParam::FromArgument => None,
NumericParam::Literal(p) => Some(p),
};
let mantissa_hex_digits = f64::MANTISSA_BITS.div_ceil(4); let should_normalize = is_subnormal
&& precision.is_some()
&& precision.unwrap() < mantissa_hex_digits as i32;
let (value, raw_exponent, mantissa) = if should_normalize {
let value = value * f64::SCALEUP;
let (_, raw_exponent, mantissa) = value.to_parts();
(value, raw_exponent, mantissa)
} else {
(value, raw_exponent, mantissa)
};
let mut temp = String::new();
let sign_char = if sign {
"-"
} else if self.force_sign {
"+"
} else if self.space_sign {
" "
} else {
""
};
match value.category() {
FpCategory::Nan => {
write!(&mut temp, "NaN")?;
}
FpCategory::Infinite => {
write!(&mut temp, "{sign_char}Infinity")?;
}
FpCategory::Zero => {
write!(&mut temp, "{sign_char}0x0.0p0")?;
}
_ => {
let bias = i32::from(f64::EXPONENT_BIAS);
let exponent = if is_subnormal && !should_normalize {
1 - bias
} else {
raw_exponent as i32 - bias
};
let final_mantissa = if let Some(p) = precision {
if p == 0 {
let shift_distance = f64::MANTISSA_BITS as i32 - 4; let shifted = mantissa >> shift_distance;
let rounding_bits = mantissa & ((1u64 << shift_distance) - 1);
let round_bit = 1u64 << (shift_distance - 1);
if rounding_bits > round_bit
|| (rounding_bits == round_bit && (shifted & 1) != 0)
{
(shifted + 1) << shift_distance
} else {
shifted << shift_distance
}
} else {
let precision_bits = p * 4; let keep_bits = f64::MANTISSA_BITS as i32;
let shift_distance = keep_bits - precision_bits;
if shift_distance > 0 {
let shifted = mantissa >> shift_distance;
let rounding_bits = mantissa & ((1u64 << shift_distance) - 1);
let round_bit = 1u64 << (shift_distance - 1);
if rounding_bits > round_bit
|| (rounding_bits == round_bit && (shifted & 1) != 0)
{
(shifted + 1) << shift_distance
} else {
shifted << shift_distance
}
} else {
mantissa
}
}
} else {
mantissa
};
if is_subnormal && !should_normalize {
if precision.is_some() {
let full_hex = format!(
"{:0width$x}",
final_mantissa,
width = mantissa_hex_digits as usize
);
write!(&mut temp, "{sign_char}0x0.{full_hex}p{exponent}")?;
} else {
let hex_digits = format!(
"{:0width$x}",
final_mantissa,
width = mantissa_hex_digits as usize
);
write!(&mut temp, "{sign_char}0x0.{hex_digits}p{exponent}")?;
}
} else {
if let Some(p) = precision {
let p = if p == 0 { 1 } else { p };
let hex_digits = format!("{final_mantissa:x}");
let formatted_digits = if p as usize >= hex_digits.len() {
format!("{:0<width$}", hex_digits, width = p as usize)
} else {
hex_digits[..p as usize].to_string()
};
write!(
&mut temp,
"{sign_char}0x1.{formatted_digits}p{exponent}"
)?;
} else {
let mut hex_digits = format!("{final_mantissa:x}");
hex_digits = trim_trailing_0s_hex(&hex_digits).to_owned();
if hex_digits.is_empty() {
write!(&mut temp, "{sign_char}0x1.0p{exponent}")?;
} else {
write!(&mut temp, "{sign_char}0x1.{hex_digits}p{exponent}")?;
}
}
}
if should_normalize {
let (prefix, exp) = temp.split_once('p').unwrap();
let iexp = exp.parse::<i32>().unwrap() - f64::SCALEUP_POWER as i32;
temp = format!("{prefix}p{iexp}");
}
}
};
if self.conversion_type.is_upper() {
temp = temp.to_ascii_uppercase();
}
let NumericParam::Literal(width) = self.width else {
writer.push_str(&temp);
return Ok(());
};
if self.left_adj {
writer.push_str(&temp);
for _ in temp.len()..width as usize {
writer.push(' ');
}
} else if self.zero_pad && value.is_finite() {
let delimiter = if self.conversion_type.is_upper() {
"0X"
} else {
"0x"
};
let (prefix, suffix) = temp.split_once(delimiter).unwrap();
writer.push_str(prefix);
writer.push_str(delimiter);
for _ in temp.len()..width as usize {
writer.push('0');
}
writer.push_str(suffix);
} else {
while temp.len() < width as usize {
temp = " ".to_owned() + &temp;
}
writer.push_str(&temp);
};
Ok(())
}
fn format_char(&self, writer: &mut String, value: char) -> Result<()> {
let upper = self.conversion_type.is_upper();
match self.conversion_type {
ConversionType::CharLower | ConversionType::CharUpper => {
let NumericParam::Literal(width) = self.width else {
if upper {
writer.push(value.to_ascii_uppercase());
} else {
writer.push(value);
}
return Ok(());
};
let start_len = writer.len();
if self.left_adj {
if upper {
writer.push(value.to_ascii_uppercase());
} else {
writer.push(value);
}
while writer.len() - start_len < width as usize {
writer.push(' ');
}
} else {
while writer.len() - start_len + value.len_utf8() < width as usize {
writer.push(' ');
}
if upper {
writer.push(value.to_ascii_uppercase());
} else {
writer.push(value);
}
}
Ok(())
}
_ => exec_err!(
"Invalid conversion type: {:?} for char",
self.conversion_type
),
}
}
fn format_boolean(&self, writer: &mut String, value: &Option<bool>) -> Result<()> {
let value = value.unwrap_or(false);
let formatted = match self.conversion_type {
ConversionType::BooleanUpper => {
if value {
"TRUE"
} else {
"FALSE"
}
}
ConversionType::BooleanLower => {
if value {
"true"
} else {
"false"
}
}
_ => {
return exec_err!(
"Invalid conversion type: {:?} for boolean array",
self.conversion_type
);
}
};
self.format_str(writer, formatted)
}
fn format_float(&self, writer: &mut String, value: f64) -> Result<()> {
self.validate_grouping_separator()?;
let mut prefix = String::new();
let mut suffix = String::new();
let mut number = String::new();
let upper = self.conversion_type.is_upper();
if value.is_sign_negative() {
if self.negative_in_parentheses {
prefix.push('(');
suffix.push(')');
} else {
prefix.push('-');
}
} else if self.space_sign {
prefix.push(' ');
} else if self.force_sign {
prefix.push('+');
}
if value.is_finite() {
let mut use_scientific = false;
let mut strip_trailing_0s = false;
let mut abs = value.abs();
let mut exponent = abs.log10().floor() as i32;
let mut precision = match self.precision {
NumericParam::Literal(p) => p,
_ => 6,
};
match self.conversion_type {
ConversionType::DecFloatLower => {
}
ConversionType::SciFloatLower => {
use_scientific = true;
}
ConversionType::SciFloatUpper => {
use_scientific = true;
}
ConversionType::CompactFloatLower | ConversionType::CompactFloatUpper => {
strip_trailing_0s = true;
if precision == 0 {
precision = 1;
}
let rounding_factor =
10.0_f64.powf((precision - 1 - exponent) as f64);
let rounded_fixed = (abs * rounding_factor).round();
abs = rounded_fixed / rounding_factor;
exponent = abs.log10().floor() as i32;
if exponent < -4 || exponent >= precision {
use_scientific = true;
precision -= 1;
} else {
precision -= 1 + exponent;
}
}
_ => {
return exec_err!(
"Invalid conversion type: {:?} for float",
self.conversion_type
);
}
}
if use_scientific {
let mantissa = abs / 10.0_f64.powf(exponent as f64);
let exp_char = if upper { 'E' } else { 'e' };
number = format!("{mantissa:.prec$}", prec = precision as usize);
if strip_trailing_0s {
number = trim_trailing_0s(&number).to_owned();
}
number = format!("{number}{exp_char}{exponent:+03}");
} else {
number = format!("{abs:.prec$}", prec = precision as usize);
if strip_trailing_0s {
number = trim_trailing_0s(&number).to_owned();
}
if self.grouping_separator {
number = insert_thousands_separator(&number);
}
}
if self.alt_form && !number.contains('.') {
number += ".";
}
} else {
match self.conversion_type {
ConversionType::DecFloatLower
| ConversionType::SciFloatLower
| ConversionType::CompactFloatLower => {
if value.is_infinite() {
number.push_str("Infinity")
} else {
number.push_str("NaN")
}
}
ConversionType::SciFloatUpper | ConversionType::CompactFloatUpper => {
if value.is_infinite() {
number.push_str("INFINITY")
} else {
number.push_str("NAN")
}
}
_ => {
return exec_err!(
"Invalid conversion type: {:?} for float",
self.conversion_type
);
}
}
}
let NumericParam::Literal(width) = self.width else {
writer.push_str(&prefix);
writer.push_str(&number);
writer.push_str(&suffix);
return Ok(());
};
if self.left_adj {
let mut full_num = prefix + &number + &suffix;
while full_num.len() < width as usize {
full_num.push(' ');
}
writer.push_str(&full_num);
} else if self.zero_pad && value.is_finite() {
while prefix.len() + number.len() + suffix.len() < width as usize {
prefix.push('0');
}
writer.push_str(&prefix);
writer.push_str(&number);
writer.push_str(&suffix);
} else {
let mut full_num = prefix + &number + &suffix;
while full_num.len() < width as usize {
full_num = " ".to_owned() + &full_num;
}
writer.push_str(&full_num);
};
Ok(())
}
fn format_signed(&self, writer: &mut String, value: i64) -> Result<()> {
let negative = value < 0;
let abs_val = value.abs();
let (sign_prefix, sign_suffix) = if negative && self.negative_in_parentheses {
("(".to_owned(), ")".to_owned())
} else if negative {
("-".to_owned(), "".to_owned())
} else if self.force_sign {
("+".to_owned(), "".to_owned())
} else if self.space_sign {
(" ".to_owned(), "".to_owned())
} else {
("".to_owned(), "".to_owned())
};
let mut mod_spec = *self;
mod_spec.width = match self.width {
NumericParam::Literal(w) => NumericParam::Literal(
w - sign_prefix.len() as i32 - sign_suffix.len() as i32,
),
_ => NumericParam::FromArgument,
};
let mut formatted = String::new();
mod_spec.format_unsigned(&mut formatted, abs_val as u64)?;
let mut actual_number = &formatted[0..];
let mut leading_spaces = &formatted[0..0];
if let Some(first_non_space) = formatted.find(|c| c != ' ') {
actual_number = &formatted[first_non_space..];
leading_spaces = &formatted[0..first_non_space];
}
write!(
writer,
"{}{}{}{}",
leading_spaces.to_owned(),
sign_prefix,
actual_number,
sign_suffix
)
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
Ok(())
}
fn format_unsigned(&self, writer: &mut String, value: u64) -> Result<()> {
let mut s = String::new();
let mut alt_prefix = "";
match self.conversion_type {
ConversionType::DecInt => {
let num_str = format!("{value}");
s = if self.grouping_separator {
insert_thousands_separator(&num_str)
} else {
num_str
};
}
ConversionType::HexIntLower => {
alt_prefix = "0x";
write!(&mut s, "{value:x}")
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
}
ConversionType::HexIntUpper => {
alt_prefix = "0X";
write!(&mut s, "{value:X}")
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
}
ConversionType::OctInt => {
alt_prefix = "0";
write!(&mut s, "{value:o}")
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
}
_ => {
return exec_err!(
"Invalid conversion type: {:?} for u64",
self.conversion_type
);
}
}
let mut prefix = if self.alt_form {
alt_prefix.to_owned()
} else {
String::new()
};
let formatted = if let NumericParam::Literal(width) = self.width {
if self.left_adj {
let mut num_str = prefix + &s;
while num_str.len() < width as usize {
num_str.push(' ');
}
num_str
} else if self.zero_pad {
while prefix.len() + s.len() < width as usize {
prefix.push('0');
}
prefix + &s
} else {
let mut num_str = prefix + &s;
while num_str.len() < width as usize {
num_str = " ".to_owned() + &num_str;
}
num_str
}
} else {
prefix + &s
};
write!(writer, "{formatted}")
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
Ok(())
}
fn format_str(&self, writer: &mut String, value: &str) -> Result<()> {
let precision: usize = match self.precision {
NumericParam::Literal(p) => p,
_ => i32::MAX,
}
.try_into()
.unwrap_or_default();
let content_len = {
let mut content_len = precision.min(value.len());
while !value.is_char_boundary(content_len) {
content_len -= 1;
}
content_len
};
let content = &value[..content_len];
if let NumericParam::Literal(width) = self.width {
let start_len = writer.len();
if self.left_adj {
writer.push_str(content);
while writer.len() - start_len < width as usize {
writer.push(' ');
}
} else {
while writer.len() - start_len + content.len() < width as usize {
writer.push(' ');
}
writer.push_str(content);
}
} else {
writer.push_str(content);
}
Ok(())
}
fn format_string(&self, writer: &mut String, value: &str) -> Result<()> {
if self.conversion_type.is_upper() {
let upper = value.to_ascii_uppercase();
self.format_str(writer, &upper)
} else {
self.format_str(writer, value)
}
}
fn format_decimal(&self, writer: &mut String, value: &str, scale: i64) -> Result<()> {
self.validate_grouping_separator()?;
let mut prefix = String::new();
let upper = self.conversion_type.is_upper();
let decimal = value
.parse::<BigInt>()
.map_err(|e| exec_datafusion_err!("Failed to parse decimal: {}", e))?;
let decimal = BigDecimal::from_bigint(decimal, scale);
let is_negative = decimal.sign() == Sign::Minus;
let abs_decimal = decimal.abs();
if is_negative {
prefix.push('-');
} else if self.space_sign {
prefix.push(' ');
} else if self.force_sign {
prefix.push('+');
}
let exp_symb = if upper { 'E' } else { 'e' };
let mut strip_trailing_0s = false;
let mut precision = match self.precision {
NumericParam::Literal(p) => p,
_ => 6,
};
let number = match self.conversion_type {
ConversionType::DecFloatLower => {
let mut n = self.format_decimal_fixed(
&abs_decimal,
precision,
strip_trailing_0s,
)?;
if self.grouping_separator {
n = insert_thousands_separator(&n);
}
n
}
ConversionType::SciFloatLower => self.format_decimal_scientific(
&abs_decimal,
precision,
'e',
strip_trailing_0s,
)?,
ConversionType::SciFloatUpper => self.format_decimal_scientific(
&abs_decimal,
precision,
'E',
strip_trailing_0s,
)?,
ConversionType::CompactFloatLower | ConversionType::CompactFloatUpper => {
strip_trailing_0s = true;
if precision == 0 {
precision = 1;
}
let log10_val = abs_decimal.to_f64().map(|f| f.log10()).unwrap_or(0.0);
if log10_val < -4.0 || log10_val >= precision as f64 {
self.format_decimal_scientific(
&abs_decimal,
precision - 1,
exp_symb,
strip_trailing_0s,
)?
} else {
let mut n = self.format_decimal_fixed(
&abs_decimal,
precision - 1 - log10_val.floor() as i32,
strip_trailing_0s,
)?;
if self.grouping_separator {
n = insert_thousands_separator(&n);
}
n
}
}
_ => {
return exec_err!(
"Invalid conversion type: {:?} for decimal",
self.conversion_type
);
}
};
let NumericParam::Literal(width) = self.width else {
writer.push_str(&prefix);
writer.push_str(&number);
return Ok(());
};
if self.left_adj {
let mut full_num = prefix + &number;
while full_num.len() < width as usize {
full_num.push(' ');
}
writer.push_str(&full_num);
} else if self.zero_pad {
while prefix.len() + number.len() < width as usize {
prefix.push('0');
}
writer.push_str(&prefix);
writer.push_str(&number);
} else {
let mut full_num = prefix + &number;
while full_num.len() < width as usize {
full_num = " ".to_owned() + &full_num;
}
writer.push_str(&full_num);
}
Ok(())
}
fn format_decimal_fixed(
&self,
decimal: &BigDecimal,
precision: i32,
strip_trailing_0s: bool,
) -> Result<String> {
if precision <= 0 {
Ok(decimal.round(0).to_string())
} else {
let scaled = decimal.round(precision as i64);
let mut number = scaled.to_string();
if strip_trailing_0s {
number = trim_trailing_0s(&number).to_owned();
}
Ok(number)
}
}
fn format_decimal_scientific(
&self,
decimal: &BigDecimal,
precision: i32,
exp_char: char,
strip_trailing_0s: bool,
) -> Result<String> {
let float_val = decimal.to_f64().unwrap_or(0.0);
if float_val == 0.0 {
return Ok(format!("0{exp_char}+00"));
}
let abs_val = float_val.abs();
let exponent = abs_val.log10().floor() as i32;
let mantissa = abs_val / 10.0_f64.powf(exponent as f64);
let mut number = if precision <= 0 {
format!("{mantissa:.0}")
} else {
format!("{mantissa:.prec$}", prec = precision as usize)
};
if strip_trailing_0s {
number = trim_trailing_0s(&number).to_owned();
}
Ok(format!("{number}{exp_char}{exponent:+03}"))
}
fn format_time(
&self,
writer: &mut String,
timestamp_nanos: i64,
timezone: &Option<Arc<str>>,
) -> Result<()> {
let upper = self.conversion_type.is_upper();
match &self.conversion_type {
ConversionType::TimeLower(time_format)
| ConversionType::TimeUpper(time_format) => {
let formatted =
self.format_time_component(timestamp_nanos, *time_format, timezone)?;
let result = if upper {
formatted.to_uppercase()
} else {
formatted
};
write!(writer, "{result}")
.map_err(|e| exec_datafusion_err!("Write error: {}", e))?;
Ok(())
}
_ => exec_err!(
"Invalid conversion type for time: {:?}",
self.conversion_type
),
}
}
fn format_date(&self, writer: &mut String, date_days: i64) -> Result<()> {
let timestamp_nanos = date_days * 24 * 60 * 60 * 1_000_000_000;
self.format_time(writer, timestamp_nanos, &None)
}
fn format_time_component(
&self,
timestamp_nanos: i64,
time_format: TimeFormat,
_timezone: &Option<Arc<str>>,
) -> Result<String> {
let secs = timestamp_nanos / 1_000_000_000;
let nanos = (timestamp_nanos % 1_000_000_000) as u32;
let dt = DateTime::<Utc>::from_timestamp(secs, nanos).ok_or_else(|| {
exec_datafusion_err!("Invalid timestamp: {}", timestamp_nanos)
})?;
match time_format {
TimeFormat::HUpper => Ok(format!("{:02}", dt.hour())),
TimeFormat::IUpper => {
let hour_12 = match dt.hour12() {
(true, h) => h, (false, h) => h, };
Ok(format!("{hour_12:02}"))
}
TimeFormat::KLower => Ok(format!("{}", dt.hour())),
TimeFormat::LLower => {
let hour_12 = match dt.hour12() {
(true, h) => h, (false, h) => h, };
Ok(format!("{hour_12}"))
}
TimeFormat::MUpper => Ok(format!("{:02}", dt.minute())),
TimeFormat::SUpper => Ok(format!("{:02}", dt.second())),
TimeFormat::LUpper => Ok(format!("{:03}", dt.timestamp_millis() % 1000)),
TimeFormat::NUpper => Ok(format!("{:09}", dt.nanosecond())),
TimeFormat::PLower => {
let (is_pm, _) = dt.hour12();
Ok(if is_pm {
"pm".to_string()
} else {
"am".to_string()
})
}
TimeFormat::ZLower => Ok("+0000".to_string()), TimeFormat::ZUpper => Ok("UTC".to_string()), TimeFormat::SLower => Ok(format!("{}", dt.timestamp())),
TimeFormat::QUpper => Ok(format!("{}", dt.timestamp_millis())),
TimeFormat::BUpper => Ok(dt.format("%B").to_string()), TimeFormat::BLower => Ok(dt.format("%b").to_string()), TimeFormat::AUpper => Ok(dt.format("%A").to_string()), TimeFormat::ALower => Ok(dt.format("%a").to_string()), TimeFormat::CUpper => Ok(format!("{:02}", dt.year() / 100)),
TimeFormat::YUpper => Ok(format!("{:04}", dt.year())),
TimeFormat::YLower => Ok(format!("{:02}", dt.year() % 100)),
TimeFormat::JLower => Ok(format!("{:03}", dt.ordinal())), TimeFormat::MLower => Ok(format!("{:02}", dt.month())),
TimeFormat::DLower => Ok(format!("{:02}", dt.day())),
TimeFormat::ELower => Ok(format!("{}", dt.day())),
TimeFormat::RUpper => Ok(dt.format("%H:%M").to_string()),
TimeFormat::TUpper => Ok(dt.format("%H:%M:%S").to_string()),
TimeFormat::RLower => {
let (is_pm, hour_12) = dt.hour12();
let am_pm = if is_pm { "PM" } else { "AM" };
Ok(format!(
"{:02}:{:02}:{:02} {}",
hour_12,
dt.minute(),
dt.second(),
am_pm
))
}
TimeFormat::DUpper => Ok(dt.format("%m/%d/%y").to_string()),
TimeFormat::FUpper => Ok(dt.format("%Y-%m-%d").to_string()),
TimeFormat::CLower => Ok(dt.format("%a %b %d %H:%M:%S UTC %Y").to_string()),
}
}
}
trait FloatFormattable: std::fmt::Display {
fn category(&self) -> FpCategory;
fn spark_string(&self) -> String {
match self.category() {
FpCategory::Nan => "NaN".to_string(),
FpCategory::Infinite => {
if self.negative() {
"-Infinity".to_string()
} else {
"Infinity".to_string()
}
}
_ => self.to_string(),
}
}
fn negative(&self) -> bool;
}
impl FloatFormattable for f32 {
fn category(&self) -> FpCategory {
self.classify()
}
fn negative(&self) -> bool {
self.is_sign_negative()
}
}
impl FloatFormattable for f64 {
fn category(&self) -> FpCategory {
self.classify()
}
fn negative(&self) -> bool {
self.is_sign_negative()
}
}
trait FloatBits: FloatFormattable {
const MANTISSA_BITS: u8;
const EXPONENT_BIAS: u16;
const SCALEUP_POWER: u8;
const SCALEUP: Self;
fn to_parts(&self) -> (bool, u16, u64);
}
impl FloatBits for f64 {
const MANTISSA_BITS: u8 = 52;
const EXPONENT_BIAS: u16 = 1023;
const SCALEUP_POWER: u8 = 54;
const SCALEUP: f64 = (1_i64 << Self::SCALEUP_POWER) as f64;
fn to_parts(&self) -> (bool, u16, u64) {
let bits = self.to_bits();
let sign: bool = (bits >> 63) == 1;
let exponent = ((bits >> 52) & 0x7FF) as u16;
let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
(sign, exponent, mantissa)
}
}
fn insert_thousands_separator(number: &str) -> String {
let (int_part, frac_part) = match number.find('.') {
Some(pos) => (&number[..pos], &number[pos..]),
None => (number, ""),
};
let mut result = String::with_capacity(number.len() + number.len() / 3);
for (i, c) in int_part.char_indices() {
if i > 0 && (int_part.len() - i) % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.push_str(frac_part);
result
}
fn trim_trailing_0s(number: &str) -> &str {
if number.contains('.') {
for (i, c) in number.chars().rev().enumerate() {
if c != '0' {
return &number[..number.len() - i];
}
}
}
number
}
fn trim_trailing_0s_hex(number: &str) -> &str {
for (i, c) in number.chars().rev().enumerate() {
if c != '0' {
return &number[..number.len() - i];
}
}
number
}
#[cfg(test)]
mod tests {
use super::*;
use crate::function::utils::test::test_scalar_function;
use arrow::array::StringArray;
use arrow::datatypes::DataType::Utf8;
#[test]
fn test_format_string_nullability() -> Result<()> {
let func = FormatStringFunc::new();
let nullable_format: FieldRef = Arc::new(Field::new("fmt", Utf8, true));
let out_nullable = func.return_field_from_args(ReturnFieldArgs {
arg_fields: &[nullable_format],
scalar_arguments: &[None],
})?;
assert!(
out_nullable.is_nullable(),
"format_string(fmt, ...) should be nullable when fmt is nullable"
);
let non_nullable_format: FieldRef = Arc::new(Field::new("fmt", Utf8, false));
let out_non_nullable = func.return_field_from_args(ReturnFieldArgs {
arg_fields: &[non_nullable_format],
scalar_arguments: &[None],
})?;
assert!(
!out_non_nullable.is_nullable(),
"format_string(fmt, ...) should NOT be nullable when fmt is NOT nullable"
);
Ok(())
}
#[test]
fn test_format_char_invalid_codepoint_errors() {
use arrow::datatypes::Field;
use datafusion_common::config::ConfigOptions;
let func = FormatStringFunc::new();
let cases: Vec<(&str, ScalarValue)> = vec![
("Int8(-1)", ScalarValue::Int8(Some(-1))),
("Int16(-1)", ScalarValue::Int16(Some(-1))),
("Int16(-10000)", ScalarValue::Int16(Some(-10000))),
("Int32(-1)", ScalarValue::Int32(Some(-1))),
("Int32(0x110000)", ScalarValue::Int32(Some(0x110000))),
("Int64(0x1FFFFFFFF)", ScalarValue::Int64(Some(0x1FFFFFFFF))),
("Int64(-1)", ScalarValue::Int64(Some(-1))),
("UInt16(0xD800)", ScalarValue::UInt16(Some(0xD800))),
("UInt32(0x110000)", ScalarValue::UInt32(Some(0x110000))),
(
"UInt64(0x1_0000_0000)",
ScalarValue::UInt64(Some(0x1_0000_0000)),
),
];
for (label, value) in cases {
let fmt = ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string())));
let arg_data_type = value.data_type();
let arg = ColumnarValue::Scalar(value);
let arg_fields = vec![
Arc::new(Field::new("fmt", Utf8, false)),
Arc::new(Field::new("v", arg_data_type, false)),
];
let res = func.invoke_with_args(ScalarFunctionArgs {
args: vec![fmt, arg],
number_rows: 1,
arg_fields,
return_field: Arc::new(Field::new("o", Utf8, false)),
config_options: Arc::new(ConfigOptions::default()),
});
assert!(
res.is_err(),
"format_string('[%c]', {label}) should error, got Ok"
);
let err = res.unwrap_err().to_string();
assert!(
err.contains("invalid Unicode scalar value for %c"),
"unexpected error for {label}: {err}"
);
}
}
#[test]
fn test_format_char_valid_codepoint_succeeds() {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
ColumnarValue::Scalar(ScalarValue::Int32(Some(0x1F680))),
],
Ok(Some("[\u{1F680}]")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
ColumnarValue::Scalar(ScalarValue::UInt32(Some(0x10FFFF))),
],
Ok(Some("[\u{10FFFF}]")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
ColumnarValue::Scalar(ScalarValue::Int16(Some(65))),
],
Ok(Some("[A]")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
ColumnarValue::Scalar(ScalarValue::Int8(Some(97))),
],
Ok(Some("[a]")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("[%c]".to_string()))),
ColumnarValue::Scalar(ScalarValue::UInt8(Some(255))),
],
Ok(Some("[\u{00FF}]")),
&str,
Utf8,
StringArray
);
}
#[test]
fn test_insert_thousands_separator() {
assert_eq!(insert_thousands_separator("1234567.89"), "1,234,567.89");
assert_eq!(insert_thousands_separator("123.45"), "123.45");
assert_eq!(insert_thousands_separator("1234"), "1,234");
assert_eq!(insert_thousands_separator("12"), "12");
assert_eq!(insert_thousands_separator("0.5"), "0.5");
assert_eq!(
insert_thousands_separator("1234567890.1234"),
"1,234,567,890.1234"
);
assert_eq!(insert_thousands_separator("1000"), "1,000");
assert_eq!(insert_thousands_separator("100"), "100");
}
#[test]
fn test_grouping_separator_float() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("1,234,567.89")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_decimal() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Ok(Some("1,234,567.89")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_scientific_float() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,e".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Err(DataFusionError::Execution(
"Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
)),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,E".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Err(DataFusionError::Execution(
"Grouping separator ',' flag is not compatible with scientific notation conversion 'E'".to_string(),
)),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0e".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Err(DataFusionError::Execution(
"Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
)),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_compact_float() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("1.23457e+06")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(12345.6))),
],
Ok(Some("12,345.6")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("1e+06")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,G".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("1.23457E+06")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_scientific_decimal() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,e".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Err(DataFusionError::Execution(
"Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
)),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0e".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Err(DataFusionError::Execution(
"Grouping separator ',' flag is not compatible with scientific notation conversion 'e'".to_string(),
)),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_compact_decimal() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Ok(Some("1.23457e+06")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(1234560), 10, 2)),
],
Ok(Some("12,345.6")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%,.0g".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Ok(Some("1e+06")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_width_sign_float() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%0,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("0001,234,567.89")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some(" +1,234,567.89")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%-,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(1234567.89))),
],
Ok(Some("1,234,567.89 ")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_width_sign_decimal() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%0,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Ok(Some("0001,234,567.89")),
&str,
Utf8,
StringArray
);
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(123456789), 10, 2)),
],
Ok(Some(" +1,234,567.89")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_parentheses_float() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Float64(Some(-1234.5))),
],
Ok(Some(" (1,234.50)")),
&str,
Utf8,
StringArray
);
Ok(())
}
#[test]
fn test_grouping_separator_parentheses_decimal() -> Result<()> {
test_scalar_function!(
FormatStringFunc::new(),
vec![
ColumnarValue::Scalar(ScalarValue::Utf8(Some("%(,15.2f".to_string()))),
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(-123450), 10, 2)),
],
Ok(Some(" -1,234.50")),
&str,
Utf8,
StringArray
);
Ok(())
}
}