laminar_sql/datafusion/json_udf/
extraction.rs1use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5
6use arrow::datatypes::DataType;
7use arrow_array::{
8 builder::{LargeBinaryBuilder, StringBuilder},
9 Array, LargeBinaryArray, ListArray, StringArray,
10};
11use datafusion_common::Result;
12use datafusion_expr::{
13 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
14};
15
16use super::{expand_args, json_types};
17
18#[derive(Debug)]
20pub struct JsonbGet {
21 signature: Signature,
22}
23
24impl JsonbGet {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 signature: Signature::new(
30 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Utf8]),
31 Volatility::Immutable,
32 ),
33 }
34 }
35}
36
37impl Default for JsonbGet {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl PartialEq for JsonbGet {
44 fn eq(&self, _other: &Self) -> bool {
45 true
46 }
47}
48
49impl Eq for JsonbGet {}
50
51impl Hash for JsonbGet {
52 fn hash<H: Hasher>(&self, state: &mut H) {
53 "jsonb_get".hash(state);
54 }
55}
56
57impl ScalarUDFImpl for JsonbGet {
58 fn as_any(&self) -> &dyn std::any::Any {
59 self
60 }
61
62 fn name(&self) -> &'static str {
63 "jsonb_get"
64 }
65
66 fn signature(&self) -> &Signature {
67 &self.signature
68 }
69
70 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
71 Ok(DataType::LargeBinary)
72 }
73
74 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
75 let expanded = expand_args(&args.args)?;
76 let jsonb_arr = expanded[0]
77 .as_any()
78 .downcast_ref::<LargeBinaryArray>()
79 .ok_or_else(|| {
80 datafusion_common::DataFusionError::Internal(
81 "jsonb_get: first arg must be LargeBinary".into(),
82 )
83 })?;
84 let key_arr = expanded[1]
85 .as_any()
86 .downcast_ref::<StringArray>()
87 .ok_or_else(|| {
88 datafusion_common::DataFusionError::Internal(
89 "jsonb_get: second arg must be Utf8".into(),
90 )
91 })?;
92
93 let mut builder = LargeBinaryBuilder::with_capacity(jsonb_arr.len(), 256);
94 for i in 0..jsonb_arr.len() {
95 if jsonb_arr.is_null(i) || key_arr.is_null(i) {
96 builder.append_null();
97 } else {
98 let jsonb = jsonb_arr.value(i);
99 let key = key_arr.value(i);
100 match json_types::jsonb_get_field(jsonb, key) {
101 Some(val) => builder.append_value(val),
102 None => builder.append_null(),
103 }
104 }
105 }
106 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
107 }
108}
109
110#[derive(Debug)]
119pub struct JsonbGetIdx {
120 signature: Signature,
121}
122
123impl JsonbGetIdx {
124 #[must_use]
126 pub fn new() -> Self {
127 Self {
128 signature: Signature::new(
129 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Int32]),
130 Volatility::Immutable,
131 ),
132 }
133 }
134}
135
136impl Default for JsonbGetIdx {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl PartialEq for JsonbGetIdx {
143 fn eq(&self, _other: &Self) -> bool {
144 true
145 }
146}
147
148impl Eq for JsonbGetIdx {}
149
150impl Hash for JsonbGetIdx {
151 fn hash<H: Hasher>(&self, state: &mut H) {
152 "jsonb_get_idx".hash(state);
153 }
154}
155
156impl ScalarUDFImpl for JsonbGetIdx {
157 fn as_any(&self) -> &dyn std::any::Any {
158 self
159 }
160
161 fn name(&self) -> &'static str {
162 "jsonb_get_idx"
163 }
164
165 fn signature(&self) -> &Signature {
166 &self.signature
167 }
168
169 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
170 Ok(DataType::LargeBinary)
171 }
172
173 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
174 let expanded = expand_args(&args.args)?;
175 let jsonb_arr = expanded[0]
176 .as_any()
177 .downcast_ref::<LargeBinaryArray>()
178 .ok_or_else(|| {
179 datafusion_common::DataFusionError::Internal(
180 "jsonb_get_idx: first arg must be LargeBinary".into(),
181 )
182 })?;
183 let idx_arr = expanded[1]
184 .as_any()
185 .downcast_ref::<arrow_array::Int32Array>()
186 .ok_or_else(|| {
187 datafusion_common::DataFusionError::Internal(
188 "jsonb_get_idx: second arg must be Int32".into(),
189 )
190 })?;
191
192 let mut builder = LargeBinaryBuilder::with_capacity(jsonb_arr.len(), 256);
193 for i in 0..jsonb_arr.len() {
194 if jsonb_arr.is_null(i) || idx_arr.is_null(i) {
195 builder.append_null();
196 } else {
197 let jsonb = jsonb_arr.value(i);
198 let idx = idx_arr.value(i);
199 match usize::try_from(idx)
200 .ok()
201 .and_then(|u| json_types::jsonb_array_get(jsonb, u))
202 {
203 Some(val) => builder.append_value(val),
204 None => builder.append_null(),
205 }
206 }
207 }
208 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
209 }
210}
211
212#[derive(Debug)]
221pub struct JsonbGetText {
222 signature: Signature,
223}
224
225impl JsonbGetText {
226 #[must_use]
228 pub fn new() -> Self {
229 Self {
230 signature: Signature::new(
231 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Utf8]),
232 Volatility::Immutable,
233 ),
234 }
235 }
236}
237
238impl Default for JsonbGetText {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244impl PartialEq for JsonbGetText {
245 fn eq(&self, _other: &Self) -> bool {
246 true
247 }
248}
249
250impl Eq for JsonbGetText {}
251
252impl Hash for JsonbGetText {
253 fn hash<H: Hasher>(&self, state: &mut H) {
254 "jsonb_get_text".hash(state);
255 }
256}
257
258impl ScalarUDFImpl for JsonbGetText {
259 fn as_any(&self) -> &dyn std::any::Any {
260 self
261 }
262
263 fn name(&self) -> &'static str {
264 "jsonb_get_text"
265 }
266
267 fn signature(&self) -> &Signature {
268 &self.signature
269 }
270
271 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
272 Ok(DataType::Utf8)
273 }
274
275 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
276 let expanded = expand_args(&args.args)?;
277 let jsonb_arr = expanded[0]
278 .as_any()
279 .downcast_ref::<LargeBinaryArray>()
280 .ok_or_else(|| {
281 datafusion_common::DataFusionError::Internal(
282 "jsonb_get_text: first arg must be LargeBinary".into(),
283 )
284 })?;
285 let key_arr = expanded[1]
286 .as_any()
287 .downcast_ref::<StringArray>()
288 .ok_or_else(|| {
289 datafusion_common::DataFusionError::Internal(
290 "jsonb_get_text: second arg must be Utf8".into(),
291 )
292 })?;
293
294 let mut builder = StringBuilder::with_capacity(jsonb_arr.len(), 256);
295 for i in 0..jsonb_arr.len() {
296 if jsonb_arr.is_null(i) || key_arr.is_null(i) {
297 builder.append_null();
298 } else {
299 let jsonb = jsonb_arr.value(i);
300 let key = key_arr.value(i);
301 match json_types::jsonb_get_field(jsonb, key) {
302 Some(val) => match json_types::jsonb_to_text(val) {
303 Some(text) => builder.append_value(&text),
304 None => builder.append_null(),
305 },
306 None => builder.append_null(),
307 }
308 }
309 }
310 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
311 }
312}
313
314#[derive(Debug)]
323pub struct JsonbGetTextIdx {
324 signature: Signature,
325}
326
327impl JsonbGetTextIdx {
328 #[must_use]
330 pub fn new() -> Self {
331 Self {
332 signature: Signature::new(
333 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Int32]),
334 Volatility::Immutable,
335 ),
336 }
337 }
338}
339
340impl Default for JsonbGetTextIdx {
341 fn default() -> Self {
342 Self::new()
343 }
344}
345
346impl PartialEq for JsonbGetTextIdx {
347 fn eq(&self, _other: &Self) -> bool {
348 true
349 }
350}
351
352impl Eq for JsonbGetTextIdx {}
353
354impl Hash for JsonbGetTextIdx {
355 fn hash<H: Hasher>(&self, state: &mut H) {
356 "jsonb_get_text_idx".hash(state);
357 }
358}
359
360impl ScalarUDFImpl for JsonbGetTextIdx {
361 fn as_any(&self) -> &dyn std::any::Any {
362 self
363 }
364
365 fn name(&self) -> &'static str {
366 "jsonb_get_text_idx"
367 }
368
369 fn signature(&self) -> &Signature {
370 &self.signature
371 }
372
373 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
374 Ok(DataType::Utf8)
375 }
376
377 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
378 let expanded = expand_args(&args.args)?;
379 let jsonb_arr = expanded[0]
380 .as_any()
381 .downcast_ref::<LargeBinaryArray>()
382 .ok_or_else(|| {
383 datafusion_common::DataFusionError::Internal(
384 "jsonb_get_text_idx: first arg must be LargeBinary".into(),
385 )
386 })?;
387 let idx_arr = expanded[1]
388 .as_any()
389 .downcast_ref::<arrow_array::Int32Array>()
390 .ok_or_else(|| {
391 datafusion_common::DataFusionError::Internal(
392 "jsonb_get_text_idx: second arg must be Int32".into(),
393 )
394 })?;
395
396 let mut builder = StringBuilder::with_capacity(jsonb_arr.len(), 256);
397 for i in 0..jsonb_arr.len() {
398 if jsonb_arr.is_null(i) || idx_arr.is_null(i) {
399 builder.append_null();
400 } else {
401 let jsonb = jsonb_arr.value(i);
402 let idx = idx_arr.value(i);
403 match usize::try_from(idx)
404 .ok()
405 .and_then(|u| json_types::jsonb_array_get(jsonb, u))
406 {
407 Some(val) => match json_types::jsonb_to_text(val) {
408 Some(text) => builder.append_value(&text),
409 None => builder.append_null(),
410 },
411 None => builder.append_null(),
412 }
413 }
414 }
415 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
416 }
417}
418
419#[derive(Debug)]
428pub struct JsonbGetPath {
429 signature: Signature,
430}
431
432impl JsonbGetPath {
433 #[must_use]
435 pub fn new() -> Self {
436 Self {
437 signature: Signature::new(
438 TypeSignature::Exact(vec![
439 DataType::LargeBinary,
440 DataType::List(Arc::new(arrow_schema::Field::new(
441 "item",
442 DataType::Utf8,
443 true,
444 ))),
445 ]),
446 Volatility::Immutable,
447 ),
448 }
449 }
450}
451
452impl Default for JsonbGetPath {
453 fn default() -> Self {
454 Self::new()
455 }
456}
457
458impl PartialEq for JsonbGetPath {
459 fn eq(&self, _other: &Self) -> bool {
460 true
461 }
462}
463
464impl Eq for JsonbGetPath {}
465
466impl Hash for JsonbGetPath {
467 fn hash<H: Hasher>(&self, state: &mut H) {
468 "jsonb_get_path".hash(state);
469 }
470}
471
472impl ScalarUDFImpl for JsonbGetPath {
473 fn as_any(&self) -> &dyn std::any::Any {
474 self
475 }
476
477 fn name(&self) -> &'static str {
478 "jsonb_get_path"
479 }
480
481 fn signature(&self) -> &Signature {
482 &self.signature
483 }
484
485 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
486 Ok(DataType::LargeBinary)
487 }
488
489 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
490 let expanded = expand_args(&args.args)?;
491 let jsonb_arr = expanded[0]
492 .as_any()
493 .downcast_ref::<LargeBinaryArray>()
494 .ok_or_else(|| {
495 datafusion_common::DataFusionError::Internal(
496 "jsonb_get_path: first arg must be LargeBinary".into(),
497 )
498 })?;
499 let path_arr = expanded[1]
500 .as_any()
501 .downcast_ref::<ListArray>()
502 .ok_or_else(|| {
503 datafusion_common::DataFusionError::Internal(
504 "jsonb_get_path: second arg must be List<Utf8>".into(),
505 )
506 })?;
507
508 let mut builder = LargeBinaryBuilder::with_capacity(jsonb_arr.len(), 256);
509 for i in 0..jsonb_arr.len() {
510 if jsonb_arr.is_null(i) || path_arr.is_null(i) {
511 builder.append_null();
512 } else {
513 let jsonb = jsonb_arr.value(i);
514 let path_list = path_arr.value(i);
515 let path_strings = path_list
516 .as_any()
517 .downcast_ref::<StringArray>()
518 .ok_or_else(|| {
519 datafusion_common::DataFusionError::Internal(
520 "jsonb_get_path: path elements must be Utf8".into(),
521 )
522 })?;
523 match walk_path(jsonb, path_strings) {
524 Some(val) => builder.append_value(val),
525 None => builder.append_null(),
526 }
527 }
528 }
529 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
530 }
531}
532
533fn walk_path<'a>(mut jsonb: &'a [u8], path: &StringArray) -> Option<&'a [u8]> {
535 for i in 0..path.len() {
536 if path.is_null(i) {
537 return None;
538 }
539 let key = path.value(i);
540 if let Some(next) = json_types::jsonb_get_field(jsonb, key) {
542 jsonb = next;
543 } else if let Ok(idx) = key.parse::<usize>() {
544 jsonb = json_types::jsonb_array_get(jsonb, idx)?;
546 } else {
547 return None;
548 }
549 }
550 Some(jsonb)
551}
552
553#[derive(Debug)]
562pub struct JsonbGetPathText {
563 signature: Signature,
564}
565
566impl JsonbGetPathText {
567 #[must_use]
569 pub fn new() -> Self {
570 Self {
571 signature: Signature::new(
572 TypeSignature::Exact(vec![
573 DataType::LargeBinary,
574 DataType::List(Arc::new(arrow_schema::Field::new(
575 "item",
576 DataType::Utf8,
577 true,
578 ))),
579 ]),
580 Volatility::Immutable,
581 ),
582 }
583 }
584}
585
586impl Default for JsonbGetPathText {
587 fn default() -> Self {
588 Self::new()
589 }
590}
591
592impl PartialEq for JsonbGetPathText {
593 fn eq(&self, _other: &Self) -> bool {
594 true
595 }
596}
597
598impl Eq for JsonbGetPathText {}
599
600impl Hash for JsonbGetPathText {
601 fn hash<H: Hasher>(&self, state: &mut H) {
602 "jsonb_get_path_text".hash(state);
603 }
604}
605
606impl ScalarUDFImpl for JsonbGetPathText {
607 fn as_any(&self) -> &dyn std::any::Any {
608 self
609 }
610
611 fn name(&self) -> &'static str {
612 "jsonb_get_path_text"
613 }
614
615 fn signature(&self) -> &Signature {
616 &self.signature
617 }
618
619 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
620 Ok(DataType::Utf8)
621 }
622
623 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
624 let expanded = expand_args(&args.args)?;
625 let jsonb_arr = expanded[0]
626 .as_any()
627 .downcast_ref::<LargeBinaryArray>()
628 .ok_or_else(|| {
629 datafusion_common::DataFusionError::Internal(
630 "jsonb_get_path_text: first arg must be LargeBinary".into(),
631 )
632 })?;
633 let path_arr = expanded[1]
634 .as_any()
635 .downcast_ref::<ListArray>()
636 .ok_or_else(|| {
637 datafusion_common::DataFusionError::Internal(
638 "jsonb_get_path_text: second arg must be List<Utf8>".into(),
639 )
640 })?;
641
642 let mut builder = StringBuilder::with_capacity(jsonb_arr.len(), 256);
643 for i in 0..jsonb_arr.len() {
644 if jsonb_arr.is_null(i) || path_arr.is_null(i) {
645 builder.append_null();
646 } else {
647 let jsonb = jsonb_arr.value(i);
648 let path_list = path_arr.value(i);
649 let path_strings = path_list
650 .as_any()
651 .downcast_ref::<StringArray>()
652 .ok_or_else(|| {
653 datafusion_common::DataFusionError::Internal(
654 "jsonb_get_path_text: path elements must be Utf8".into(),
655 )
656 })?;
657 match walk_path(jsonb, path_strings) {
658 Some(val) => match json_types::jsonb_to_text(val) {
659 Some(text) => builder.append_value(&text),
660 None => builder.append_null(),
661 },
662 None => builder.append_null(),
663 }
664 }
665 }
666 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
667 }
668}