Skip to content

Commit 274a1f9

Browse files
committed
[SQLite] Add option to execute PRAGMA optimize; on close of a connection
1 parent 76ae286 commit 274a1f9

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

sqlx-core/src/sqlite/connection/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ use futures_core::future::BoxFuture;
66
use futures_intrusive::sync::MutexGuard;
77
use futures_util::future;
88
use libsqlite3_sys::sqlite3;
9+
use std::fmt::Write;
910

1011
pub(crate) use handle::{ConnectionHandle, ConnectionHandleRaw};
1112

1213
use crate::common::StatementCache;
1314
use crate::connection::{Connection, LogSettings};
1415
use crate::error::Error;
16+
use crate::executor::Executor;
1517
use crate::sqlite::connection::establish::EstablishParams;
1618
use crate::sqlite::connection::worker::ConnectionWorker;
19+
use crate::sqlite::options::OptimizeOnClose;
1720
use crate::sqlite::statement::VirtualStatement;
1821
use crate::sqlite::{Sqlite, SqliteConnectOptions};
1922
use crate::transaction::Transaction;
@@ -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
})
@@ -146,6 +151,14 @@ impl Connection for SqliteConnection {
146151

147152
fn close(mut self) -> BoxFuture<'static, Result<(), Error>> {
148153
Box::pin(async move {
154+
if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close {
155+
let mut pragma_string = String::new();
156+
if let Some(limit) = analysis_limit {
157+
write!(pragma_string, "PRAGMA analysis_limit = {}; ", limit).ok();
158+
}
159+
pragma_string.push_str("PRAGMA optimize;");
160+
self.execute(&*pragma_string).await?;
161+
}
149162
let shutdown = self.worker.shutdown();
150163
// Drop the statement worker, which should
151164
// cover all references to the connection handle outside of the worker thread

sqlx-core/src/sqlite/options/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ pub struct SqliteConnectOptions {
7979

8080
pub(crate) serialized: bool,
8181
pub(crate) thread_name: Arc<DebugFn<dyn Fn(u64) -> String + Send + Sync + 'static>>,
82+
83+
pub(crate) optimize_on_close: OptimizeOnClose,
84+
}
85+
86+
#[derive(Clone, Debug)]
87+
pub enum OptimizeOnClose {
88+
Enabled { analysis_limit: Option<u32> },
89+
Disabled,
8290
}
8391

8492
impl Default for SqliteConnectOptions {
@@ -167,6 +175,9 @@ impl SqliteConnectOptions {
167175

168176
pragmas.insert("auto_vacuum".into(), None);
169177

178+
// Soft limit on the number of rows that `ANALYZE` touches per index.
179+
pragmas.insert("analysis_limit".into(), None);
180+
170181
Self {
171182
filename: Cow::Borrowed(Path::new(":memory:")),
172183
in_memory: false,
@@ -185,6 +196,7 @@ impl SqliteConnectOptions {
185196
thread_name: Arc::new(DebugFn(|id| format!("sqlx-sqlite-worker-{}", id))),
186197
command_channel_size: 50,
187198
row_channel_size: 50,
199+
optimize_on_close: OptimizeOnClose::Disabled,
188200
}
189201
}
190202

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

0 commit comments

Comments
 (0)