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