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