Skip to main content

mz_expr/scalar/func/impls/
record.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 itertools::Itertools;
13use mz_lowertest::MzReflect;
14use mz_repr::{Datum, RowArena, SqlColumnType, SqlScalarType};
15use serde::{Deserialize, Serialize};
16
17use crate::scalar::func::{LazyUnaryFunc, stringify_datum};
18use crate::{EvalError, MirScalarExpr};
19
20#[derive(
21    Ord,
22    PartialOrd,
23    Clone,
24    Debug,
25    Eq,
26    PartialEq,
27    Serialize,
28    Deserialize,
29    Hash,
30    MzReflect
31)]
32pub struct CastRecordToString {
33    pub ty: SqlScalarType,
34}
35
36impl LazyUnaryFunc for CastRecordToString {
37    fn eval<'a>(
38        &'a self,
39        datums: &[Datum<'a>],
40        temp_storage: &'a RowArena,
41        a: &'a MirScalarExpr,
42    ) -> Result<Datum<'a>, EvalError> {
43        let a = a.eval(datums, temp_storage)?;
44        if a.is_null() {
45            return Ok(Datum::Null);
46        }
47        let mut buf = String::new();
48        stringify_datum(&mut buf, a, &self.ty)?;
49        Ok(Datum::String(temp_storage.push_string(buf)))
50    }
51
52    fn output_type(&self, input_type: SqlColumnType) -> SqlColumnType {
53        SqlScalarType::String.nullable(input_type.nullable)
54    }
55
56    fn propagates_nulls(&self) -> bool {
57        true
58    }
59
60    fn introduces_nulls(&self) -> bool {
61        false
62    }
63
64    fn preserves_uniqueness(&self) -> bool {
65        true
66    }
67
68    fn inverse(&self) -> Option<crate::UnaryFunc> {
69        // TODO? if we moved typeconv into expr, we could evaluate this
70        None
71    }
72
73    fn is_monotone(&self) -> bool {
74        false
75    }
76}
77
78impl fmt::Display for CastRecordToString {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        f.write_str("recordtostr")
81    }
82}
83
84/// Casts between two record types by casting each element of `a` ("record1") using
85/// `cast_expr` and collecting the results into a new record ("record2").
86#[derive(
87    Ord,
88    PartialOrd,
89    Clone,
90    Debug,
91    Eq,
92    PartialEq,
93    Serialize,
94    Deserialize,
95    Hash,
96    MzReflect
97)]
98pub struct CastRecord1ToRecord2 {
99    pub return_ty: SqlScalarType,
100    pub cast_exprs: Box<[MirScalarExpr]>,
101}
102
103impl LazyUnaryFunc for CastRecord1ToRecord2 {
104    fn eval<'a>(
105        &'a self,
106        datums: &[Datum<'a>],
107        temp_storage: &'a RowArena,
108        a: &'a MirScalarExpr,
109    ) -> Result<Datum<'a>, EvalError> {
110        let a = a.eval(datums, temp_storage)?;
111        if a.is_null() {
112            return Ok(Datum::Null);
113        }
114        let mut cast_datums = Vec::new();
115        for (el, cast_expr) in a.unwrap_list().iter().zip_eq(&self.cast_exprs) {
116            cast_datums.push(cast_expr.eval(&[el], temp_storage)?);
117        }
118        Ok(temp_storage.make_datum(|packer| packer.push_list(cast_datums)))
119    }
120
121    fn output_type(&self, input_type: SqlColumnType) -> SqlColumnType {
122        self.return_ty
123            .without_modifiers()
124            .nullable(input_type.nullable)
125    }
126
127    fn propagates_nulls(&self) -> bool {
128        true
129    }
130
131    fn introduces_nulls(&self) -> bool {
132        false
133    }
134
135    fn preserves_uniqueness(&self) -> bool {
136        false
137    }
138
139    fn inverse(&self) -> Option<crate::UnaryFunc> {
140        // TODO: we could determine Record1's type from `cast_exprs`
141        None
142    }
143
144    fn is_monotone(&self) -> bool {
145        // In theory this could be marked as monotone if we knew that all the expressions were
146        // monotone in the same direction. (ie. all increasing or all decreasing.) We don't yet
147        // track enough information to make that call, though!
148        false
149    }
150}
151
152impl fmt::Display for CastRecord1ToRecord2 {
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        f.write_str("record1torecord2")
155    }
156}
157
158#[derive(
159    Ord,
160    PartialOrd,
161    Clone,
162    Debug,
163    Eq,
164    PartialEq,
165    Serialize,
166    Deserialize,
167    Hash,
168    MzReflect
169)]
170pub struct RecordGet(pub usize);
171
172impl LazyUnaryFunc for RecordGet {
173    fn eval<'a>(
174        &'a self,
175        datums: &[Datum<'a>],
176        temp_storage: &'a RowArena,
177        a: &'a MirScalarExpr,
178    ) -> Result<Datum<'a>, EvalError> {
179        let a = a.eval(datums, temp_storage)?;
180        if a.is_null() {
181            return Ok(Datum::Null);
182        }
183        Ok(a.unwrap_list().iter().nth(self.0).unwrap())
184    }
185
186    fn output_type(&self, input_type: SqlColumnType) -> SqlColumnType {
187        match input_type.scalar_type {
188            SqlScalarType::Record { fields, .. } => {
189                let (_name, ty) = &fields[self.0];
190                let mut ty = ty.clone();
191                ty.nullable = ty.nullable || input_type.nullable;
192                ty
193            }
194            _ => unreachable!(
195                "RecordGet on non-record input: {:?}",
196                input_type.scalar_type
197            ),
198        }
199    }
200
201    fn propagates_nulls(&self) -> bool {
202        true
203    }
204
205    fn introduces_nulls(&self) -> bool {
206        // Return null if the inner field is null
207        true
208    }
209
210    fn preserves_uniqueness(&self) -> bool {
211        false
212    }
213
214    fn inverse(&self) -> Option<crate::UnaryFunc> {
215        None
216    }
217
218    fn is_monotone(&self) -> bool {
219        false
220    }
221}
222
223impl fmt::Display for RecordGet {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        write!(f, "record_get[{}]", self.0)
226    }
227}