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_lowertest::MzReflect;
13use mz_repr::adt::jsonb::{Jsonb, JsonbRef};
14use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
15use mz_repr::{Datum, Row, RowPacker, SqlColumnType, SqlScalarType, strconv};
16use serde::{Deserialize, Serialize};
17
18use crate::EvalError;
19use crate::scalar::func::EagerUnaryFunc;
20use crate::scalar::func::impls::numeric::*;
21
22sqlfunc!(
23    #[sqlname = "jsonb_to_text"]
24    #[preserves_uniqueness = false]
25    #[inverse = to_unary!(super::CastStringToJsonb)]
26    fn cast_jsonb_to_string<'a>(a: JsonbRef<'a>) -> String {
27        let mut buf = String::new();
28        strconv::format_jsonb(&mut buf, a);
29        buf
30    }
31);
32
33sqlfunc!(
34    #[sqlname = "jsonb_to_smallint"]
35    #[is_monotone = true]
36    fn cast_jsonb_to_int16<'a>(a: JsonbRef<'a>) -> Result<i16, EvalError> {
37        match a.into_datum() {
38            Datum::Numeric(a) => cast_numeric_to_int16(a.into_inner()),
39            datum => Err(EvalError::InvalidJsonbCast {
40                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
41                to: "smallint".into(),
42            }),
43        }
44    }
45);
46
47sqlfunc!(
48    #[sqlname = "jsonb_to_integer"]
49    #[is_monotone = true]
50    fn cast_jsonb_to_int32<'a>(a: JsonbRef<'a>) -> Result<i32, EvalError> {
51        match a.into_datum() {
52            Datum::Numeric(a) => cast_numeric_to_int32(a.into_inner()),
53            datum => Err(EvalError::InvalidJsonbCast {
54                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
55                to: "integer".into(),
56            }),
57        }
58    }
59);
60
61sqlfunc!(
62    #[sqlname = "jsonb_to_bigint"]
63    #[is_monotone = true]
64    fn cast_jsonb_to_int64<'a>(a: JsonbRef<'a>) -> Result<i64, EvalError> {
65        match a.into_datum() {
66            Datum::Numeric(a) => cast_numeric_to_int64(a.into_inner()),
67            datum => Err(EvalError::InvalidJsonbCast {
68                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
69                to: "bigint".into(),
70            }),
71        }
72    }
73);
74
75sqlfunc!(
76    #[sqlname = "jsonb_to_real"]
77    #[is_monotone = true]
78    fn cast_jsonb_to_float32<'a>(a: JsonbRef<'a>) -> Result<f32, EvalError> {
79        match a.into_datum() {
80            Datum::Numeric(a) => cast_numeric_to_float32(a.into_inner()),
81            datum => Err(EvalError::InvalidJsonbCast {
82                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
83                to: "real".into(),
84            }),
85        }
86    }
87);
88
89sqlfunc!(
90    #[sqlname = "jsonb_to_double"]
91    #[is_monotone = true]
92    fn cast_jsonb_to_float64<'a>(a: JsonbRef<'a>) -> Result<f64, EvalError> {
93        match a.into_datum() {
94            Datum::Numeric(a) => cast_numeric_to_float64(a.into_inner()),
95            datum => Err(EvalError::InvalidJsonbCast {
96                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
97                to: "double precision".into(),
98            }),
99        }
100    }
101);
102
103#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
104pub struct CastJsonbToNumeric(pub Option<NumericMaxScale>);
105
106impl<'a> EagerUnaryFunc<'a> for CastJsonbToNumeric {
107    type Input = JsonbRef<'a>;
108    type Output = Result<Numeric, EvalError>;
109
110    fn call(&self, a: JsonbRef<'a>) -> Result<Numeric, EvalError> {
111        match a.into_datum() {
112            Datum::Numeric(mut num) => match self.0 {
113                None => Ok(num.into_inner()),
114                Some(scale) => {
115                    if numeric::rescale(&mut num.0, scale.into_u8()).is_err() {
116                        return Err(EvalError::NumericFieldOverflow);
117                    };
118                    Ok(num.into_inner())
119                }
120            },
121            datum => Err(EvalError::InvalidJsonbCast {
122                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
123                to: "numeric".into(),
124            }),
125        }
126    }
127
128    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
129        SqlScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
130    }
131
132    fn is_monotone(&self) -> bool {
133        true
134    }
135}
136
137impl fmt::Display for CastJsonbToNumeric {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        f.write_str("jsonb_to_numeric")
140    }
141}
142
143sqlfunc!(
144    #[sqlname = "jsonb_to_boolean"]
145    #[is_monotone = true]
146    fn cast_jsonb_to_bool<'a>(a: JsonbRef<'a>) -> Result<bool, EvalError> {
147        match a.into_datum() {
148            Datum::True => Ok(true),
149            Datum::False => Ok(false),
150            datum => Err(EvalError::InvalidJsonbCast {
151                from: jsonb_typeof(JsonbRef::from_datum(datum)).into(),
152                to: "boolean".into(),
153            }),
154        }
155    }
156);
157
158sqlfunc!(
159    #[sqlname = "jsonbable_to_jsonb"]
160    fn cast_jsonbable_to_jsonb<'a>(a: JsonbRef<'a>) -> JsonbRef<'a> {
161        match a.into_datum() {
162            Datum::Numeric(n) => {
163                let n = n.into_inner();
164                let datum = if n.is_finite() {
165                    Datum::from(n)
166                } else if n.is_nan() {
167                    Datum::String("NaN")
168                } else if n.is_negative() {
169                    Datum::String("-Infinity")
170                } else {
171                    Datum::String("Infinity")
172                };
173                JsonbRef::from_datum(datum)
174            }
175            datum => JsonbRef::from_datum(datum),
176        }
177    }
178);
179
180sqlfunc!(
181    fn jsonb_array_length<'a>(a: JsonbRef<'a>) -> Result<Option<i32>, EvalError> {
182        match a.into_datum() {
183            Datum::List(list) => {
184                let count = list.iter().count();
185                match i32::try_from(count) {
186                    Ok(len) => Ok(Some(len)),
187                    Err(_) => Err(EvalError::Int32OutOfRange(count.to_string().into())),
188                }
189            }
190            _ => Ok(None),
191        }
192    }
193);
194
195sqlfunc!(
196    fn jsonb_typeof<'a>(a: JsonbRef<'a>) -> &'a str {
197        match a.into_datum() {
198            Datum::Map(_) => "object",
199            Datum::List(_) => "array",
200            Datum::String(_) => "string",
201            Datum::Numeric(_) => "number",
202            Datum::True | Datum::False => "boolean",
203            Datum::JsonNull => "null",
204            d => panic!("Not jsonb: {:?}", d),
205        }
206    }
207);
208
209sqlfunc!(
210    fn jsonb_strip_nulls<'a>(a: JsonbRef<'a>) -> Jsonb {
211        fn strip_nulls(a: Datum, row: &mut RowPacker) {
212            match a {
213                Datum::Map(dict) => row.push_dict_with(|row| {
214                    for (k, v) in dict.iter() {
215                        match v {
216                            Datum::JsonNull => (),
217                            _ => {
218                                row.push(Datum::String(k));
219                                strip_nulls(v, row);
220                            }
221                        }
222                    }
223                }),
224                Datum::List(list) => row.push_list_with(|row| {
225                    for elem in list.iter() {
226                        strip_nulls(elem, row);
227                    }
228                }),
229                _ => row.push(a),
230            }
231        }
232        let mut row = Row::default();
233        strip_nulls(a.into_datum(), &mut row.packer());
234        Jsonb::from_row(row)
235    }
236);
237
238sqlfunc!(
239    fn jsonb_pretty<'a>(a: JsonbRef<'a>) -> String {
240        let mut buf = String::new();
241        strconv::format_jsonb_pretty(&mut buf, a);
242        buf
243    }
244);