risingwave_expr_impl/scalar/
jsonb_path.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 jsonbb::ValueRef;
16use risingwave_common::types::{JsonbRef, JsonbVal};
17use risingwave_expr::{ExprError, Result, function};
18use sql_json_path::{EvalError, JsonPath, ParseError};
19use thiserror_ext::AsReport;
20
21#[function(
22    "jsonb_path_exists(jsonb, varchar) -> boolean",
23    prebuild = "JsonPath::new($1).map_err(parse_error)?"
24)]
25fn jsonb_path_exists2(target: JsonbRef<'_>, path: &JsonPath) -> Result<bool> {
26    path.exists::<ValueRef<'_>>(target.into())
27        .map_err(eval_error)
28}
29
30#[function(
31    "jsonb_path_exists(jsonb, varchar, jsonb) -> boolean",
32    prebuild = "JsonPath::new($1).map_err(parse_error)?"
33)]
34fn jsonb_path_exists3(target: JsonbRef<'_>, vars: JsonbRef<'_>, path: &JsonPath) -> Result<bool> {
35    path.exists_with_vars::<ValueRef<'_>>(target.into(), vars.into())
36        .map_err(eval_error)
37}
38
39/// Checks whether the JSON path returns any item for the specified JSON value.
40/// If the vars argument is specified, it must be a JSON object, and its fields
41/// provide named values to be substituted into the jsonpath expression. If the
42/// silent argument is specified and is true, the function suppresses the same
43/// errors as the @? and @@ operators do.
44///
45/// # Examples
46///
47/// ```slt
48/// query B
49/// select jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}');
50/// ----
51/// t
52/// ```
53#[function(
54    "jsonb_path_exists(jsonb, varchar, jsonb, boolean) -> boolean",
55    prebuild = "JsonPath::new($1).map_err(parse_error)?"
56)]
57fn jsonb_path_exists4(
58    target: JsonbRef<'_>,
59    vars: JsonbRef<'_>,
60    silent: bool,
61    path: &JsonPath,
62) -> Result<Option<bool>> {
63    match jsonb_path_exists3(target, vars, path) {
64        Ok(x) => Ok(Some(x)),
65        Err(_) if silent => Ok(None),
66        Err(e) => Err(e),
67    }
68}
69
70#[function(
71    "jsonb_path_match(jsonb, varchar) -> boolean",
72    prebuild = "JsonPath::new($1).map_err(parse_error)?"
73)]
74fn jsonb_path_match2(target: JsonbRef<'_>, path: &JsonPath) -> Result<Option<bool>> {
75    let matched = path
76        .query::<ValueRef<'_>>(target.into())
77        .map_err(eval_error)?;
78
79    if matched.len() != 1 || !matched[0].as_ref().is_boolean() && !matched[0].as_ref().is_null() {
80        return Err(ExprError::InvalidParam {
81            name: "jsonb_path_match",
82            reason: "single boolean result is expected".into(),
83        });
84    }
85    Ok(matched[0].as_ref().as_bool())
86}
87
88#[function(
89    "jsonb_path_match(jsonb, varchar, jsonb) -> boolean",
90    prebuild = "JsonPath::new($1).map_err(parse_error)?"
91)]
92fn jsonb_path_match3(
93    target: JsonbRef<'_>,
94    vars: JsonbRef<'_>,
95    path: &JsonPath,
96) -> Result<Option<bool>> {
97    let matched = path
98        .query_with_vars::<ValueRef<'_>>(target.into(), vars.into())
99        .map_err(eval_error)?;
100
101    if matched.len() != 1 || !matched[0].as_ref().is_boolean() && !matched[0].as_ref().is_null() {
102        return Err(ExprError::InvalidParam {
103            name: "jsonb_path_match",
104            reason: "single boolean result is expected".into(),
105        });
106    }
107    Ok(matched[0].as_ref().as_bool())
108}
109
110/// Returns the result of a JSON path predicate check for the specified JSON value.
111/// Only the first item of the result is taken into account.
112/// If the result is not Boolean, then NULL is returned.
113/// The optional vars and silent arguments act the same as for `jsonb_path_exists`.
114///
115/// # Examples
116///
117/// ```slt
118/// query B
119/// select jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2, "max":4}');
120/// ----
121/// t
122/// ```
123#[function(
124    "jsonb_path_match(jsonb, varchar, jsonb, boolean) -> boolean",
125    prebuild = "JsonPath::new($1).map_err(parse_error)?"
126)]
127fn jsonb_path_match4(
128    target: JsonbRef<'_>,
129    vars: JsonbRef<'_>,
130    silent: bool,
131    path: &JsonPath,
132) -> Result<Option<bool>> {
133    match jsonb_path_match3(target, vars, path) {
134        Ok(x) => Ok(x),
135        Err(_) if silent => Ok(None),
136        Err(e) => Err(e),
137    }
138}
139
140#[function(
141    "jsonb_path_query(jsonb, varchar) -> setof jsonb",
142    prebuild = "JsonPath::new($1).map_err(parse_error)?"
143)]
144fn jsonb_path_query2<'a>(
145    target: JsonbRef<'a>,
146    path: &JsonPath,
147) -> Result<impl Iterator<Item = JsonbVal> + 'a> {
148    let matched = path
149        .query::<ValueRef<'_>>(target.into())
150        .map_err(eval_error)?;
151    Ok(matched.into_iter().map(|json| json.into_owned().into()))
152}
153
154#[function(
155    "jsonb_path_query(jsonb, varchar, jsonb) -> setof jsonb",
156    prebuild = "JsonPath::new($1).map_err(parse_error)?"
157)]
158fn jsonb_path_query3<'a>(
159    target: JsonbRef<'a>,
160    vars: JsonbRef<'a>,
161    path: &JsonPath,
162) -> Result<impl Iterator<Item = JsonbVal> + 'a> {
163    let matched = path
164        .query_with_vars::<ValueRef<'_>>(target.into(), vars.into())
165        .map_err(eval_error)?;
166    Ok(matched.into_iter().map(|json| json.into_owned().into()))
167}
168
169/// Returns all JSON items returned by the JSON path for the specified JSON value.
170/// The optional vars and silent arguments act the same as for `jsonb_path_exists`.
171///
172/// # Examples
173///
174/// ```slt
175/// query I
176/// select * from jsonb_path_query(jsonb '{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', jsonb '{"min":2, "max":4}');
177/// ----
178/// 2
179/// 3
180/// 4
181/// ```
182#[function(
183    "jsonb_path_query(jsonb, varchar, jsonb, boolean) -> setof jsonb",
184    prebuild = "JsonPath::new($1).map_err(parse_error)?"
185)]
186fn jsonb_path_query4<'a>(
187    target: JsonbRef<'a>,
188    vars: JsonbRef<'a>,
189    silent: bool,
190    path: &JsonPath,
191) -> Result<Option<impl Iterator<Item = JsonbVal> + 'a>> {
192    match jsonb_path_query3(target, vars, path) {
193        Ok(x) => Ok(Some(x)),
194        Err(_) if silent => Ok(None),
195        Err(e) => Err(e),
196    }
197}
198
199#[function(
200    "jsonb_path_query_array(jsonb, varchar) -> jsonb",
201    prebuild = "JsonPath::new($1).map_err(parse_error)?"
202)]
203fn jsonb_path_query_array2(
204    target: JsonbRef<'_>,
205    path: &JsonPath,
206    writer: &mut jsonbb::Builder,
207) -> Result<()> {
208    let matched = path
209        .query::<ValueRef<'_>>(target.into())
210        .map_err(eval_error)?;
211    writer.begin_array();
212    for json in matched {
213        writer.add_value(json.as_ref());
214    }
215    writer.end_array();
216    Ok(())
217}
218
219#[function(
220    "jsonb_path_query_array(jsonb, varchar, jsonb) -> jsonb",
221    prebuild = "JsonPath::new($1).map_err(parse_error)?"
222)]
223fn jsonb_path_query_array3(
224    target: JsonbRef<'_>,
225    vars: JsonbRef<'_>,
226    path: &JsonPath,
227    writer: &mut jsonbb::Builder,
228) -> Result<()> {
229    let matched = path
230        .query_with_vars::<ValueRef<'_>>(target.into(), vars.into())
231        .map_err(eval_error)?;
232    writer.begin_array();
233    for json in matched {
234        writer.add_value(json.as_ref());
235    }
236    writer.end_array();
237    Ok(())
238}
239
240/// Returns all JSON items returned by the JSON path for the specified JSON value, as a JSON array.
241/// The optional vars and silent arguments act the same as for `jsonb_path_exists`.
242///
243/// # Examples
244///
245/// ```slt
246/// query T
247/// select jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}');
248/// ----
249/// [2, 3, 4]
250/// ```
251#[function(
252    "jsonb_path_query_array(jsonb, varchar, jsonb, boolean) -> jsonb",
253    prebuild = "JsonPath::new($1).map_err(parse_error)?"
254)]
255fn jsonb_path_query_array4(
256    target: JsonbRef<'_>,
257    vars: JsonbRef<'_>,
258    silent: bool,
259    path: &JsonPath,
260    writer: &mut jsonbb::Builder,
261) -> Result<Option<()>> {
262    match jsonb_path_query_array3(target, vars, path, writer) {
263        Ok(()) => Ok(Some(())),
264        Err(_) if silent => Ok(None),
265        Err(e) => Err(e),
266    }
267}
268
269#[function(
270    "jsonb_path_query_first(jsonb, varchar) -> jsonb",
271    prebuild = "JsonPath::new($1).map_err(parse_error)?"
272)]
273fn jsonb_path_query_first2(
274    target: JsonbRef<'_>,
275    path: &JsonPath,
276    writer: &mut jsonbb::Builder,
277) -> Result<Option<()>> {
278    let matched = path
279        .query_first::<ValueRef<'_>>(target.into())
280        .map_err(eval_error)?;
281    match matched {
282        Some(json) => {
283            writer.add_value(json.as_ref());
284            Ok(Some(()))
285        }
286        None => Ok(None),
287    }
288}
289
290#[function(
291    "jsonb_path_query_first(jsonb, varchar, jsonb) -> jsonb",
292    prebuild = "JsonPath::new($1).map_err(parse_error)?"
293)]
294fn jsonb_path_query_first3(
295    target: JsonbRef<'_>,
296    vars: JsonbRef<'_>,
297    path: &JsonPath,
298    writer: &mut jsonbb::Builder,
299) -> Result<Option<()>> {
300    let matched = path
301        .query_first_with_vars::<ValueRef<'_>>(target.into(), vars.into())
302        .map_err(eval_error)?;
303    match matched {
304        Some(json) => {
305            writer.add_value(json.as_ref());
306            Ok(Some(()))
307        }
308        None => Ok(None),
309    }
310}
311
312/// Returns the first JSON item returned by the JSON path for the specified JSON value.
313/// Returns NULL if there are no results.
314/// The optional vars and silent arguments act the same as for `jsonb_path_exists`.
315///
316/// # Examples
317///
318/// ```slt
319/// query T
320/// select jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}');
321/// ----
322/// 2
323/// ```
324#[function(
325    "jsonb_path_query_first(jsonb, varchar, jsonb, boolean) -> jsonb",
326    prebuild = "JsonPath::new($1).map_err(parse_error)?"
327)]
328fn jsonb_path_query_first4(
329    target: JsonbRef<'_>,
330    vars: JsonbRef<'_>,
331    silent: bool,
332    path: &JsonPath,
333    writer: &mut jsonbb::Builder,
334) -> Result<Option<()>> {
335    match jsonb_path_query_first3(target, vars, path, writer) {
336        Ok(x) => Ok(x),
337        Err(_) if silent => Ok(None),
338        Err(e) => Err(e),
339    }
340}
341
342fn parse_error(e: ParseError) -> ExprError {
343    ExprError::Parse(e.to_report_string().into())
344}
345
346fn eval_error(e: EvalError) -> ExprError {
347    ExprError::InvalidParam {
348        name: "jsonpath",
349        reason: e.to_report_string().into(),
350    }
351}