Skip to main content

mz_expr/scalar/func/impls/
jsonb.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 mz_expr_derive::sqlfunc;
13use mz_lowertest::MzReflect;
14use mz_repr::adt::jsonb::{Jsonb, JsonbRef};
15use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem};
16use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
17use mz_repr::role_id::RoleId;
18use mz_repr::{ArrayRustType, Datum, Row, RowPacker, SqlColumnType, SqlScalarType, strconv};
19use serde::{Deserialize, Serialize};
20
21use crate::EvalError;
22use crate::scalar::func::EagerUnaryFunc;
23use crate::scalar::func::impls::numeric::*;
24
25#[sqlfunc(
26    sqlname = "jsonb_to_text",
27    preserves_uniqueness = false,
28    inverse = to_unary!(super::CastStringToJsonb)
29)]
30pub fn cast_jsonb_to_string<'a>(a: JsonbRef<'a>) -> String {
31    let mut buf = String::new();
32    strconv::format_jsonb(&mut buf, a);
33    buf
34}
35
36#[sqlfunc(sqlname = "jsonb_to_smallint", is_monotone = true)]
37fn cast_jsonb_to_int16<'a>(a: JsonbRef<'a>) -> Result<i16, EvalError> {
38    match a.into_datum() {
39        Datum::Numeric(a) => cast_numeric_to_int16(a.into_inner()),
40        datum => Err(EvalError::InvalidJsonbCast {
41            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
42            to: "smallint".into(),
43        }),
44    }
45}
46
47#[sqlfunc(sqlname = "jsonb_to_integer", is_monotone = true)]
48fn cast_jsonb_to_int32<'a>(a: JsonbRef<'a>) -> Result<i32, EvalError> {
49    match a.into_datum() {
50        Datum::Numeric(a) => cast_numeric_to_int32(a.into_inner()),
51        datum => Err(EvalError::InvalidJsonbCast {
52            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
53            to: "integer".into(),
54        }),
55    }
56}
57
58#[sqlfunc(sqlname = "jsonb_to_bigint", is_monotone = true)]
59fn cast_jsonb_to_int64<'a>(a: JsonbRef<'a>) -> Result<i64, EvalError> {
60    match a.into_datum() {
61        Datum::Numeric(a) => cast_numeric_to_int64(a.into_inner()),
62        datum => Err(EvalError::InvalidJsonbCast {
63            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
64            to: "bigint".into(),
65        }),
66    }
67}
68
69#[sqlfunc(sqlname = "jsonb_to_real", is_monotone = true)]
70fn cast_jsonb_to_float32<'a>(a: JsonbRef<'a>) -> Result<f32, EvalError> {
71    match a.into_datum() {
72        Datum::Numeric(a) => cast_numeric_to_float32(a.into_inner()),
73        datum => Err(EvalError::InvalidJsonbCast {
74            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
75            to: "real".into(),
76        }),
77    }
78}
79
80#[sqlfunc(sqlname = "jsonb_to_double", is_monotone = true)]
81fn cast_jsonb_to_float64<'a>(a: JsonbRef<'a>) -> Result<f64, EvalError> {
82    match a.into_datum() {
83        Datum::Numeric(a) => cast_numeric_to_float64(a.into_inner()),
84        datum => Err(EvalError::InvalidJsonbCast {
85            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
86            to: "double precision".into(),
87        }),
88    }
89}
90
91#[derive(
92    Ord,
93    PartialOrd,
94    Clone,
95    Debug,
96    Eq,
97    PartialEq,
98    Serialize,
99    Deserialize,
100    Hash,
101    MzReflect
102)]
103pub struct CastJsonbToNumeric(pub Option<NumericMaxScale>);
104
105impl EagerUnaryFunc for CastJsonbToNumeric {
106    type Input<'a> = JsonbRef<'a>;
107    type Output<'a> = Result<Numeric, EvalError>;
108
109    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
110        match a.into_datum() {
111            Datum::Numeric(mut num) => match self.0 {
112                None => Ok(num.into_inner()),
113                Some(scale) => {
114                    if numeric::rescale(&mut num.0, scale.into_u8()).is_err() {
115                        return Err(EvalError::NumericFieldOverflow);
116                    };
117                    Ok(num.into_inner())
118                }
119            },
120            datum => Err(EvalError::InvalidJsonbCast {
121                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
122                to: "numeric".into(),
123            }),
124        }
125    }
126
127    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
128        SqlScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
129    }
130
131    fn is_monotone(&self) -> bool {
132        true
133    }
134}
135
136impl fmt::Display for CastJsonbToNumeric {
137    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138        f.write_str("jsonb_to_numeric")
139    }
140}
141
142#[sqlfunc(sqlname = "jsonb_to_boolean", is_monotone = true)]
143fn cast_jsonb_to_bool<'a>(a: JsonbRef<'a>) -> Result<bool, EvalError> {
144    match a.into_datum() {
145        Datum::True => Ok(true),
146        Datum::False => Ok(false),
147        datum => Err(EvalError::InvalidJsonbCast {
148            from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
149            to: "boolean".into(),
150        }),
151    }
152}
153
154#[sqlfunc(sqlname = "jsonbable_to_jsonb")]
155fn cast_jsonbable_to_jsonb<'a>(a: JsonbRef<'a>) -> JsonbRef<'a> {
156    match a.into_datum() {
157        Datum::Numeric(n) => {
158            let n = n.into_inner();
159            let datum = if n.is_finite() {
160                Datum::from(n)
161            } else if n.is_nan() {
162                Datum::String("NaN")
163            } else if n.is_negative() {
164                Datum::String("-Infinity")
165            } else {
166                Datum::String("Infinity")
167            };
168            JsonbRef::from_datum(datum)
169        }
170        datum => JsonbRef::from_datum(datum),
171    }
172}
173
174#[sqlfunc]
175fn jsonb_array_length<'a>(a: JsonbRef<'a>) -> Result<Option<i32>, EvalError> {
176    match a.into_datum() {
177        Datum::List(list) => {
178            let count = list.iter().count();
179            match i32::try_from(count) {
180                Ok(len) => Ok(Some(len)),
181                Err(_) => Err(EvalError::Int32OutOfRange(count.to_string().into())),
182            }
183        }
184        _ => Ok(None),
185    }
186}
187
188#[sqlfunc]
189fn jsonb_typeof<'a>(a: JsonbRef<'a>) -> &'a str {
190    match a.into_datum() {
191        Datum::Map(_) => "object",
192        Datum::List(_) => "array",
193        Datum::String(_) => "string",
194        Datum::Numeric(_) => "number",
195        Datum::True | Datum::False => "boolean",
196        Datum::JsonNull => "null",
197        d => panic!("Not jsonb: {:?}", d),
198    }
199}
200
201#[sqlfunc]
202fn jsonb_strip_nulls<'a>(a: JsonbRef<'a>) -> Jsonb {
203    fn strip_nulls(a: Datum, row: &mut RowPacker) {
204        match a {
205            Datum::Map(dict) => row.push_dict_with(|row| {
206                for (k, v) in dict.iter() {
207                    match v {
208                        Datum::JsonNull => (),
209                        _ => {
210                            row.push(Datum::String(k));
211                            strip_nulls(v, row);
212                        }
213                    }
214                }
215            }),
216            Datum::List(list) => row.push_list_with(|row| {
217                for elem in list.iter() {
218                    strip_nulls(elem, row);
219                }
220            }),
221            _ => row.push(a),
222        }
223    }
224    let mut row = Row::default();
225    strip_nulls(a.into_datum(), &mut row.packer());
226    Jsonb::from_row(row)
227}
228
229#[sqlfunc]
230fn jsonb_pretty<'a>(a: JsonbRef<'a>) -> String {
231    let mut buf = String::new();
232    strconv::format_jsonb_pretty(&mut buf, a);
233    buf
234}
235
236/// Converts a JSONB `Datum` into a `u64`.
237fn jsonb_datum_to_u64<'a>(d: Datum<'a>) -> Result<u64, String> {
238    let Datum::Numeric(n) = d else {
239        return Err("expected numeric value".into());
240    };
241
242    let mut cx = numeric::cx_datum();
243    cx.try_into_u64(n.0)
244        .map_err(|_| format!("number out of u64 range: {n}"))
245}
246
247/// Converts a JSONB `Datum` into a `RoleId`.
248fn jsonb_datum_to_role_id(d: Datum) -> Result<RoleId, String> {
249    match d {
250        Datum::String("Public") => Ok(RoleId::Public),
251        Datum::String(other) => Err(format!("unexpected role ID variant: {other}")),
252        Datum::Map(dict) => {
253            let (key, val) = dict.iter().next().ok_or_else(|| "empty".to_string())?;
254            let n = jsonb_datum_to_u64(val)?;
255            match key {
256                "User" => Ok(RoleId::User(n)),
257                "System" => Ok(RoleId::System(n)),
258                "Predefined" => Ok(RoleId::Predefined(n)),
259                other => Err(format!("unexpected role ID variant: {other}")),
260            }
261        }
262        _ => Err("expected string or object".into()),
263    }
264}
265
266/// Converts a catalog JSON-serialized ID value into the appropriate string format.
267///
268/// Supports all of Materialize's various ID types of the form `<prefix><u64>`.
269#[sqlfunc]
270fn parse_catalog_id<'a>(a: JsonbRef<'a>) -> Result<String, EvalError> {
271    let parse = || match a.into_datum() {
272        // Unit variant, e.g. "Public"
273        Datum::String(variant) => match variant {
274            "Explain" => Ok("e".to_string()),
275            "Public" => Ok("p".to_string()),
276            other => Err(format!("unexpected ID variant: {other}")),
277        },
278        // Newtype variant, e.g. {"User": 1}
279        Datum::Map(dict) => {
280            let (key, val) = dict.iter().next().ok_or_else(|| "empty".to_string())?;
281            let prefix = match key {
282                "IntrospectionSourceIndex" => "si",
283                "Predefined" => "g",
284                "System" => "s",
285                "Transient" => "t",
286                "User" => "u",
287                other => return Err(format!("unexpected ID variant: {other}")),
288            };
289            let n = jsonb_datum_to_u64(val)?;
290            Ok(format!("{prefix}{n}"))
291        }
292        _ => Err("expected string or object".into()),
293    };
294
295    parse().map_err(|e| EvalError::InvalidCatalogJson(e.into()))
296}
297
298/// Converts a catalog JSON-serialized privilege array into an `mz_aclitem[]`.
299#[sqlfunc]
300fn parse_catalog_privileges<'a>(a: JsonbRef<'a>) -> Result<ArrayRustType<MzAclItem>, EvalError> {
301    let parse_one = |datum| match datum {
302        Datum::Map(dict) => {
303            let mut grantee = None;
304            let mut grantor = None;
305            let mut acl_mode = None;
306            for (key, val) in dict.iter() {
307                match key {
308                    "grantee" => {
309                        let id = jsonb_datum_to_role_id(val)?;
310                        grantee = Some(id);
311                    }
312                    "grantor" => {
313                        let id = jsonb_datum_to_role_id(val)?;
314                        grantor = Some(id);
315                    }
316                    "acl_mode" => {
317                        let Datum::Map(mode_dict) = val else {
318                            return Err(format!("unexpected acl_mode: {val}"));
319                        };
320                        let (key, val) = mode_dict.iter().next().ok_or("empty acl_mode")?;
321                        if key != "bitflags" {
322                            return Err(format!("unexpected acl_mode field: {key}"));
323                        }
324                        let bits = jsonb_datum_to_u64(val)?;
325                        let Some(mode) = AclMode::from_bits(bits) else {
326                            return Err(format!("invalid acl_mode bitflags: {bits}"));
327                        };
328                        acl_mode = Some(mode);
329                    }
330                    other => return Err(format!("unexpected privilege field: {other}")),
331                }
332            }
333            Ok(MzAclItem {
334                grantee: grantee.ok_or_else(|| format!("missing grantee: {dict:?}"))?,
335                grantor: grantor.ok_or_else(|| "missing grantor in privilege".to_string())?,
336                acl_mode: acl_mode.ok_or_else(|| "missing acl_mode in privilege".to_string())?,
337            })
338        }
339        other => Err(format!("expected object in array, found: {other}")),
340    };
341
342    let parse = || match a.into_datum() {
343        Datum::List(list) => {
344            let mut result = Vec::new();
345            for item in list.iter() {
346                result.push(parse_one(item)?);
347            }
348            Ok(result)
349        }
350        _ => Err("expected array".to_string()),
351    };
352
353    parse()
354        .map(ArrayRustType)
355        .map_err(|e| EvalError::InvalidCatalogJson(e.into()))
356}