sqlutil 0.1.6

A utility library for building SQL queries
Documentation
use std::collections::HashMap;
use std::hash::Hash;

use serde_json::Value;

use crate::{quote, quote_identifier};

pub fn build_update_sql<K>(table: &str, data: HashMap<K, Value>, where_clause: Option<&str>) -> String
where
    K: AsRef<str> + Eq + Hash,
{
    // Prepare values vector
    let mut sets = Vec::new();

    // Iterate over the data to populate columns and values
    for (key, value) in data {
        let val_str = match value {
            Value::Object(_) => quote(serde_json::to_string(&value).unwrap().as_str()),
            Value::Array(_) => quote(serde_json::to_string(&value).unwrap().as_str()),
            Value::String(s) => quote(&s),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Null => "NULL".to_string(),
        };
        sets.push(format!("{} = {}", quote_identifier(key.as_ref()), val_str));
    }

    let mut query = format!("UPDATE {} SET {}", quote_identifier(table.trim()), sets.join(" , "));

    if let Some(cond) = where_clause {
        if !cond.trim().is_empty() {
            query.push_str(&format!(" WHERE {}", cond));
        }
    }

    query
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_update_single() {
        let mut data = HashMap::new();
        data.insert("name".to_string(), json!("John"));
        let sql = build_update_sql("users", data, Some("id = 1"));
        assert_eq!(sql, "UPDATE \"users\" SET \"name\" = 'John' WHERE id = 1");
    }

    #[test]
    fn test_update_multiple() {
        let mut data = HashMap::new();
        data.insert("name".to_string(), json!("John"));
        data.insert("age".to_string(), json!(30));
        let sql = build_update_sql("users", data, Some("id = 1"));
        
        let valid1 = "UPDATE \"users\" SET \"name\" = 'John' , \"age\" = 30 WHERE id = 1";
        let valid2 = "UPDATE \"users\" SET \"age\" = 30 , \"name\" = 'John' WHERE id = 1";
        assert!(sql == valid1 || sql == valid2, "Generated SQL: {}", sql);
    }

    #[test]
    fn test_update_no_where() {
        let mut data = HashMap::new();
        data.insert("active".to_string(), json!(false));
        let sql = build_update_sql("users", data, None);
        assert_eq!(sql, "UPDATE \"users\" SET \"active\" = false");
    }

    #[test]
    fn test_update_str_keys() {
        let mut data = HashMap::new();
        data.insert("name", json!("Jane"));
        let sql = build_update_sql("users", data, Some("id = 2"));
        assert_eq!(sql, "UPDATE \"users\" SET \"name\" = 'Jane' WHERE id = 2");
    }

    #[test]
    fn test_update_empty_where_string_is_skipped() {
        let mut data = HashMap::new();
        data.insert("active".to_string(), json!(true));
        let sql = build_update_sql("users", data, Some(""));
        assert_eq!(sql, "UPDATE \"users\" SET \"active\" = true");
    }

    #[test]
    fn test_update_whitespace_where_string_is_skipped() {
        let mut data = HashMap::new();
        data.insert("active".to_string(), json!(true));
        let sql = build_update_sql("users", data, Some("   "));
        assert_eq!(sql, "UPDATE \"users\" SET \"active\" = true");
    }

    #[test]
    fn test_update_null_value() {
        let mut data = HashMap::new();
        data.insert("deleted_at".to_string(), json!(null));
        let sql = build_update_sql("users", data, Some("id = 5"));
        assert_eq!(sql, "UPDATE \"users\" SET \"deleted_at\" = NULL WHERE id = 5");
    }

    #[test]
    fn test_update_string_with_single_quote() {
        let mut data = HashMap::new();
        data.insert("name".to_string(), json!("O'Brien"));
        let sql = build_update_sql("users", data, Some("id = 1"));
        assert_eq!(sql, "UPDATE \"users\" SET \"name\" = 'O''Brien' WHERE id = 1");
    }

    #[test]
    fn test_update_json_object() {
        let mut data = HashMap::new();
        data.insert("meta".to_string(), json!({"role": "admin"}));
        let sql = build_update_sql("users", data, Some("id = 1"));
        // JSON object should be serialized and quoted as a string
        assert!(sql.contains("UPDATE \"users\" SET"));
        assert!(sql.contains("\"meta\""));
        assert!(sql.contains("role"));
        assert!(sql.contains("WHERE id = 1"));
    }

    #[test]
    fn test_update_table_name_is_trimmed() {
        let mut data = HashMap::new();
        data.insert("name".to_string(), json!("Bob"));
        let sql = build_update_sql("  users  ", data, Some("id = 3"));
        assert_eq!(sql, "UPDATE \"users\" SET \"name\" = 'Bob' WHERE id = 3");
    }
}