mz_expr/scalar/func/impls/
jsonb.rs1use 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
236fn 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
247fn 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#[sqlfunc]
270fn parse_catalog_id<'a>(a: JsonbRef<'a>) -> Result<String, EvalError> {
271 let parse = || match a.into_datum() {
272 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 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#[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}