sqlint/ast/function/
json_extract.rs

1use super::Function;
2use crate::ast::Expression;
3use std::borrow::Cow;
4
5#[derive(Debug, Clone, PartialEq)]
6pub struct JsonExtract<'a> {
7    pub(crate) column: Box<Expression<'a>>,
8    pub(crate) path: JsonPath<'a>,
9    pub(crate) extract_as_string: bool,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum JsonPath<'a> {
14    #[cfg(feature = "mysql")]
15    String(Cow<'a, str>),
16    #[cfg(feature = "postgresql")]
17    Array(Vec<Cow<'a, str>>),
18}
19
20impl<'a> JsonPath<'a> {
21    #[cfg(feature = "mysql")]
22    pub fn string<S>(string: S) -> JsonPath<'a>
23    where
24        S: Into<Cow<'a, str>>,
25    {
26        JsonPath::String(string.into())
27    }
28
29    #[cfg(feature = "postgresql")]
30    pub fn array<A, V>(array: A) -> JsonPath<'a>
31    where
32        V: Into<Cow<'a, str>>,
33        A: Into<Vec<V>>,
34    {
35        JsonPath::Array(array.into().into_iter().map(|v| v.into()).collect())
36    }
37}
38
39/// Extracts a subset of a JSON blob given a path.
40/// Two types of paths can be used:
41/// - `String` paths, referring to JSON paths. This is supported by MySQL only.
42/// - `Array` paths, supported by Postgres only.
43///
44/// For PostgreSQL:
45/// ```rust
46/// # use sqlint::{ast::*, visitor::{Visitor, Postgres}};
47/// # fn main() -> Result<(), sqlint::error::Error> {
48/// let extract: Expression = json_extract(Column::from(("users", "json")), JsonPath::array(["a", "b"]), false).into();
49/// let query = Select::from_table("users").so_that(extract.equals("c"));
50/// let (sql, params) = Postgres::build(query)?;
51/// assert_eq!("SELECT \"users\".* FROM \"users\" WHERE (\"users\".\"json\"#>ARRAY[$1, $2]::text[])::jsonb = $3", sql);
52/// assert_eq!(vec![Value::text("a"), Value::text("b"), Value::text("c")], params);
53/// # Ok(())
54/// # }
55/// ```
56/// For MySQL:
57/// ```rust
58/// # use sqlint::{ast::*, visitor::{Visitor, Mysql}};
59/// # fn main() -> Result<(), sqlint::error::Error> {
60/// let extract: Expression = json_extract(Column::from(("users", "json")), JsonPath::string("$.a.b"), false).into();
61/// let query = Select::from_table("users").so_that(extract.equals("c"));
62/// let (sql, params) = Mysql::build(query)?;
63/// assert_eq!(r#"SELECT `users`.* FROM `users` WHERE (JSON_CONTAINS(JSON_EXTRACT(`users`.`json`, ?), ?) AND JSON_CONTAINS(?, JSON_EXTRACT(`users`.`json`, ?)))"#, sql);
64/// assert_eq!(vec![Value::text("$.a.b"), Value::text("c"), Value::text("c"), Value::text("$.a.b")], params);
65/// # Ok(())
66/// # }
67/// ```
68pub fn json_extract<'a, C, P>(column: C, path: P, extract_as_string: bool) -> Function<'a>
69where
70    C: Into<Expression<'a>>,
71    P: Into<JsonPath<'a>>,
72{
73    let fun = JsonExtract { column: Box::new(column.into()), path: path.into(), extract_as_string };
74
75    fun.into()
76}