Skip to main content

mz_expr/scalar/func/impls/
array.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::fmt;
11
12use mz_expr_derive::sqlfunc;
13use mz_lowertest::MzReflect;
14use mz_repr::adt::array::{Array, ArrayDimension};
15use mz_repr::{Datum, DatumList, Row, RowArena, RowPacker, SqlColumnType, SqlScalarType};
16use serde::{Deserialize, Serialize};
17
18use crate::scalar::func::{LazyUnaryFunc, stringify_datum};
19use crate::{EvalError, MirScalarExpr};
20
21#[sqlfunc(
22    sqlname = "arraytolist",
23    preserves_uniqueness = true,
24    introduces_nulls = false
25)]
26fn cast_array_to_list_one_dim<'a, T>(a: Array<'a, T>) -> Result<DatumList<'a, T>, EvalError> {
27    let ndims = a.dims().ndims();
28    if ndims > 1 {
29        return Err(EvalError::Unsupported {
30            feature: format!(
31                "casting multi-dimensional array to list; got array with {} dimensions",
32                ndims
33            )
34            .into(),
35            discussion_no: None,
36        });
37    }
38    Ok(a.elements())
39}
40
41#[derive(
42    Ord,
43    PartialOrd,
44    Clone,
45    Debug,
46    Eq,
47    PartialEq,
48    Serialize,
49    Deserialize,
50    Hash,
51    MzReflect
52)]
53pub struct CastArrayToString {
54    pub ty: SqlScalarType,
55}
56
57impl LazyUnaryFunc for CastArrayToString {
58    fn eval<'a>(
59        &'a self,
60        datums: &[Datum<'a>],
61        temp_storage: &'a RowArena,
62        a: &'a MirScalarExpr,
63    ) -> Result<Datum<'a>, EvalError> {
64        let a = a.eval(datums, temp_storage)?;
65        if a.is_null() {
66            return Ok(Datum::Null);
67        }
68        let mut buf = String::new();
69        stringify_datum(&mut buf, a, &self.ty)?;
70        Ok(Datum::String(temp_storage.push_string(buf)))
71    }
72
73    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
74        SqlScalarType::String.nullable(input_type.nullable)
75    }
76
77    fn propagates_nulls(&self) -> bool {
78        true
79    }
80
81    fn introduces_nulls(&self) -> bool {
82        false
83    }
84
85    fn preserves_uniqueness(&self) -> bool {
86        true
87    }
88
89    fn inverse(&self) -> Option<crate::UnaryFunc> {
90        // TODO? If we moved typeconv into `expr` we could determine the right
91        // inverse of this.
92        None
93    }
94
95    fn is_monotone(&self) -> bool {
96        false
97    }
98
99    fn is_eliminable_cast(&self) -> bool {
100        false
101    }
102}
103
104impl fmt::Display for CastArrayToString {
105    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106        f.write_str("arraytostr")
107    }
108}
109
110#[derive(
111    Ord,
112    PartialOrd,
113    Clone,
114    Debug,
115    Eq,
116    PartialEq,
117    Serialize,
118    Deserialize,
119    Hash,
120    MzReflect
121)]
122pub struct CastArrayToJsonb {
123    pub cast_element: Box<MirScalarExpr>,
124}
125
126impl LazyUnaryFunc for CastArrayToJsonb {
127    fn eval<'a>(
128        &'a self,
129        datums: &[Datum<'a>],
130        temp_storage: &'a RowArena,
131        a: &'a MirScalarExpr,
132    ) -> Result<Datum<'a>, EvalError> {
133        fn pack<'a>(
134            temp_storage: &RowArena,
135            elems: &mut impl Iterator<Item = Datum<'a>>,
136            dims: &[ArrayDimension],
137            cast_element: &MirScalarExpr,
138            packer: &mut RowPacker,
139        ) -> Result<(), EvalError> {
140            packer.push_list_with(|packer| match dims {
141                [] => Ok(()),
142                [dim] => {
143                    for _ in 0..dim.length {
144                        let elem = elems.next().unwrap();
145                        let elem = match cast_element.eval(&[elem], temp_storage)? {
146                            Datum::Null => Datum::JsonNull,
147                            d => d,
148                        };
149                        packer.push(elem);
150                    }
151                    Ok(())
152                }
153                [dim, rest @ ..] => {
154                    for _ in 0..dim.length {
155                        pack(temp_storage, elems, rest, cast_element, packer)?;
156                    }
157                    Ok(())
158                }
159            })
160        }
161
162        let a = a.eval(datums, temp_storage)?;
163        if a.is_null() {
164            return Ok(Datum::Null);
165        }
166        let a = a.unwrap_array();
167        let elements = a.elements();
168        let dims = a.dims().into_iter().collect::<Vec<_>>();
169        let mut row = Row::default();
170        pack(
171            temp_storage,
172            &mut elements.into_iter(),
173            &dims,
174            &self.cast_element,
175            &mut row.packer(),
176        )?;
177        Ok(temp_storage.push_unary_row(row))
178    }
179
180    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
181        SqlScalarType::Jsonb.nullable(input_type.nullable)
182    }
183
184    fn propagates_nulls(&self) -> bool {
185        true
186    }
187
188    fn introduces_nulls(&self) -> bool {
189        false
190    }
191
192    fn preserves_uniqueness(&self) -> bool {
193        true
194    }
195
196    fn inverse(&self) -> Option<crate::UnaryFunc> {
197        // TODO? If we moved typeconv into `expr` we could determine the right
198        // inverse of this.
199        None
200    }
201
202    fn is_monotone(&self) -> bool {
203        false
204    }
205
206    fn is_eliminable_cast(&self) -> bool {
207        false
208    }
209}
210
211impl fmt::Display for CastArrayToJsonb {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        f.write_str("arraytojsonb")
214    }
215}
216
217/// Casts an array of one type to an array of another type. Does so by casting
218/// each element of the first array to the desired inner type and collecting
219/// the results into a new array.
220#[derive(
221    Ord,
222    PartialOrd,
223    Clone,
224    Debug,
225    Eq,
226    PartialEq,
227    Serialize,
228    Deserialize,
229    Hash,
230    MzReflect
231)]
232pub struct CastArrayToArray {
233    pub return_ty: SqlScalarType,
234    pub cast_expr: Box<MirScalarExpr>,
235}
236
237impl LazyUnaryFunc for CastArrayToArray {
238    fn eval<'a>(
239        &'a self,
240        datums: &[Datum<'a>],
241        temp_storage: &'a RowArena,
242        a: &'a MirScalarExpr,
243    ) -> Result<Datum<'a>, EvalError> {
244        let a = a.eval(datums, temp_storage)?;
245        if a.is_null() {
246            return Ok(Datum::Null);
247        }
248
249        let arr = a.unwrap_array();
250        let dims = arr.dims().into_iter().collect::<Vec<ArrayDimension>>();
251
252        let casted_datums = arr
253            .elements()
254            .iter()
255            .map(|datum| self.cast_expr.eval(&[datum], temp_storage))
256            .collect::<Result<Vec<Datum<'a>>, EvalError>>()?;
257
258        Ok(temp_storage.try_make_datum(|packer| packer.try_push_array(&dims, casted_datums))?)
259    }
260
261    fn output_sql_type(&self, _input_type: SqlColumnType) -> SqlColumnType {
262        self.return_ty.clone().nullable(true)
263    }
264
265    fn propagates_nulls(&self) -> bool {
266        true
267    }
268
269    fn introduces_nulls(&self) -> bool {
270        false
271    }
272
273    fn preserves_uniqueness(&self) -> bool {
274        false
275    }
276
277    fn inverse(&self) -> Option<crate::UnaryFunc> {
278        None
279    }
280
281    fn is_monotone(&self) -> bool {
282        false
283    }
284
285    fn is_eliminable_cast(&self) -> bool {
286        false
287    }
288}
289
290impl fmt::Display for CastArrayToArray {
291    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
292        f.write_str("arraytoarray")
293    }
294}