risingwave_expr_impl/scalar/
concat_ws.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use risingwave_common::row::Row;
16use risingwave_common::types::ToText;
17use risingwave_expr::function;
18
19/// Concatenates all but the first argument, with separators. The first argument is used as the
20/// separator string, and should not be NULL. Other NULL arguments are ignored.
21///
22/// # Example
23///
24/// ```slt
25/// query T
26/// select concat_ws(',', 'abcde', 2, NULL, 22);
27/// ----
28/// abcde,2,22
29///
30/// query T
31/// select concat_ws(',', variadic array['abcde', 2, NULL, 22] :: varchar[]);
32/// ----
33/// abcde,2,22
34/// ```
35#[function("concat_ws(varchar, variadic anyarray) -> varchar")]
36fn concat_ws(sep: &str, vals: impl Row, writer: &mut impl std::fmt::Write) {
37    let mut string_iter = vals.iter().flatten();
38    if let Some(string) = string_iter.next() {
39        string.write(writer).unwrap();
40    }
41    for string in string_iter {
42        write!(writer, "{}", sep).unwrap();
43        string.write(writer).unwrap();
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use risingwave_common::array::DataChunk;
50    use risingwave_common::row::Row;
51    use risingwave_common::test_prelude::DataChunkTestExt;
52    use risingwave_common::types::ToOwnedDatum;
53    use risingwave_common::util::iter_util::ZipEqDebug;
54    use risingwave_expr::expr::build_from_pretty;
55
56    #[tokio::test]
57    async fn test_concat_ws() {
58        let concat_ws =
59            build_from_pretty("(concat_ws:varchar $0:varchar $1:varchar $2:varchar $3:varchar)");
60        let (input, expected) = DataChunk::from_pretty(
61            "T T T T  T
62             , a b c  a,b,c
63             , . b c  b,c
64             . a b c  .
65             , . . .  (empty)
66             . . . .  .",
67        )
68        .split_column_at(4);
69
70        // test eval
71        let output = concat_ws.eval(&input).await.unwrap();
72        assert_eq!(&output, expected.column_at(0));
73
74        // test eval_row
75        for (row, expected) in input.rows().zip_eq_debug(expected.rows()) {
76            let result = concat_ws.eval_row(&row.to_owned_row()).await.unwrap();
77            assert_eq!(result, expected.datum_at(0).to_owned_datum());
78        }
79    }
80}