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_sql_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    fn is_eliminable_cast(&self) -> bool {
78        false
79    }
80}
81
82impl fmt::Display for CastRecordToString {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        f.write_str("recordtostr")
85    }
86}
87
88/// Casts between two record types by casting each element of `a` ("record1") using
89/// `cast_expr` and collecting the results into a new record ("record2").
90#[derive(
91    Ord,
92    PartialOrd,
93    Clone,
94    Debug,
95    Eq,
96    PartialEq,
97    Serialize,
98    Deserialize,
99    Hash,
100    MzReflect
101)]
102pub struct CastRecord1ToRecord2 {
103    pub return_ty: SqlScalarType,
104    pub cast_exprs: Box<[MirScalarExpr]>,
105}
106
107impl LazyUnaryFunc for CastRecord1ToRecord2 {
108    fn eval<'a>(
109        &'a self,
110        datums: &[Datum<'a>],
111        temp_storage: &'a RowArena,
112        a: &'a MirScalarExpr,
113    ) -> Result<Datum<'a>, EvalError> {
114        let a = a.eval(datums, temp_storage)?;
115        if a.is_null() {
116            return Ok(Datum::Null);
117        }
118        let mut cast_datums = Vec::new();
119        for (el, cast_expr) in a.unwrap_list().iter().zip_eq(&self.cast_exprs) {
120            cast_datums.push(cast_expr.eval(&[el], temp_storage)?);
121        }
122        Ok(temp_storage.make_datum(|packer| packer.push_list(cast_datums)))
123    }
124
125    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
126        self.return_ty
127            .without_modifiers()
128            .nullable(input_type.nullable)
129    }
130
131    fn propagates_nulls(&self) -> bool {
132        true
133    }
134
135    fn introduces_nulls(&self) -> bool {
136        false
137    }
138
139    fn preserves_uniqueness(&self) -> bool {
140        false
141    }
142
143    fn inverse(&self) -> Option<crate::UnaryFunc> {
144        // TODO: we could determine Record1's type from `cast_exprs`
145        None
146    }
147
148    fn is_monotone(&self) -> bool {
149        // In theory this could be marked as monotone if we knew that all the expressions were
150        // monotone in the same direction. (ie. all increasing or all decreasing.) We don't yet
151        // track enough information to make that call, though!
152        false
153    }
154
155    fn is_eliminable_cast(&self) -> bool {
156        false
157    }
158}
159
160impl fmt::Display for CastRecord1ToRecord2 {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        f.write_str("record1torecord2")
163    }
164}
165
166#[derive(
167    Ord,
168    PartialOrd,
169    Clone,
170    Debug,
171    Eq,
172    PartialEq,
173    Serialize,
174    Deserialize,
175    Hash,
176    MzReflect
177)]
178pub struct RecordGet(pub usize);
179
180impl LazyUnaryFunc for RecordGet {
181    fn eval<'a>(
182        &'a self,
183        datums: &[Datum<'a>],
184        temp_storage: &'a RowArena,
185        a: &'a MirScalarExpr,
186    ) -> Result<Datum<'a>, EvalError> {
187        let a = a.eval(datums, temp_storage)?;
188        if a.is_null() {
189            return Ok(Datum::Null);
190        }
191        Ok(a.unwrap_list().iter().nth(self.0).unwrap())
192    }
193
194    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
195        match input_type.scalar_type {
196            SqlScalarType::Record { fields, .. } => {
197                let (_name, ty) = &fields[self.0];
198                let mut ty = ty.clone();
199                ty.nullable = ty.nullable || input_type.nullable;
200                ty
201            }
202            _ => unreachable!(
203                "RecordGet on non-record input: {:?}",
204                input_type.scalar_type
205            ),
206        }
207    }
208
209    fn propagates_nulls(&self) -> bool {
210        true
211    }
212
213    fn introduces_nulls(&self) -> bool {
214        // Return null if the inner field is null
215        true
216    }
217
218    fn preserves_uniqueness(&self) -> bool {
219        false
220    }
221
222    fn inverse(&self) -> Option<crate::UnaryFunc> {
223        None
224    }
225
226    fn is_monotone(&self) -> bool {
227        false
228    }
229
230    fn is_eliminable_cast(&self) -> bool {
231        false
232    }
233}
234
235impl fmt::Display for RecordGet {
236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237        write!(f, "record_get[{}]", self.0)
238    }
239}