use std::fmt::{Debug, Display};
use vantage_core::util::IntoVec;
use vantage_expressions::{Expression, Expressive, ExpressiveEnum};
#[derive(Debug, Clone)]
pub struct JsonExtract<T: Debug + Display + Clone> {
source: Expression<T>,
path: Vec<String>,
as_json: bool,
}
impl<T: Debug + Display + Clone> JsonExtract<T> {
pub fn new(source: impl Expressive<T>, path: impl IntoVec<String>) -> Self {
Self {
source: source.expr(),
path: path.into_vec(),
as_json: false,
}
}
pub fn as_json(mut self) -> Self {
self.as_json = true;
self
}
}
pub fn json_extract<T: Debug + Display + Clone>(
source: impl Expressive<T>,
path: impl IntoVec<String>,
) -> JsonExtract<T> {
JsonExtract::new(source, path)
}
fn sql_lit<T: Debug + Display + Clone>(s: &str) -> Expression<T> {
let escaped = s.replace('\'', "''");
Expression::new(format!("'{escaped}'"), vec![])
}
#[cfg(feature = "sqlite")]
impl Expressive<crate::sqlite::types::AnySqliteType>
for JsonExtract<crate::sqlite::types::AnySqliteType>
{
fn expr(&self) -> Expression<crate::sqlite::types::AnySqliteType> {
let json_path = format!("$.{}", self.path.join("."));
Expression::new(
"JSON_EXTRACT({}, {})",
vec![
ExpressiveEnum::Nested(self.source.clone()),
ExpressiveEnum::Nested(sql_lit(&json_path)),
],
)
}
}
#[cfg(feature = "mysql")]
impl Expressive<crate::mysql::types::AnyMysqlType>
for JsonExtract<crate::mysql::types::AnyMysqlType>
{
fn expr(&self) -> Expression<crate::mysql::types::AnyMysqlType> {
let json_path = format!("$.{}", self.path.join("."));
let op = if self.as_json { " -> " } else { " ->> " };
Expression::new(
format!("{{}}{op}{{}}"),
vec![
ExpressiveEnum::Nested(self.source.clone()),
ExpressiveEnum::Nested(sql_lit(&json_path)),
],
)
}
}
#[cfg(feature = "postgres")]
impl Expressive<crate::postgres::types::AnyPostgresType>
for JsonExtract<crate::postgres::types::AnyPostgresType>
{
fn expr(&self) -> Expression<crate::postgres::types::AnyPostgresType> {
assert!(
!self.path.is_empty(),
"JsonExtract requires at least one path segment"
);
let mut current = self.source.clone();
let last = self.path.len() - 1;
for (i, field) in self.path.iter().enumerate() {
let op = if i == last && !self.as_json {
" ->> "
} else {
" -> "
};
current = Expression::new(
format!("{{}}{op}{{}}"),
vec![
ExpressiveEnum::Nested(current),
ExpressiveEnum::Nested(sql_lit(field)),
],
);
}
current
}
}