use crate::DbType;
pub struct JsonQuery {
db_type: DbType,
column: String,
path: Option<String>,
}
impl JsonQuery {
pub fn new(db_type: DbType, column: impl Into<String>) -> Self {
Self {
db_type,
column: column.into(),
path: None,
}
}
#[must_use]
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub fn build_extract(&self) -> String {
let path = self.path.as_deref().unwrap_or("");
if path.is_empty() {
return match self.db_type {
DbType::PostgreSQL => format!("\"{}\"", self.column),
_ => format!("`{}`", self.column),
};
}
match self.db_type {
DbType::MySQL => {
format!("`{}`->'$.{}'", self.column, path)
}
DbType::PostgreSQL => {
let parts: Vec<&str> = path.split('.').collect();
let mut expr = format!("\"{}\"", self.column);
for p in parts {
expr.push_str(&format!("->>'{}'", p));
}
expr
}
DbType::Sqlite => {
format!("json_extract(`{}`, '$.{}')", self.column, path)
}
_ => {
format!("`{}`->'$.{}'", self.column, path)
}
}
}
pub fn eq_string(self, value: &str) -> String {
format!("{} = '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn eq_i64(self, value: i64) -> String {
format!("{} = {}", self.build_extract(), value)
}
pub fn eq_f64(self, value: f64) -> String {
format!("{} = {}", self.build_extract(), value)
}
pub fn ne_string(self, value: &str) -> String {
format!("{} != '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn gt_string(self, value: &str) -> String {
format!("{} > '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn lt_string(self, value: &str) -> String {
format!("{} < '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn ge_string(self, value: &str) -> String {
format!("{} >= '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn le_string(self, value: &str) -> String {
format!("{} <= '{}'", self.build_extract(), escape_sql_str(value))
}
pub fn ge_i64(self, value: i64) -> String {
format!("{} >= {}", self.build_extract(), value)
}
pub fn le_i64(self, value: i64) -> String {
format!("{} <= {}", self.build_extract(), value)
}
pub fn gt_i64(self, value: i64) -> String {
format!("{} > {}", self.build_extract(), value)
}
pub fn lt_i64(self, value: i64) -> String {
format!("{} < {}", self.build_extract(), value)
}
pub fn between_i64(self, low: i64, high: i64) -> String {
format!("{} BETWEEN {} AND {}", self.build_extract(), low, high)
}
pub fn in_strs(self, values: &[&str]) -> String {
let list: Vec<String> = values
.iter()
.map(|v| format!("'{}'", escape_sql_str(v)))
.collect();
format!("{} IN ({})", self.build_extract(), list.join(", "))
}
pub fn in_i64s(self, values: &[i64]) -> String {
let list: Vec<String> = values.iter().map(|v| v.to_string()).collect();
format!("{} IN ({})", self.build_extract(), list.join(", "))
}
pub fn like(self, value: &str) -> String {
format!(
"{} LIKE '%{}%'",
self.build_extract(),
escape_sql_str(value)
)
}
pub fn is_null(self) -> String {
format!("{} IS NULL", self.build_extract())
}
pub fn is_not_null(self) -> String {
format!("{} IS NOT NULL", self.build_extract())
}
pub fn has_key(self) -> String {
let path = self.path.as_deref().unwrap_or("");
match self.db_type {
DbType::MySQL => format!("JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", self.column, path),
DbType::PostgreSQL => {
let parts: Vec<&str> = path.split('.').collect();
if parts.len() <= 1 {
format!("\"{}\" ? '{}'", self.column, path)
} else {
let path_braced = parts.join(",");
format!(
"\"{}\" #> '{{{{{}}}}}' IS NOT NULL",
self.column, path_braced
)
}
}
DbType::Sqlite => format!("json_type(`{}`, '$.{}') IS NOT NULL", self.column, path),
_ => format!("JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", self.column, path),
}
}
pub fn json_type_eq(self, expected_type: &str) -> String {
let path = self.path.as_deref().unwrap_or("");
match self.db_type {
DbType::MySQL => {
let upper = expected_type.to_uppercase();
format!("JSON_TYPE(`{}`->'$.{}') = '{}'", self.column, path, upper)
}
DbType::PostgreSQL => {
let parts: Vec<&str> = path.split('.').collect();
let path_braced = parts.join(",");
format!(
"json_typeof(\"{}\"#>>'{{{}}}') = '{}'",
self.column, path_braced, expected_type
)
}
DbType::Sqlite => format!(
"json_type(`{}`, '$.{}') = '{}'",
self.column, path, expected_type
),
_ => {
let upper = expected_type.to_uppercase();
format!("JSON_TYPE(`{}`->'$.{}') = '{}'", self.column, path, upper)
}
}
}
pub fn contains(self, value: &str) -> String {
match self.db_type {
DbType::MySQL => {
let path = self.path.as_deref().unwrap_or("");
format!(
"JSON_CONTAINS(`{}`, '\"{}\"', '$.{}')",
self.column,
escape_sql_str(value),
path
)
}
DbType::PostgreSQL => {
let path = self.path.as_deref().unwrap_or("");
format!(
"\"{}\" @> '{{\"{}\":\"{}\"}}'",
self.column,
path,
escape_sql_str(value)
)
}
DbType::Sqlite => {
let path = self.path.as_deref().unwrap_or("");
format!(
"EXISTS (SELECT 1 FROM json_each(json_extract(`{}`, '$.{}')) WHERE value = '{}')",
self.column,
path,
escape_sql_str(value)
)
}
_ => {
let path = self.path.as_deref().unwrap_or("");
format!(
"JSON_CONTAINS(`{}`, '\"{}\"', '$.{}')",
self.column,
escape_sql_str(value),
path
)
}
}
}
pub fn array_length_eq(self, length: i64) -> String {
let path = self.path.as_deref().unwrap_or("");
match self.db_type {
DbType::MySQL => {
format!("JSON_LENGTH(`{}`->'$.{}') = {}", self.column, path, length)
}
DbType::PostgreSQL => {
let parts: Vec<&str> = path.split('.').collect();
let path_str = parts.join(",");
format!(
"jsonb_array_length(\"{}\"#>>'{{{}}}') = {}",
self.column, path_str, length
)
}
DbType::Sqlite => {
format!(
"json_array_length(json_extract(`{}`, '$.{}')) = {}",
self.column, path, length
)
}
_ => {
format!("JSON_LENGTH(`{}`->'$.{}') = {}", self.column, path, length)
}
}
}
pub fn column(&self) -> &str {
&self.column
}
pub fn db_type(&self) -> DbType {
self.db_type
}
}
pub struct JsonUpdate {
db_type: DbType,
column: String,
sets: Vec<(String, String)>,
array_appends: Vec<(String, String)>,
removes: Vec<String>,
}
impl JsonUpdate {
pub fn new(db_type: DbType, column: impl Into<String>) -> Self {
Self {
db_type,
column: column.into(),
sets: Vec::new(),
array_appends: Vec::new(),
removes: Vec::new(),
}
}
#[must_use]
pub fn set_str(mut self, key: impl Into<String>, value: &str) -> Self {
self.sets
.push((key.into(), format!("'{}'", escape_sql_str(value))));
self
}
#[must_use]
pub fn set_i64(mut self, key: impl Into<String>, value: i64) -> Self {
self.sets.push((key.into(), value.to_string()));
self
}
#[must_use]
pub fn set_bool(mut self, key: impl Into<String>, value: bool) -> Self {
self.sets.push((
key.into(),
if value {
"true".to_string()
} else {
"false".to_string()
},
));
self
}
#[must_use]
pub fn array_append_str(mut self, key: impl Into<String>, value: &str) -> Self {
let k = key.into();
let v = format!("'{}'", escape_sql_str(value));
self.array_appends.push((k, v));
self
}
#[must_use]
pub fn array_append_i64(mut self, key: impl Into<String>, value: i64) -> Self {
let k = key.into();
let v = value.to_string();
self.array_appends.push((k, v));
self
}
#[must_use]
pub fn remove_key(mut self, key: impl Into<String>) -> Self {
self.removes.push(key.into());
self
}
pub fn build_set(&self) -> String {
let empty =
self.sets.is_empty() && self.array_appends.is_empty() && self.removes.is_empty();
if empty {
return match self.db_type {
DbType::PostgreSQL => format!("\"{}\" = \"{}\"", self.column, self.column),
_ => format!("`{}` = `{}`", self.column, self.column),
};
}
match self.db_type {
DbType::MySQL => self.build_set_mysql(),
DbType::PostgreSQL => self.build_set_pg(),
DbType::Sqlite => self.build_set_sqlite(),
_ => self.build_set_mysql(),
}
}
fn build_set_mysql(&self) -> String {
let mut expr = format!("`{}`", self.column);
if !self.sets.is_empty() {
let args: Vec<String> = self
.sets
.iter()
.map(|(k, v)| format!("'$.{}', {}", k, v))
.collect();
expr = format!("JSON_SET({}, {})", expr, args.join(", "));
}
for (k, v) in &self.array_appends {
expr = format!("JSON_ARRAY_APPEND({}, '$.{}', {})", expr, k, v);
}
if !self.removes.is_empty() {
let args: Vec<String> = self.removes.iter().map(|k| format!("'$.{}'", k)).collect();
expr = format!("JSON_REMOVE({}, {})", expr, args.join(", "));
}
format!("`{}` = {}", self.column, expr)
}
fn build_set_pg(&self) -> String {
let mut expr = format!("\"{}\"", self.column);
for (k, v) in &self.sets {
expr = format!("jsonb_set({}, '{{{}}}', {})", expr, k, v);
}
for (k, v) in &self.array_appends {
let current = expr.clone();
expr = format!(
"jsonb_set({}, '{{{}}}', ({}#>'{{{}}}') || to_jsonb({}::text))",
current, k, current, k, v
);
}
for k in &self.removes {
expr = format!("({} - '{}')", expr, k);
}
format!("\"{}\" = {}", self.column, expr)
}
fn build_set_sqlite(&self) -> String {
let mut expr = format!("`{}`", self.column);
if !self.sets.is_empty() {
let args: Vec<String> = self
.sets
.iter()
.map(|(k, v)| format!("'$.{}', {}", k, v))
.collect();
expr = format!("json_set({}, {})", expr, args.join(", "));
}
for (k, v) in &self.array_appends {
let current = expr.clone();
expr = format!(
"json_set({}, '$.{}', json_insert({}->'$.{}', '$[#]', {}))",
current, k, current, k, v
);
}
if !self.removes.is_empty() {
let args: Vec<String> = self.removes.iter().map(|k| format!("'$.{}'", k)).collect();
expr = format!("json_remove({}, {})", expr, args.join(", "));
}
format!("`{}` = {}", self.column, expr)
}
}
fn escape_sql_str(s: &str) -> String {
s.replace('\'', "''")
}
#[cfg(test)]
#[allow(deprecated)] mod tests {
use super::*;
#[test]
fn mysql_extract_single_field() {
let q = JsonQuery::new(DbType::MySQL, "prefs").path("theme");
assert_eq!(q.build_extract(), "`prefs`->'$.theme'");
}
#[test]
fn mysql_extract_nested_path() {
let q = JsonQuery::new(DbType::MySQL, "prefs").path("a.b.c");
assert_eq!(q.build_extract(), "`prefs`->'$.a.b.c'");
}
#[test]
fn pg_extract_single_field() {
let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("theme");
assert_eq!(q.build_extract(), "\"prefs\"->>'theme'");
}
#[test]
fn pg_extract_nested_path() {
let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("a.b.c");
assert_eq!(q.build_extract(), "\"prefs\"->>'a'->>'b'->>'c'");
}
#[test]
fn sqlite_extract_single_field() {
let q = JsonQuery::new(DbType::Sqlite, "prefs").path("theme");
assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.theme')");
}
#[test]
fn sqlite_extract_nested_path() {
let q = JsonQuery::new(DbType::Sqlite, "prefs").path("a.b.c");
assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.a.b.c')");
}
#[test]
fn mysql_eq_string() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("theme")
.eq_string("dark");
assert_eq!(cond, "`prefs`->'$.theme' = 'dark'");
}
#[test]
fn mysql_eq_i64() {
let cond = JsonQuery::new(DbType::MySQL, "stats")
.path("visits")
.eq_i64(100);
assert_eq!(cond, "`stats`->'$.visits' = 100");
}
#[test]
fn mysql_eq_f64() {
let cond = JsonQuery::new(DbType::MySQL, "stats")
.path("rate")
.eq_f64(0.95);
assert!(cond.starts_with("`stats`->'$.rate' = 0.95"));
}
#[test]
fn mysql_ne_string() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("theme")
.ne_string("dark");
assert_eq!(cond, "`prefs`->'$.theme' != 'dark'");
}
#[test]
fn mysql_gt_lt_string() {
let gt = JsonQuery::new(DbType::MySQL, "prefs")
.path("name")
.gt_string("m");
assert_eq!(gt, "`prefs`->'$.name' > 'm'");
let lt = JsonQuery::new(DbType::MySQL, "prefs")
.path("name")
.lt_string("n");
assert_eq!(lt, "`prefs`->'$.name' < 'n'");
}
#[test]
fn mysql_like() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("bio")
.like("engineer");
assert_eq!(cond, "`prefs`->'$.bio' LIKE '%engineer%'");
}
#[test]
fn mysql_is_null_and_not_null() {
let n = JsonQuery::new(DbType::MySQL, "prefs").path("opt").is_null();
assert_eq!(n, "`prefs`->'$.opt' IS NULL");
let nn = JsonQuery::new(DbType::MySQL, "prefs")
.path("opt")
.is_not_null();
assert_eq!(nn, "`prefs`->'$.opt' IS NOT NULL");
}
#[test]
fn escape_single_quote_in_value() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("name")
.eq_string("O'Brien");
assert_eq!(cond, "`prefs`->'$.name' = 'O''Brien'");
}
#[test]
fn mysql_contains() {
let cond = JsonQuery::new(DbType::MySQL, "tags")
.path("category")
.contains("rust");
assert_eq!(cond, "JSON_CONTAINS(`tags`, '\"rust\"', '$.category')");
}
#[test]
fn pg_contains() {
let cond = JsonQuery::new(DbType::PostgreSQL, "tags")
.path("category")
.contains("rust");
assert_eq!(cond, "\"tags\" @> '{\"category\":\"rust\"}'");
}
#[test]
fn sqlite_contains() {
let cond = JsonQuery::new(DbType::Sqlite, "tags")
.path("category")
.contains("rust");
assert_eq!(
cond,
"EXISTS (SELECT 1 FROM json_each(json_extract(`tags`, '$.category')) WHERE value = 'rust')"
);
}
#[test]
fn mysql_array_length() {
let cond = JsonQuery::new(DbType::MySQL, "items")
.path("list")
.array_length_eq(3);
assert_eq!(cond, "JSON_LENGTH(`items`->'$.list') = 3");
}
#[test]
fn pg_array_length() {
let cond = JsonQuery::new(DbType::PostgreSQL, "items")
.path("a.b")
.array_length_eq(3);
assert_eq!(cond, "jsonb_array_length(\"items\"#>>'{a,b}') = 3");
}
#[test]
fn sqlite_array_length() {
let cond = JsonQuery::new(DbType::Sqlite, "items")
.path("list")
.array_length_eq(3);
assert_eq!(
cond,
"json_array_length(json_extract(`items`, '$.list')) = 3"
);
}
#[test]
fn mysql_json_set_single() {
let set = JsonUpdate::new(DbType::MySQL, "prefs")
.set_str("theme", "dark")
.build_set();
assert_eq!(set, "`prefs` = JSON_SET(`prefs`, '$.theme', 'dark')");
}
#[test]
fn mysql_json_set_multi() {
let set = JsonUpdate::new(DbType::MySQL, "prefs")
.set_str("theme", "dark")
.set_i64("volume", 80)
.set_bool("autoplay", true)
.build_set();
assert!(set.contains("JSON_SET(`prefs`"));
assert!(set.contains("'$.theme', 'dark'"));
assert!(set.contains("'$.volume', 80"));
assert!(set.contains("'$.autoplay', true"));
}
#[test]
fn pg_json_set_single() {
let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
.set_str("theme", "dark")
.build_set();
assert_eq!(set, "\"prefs\" = jsonb_set(\"prefs\", '{theme}', 'dark')");
}
#[test]
fn sqlite_json_set_single() {
let set = JsonUpdate::new(DbType::Sqlite, "prefs")
.set_str("theme", "dark")
.build_set();
assert_eq!(set, "`prefs` = json_set(`prefs`, '$.theme', 'dark')");
}
#[test]
fn json_update_empty_set() {
let set = JsonUpdate::new(DbType::MySQL, "prefs").build_set();
assert_eq!(set, "`prefs` = `prefs`");
}
#[test]
fn json_query_integrate_with_quick_query() {
use crate::get_dialect;
use crate::quick_query::Db;
let dialect = get_dialect(DbType::MySQL).expect("MySQL");
let json_cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("theme")
.eq_string("dark");
let sql = Db::new(dialect)
.name("users")
.where_cond(json_cond)
.build_select();
assert_eq!(
sql,
"SELECT * FROM `users` WHERE `prefs`->'$.theme' = 'dark'"
);
}
#[test]
fn json_update_integrate_with_quick_query() {
use crate::get_dialect;
let _dialect = get_dialect(DbType::MySQL).expect("MySQL");
let set_clause = JsonUpdate::new(DbType::MySQL, "prefs")
.set_str("theme", "light")
.build_set();
assert!(set_clause.contains("JSON_SET(`prefs`"));
assert!(set_clause.contains("'$.theme', 'light'"));
let sql = format!("UPDATE `users` SET {} WHERE id = 1", set_clause);
assert!(sql.starts_with("UPDATE `users` SET `prefs` = JSON_SET(`prefs`"));
assert!(sql.contains("WHERE id = 1"));
}
#[test]
fn empty_path_extracts_root() {
let q = JsonQuery::new(DbType::MySQL, "data").build_extract();
assert_eq!(q, "`data`");
}
#[test]
fn empty_path_extracts_root_pg() {
let q = JsonQuery::new(DbType::PostgreSQL, "data").build_extract();
assert_eq!(q, "\"data\"");
}
#[test]
fn empty_path_extracts_root_sqlite() {
let q = JsonQuery::new(DbType::Sqlite, "data").build_extract();
assert_eq!(q, "`data`");
}
#[test]
fn unsupported_db_falls_back_to_mysql() {
let q = JsonQuery::new(DbType::Redis, "data").path("x");
assert_eq!(q.build_extract(), "`data`->'$.x'");
}
#[test]
fn special_chars_in_value_escaped() {
let cond = JsonQuery::new(DbType::MySQL, "d")
.path("k")
.eq_string("a'b'c");
assert_eq!(cond, "`d`->'$.k' = 'a''b''c'");
}
#[test]
fn mysql_ge_le_string() {
let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_string("m");
assert_eq!(ge, "`d`->'$.k' >= 'm'");
let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_string("m");
assert_eq!(le, "`d`->'$.k' <= 'm'");
}
#[test]
fn mysql_ge_le_i64() {
let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_i64(10);
assert_eq!(ge, "`d`->'$.k' >= 10");
let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_i64(99);
assert_eq!(le, "`d`->'$.k' <= 99");
let gt = JsonQuery::new(DbType::MySQL, "d").path("k").gt_i64(5);
assert_eq!(gt, "`d`->'$.k' > 5");
let lt = JsonQuery::new(DbType::MySQL, "d").path("k").lt_i64(8);
assert_eq!(lt, "`d`->'$.k' < 8");
}
#[test]
fn mysql_between_i64() {
let cond = JsonQuery::new(DbType::MySQL, "stats")
.path("visits")
.between_i64(10, 100);
assert_eq!(cond, "`stats`->'$.visits' BETWEEN 10 AND 100");
}
#[test]
fn mysql_in_strs() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("theme")
.in_strs(&["dark", "light"]);
assert_eq!(cond, "`prefs`->'$.theme' IN ('dark', 'light')");
}
#[test]
fn mysql_in_i64s() {
let cond = JsonQuery::new(DbType::MySQL, "stats")
.path("level")
.in_i64s(&[1, 2, 3]);
assert_eq!(cond, "`stats`->'$.level' IN (1, 2, 3)");
}
#[test]
fn mysql_in_strs_with_quote_escape() {
let cond = JsonQuery::new(DbType::MySQL, "d")
.path("k")
.in_strs(&["a'b", "c"]);
assert_eq!(cond, "`d`->'$.k' IN ('a''b', 'c')");
}
#[test]
fn mysql_in_empty_list() {
let cond = JsonQuery::new(DbType::MySQL, "d").path("k").in_strs(&[]);
assert_eq!(cond, "`d`->'$.k' IN ()");
}
#[test]
fn mysql_has_key() {
let cond = JsonQuery::new(DbType::MySQL, "prefs")
.path("theme")
.has_key();
assert_eq!(cond, "JSON_CONTAINS_PATH(`prefs`, 'one', '$.theme')");
}
#[test]
fn pg_has_key_single_level() {
let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
.path("theme")
.has_key();
assert_eq!(cond, "\"prefs\" ? 'theme'");
}
#[test]
fn pg_has_key_multi_level() {
let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
.path("a.b.c")
.has_key();
assert!(cond.contains("#>"));
assert!(cond.contains("IS NOT NULL"));
}
#[test]
fn sqlite_has_key() {
let cond = JsonQuery::new(DbType::Sqlite, "prefs")
.path("theme")
.has_key();
assert_eq!(cond, "json_type(`prefs`, '$.theme') IS NOT NULL");
}
#[test]
fn mysql_json_type_eq_integer() {
let cond = JsonQuery::new(DbType::MySQL, "stats")
.path("visits")
.json_type_eq("integer");
assert_eq!(cond, "JSON_TYPE(`stats`->'$.visits') = 'INTEGER'");
}
#[test]
fn mysql_json_type_eq_array() {
let cond = JsonQuery::new(DbType::MySQL, "data")
.path("tags")
.json_type_eq("array");
assert_eq!(cond, "JSON_TYPE(`data`->'$.tags') = 'ARRAY'");
}
#[test]
fn pg_json_type_eq() {
let cond = JsonQuery::new(DbType::PostgreSQL, "stats")
.path("visits")
.json_type_eq("integer");
assert_eq!(cond, "json_typeof(\"stats\"#>>'{visits}') = 'integer'");
}
#[test]
fn sqlite_json_type_eq() {
let cond = JsonQuery::new(DbType::Sqlite, "data")
.path("tags")
.json_type_eq("array");
assert_eq!(cond, "json_type(`data`, '$.tags') = 'array'");
}
#[test]
fn mysql_array_append_str_single() {
let set = JsonUpdate::new(DbType::MySQL, "tags")
.array_append_str("list", "rust")
.build_set();
assert_eq!(set, "`tags` = JSON_ARRAY_APPEND(`tags`, '$.list', 'rust')");
}
#[test]
fn mysql_array_append_i64_single() {
let set = JsonUpdate::new(DbType::MySQL, "nums")
.array_append_i64("list", 42)
.build_set();
assert_eq!(set, "`nums` = JSON_ARRAY_APPEND(`nums`, '$.list', 42)");
}
#[test]
fn mysql_array_append_multiple() {
let set = JsonUpdate::new(DbType::MySQL, "tags")
.array_append_str("list", "rust")
.array_append_str("list", "orm")
.build_set();
assert!(set.contains("JSON_ARRAY_APPEND(JSON_ARRAY_APPEND"));
assert!(set.contains("'rust'"));
assert!(set.contains("'orm'"));
}
#[test]
fn mysql_remove_key_single() {
let set = JsonUpdate::new(DbType::MySQL, "prefs")
.remove_key("deprecated_field")
.build_set();
assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.deprecated_field')");
}
#[test]
fn mysql_remove_key_multiple() {
let set = JsonUpdate::new(DbType::MySQL, "prefs")
.remove_key("a")
.remove_key("b")
.build_set();
assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.a', '$.b')");
}
#[test]
fn mysql_combined_set_append_remove() {
let set = JsonUpdate::new(DbType::MySQL, "prefs")
.set_str("theme", "dark")
.array_append_str("tags", "new")
.remove_key("old_field")
.build_set();
assert!(set.starts_with("`prefs` = JSON_REMOVE(JSON_ARRAY_APPEND(JSON_SET("));
assert!(set.contains("'$.theme', 'dark'"));
assert!(set.contains("'$.tags', 'new'"));
assert!(set.contains("'$.old_field'"));
}
#[test]
fn sqlite_remove_key() {
let set = JsonUpdate::new(DbType::Sqlite, "prefs")
.remove_key("old")
.build_set();
assert_eq!(set, "`prefs` = json_remove(`prefs`, '$.old')");
}
#[test]
fn sqlite_array_append() {
let set = JsonUpdate::new(DbType::Sqlite, "tags")
.array_append_str("list", "rust")
.build_set();
assert!(set.contains("json_set"));
assert!(set.contains("json_insert"));
assert!(set.contains("'rust'"));
}
#[test]
fn pg_remove_key() {
let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
.remove_key("old")
.build_set();
assert_eq!(set, "\"prefs\" = (\"prefs\" - 'old')");
}
#[test]
fn pg_combined_set_remove() {
let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
.set_str("theme", "dark")
.remove_key("old")
.build_set();
assert!(set.contains("jsonb_set"));
assert!(set.contains("- 'old'"));
}
}