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_expr_derive::sqlfunc;
14use mz_repr::{Datum, DatumList, ExcludeNull, RowArena, SqlColumnType, SqlScalarType};
15use serde::{Deserialize, Serialize};
16
17use crate::scalar::func::{LazyUnaryFunc, stringify_datum};
18use crate::{Eval, EvalError, MirScalarExpr};
19
20#[derive(
21    Ord,
22    PartialOrd,
23    Clone,
24    Debug,
25    Eq,
26    PartialEq,
27    Serialize,
28    Deserialize,
29    Hash
30)]
31pub struct CastRecordToString {
32    pub ty: SqlScalarType,
33}
34
35// TODO? if we moved typeconv into expr, we could evaluate the inverse of this cast
36#[sqlfunc(
37    CastRecordToString,
38    sqlname = "recordtostr",
39    preserves_uniqueness = true
40)]
41fn cast_record_to_string<'a>(&self, a: ExcludeNull<Datum<'a>>) -> Result<String, EvalError> {
42    let mut buf = String::new();
43    stringify_datum(&mut buf, *a, &self.ty)?;
44    Ok(buf)
45}
46
47/// Casts between two record types by casting each element of `a` ("record1") using
48/// `cast_expr` and collecting the results into a new record ("record2").
49#[derive(
50    Ord,
51    PartialOrd,
52    Clone,
53    Debug,
54    Eq,
55    PartialEq,
56    Serialize,
57    Deserialize,
58    Hash
59)]
60pub struct CastRecord1ToRecord2<E = MirScalarExpr> {
61    pub return_ty: SqlScalarType,
62    pub cast_exprs: Box<[E]>,
63}
64
65impl<E: Eval> LazyUnaryFunc for CastRecord1ToRecord2<E> {
66    fn eval<'a>(
67        &'a self,
68        datums: &[Datum<'a>],
69        temp_storage: &'a RowArena,
70        a: &'a impl Eval,
71    ) -> Result<Datum<'a>, EvalError> {
72        let a = a.eval(datums, temp_storage)?;
73        if a.is_null() {
74            return Ok(Datum::Null);
75        }
76        let mut cast_datums = Vec::new();
77        for (el, cast_expr) in a.unwrap_list().iter().zip_eq(&self.cast_exprs) {
78            cast_datums.push(cast_expr.eval(&[el], temp_storage)?);
79        }
80        Ok(temp_storage.make_datum(|packer| packer.push_list(cast_datums)))
81    }
82
83    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
84        self.return_ty
85            .without_modifiers()
86            .nullable(input_type.nullable)
87    }
88
89    fn propagates_nulls(&self) -> bool {
90        true
91    }
92
93    fn introduces_nulls(&self) -> bool {
94        false
95    }
96
97    fn preserves_uniqueness(&self) -> bool {
98        false
99    }
100
101    fn inverse(&self) -> Option<crate::UnaryFunc> {
102        // TODO: we could determine Record1's type from `cast_exprs`
103        None
104    }
105
106    fn is_monotone(&self) -> bool {
107        // In theory this could be marked as monotone if we knew that all the expressions were
108        // monotone in the same direction. (ie. all increasing or all decreasing.) We don't yet
109        // track enough information to make that call, though!
110        false
111    }
112
113    fn is_eliminable_cast(&self) -> bool {
114        false
115    }
116}
117
118impl<E> CastRecord1ToRecord2<E> {
119    /// Rebuilds this function with the field cast expressions converted to
120    /// `E2`. Reports the first failing field, not all of them. Callers treat
121    /// any failure as a lowering bug.
122    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
123        &'a self,
124    ) -> Result<CastRecord1ToRecord2<E2>, E2::Error> {
125        Ok(CastRecord1ToRecord2 {
126            return_ty: self.return_ty.clone(),
127            cast_exprs: self
128                .cast_exprs
129                .iter()
130                .map(E2::try_from)
131                .collect::<Result<Box<[E2]>, _>>()?,
132        })
133    }
134
135    /// Rebuilds this function with the field cast expressions converted to
136    /// `E2`.
137    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastRecord1ToRecord2<E2> {
138        CastRecord1ToRecord2 {
139            return_ty: self.return_ty.clone(),
140            cast_exprs: self.cast_exprs.iter().map(E2::from).collect(),
141        }
142    }
143}
144
145impl<E> fmt::Display for CastRecord1ToRecord2<E> {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        f.write_str("record1torecord2")
148    }
149}
150
151#[derive(
152    Ord,
153    PartialOrd,
154    Clone,
155    Debug,
156    Eq,
157    PartialEq,
158    Serialize,
159    Deserialize,
160    Hash
161)]
162pub struct RecordGet(pub usize);
163
164// The unary tail ORs the input's nullability onto whatever the expression returns,
165// so the expression hands back the field's own column type unchanged.
166// The `Datum` output keeps `introduces_nulls` on, because a nullable field yields NULL for a
167// record that is itself not NULL.
168#[sqlfunc(
169    RecordGet,
170    skip_display = true,
171    output_type_expr = match &input_type.scalar_type {
172        SqlScalarType::Record { fields, .. } => fields[self.0].1.clone(),
173        other => unreachable!("RecordGet on non-record input: {other:?}"),
174    }
175)]
176fn record_get<'a>(&self, a: DatumList<'a>) -> Datum<'a> {
177    a.iter().nth(self.0).unwrap()
178}
179
180impl fmt::Display for RecordGet {
181    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182        write!(f, "record_get[{}]", self.0)
183    }
184}