Skip to main content

mz_expr_parser/
printer.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
10//! Printing of [`MirScalarExpr`] in the syntax accepted by the parser.
11//!
12//! Unlike the EXPLAIN printer, which renders function variants by their SQL
13//! name (losing the exact variant, e.g. `+` for every add function), this
14//! printer renders exact variants by their canonical name. Parsing the output
15//! reconstructs the original expression. Test tooling uses this to generate
16//! parser inputs.
17
18use mz_expr::{BinaryFunc, MirScalarExpr, UnaryFunc, VariadicFunc, func};
19use mz_repr::adt::jsonb::JsonbRef;
20use mz_repr::{Datum, ReprColumnType, ReprScalarType, SqlScalarType};
21
22/// Renders a [`MirScalarExpr`] such that [`crate::try_parse_scalar`] parses it
23/// back to an equal expression.
24///
25/// Returns an error for datum types the literal syntax cannot express.
26pub fn print_scalar(expr: &MirScalarExpr) -> Result<String, String> {
27    use MirScalarExpr::*;
28    match expr {
29        Column(i, _name) => Ok(format!("#{i}")),
30        Literal(Ok(row), typ) => print_literal(row.unpack_first(), typ),
31        Literal(Err(err), _typ) => match err {
32            mz_expr::EvalError::Internal(msg) => Ok(format!("error(\"internal error: {msg}\")")),
33            err => Err(format!("cannot print error literal: {err}")),
34        },
35        CallUnmaterializable(func) => Ok(format!("{func}()")),
36        CallUnary { func, expr } => {
37            let expr = print_scalar(expr)?;
38            match func {
39                UnaryFunc::IsNull(_) => Ok(format!("(({expr}) IS NULL)")),
40                UnaryFunc::RecordGet(func::RecordGet(index)) => {
41                    Ok(format!("record_get[{index}]({expr})"))
42                }
43                UnaryFunc::CastInt32ToNumeric(func::CastInt32ToNumeric(Some(scale))) => Ok(
44                    format!("cast_int32_to_numeric[{}]({expr})", scale.into_u8()),
45                ),
46                func => Ok(format!("{}({expr})", func.variant_name())),
47            }
48        }
49        CallBinary { func, expr1, expr2 } => {
50            let expr1 = print_scalar(expr1)?;
51            let expr2 = print_scalar(expr2)?;
52            // Infix operators with a unique variant can render in the more
53            // readable EXPLAIN form without losing the exact variant.
54            let infix = match func {
55                BinaryFunc::Eq(_) => Some("="),
56                BinaryFunc::NotEq(_) => Some("!="),
57                BinaryFunc::Lt(_) => Some("<"),
58                BinaryFunc::Lte(_) => Some("<="),
59                BinaryFunc::Gt(_) => Some(">"),
60                BinaryFunc::Gte(_) => Some(">="),
61                _ => None,
62            };
63            match infix {
64                Some(op) => Ok(format!("({expr1} {op} {expr2})")),
65                None => Ok(format!("{}({expr1}, {expr2})", func.variant_name())),
66            }
67        }
68        CallVariadic { func, exprs } => {
69            let args = exprs
70                .iter()
71                .map(print_scalar)
72                .collect::<Result<Vec<_>, _>>()?;
73            // The parser flattens nested infix AND/OR chains into a single
74            // variadic call, so the infix form is only faithful for calls
75            // with at least two arguments and no directly nested call of the
76            // same function.
77            let infix_safe = || {
78                exprs.len() >= 2
79                    && !exprs
80                        .iter()
81                        .any(|e| matches!(e, CallVariadic { func: f, .. } if f == func))
82            };
83            match func {
84                VariadicFunc::And(_) if infix_safe() => Ok(format!("({})", args.join(" AND "))),
85                VariadicFunc::Or(_) if infix_safe() => Ok(format!("({})", args.join(" OR "))),
86                VariadicFunc::RecordCreate(func::variadic::RecordCreate { field_names }) => {
87                    let names = field_names
88                        .iter()
89                        .map(|n| format!("{:?}", n.as_str()))
90                        .collect::<Vec<_>>()
91                        .join(", ");
92                    Ok(format!("record_create[{names}]({})", args.join(", ")))
93                }
94                VariadicFunc::ListCreate(func::variadic::ListCreate { elem_type }) => {
95                    let elem_type = print_sql_scalar_type(elem_type)?;
96                    Ok(format!("list_create[{elem_type}]({})", args.join(", ")))
97                }
98                func => Ok(format!("{}({})", func.variant_name(), args.join(", "))),
99            }
100        }
101        If { cond, then, els } => Ok(format!(
102            "case when {} then {} else {} end",
103            print_scalar(cond)?,
104            print_scalar(then)?,
105            print_scalar(els)?
106        )),
107    }
108}
109
110fn print_literal(datum: Datum, typ: &ReprColumnType) -> Result<String, String> {
111    let unsupported = |what: &dyn std::fmt::Debug| Err(format!("cannot print literal {what:?}"));
112    match datum {
113        Datum::Null => Ok(format!(
114            "null::{}",
115            print_repr_scalar_type(&typ.scalar_type)?
116        )),
117        Datum::True => Ok("true".to_string()),
118        Datum::False => Ok("false".to_string()),
119        Datum::Int16(i) => Ok(format!("{i}::smallint")),
120        Datum::Int32(i) => Ok(format!("{i}::integer")),
121        Datum::Int64(i) => Ok(format!("{i}")),
122        Datum::Float64(f) => Ok(format!("{:?}", f.into_inner())),
123        Datum::Numeric(n) => Ok(format!("{n}::numeric")),
124        Datum::String(s) => Ok(format!("{s:?}")),
125        datum if typ.scalar_type == ReprScalarType::Jsonb => Ok(format!(
126            "{:?}::jsonb",
127            JsonbRef::from_datum(datum).to_string()
128        )),
129        datum => unsupported(&datum),
130    }
131}
132
133fn print_repr_scalar_type(typ: &ReprScalarType) -> Result<String, String> {
134    let name = match typ {
135        ReprScalarType::Bool => "boolean",
136        ReprScalarType::Int16 => "smallint",
137        ReprScalarType::Int32 => "integer",
138        ReprScalarType::Int64 => "bigint",
139        ReprScalarType::Float64 => "double precision",
140        ReprScalarType::Numeric => "numeric",
141        ReprScalarType::String => "text",
142        ReprScalarType::Jsonb => "jsonb",
143        typ => Err(format!("cannot print scalar type {typ:?}"))?,
144    };
145    Ok(name.to_string())
146}
147
148fn print_sql_scalar_type(typ: &SqlScalarType) -> Result<String, String> {
149    print_repr_scalar_type(&ReprScalarType::from(typ))
150}