#[cfg(not(feature = "std"))]
use crate::prelude::*;
use crate::values::SQLiteValue;
use drizzle_core::{SQL, ToSQL};
pub fn json<'a>(value: impl ToSQL<'a, SQLiteValue<'a>>) -> SQL<'a, SQLiteValue<'a>> {
SQL::func("json", value.to_sql())
}
pub fn jsonb<'a>(value: impl ToSQL<'a, SQLiteValue<'a>>) -> SQL<'a, SQLiteValue<'a>> {
SQL::func("jsonb", value.to_sql())
}
pub fn json_eq<'a, L, R>(left: L, field: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
left.to_sql()
.append(SQL::raw(" ->> "))
.append(SQL::param(SQLiteValue::from(field)))
.append(SQL::raw(" = "))
.append(SQL::param(value.into()))
}
pub fn json_ne<'a, L, R>(left: L, field: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
left.to_sql()
.append(SQL::raw(" ->> "))
.append(SQL::param(SQLiteValue::from(field)))
.append(SQL::raw(" != "))
.append(SQL::param(value.into()))
}
pub fn json_contains<'a, L, R>(left: L, path: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
SQL::raw("json_extract(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(") = "))
.append(SQL::param(value.into()))
}
pub fn json_exists<'a, L>(left: L, path: &'a str) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
{
SQL::raw("json_type(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(") IS NOT NULL"))
}
pub fn json_not_exists<'a, L>(left: L, path: &'a str) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
{
SQL::raw("json_type(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(") IS NULL"))
}
pub fn json_array_contains<'a, L, R>(left: L, path: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
SQL::raw("EXISTS(SELECT 1 FROM json_each(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(") WHERE value = "))
.append(SQL::param(value.into()))
.append(SQL::raw(")"))
}
pub fn json_object_contains_key<'a, L>(
left: L,
path: &'a str,
key: &'a str,
) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
{
let full_path = if path.ends_with('$') || path.is_empty() {
format!("$.{key}")
} else {
format!("{path}.{key}")
};
SQL::raw("json_type(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(full_path)))
.append(SQL::raw(") IS NOT NULL"))
}
pub fn json_text_contains<'a, L, R>(left: L, path: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
SQL::raw("instr(lower(json_extract(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(")), lower("))
.append(SQL::param(value.into()))
.append(SQL::raw(")) > 0"))
}
pub fn json_gt<'a, L, R>(left: L, path: &'a str, value: R) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
R: Into<SQLiteValue<'a>>,
{
SQL::raw("CAST(json_extract(")
.append(left.to_sql())
.append(SQL::raw(", "))
.append(SQL::param(SQLiteValue::from(path)))
.append(SQL::raw(") AS NUMERIC) > "))
.append(SQL::param(value.into()))
}
pub fn json_extract<'a, L>(left: L, path: impl AsRef<str>) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
{
left.to_sql()
.append(SQL::raw(" ->> "))
.append(SQL::param(SQLiteValue::from(path.as_ref().to_owned())))
}
pub fn json_extract_text<'a, L>(left: L, path: &'a str) -> SQL<'a, SQLiteValue<'a>>
where
L: ToSQL<'a, SQLiteValue<'a>>,
{
left.to_sql()
.append(SQL::raw(" -> "))
.append(SQL::param(SQLiteValue::from(path)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_paths_are_parameters_in_stable_order() {
let expression = json_contains(
SQL::param(SQLiteValue::from("document")),
"$.preferences['quoted']",
"dark",
);
assert_eq!(expression.sql(), "json_extract( ? , ? ) = ?");
let document = SQLiteValue::from("document");
let path = SQLiteValue::from("$.preferences['quoted']");
let value = SQLiteValue::from("dark");
assert_eq!(
expression.params().collect::<Vec<_>>(),
vec![&document, &path, &value]
);
}
#[test]
fn json_object_key_is_bound_as_data() {
let expression =
json_object_contains_key(SQL::<SQLiteValue>::raw("metadata"), "$", "quote'key");
assert_eq!(expression.sql(), "json_type( metadata , ? ) IS NOT NULL");
let path = SQLiteValue::from("$.quote'key");
assert_eq!(expression.params().collect::<Vec<_>>(), vec![&path]);
}
}