datafusion_spark/function/string/
concat_ws.rs1use std::fmt::Write as _;
30use std::sync::Arc;
31
32use arrow::array::{
33 Array, ArrayRef, AsArray, GenericListArray, LargeStringArray, OffsetSizeTrait,
34 StringArray, StringBuilder, StringViewArray,
35};
36use arrow::datatypes::{DataType, Field};
37use datafusion_common::{Result, ScalarValue};
38use datafusion_expr::{
39 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
40};
41
42use crate::function::error_utils::{
43 invalid_arg_count_exec_err, unsupported_data_type_exec_err,
44};
45
46#[derive(Debug, PartialEq, Eq, Hash)]
47pub struct SparkConcatWs {
48 signature: Signature,
49}
50
51impl Default for SparkConcatWs {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl SparkConcatWs {
58 pub fn new() -> Self {
59 Self {
60 signature: Signature::user_defined(Volatility::Immutable),
61 }
62 }
63}
64
65impl ScalarUDFImpl for SparkConcatWs {
66 fn name(&self) -> &str {
67 "concat_ws"
68 }
69
70 fn signature(&self) -> &Signature {
71 &self.signature
72 }
73
74 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
75 Ok(DataType::Utf8)
76 }
77
78 fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
79 if arg_types.is_empty() {
80 return Err(invalid_arg_count_exec_err("concat_ws", (1, i32::MAX), 0));
81 }
82 Ok(arg_types
83 .iter()
84 .enumerate()
85 .map(|(i, dt)| match dt {
86 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => dt.clone(),
87 DataType::List(f)
93 | DataType::ListView(f)
94 | DataType::FixedSizeList(f, _)
95 if i > 0 =>
96 {
97 DataType::List(Arc::new(Field::new(
98 f.name(),
99 DataType::Utf8,
100 f.is_nullable(),
101 )))
102 }
103 DataType::LargeList(f) | DataType::LargeListView(f) if i > 0 => {
104 DataType::LargeList(Arc::new(Field::new(
105 f.name(),
106 DataType::Utf8,
107 f.is_nullable(),
108 )))
109 }
110 _ => DataType::Utf8,
113 })
114 .collect())
115 }
116
117 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
118 if args.args.len() == 1 {
121 return only_separator(&args.args[0]);
122 }
123
124 spark_concat_ws(&args.args, args.number_rows)
125 }
126}
127
128fn only_separator(sep: &ColumnarValue) -> Result<ColumnarValue> {
129 match sep {
130 ColumnarValue::Scalar(s) if s.is_null() => {
131 Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
132 }
133 ColumnarValue::Scalar(_) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
134 String::new(),
135 )))),
136 ColumnarValue::Array(arr) => {
137 let mut builder = StringBuilder::with_capacity(arr.len(), 0);
138 for row_idx in 0..arr.len() {
139 if arr.is_null(row_idx) {
140 builder.append_null();
141 } else {
142 builder.append_value("");
143 }
144 }
145 Ok(ColumnarValue::Array(Arc::new(builder.finish()) as ArrayRef))
146 }
147 }
148}
149
150fn spark_concat_ws(args: &[ColumnarValue], num_rows: usize) -> Result<ColumnarValue> {
151 let arrays = ColumnarValue::values_to_arrays(args)?;
152 let sep_view = StringView::try_new(&arrays[0])?;
153 let arg_views: Vec<ArgView> = arrays[1..]
154 .iter()
155 .map(ArgView::try_new)
156 .collect::<Result<_>>()?;
157
158 let mut builder = StringBuilder::with_capacity(num_rows, num_rows * 16);
159
160 for row_idx in 0..num_rows {
161 if sep_view.is_null(row_idx) {
162 builder.append_null();
163 continue;
164 }
165
166 let separator = sep_view.value(row_idx);
170 let mut first = true;
171 for view in &arg_views {
172 view.write_row(row_idx, separator, &mut builder, &mut first)?;
173 }
174 builder.append_value("");
175 }
176
177 Ok(ColumnarValue::Array(Arc::new(builder.finish()) as ArrayRef))
178}
179
180enum StringView<'a> {
183 Utf8(&'a StringArray),
184 LargeUtf8(&'a LargeStringArray),
185 Utf8View(&'a StringViewArray),
186}
187
188impl<'a> StringView<'a> {
189 fn try_new(arr: &'a ArrayRef) -> Result<Self> {
190 match arr.data_type() {
191 DataType::Utf8 => Ok(Self::Utf8(arr.as_string::<i32>())),
192 DataType::LargeUtf8 => Ok(Self::LargeUtf8(arr.as_string::<i64>())),
193 DataType::Utf8View => Ok(Self::Utf8View(arr.as_string_view())),
194 other => Err(unsupported_data_type_exec_err("concat_ws", "STRING", other)),
195 }
196 }
197
198 fn value(&self, idx: usize) -> &str {
199 match self {
200 Self::Utf8(a) => a.value(idx),
201 Self::LargeUtf8(a) => a.value(idx),
202 Self::Utf8View(a) => a.value(idx),
203 }
204 }
205
206 fn is_null(&self, idx: usize) -> bool {
207 match self {
208 Self::Utf8(a) => a.is_null(idx),
209 Self::LargeUtf8(a) => a.is_null(idx),
210 Self::Utf8View(a) => a.is_null(idx),
211 }
212 }
213}
214
215enum ArgView<'a> {
219 Str(StringView<'a>),
220 List(&'a GenericListArray<i32>),
221 LargeList(&'a GenericListArray<i64>),
222}
223
224impl<'a> ArgView<'a> {
225 fn try_new(arr: &'a ArrayRef) -> Result<Self> {
226 match arr.data_type() {
227 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => {
228 Ok(Self::Str(StringView::try_new(arr)?))
229 }
230 DataType::List(_) => Ok(Self::List(arr.as_list::<i32>())),
231 DataType::LargeList(_) => Ok(Self::LargeList(arr.as_list::<i64>())),
232 other => Err(unsupported_data_type_exec_err(
233 "concat_ws",
234 "STRING or ARRAY<STRING>",
235 other,
236 )),
237 }
238 }
239
240 fn write_row(
241 &self,
242 row_idx: usize,
243 sep: &str,
244 builder: &mut StringBuilder,
245 first: &mut bool,
246 ) -> Result<()> {
247 match self {
248 Self::Str(view) => {
249 if !view.is_null(row_idx) {
250 push_part(builder, view.value(row_idx), sep, first);
251 }
252 }
253 Self::List(list) => write_list_row(*list, row_idx, sep, builder, first)?,
254 Self::LargeList(list) => write_list_row(*list, row_idx, sep, builder, first)?,
255 }
256 Ok(())
257 }
258}
259
260fn write_list_row<O: OffsetSizeTrait>(
261 list: &GenericListArray<O>,
262 row_idx: usize,
263 sep: &str,
264 builder: &mut StringBuilder,
265 first: &mut bool,
266) -> Result<()> {
267 if list.is_null(row_idx) {
268 return Ok(());
269 }
270 let values = list.value(row_idx);
271 if values.is_empty() {
274 return Ok(());
275 }
276 let view = StringView::try_new(&values)?;
277 for i in 0..values.len() {
278 if !view.is_null(i) {
279 push_part(builder, view.value(i), sep, first);
280 }
281 }
282 Ok(())
283}
284
285fn push_part(builder: &mut StringBuilder, part: &str, sep: &str, first: &mut bool) {
288 if !*first {
289 builder
290 .write_str(sep)
291 .expect("StringBuilder::write_str is infallible");
292 }
293 *first = false;
294 builder
295 .write_str(part)
296 .expect("StringBuilder::write_str is infallible");
297}