diff --git a/sqlx-sqlite/src/connection/mod.rs b/sqlx-sqlite/src/connection/mod.rs index 1fa53f38d7..2ea1b66ed9 100644 --- a/sqlx-sqlite/src/connection/mod.rs +++ b/sqlx-sqlite/src/connection/mod.rs @@ -11,8 +11,11 @@ use std::ptr::NonNull; use crate::connection::establish::EstablishParams; use crate::connection::worker::ConnectionWorker; +use crate::options::OptimizeOnClose; use crate::statement::VirtualStatement; use crate::{Sqlite, SqliteConnectOptions}; +use sqlx_core::executor::Executor; +use std::fmt::Write; pub(crate) use sqlx_core::connection::*; @@ -39,6 +42,7 @@ mod worker; /// You can explicitly call [`.close()`][Self::close] to ensure the database is closed successfully /// or get an error otherwise. pub struct SqliteConnection { + optimize_on_close: OptimizeOnClose, pub(crate) worker: ConnectionWorker, pub(crate) row_channel_size: usize, } @@ -70,6 +74,7 @@ impl SqliteConnection { let params = EstablishParams::from_options(options)?; let worker = ConnectionWorker::establish(params).await?; Ok(Self { + optimize_on_close: options.optimize_on_close.clone(), worker, row_channel_size: options.row_channel_size, }) @@ -102,6 +107,14 @@ impl Connection for SqliteConnection { fn close(mut self) -> BoxFuture<'static, Result<(), Error>> { Box::pin(async move { + if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close { + let mut pragma_string = String::new(); + if let Some(limit) = analysis_limit { + write!(pragma_string, "PRAGMA analysis_limit = {}; ", limit).ok(); + } + pragma_string.push_str("PRAGMA optimize;"); + self.execute(&*pragma_string).await?; + } let shutdown = self.worker.shutdown(); // Drop the statement worker, which should // cover all references to the connection handle outside of the worker thread diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index 43d2939d6a..21013547ba 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -80,10 +80,18 @@ pub struct SqliteConnectOptions { pub(crate) serialized: bool, pub(crate) thread_name: Arc String + Send + Sync + 'static>>, + pub(crate) optimize_on_close: OptimizeOnClose, + #[cfg(feature = "regexp")] pub(crate) register_regexp_function: bool, } +#[derive(Clone, Debug)] +pub enum OptimizeOnClose { + Enabled { analysis_limit: Option }, + Disabled, +} + impl Default for SqliteConnectOptions { fn default() -> Self { Self::new() @@ -170,6 +178,9 @@ impl SqliteConnectOptions { pragmas.insert("auto_vacuum".into(), None); + // Soft limit on the number of rows that `ANALYZE` touches per index. + pragmas.insert("analysis_limit".into(), None); + Self { filename: Cow::Borrowed(Path::new(":memory:")), in_memory: false, @@ -188,6 +199,7 @@ impl SqliteConnectOptions { thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{}", id))), command_channel_size: 50, row_channel_size: 50, + optimize_on_close: OptimizeOnClose::Disabled, #[cfg(feature = "regexp")] register_regexp_function: false, } @@ -464,6 +476,54 @@ impl SqliteConnectOptions { self } + /// Execute `PRAGMA optimize;` on the SQLite connection before closing. + /// + /// The SQLite manual recommends using this for long-lived databases. + /// + /// This will collect and store statistics about the layout of data in your tables to help the query planner make better decisions. + /// Over the connection's lifetime, the query planner will make notes about which tables could use up-to-date statistics so this + /// command doesn't have to scan the whole database every time. Thus, the best time to execute this is on connection close. + /// + /// `analysis_limit` sets a soft limit on the maximum number of rows to scan per index. + /// It is equivalent to setting [`Self::analysis_limit`] but only takes effect for the `PRAGMA optimize;` call + /// and does not affect the behavior of any `ANALYZE` statements made during the connection's lifetime. + /// + /// If not `None`, the `analysis_limit` here overrides the global `analysis_limit` setting, + /// but only for the `PRAGMA optimize;` call. + /// + /// Not enabled by default. + /// + /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#automatically_running_analyze) for details. + pub fn optimize_on_close( + mut self, + enabled: bool, + analysis_limit: impl Into>, + ) -> Self { + self.optimize_on_close = if enabled { + OptimizeOnClose::Enabled { + analysis_limit: (analysis_limit.into()), + } + } else { + OptimizeOnClose::Disabled + }; + self + } + + /// Set a soft limit on the number of rows that `ANALYZE` touches per index. + /// + /// This also affects `PRAGMA optimize` which is set by [Self::optimize_on_close]. + /// + /// The value recommended by SQLite is `400`. There is no default. + /// + /// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#approx) for details. + pub fn analysis_limit(mut self, limit: impl Into>) -> Self { + if let Some(limit) = limit.into() { + return self.pragma("analysis_limit", limit.to_string()); + } + self.pragmas.insert("analysis_limit".into(), None); + self + } + /// Register a regexp function that allows using regular expressions in queries. /// /// ```