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}