Skip to content

Commit 7d6d502

Browse files
miles170abonander
andauthored
[SQLite] Add option to execute PRAGMA optimize; on close of a connection (#2116)
* CHANGELOG: mention that users should upgrade CLI * [SQLite] Add option to execute `PRAGMA optimize;` on close of a connection * Update sqlx-sqlite/src/options/mod.rs * Update sqlx-sqlite/src/options/mod.rs * Update sqlx-sqlite/src/options/mod.rs --------- Co-authored-by: Austin Bonander <[email protected]>
1 parent 3e611e1 commit 7d6d502

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

sqlx-sqlite/src/connection/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ use std::ptr::NonNull;
1111

1212
use crate::connection::establish::EstablishParams;
1313
use crate::connection::worker::ConnectionWorker;
14+
use crate::options::OptimizeOnClose;
1415
use crate::statement::VirtualStatement;
1516
use crate::{Sqlite, SqliteConnectOptions};
17+
use sqlx_core::executor::Executor;
18+
use std::fmt::Write;
1619

1720
pub(crate) use sqlx_core::connection::*;
1821

@@ -39,6 +42,7 @@ mod worker;
3942
/// You can explicitly call [`.close()`][Self::close] to ensure the database is closed successfully
4043
/// or get an error otherwise.
4144
pub struct SqliteConnection {
45+
optimize_on_close: OptimizeOnClose,
4246
pub(crate) worker: ConnectionWorker,
4347
pub(crate) row_channel_size: usize,
4448
}
@@ -70,6 +74,7 @@ impl SqliteConnection {
7074
let params = EstablishParams::from_options(options)?;
7175
let worker = ConnectionWorker::establish(params).await?;
7276
Ok(Self {
77+
optimize_on_close: options.optimize_on_close.clone(),
7378
worker,
7479
row_channel_size: options.row_channel_size,
7580
})
@@ -102,6 +107,14 @@ impl Connection for SqliteConnection {
102107

103108
fn close(mut self) -> BoxFuture<'static, Result<(), Error>> {
104109
Box::pin(async move {
110+
if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close {
111+
let mut pragma_string = String::new();
112+
if let Some(limit) = analysis_limit {
113+
write!(pragma_string, "PRAGMA analysis_limit = {}; ", limit).ok();
114+
}
115+
pragma_string.push_str("PRAGMA optimize;");
116+
self.execute(&*pragma_string).await?;
117+
}
105118
let shutdown = self.worker.shutdown();
106119
// Drop the statement worker, which should
107120
// cover all references to the connection handle outside of the worker thread

sqlx-sqlite/src/options/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,18 @@ pub struct SqliteConnectOptions {
8080
pub(crate) serialized: bool,
8181
pub(crate) thread_name: Arc<DebugFn<dyn Fn(u64) -> String + Send + Sync + 'static>>,
8282

83+
pub(crate) optimize_on_close: OptimizeOnClose,
84+
8385
#[cfg(feature = "regexp")]
8486
pub(crate) register_regexp_function: bool,
8587
}
8688

89+
#[derive(Clone, Debug)]
90+
pub enum OptimizeOnClose {
91+
Enabled { analysis_limit: Option<u32> },
92+
Disabled,
93+
}
94+
8795
impl Default for SqliteConnectOptions {
8896
fn default() -> Self {
8997
Self::new()
@@ -170,6 +178,9 @@ impl SqliteConnectOptions {
170178

171179
pragmas.insert("auto_vacuum".into(), None);
172180

181+
// Soft limit on the number of rows that `ANALYZE` touches per index.
182+
pragmas.insert("analysis_limit".into(), None);
183+
173184
Self {
174185
filename: Cow::Borrowed(Path::new(":memory:")),
175186
in_memory: false,
@@ -188,6 +199,7 @@ impl SqliteConnectOptions {
188199
thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{}", id))),
189200
command_channel_size: 50,
190201
row_channel_size: 50,
202+
optimize_on_close: OptimizeOnClose::Disabled,
191203
#[cfg(feature = "regexp")]
192204
register_regexp_function: false,
193205
}
@@ -464,6 +476,54 @@ impl SqliteConnectOptions {
464476
self
465477
}
466478

479+
/// Execute `PRAGMA optimize;` on the SQLite connection before closing.
480+
///
481+
/// The SQLite manual recommends using this for long-lived databases.
482+
///
483+
/// This will collect and store statistics about the layout of data in your tables to help the query planner make better decisions.
484+
/// Over the connection's lifetime, the query planner will make notes about which tables could use up-to-date statistics so this
485+
/// command doesn't have to scan the whole database every time. Thus, the best time to execute this is on connection close.
486+
///
487+
/// `analysis_limit` sets a soft limit on the maximum number of rows to scan per index.
488+
/// It is equivalent to setting [`Self::analysis_limit`] but only takes effect for the `PRAGMA optimize;` call
489+
/// and does not affect the behavior of any `ANALYZE` statements made during the connection's lifetime.
490+
///
491+
/// If not `None`, the `analysis_limit` here overrides the global `analysis_limit` setting,
492+
/// but only for the `PRAGMA optimize;` call.
493+
///
494+
/// Not enabled by default.
495+
///
496+
/// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#automatically_running_analyze) for details.
497+
pub fn optimize_on_close(
498+
mut self,
499+
enabled: bool,
500+
analysis_limit: impl Into<Option<u32>>,
501+
) -> Self {
502+
self.optimize_on_close = if enabled {
503+
OptimizeOnClose::Enabled {
504+
analysis_limit: (analysis_limit.into()),
505+
}
506+
} else {
507+
OptimizeOnClose::Disabled
508+
};
509+
self
510+
}
511+
512+
/// Set a soft limit on the number of rows that `ANALYZE` touches per index.
513+
///
514+
/// This also affects `PRAGMA optimize` which is set by [Self::optimize_on_close].
515+
///
516+
/// The value recommended by SQLite is `400`. There is no default.
517+
///
518+
/// See [the SQLite manual](https://www.sqlite.org/lang_analyze.html#approx) for details.
519+
pub fn analysis_limit(mut self, limit: impl Into<Option<u32>>) -> Self {
520+
if let Some(limit) = limit.into() {
521+
return self.pragma("analysis_limit", limit.to_string());
522+
}
523+
self.pragmas.insert("analysis_limit".into(), None);
524+
self
525+
}
526+
467527
/// Register a regexp function that allows using regular expressions in queries.
468528
///
469529
/// ```

0 commit comments

Comments
 (0)