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