sqlutil 0.1.6

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

use serde_json::Value;

use crate::build_insert_sql;

pub fn make_insert<K: AsRef<str> + Eq + std::hash::Hash>(
    table: &str,
    data: HashMap<K, Value>,
) -> String {
    build_insert_sql(table, data)
}

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

    // ── Single-field inserts ──────────────────────────────────────────────────

    #[test]
    fn test_single_string_value() {
        let mut data = HashMap::new();
        data.insert("name", json!("Alice"));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("name") VALUES ('Alice')"#);
    }

    #[test]
    fn test_single_integer_value() {
        let mut data = HashMap::new();
        data.insert("age", json!(30));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("age") VALUES (30)"#);
    }

    #[test]
    fn test_single_float_value() {
        let mut data = HashMap::new();
        data.insert("score", json!(9.5));
        let sql = make_insert("stats", data);
        assert_eq!(sql, r#"INSERT INTO "stats" ("score") VALUES (9.5)"#);
    }

    #[test]
    fn test_single_bool_true() {
        let mut data = HashMap::new();
        data.insert("active", json!(true));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("active") VALUES (true)"#);
    }

    #[test]
    fn test_single_bool_false() {
        let mut data = HashMap::new();
        data.insert("active", json!(false));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("active") VALUES (false)"#);
    }

    #[test]
    fn test_single_null_value() {
        let mut data = HashMap::new();
        data.insert("deleted_at", json!(null));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("deleted_at") VALUES (NULL)"#);
    }

    // ── String key type ───────────────────────────────────────────────────────

    #[test]
    fn test_string_key_type() {
        let mut data: HashMap<String, Value> = HashMap::new();
        data.insert("email".to_string(), json!("alice@example.com"));
        let sql = make_insert("users", data);
        assert_eq!(
            sql,
            r#"INSERT INTO "users" ("email") VALUES ('alice@example.com')"#
        );
    }

    // ── SQL injection / quoting ───────────────────────────────────────────────

    #[test]
    fn test_string_with_single_quote() {
        let mut data = HashMap::new();
        data.insert("name", json!("O'Brien"));
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" ("name") VALUES ('O''Brien')"#);
    }

    #[test]
    fn test_table_name_with_double_quote() {
        let mut data = HashMap::new();
        data.insert("id", json!(1));
        let sql = make_insert(r#"my"table"#, data);
        assert_eq!(sql, r#"INSERT INTO "my""table" ("id") VALUES (1)"#);
    }

    #[test]
    fn test_column_name_with_double_quote() {
        let mut data = HashMap::new();
        data.insert(r#"col"name"#, json!("val"));
        let sql = make_insert("t", data);
        assert_eq!(sql, r#"INSERT INTO "t" ("col""name") VALUES ('val')"#);
    }

    // ── Table name trimming ───────────────────────────────────────────────────

    #[test]
    fn test_table_name_trimmed() {
        let mut data = HashMap::new();
        data.insert("x", json!(1));
        let sql = make_insert("  orders  ", data);
        assert_eq!(sql, r#"INSERT INTO "orders" ("x") VALUES (1)"#);
    }

    // ── Multiple fields (order-agnostic) ─────────────────────────────────────

    #[test]
    fn test_multiple_fields_contain_all_parts() {
        let mut data = HashMap::new();
        data.insert("name", json!("Bob"));
        data.insert("age", json!(25));
        data.insert("active", json!(true));
        let sql = make_insert("users", data);
        assert!(sql.starts_with(r#"INSERT INTO "users" ("#));
        assert!(sql.contains(r#""name""#));
        assert!(sql.contains(r#""age""#));
        assert!(sql.contains(r#""active""#));
        assert!(sql.contains("'Bob'"));
        assert!(sql.contains("25"));
        assert!(sql.contains("true"));
    }

    // ── JSON object / array values ────────────────────────────────────────────

    #[test]
    fn test_json_object_value() {
        let mut data = HashMap::new();
        data.insert("meta", json!({"role": "admin"}));
        let sql = make_insert("users", data);
        assert!(sql.starts_with(r#"INSERT INTO "users" ("meta") VALUES ('"#));
        assert!(sql.contains("role"));
        assert!(sql.contains("admin"));
    }

    #[test]
    fn test_json_array_value() {
        let mut data = HashMap::new();
        data.insert("tags", json!(["rust", "sql", "db"]));
        let sql = make_insert("posts", data);
        assert!(sql.starts_with(r#"INSERT INTO "posts" ("tags") VALUES ('"#));
        assert!(sql.contains("rust"));
        assert!(sql.contains("sql"));
        assert!(sql.contains("db"));
    }

    // ── Empty data ────────────────────────────────────────────────────────────

    #[test]
    fn test_empty_data_produces_valid_structure() {
        let data: HashMap<&str, Value> = HashMap::new();
        let sql = make_insert("users", data);
        assert_eq!(sql, r#"INSERT INTO "users" () VALUES ()"#);
    }
}