/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Lower-level, generic SQLite connection management. //! //! This module is inspired by, and borrows concepts from, the //! Application Services `sql-support` crate.
use std::{borrow::Cow, fmt::Debug, num::NonZeroU32, path::Path, sync::Mutex};
use rusqlite::{config::DbConfig, InterruptHandle, OpenFlags, Transaction, TransactionBehavior};
usecrate::skv;
/// A path to a physical SQLite database, and optional [`OpenFlags`] for /// interpreting that path. pubtrait ConnectionPath { fn as_path(&self) -> Cow<'_, Path>;
fn flags(&self) -> OpenFlags;
}
/// An error that indicates a potential problem with an SQLite database. pubtrait ToConnectionIncident { fn to_incident(&self) -> Option<ConnectionIncident>;
}
/// Migrates an SQLite database to the highest supported schema version. pubtrait ConnectionMigrator { /// The highest supported schema version. const MAX_SCHEMA_VERSION: u32;
type Error;
/// Initializes an SQLite database with the latest schema. fn create(tx: &mut Transaction<'_>) -> Result<(), Self::Error>;
/// Upgrades an SQLite database to the schema with the given version. fn upgrade(tx: &mut Transaction<'_>, to_version: NonZeroU32) -> Result<(), Self::Error>;
}
/// A maintenance task to run on an SQLite database. pubtrait ConnectionMaintenanceTask { type Error;
/// A thread-safe wrapper around a connection to a physical SQLite database. pubstruct Connection { /// The inner connection.
conn: Mutex<rusqlite::Connection>,
/// An object that's used to interrupt an ongoing SQLite operation. /// /// When [`InterruptHandle::interrupt`] is called on this thread, and /// another thread is currently running a database operation, /// that operation will be aborted, and the other thread can release /// its lock on the inner connection.
interrupt_handle: InterruptHandle,
/// Anomalies that indicate potential problems with the database.
incidents: Mutex<Vec<ConnectionIncident>>,
}
impl Connection { /// Opens a connection to an SQLite database at the given path. pubfn new<M>(path: &impl ConnectionPath, type_: ConnectionType) -> Result<Self, M::Error> where
M: ConnectionMigrator,
M::Error: From<rusqlite::Error>,
{ letmut conn = rusqlite::Connection::open_with_flags(
path.as_path(),
path.flags().union(type_.flags()),
)?;
// Set hardening flags.
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_DEFENSIVE, true)?;
// Turn off misfeatures: double-quoted strings and untrusted schemas.
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_DQS_DML, false)?;
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_DQS_DDL, false)?;
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_TRUSTED_SCHEMA, true)?;
skv::functions::register(&mut conn)?;
match type_ {
ConnectionType::ReadOnly => Ok(Self::with_connection(conn)),
ConnectionType::ReadWrite => { // Read-write connections should upgrade the schema to // the latest version. letmut tx = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?; match tx.query_row_and_then("PRAGMA user_version", [], |row| row.get(0)) {
Ok(mut version @ 1..) => { while version < M::MAX_SCHEMA_VERSION {
M::upgrade(&mut tx, NonZeroU32::new(version + 1).unwrap())?;
version += 1;
}
}
Ok(0) => M::create(&mut tx)?,
Err(err) => Err(err)?,
} // Set the schema version to the highest that we support. // If the current version is higher than ours, downgrade it, // so that upgrading to it again in the future can fix up any // invariants that our version might not uphold.
tx.execute_batch(&format!("PRAGMA user_version = {};", M::MAX_SCHEMA_VERSION))?;
tx.commit()?; let _ = conn.execute_batch("PRAGMA optimize(0x10002);");
Ok(Self::with_connection(conn))
}
}
}
/// Returns all incidents that have occurred on this connection. pubfn incidents(&self) -> ConnectionIncidents<'_> {
ConnectionIncidents(self)
}
/// Accesses the database for reading. pubfn read<T, E>(&self, f: impl FnOnce(&rusqlite::Connection) -> Result<T, E>) -> Result<T, E> where
E: ToConnectionIncident,
{ self.reporting(|conn| f(conn))
}
/// Accesses the database in a transaction for reading and writing. pubfn write<T, E>(&self, f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>) -> Result<T, E> where
E: From<rusqlite::Error> + ToConnectionIncident,
{ self.reporting(|conn| { letmut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?; let value = f(&mut tx)?;
tx.commit()?;
Ok(value)
})
}
/// Runs a maintenance task on the database. pubfn maintenance<M>(&self, task: M) -> Result<(), M::Error> where
M: ConnectionMaintenanceTask,
{ // Don't report new incidents, because maintenance errors likely // indicate that the database needs to be replaced. letmut conn = self.conn.lock().unwrap();
task.run(&mut conn)
}
/// Interrupts any ongoing operations. pubfn interrupt(&self) { self.interrupt_handle.interrupt()
}
/// Closes this database connection. pubfn close(self) { let conn = Mutex::into_inner(self.conn).unwrap(); let _ = conn.execute_batch("PRAGMA optimize;"); // We can't meaningfully recover from failing to close // a connection, so ignore errors. let _ = conn.close();
}
/// Accesses the database and reports any incidents that occur. fn reporting<T, E>(
&self,
f: impl FnOnce(&mut rusqlite::Connection) -> Result<T, E>,
) -> Result<T, E> where
E: ToConnectionIncident,
{ let result = { letmut conn = self.conn.lock().unwrap();
f(&mut *conn)
};
result.inspect_err(|err| { iflet Some(incident) = err.to_incident() { self.incidents.lock().unwrap().push(incident);
}
})
}
}
/// Anomalies on an SQLite connection that indicate potential problems with /// the database file. #[derive(Debug)] pubstruct ConnectionIncidents<'a>(&'a Connection);
impl<'a> ConnectionIncidents<'a> { /// Transforms the connection's incidents into a different value. pubfn map<T>(self, f: impl FnOnce(UnresolvedIncidents<'_>) -> T) -> T { letmut incidents = self.0.incidents.lock().unwrap();
f(UnresolvedIncidents(&mut incidents))
}
}
impl<'a> UnresolvedIncidents<'a> { /// Returns an iterator over the connection's incidents. pubfn iter(&self) -> impl Iterator<Item = ConnectionIncident> + '_ { self.0.iter().copied()
}
/// Marks all incidents as resolved, removing them from the connection. pubfn resolve(self) { self.0.clear();
}
}
/// The type of a physical SQLite database connection. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pubenum ConnectionType {
ReadOnly,
ReadWrite,
}
impl ConnectionType { fn flags(self) -> OpenFlags { matchself {
ConnectionType::ReadOnly => { // We already guard the inner connection with a mutex, so // opt out of unnecessary locking with `SQLITE_OPEN_NO_MUTEX`.
OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_EXRESCODE
| OpenFlags::SQLITE_OPEN_READ_ONLY
}
ConnectionType::ReadWrite => {
OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_EXRESCODE
| OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_READ_WRITE
}
}
}
}
/// An anomaly on an SQLite connection that indicates a potential problem /// with the database file. /// /// Many SQLite errors are transient, but certain kinds of errors can be /// caused by permanent database corruption. Each [`Connection`] tracks /// occurrences of these errors as "incidents". #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pubenum ConnectionIncident {
CorruptFile,
CorruptIndex,
CorruptForeignKey,
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.