risingwave_expr_impl/scalar/
jsonb_access.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use risingwave_common::row::Row;
16use risingwave_common::types::{JsonbRef, ScalarRefImpl};
17use risingwave_expr::{ExprError, Result, function};
18
19/// Extracts JSON object field with the given key.
20///
21/// `jsonb -> text → jsonb`
22///
23/// # Examples
24///
25/// ```slt
26/// query T
27/// select '{"a": {"b":"foo"}}'::jsonb -> 'a';
28/// ----
29/// {"b": "foo"}
30/// ```
31#[function("jsonb_access(jsonb, varchar) -> jsonb")]
32pub fn jsonb_object_field<'a>(v: JsonbRef<'a>, p: &str) -> Option<JsonbRef<'a>> {
33    v.access_object_field(p)
34}
35
36/// Extracts n'th element of JSON array (array elements are indexed from zero,
37/// but negative integers count from the end).
38///
39/// `jsonb -> integer → jsonb`
40///
41/// # Examples
42///
43/// ```slt
44/// query T
45/// select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> 2;
46/// ----
47/// {"c": "baz"}
48///
49/// query T
50/// select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> -3;
51/// ----
52/// {"a": "foo"}
53/// ```
54#[function("jsonb_access(jsonb, int4) -> jsonb")]
55pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option<JsonbRef<'_>> {
56    let idx = if p < 0 {
57        let Ok(len) = v.array_len() else {
58            return None;
59        };
60        if ((-p) as usize) > len {
61            return None;
62        } else {
63            len - ((-p) as usize)
64        }
65    } else {
66        p as usize
67    };
68    v.access_array_element(idx)
69}
70
71/// Extracts JSON sub-object at the specified path, where path elements can be either field keys or array indexes.
72///
73/// - `jsonb #> text[] → jsonb`
74/// - `jsonb_extract_path ( from_json jsonb, VARIADIC path_elems text[] ) → jsonb`
75///
76/// # Examples
77///
78/// ```slt
79/// query T
80/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #> '{a,b,1}'::text[];
81/// ----
82/// "bar"
83///
84/// query T
85/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #> '{a,b,null}'::text[];
86/// ----
87/// NULL
88///
89/// query T
90/// select jsonb_extract_path('{"a": {"b": ["foo","bar"]}}', 'a', 'b', '1');
91/// ----
92/// "bar"
93///
94/// query T
95/// select jsonb_extract_path('{"a": {"b": ["foo","bar"]}}', variadic array['a', 'b', '1']);
96/// ----
97/// "bar"
98/// ```
99#[function("jsonb_extract_path(jsonb, variadic varchar[]) -> jsonb")]
100pub fn jsonb_extract_path(v: JsonbRef<'_>, path: impl Row) -> Option<JsonbRef<'_>> {
101    let mut jsonb = v;
102    for key in path.iter() {
103        // return null if any element is null
104        let key = key?.into_utf8();
105        if jsonb.is_array() {
106            // return null if the key is not an integer
107            let idx = key.parse().ok()?;
108            jsonb = jsonb_array_element(jsonb, idx)?;
109        } else if jsonb.is_object() {
110            jsonb = jsonb_object_field(jsonb, key)?;
111        } else {
112            return None;
113        }
114    }
115    Some(jsonb)
116}
117
118/// Extracts JSON object field with the given key, as text.
119///
120/// `jsonb ->> text → text`
121///
122/// # Examples
123///
124/// ```slt
125/// query T
126/// select '{"a":1,"b":2}'::jsonb ->> 'b';
127/// ----
128/// 2
129///
130/// query T
131/// select '{"a":1,"b":null}'::jsonb ->> 'b';
132/// ----
133/// NULL
134/// ```
135#[function("jsonb_access_str(jsonb, varchar) -> varchar")]
136pub fn jsonb_object_field_str(
137    v: JsonbRef<'_>,
138    p: &str,
139    writer: &mut impl std::fmt::Write,
140) -> Option<()> {
141    let jsonb = jsonb_object_field(v, p)?;
142    if jsonb.is_jsonb_null() {
143        return None;
144    }
145    jsonb.force_str(writer).unwrap();
146    Some(())
147}
148
149/// Extracts n'th element of JSON array, as text.
150///
151/// `jsonb ->> integer → text`
152///
153/// # Examples
154///
155/// ```slt
156/// query T
157/// select '[1,2,3]'::jsonb ->> 2;
158/// ----
159/// 3
160///
161/// query T
162/// select '[1,2,null]'::jsonb ->> 2;
163/// ----
164/// NULL
165/// ```
166#[function("jsonb_access_str(jsonb, int4) -> varchar")]
167pub fn jsonb_array_element_str(
168    v: JsonbRef<'_>,
169    p: i32,
170    writer: &mut impl std::fmt::Write,
171) -> Option<()> {
172    let jsonb = jsonb_array_element(v, p)?;
173    if jsonb.is_jsonb_null() {
174        return None;
175    }
176    jsonb.force_str(writer).unwrap();
177    Some(())
178}
179
180/// Extracts JSON sub-object at the specified path as text.
181///
182/// - `jsonb #>> text[] → text`
183/// - `jsonb_extract_path_text ( from_json jsonb, VARIADIC path_elems text[] ) → text`
184///
185/// # Examples
186///
187/// ```slt
188/// query T
189/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #>> '{a,b,1}'::text[];
190/// ----
191/// bar
192///
193/// query T
194/// select '{"a": {"b": ["foo",null]}}'::jsonb #>> '{a,b,1}'::text[];
195/// ----
196/// NULL
197///
198/// query T
199/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #>> '{a,b,null}'::text[];
200/// ----
201/// NULL
202///
203/// query T
204/// select jsonb_extract_path_text('{"a": {"b": ["foo","bar"]}}', 'a', 'b', '1');
205/// ----
206/// bar
207///
208/// query T
209/// select jsonb_extract_path_text('{"a": {"b": ["foo","bar"]}}', variadic array['a', 'b', '1']);
210/// ----
211/// bar
212/// ```
213#[function("jsonb_extract_path_text(jsonb, variadic varchar[]) -> varchar")]
214pub fn jsonb_extract_path_text(
215    v: JsonbRef<'_>,
216    path: impl Row,
217    writer: &mut impl std::fmt::Write,
218) -> Option<()> {
219    let jsonb = jsonb_extract_path(v, path)?;
220    if jsonb.is_jsonb_null() {
221        return None;
222    }
223    jsonb.force_str(writer).unwrap();
224    Some(())
225}
226
227/// Converts the a JSONB array to a SQL array of JSONB elements.
228///
229/// This is equivalent to `jsonb_array_elements` followed by `array_agg` or `array` in most cases.
230///
231/// ```slt
232/// query T
233/// select
234///     input,
235///     (select array_agg(v) from jsonb_array_elements(input) as v) as array_agg,
236///     array(select jsonb_array_elements(input)),
237///     jsonb_to_array(input)
238/// from (values
239///     (null::jsonb),
240///     ('[]'::jsonb)
241/// ) as t(input);
242/// ----
243/// NULL NULL {} NULL
244/// []   NULL {} {}
245///
246/// query T
247/// select jsonb_to_array('[1,"foo",null,true,[false,"bar"],{"a":2}]');
248/// ----
249/// {1,"\"foo\"","null",true,"[false, \"bar\"]","{\"a\": 2}"}
250///
251/// query error parsing
252/// select jsonb_to_array('');
253///
254/// query error cannot extract elements from a jsonb object
255/// select jsonb_to_array('{"a": 1}');
256/// ```
257#[function("jsonb_to_array(jsonb) -> jsonb[]")]
258fn jsonb_to_array(
259    v: JsonbRef<'_>,
260    writer: &mut impl risingwave_common::array::ListWrite,
261) -> Result<()> {
262    let iter = v
263        .array_elements()
264        .map_err(|e| ExprError::InvalidParam {
265            name: "jsonb",
266            reason: e.into(),
267        })?
268        .map(|elem| Some(ScalarRefImpl::Jsonb(elem)));
269    writer.write_iter(iter);
270    Ok(())
271}