use rusqlite::Connection;
use crate::error::table::TableError;
pub(crate) fn count_query(db: &Connection, sql: &str) -> Result<usize, TableError> {
let count = db.prepare(sql)?.query_row([], |row| row.get::<_, i64>(0))?;
usize::try_from(count)
.map_err(|_| TableError::QueryError(rusqlite::Error::IntegralValueOutOfRange(0, count)))
}
pub(crate) fn table_exists(db: &Connection, table_name: &str) -> Result<bool, TableError> {
let exists = db.query_row(
"
SELECT EXISTS(
SELECT 1
FROM sqlite_master
WHERE type = 'table'
AND name = ?1
)
",
[table_name],
|row| row.get::<_, i64>(0),
)?;
Ok(exists != 0)
}
pub(crate) fn column_exists(
db: &Connection,
table_name: &str,
column_name: &str,
) -> Result<bool, TableError> {
let mut statement = db.prepare(&format!(
"PRAGMA table_info({})",
quote_sqlite_identifier(table_name)
))?;
let columns = statement.query_map([], |row| row.get::<_, String>(1))?;
for column in columns {
if column? == column_name {
return Ok(true);
}
}
Ok(false)
}
fn quote_sqlite_identifier(identifier: &str) -> String {
format!("\"{}\"", identifier.replace('"', "\"\""))
}
#[derive(Debug)]
pub struct HandleDiagnostic {
pub total_handles: usize,
pub handles_with_multiple_ids: Option<usize>,
pub total_duplicated: usize,
}
#[derive(Debug)]
pub struct MessageDiagnostic {
pub total_messages: usize,
pub messages_without_chat: usize,
pub messages_in_multiple_chats: usize,
pub recoverable_messages: Option<usize>,
pub first_message_date: Option<i64>,
pub last_message_date: Option<i64>,
}
#[derive(Debug)]
pub struct AttachmentDiagnostic {
pub total_attachments: usize,
pub total_bytes_referenced: u64,
pub total_bytes_on_disk: u64,
pub missing_files: usize,
pub no_path_provided: usize,
}
impl AttachmentDiagnostic {
#[must_use]
pub fn no_file_located(&self) -> usize {
self.missing_files.saturating_sub(self.no_path_provided)
}
#[must_use]
pub fn missing_percent(&self) -> Option<f64> {
if self.total_attachments > 0 {
Some(self.missing_files as f64 / self.total_attachments as f64 * 100.0)
} else {
None
}
}
}
#[derive(Debug)]
pub struct ChatHandleDiagnostic {
pub total_chats: usize,
pub total_duplicated: usize,
pub chats_with_no_handles: usize,
}
#[cfg(test)]
mod tests {
use rusqlite::Connection;
use super::{column_exists, table_exists};
#[test]
fn table_exists_detects_existing_and_missing_tables() {
let db = Connection::open_in_memory().unwrap();
db.execute("CREATE TABLE test_table (id INTEGER)", [])
.unwrap();
assert!(table_exists(&db, "test_table").unwrap());
assert!(!table_exists(&db, "missing_table").unwrap());
}
#[test]
fn column_exists_detects_existing_and_missing_columns() {
let db = Connection::open_in_memory().unwrap();
db.execute("CREATE TABLE test_table (id INTEGER, name TEXT)", [])
.unwrap();
assert!(column_exists(&db, "test_table", "name").unwrap());
assert!(!column_exists(&db, "test_table", "missing_column").unwrap());
assert!(!column_exists(&db, "missing_table", "name").unwrap());
}
#[test]
fn column_exists_quotes_table_identifiers() {
let db = Connection::open_in_memory().unwrap();
db.execute(
"CREATE TABLE \"quoted\"\"table\" (\"weird column\" TEXT)",
[],
)
.unwrap();
assert!(column_exists(&db, "quoted\"table", "weird column").unwrap());
}
}