/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: Apache-2.0 OR MIT */ use rusqlite::{Connection, OptionalExtension, params}; #[derive(Debug, Clone)] pub struct SourceKey { pub kind: String, pub session_url: String, pub account_id: String, } pub fn find_source(conn: &Connection, key: &SourceKey) -> Result, rusqlite::Error> { conn.query_row( "SELECT id FROM sources WHERE kind = ?1 AND session_url = ?2 AND account_id = ?3", params![key.kind, key.session_url, key.account_id], |row| row.get(0), ) .optional() } pub fn conflicting_source( conn: &Connection, kind: &str, session_url: &str, account_id: &str, ) -> Result, rusqlite::Error> { conn.query_row( "SELECT session_url, account_id FROM sources WHERE kind = ?1 AND NOT (session_url = ?2 AND account_id = ?3) LIMIT 1", params![kind, session_url, account_id], |row| Ok((row.get(0)?, row.get(1)?)), ) .optional() } pub fn upsert_source( conn: &Connection, key: &SourceKey, account_name: Option<&str>, username: &str, ) -> Result { conn.execute( "INSERT INTO sources (kind, session_url, account_id, account_name, username) VALUES (?1, ?2, ?3, ?4, ?5) ON CONFLICT (kind, session_url, account_id) DO UPDATE SET account_name = excluded.account_name, username = excluded.username", params![ key.kind, key.session_url, key.account_id, account_name, username ], )?; find_source(conn, key).map(|opt| opt.unwrap_or_else(|| conn.last_insert_rowid())) } #[cfg(test)] mod tests { use super::*; use crate::db::init; fn mem() -> Connection { let c = Connection::open_in_memory().unwrap(); init::apply_schema(&c).unwrap(); c } fn key(url: &str, acc: &str) -> SourceKey { SourceKey { kind: "jmap".to_owned(), session_url: url.to_owned(), account_id: acc.to_owned(), } } #[test] fn upsert_is_idempotent_and_returns_stable_id() { let c = mem(); let k = key("https://a/jmap", "w"); let id1 = upsert_source(&c, &k, Some("alice"), "alice").unwrap(); let id2 = upsert_source(&c, &k, Some("alice2"), "alice").unwrap(); assert_eq!(id1, id2); assert!(find_source(&c, &k).unwrap().is_some()); } #[test] fn conflicting_source_detects_a_different_account() { let c = mem(); upsert_source(&c, &key("https://a/jmap", "w"), None, "alice").unwrap(); assert!( conflicting_source(&c, "jmap", "https://a/jmap", "w") .unwrap() .is_none() ); let conflict = conflicting_source(&c, "jmap", "https://b/jmap", "v") .unwrap() .unwrap(); assert_eq!(conflict, ("https://a/jmap".to_owned(), "w".to_owned())); } }