Skip to content

feat: expose various low level memory management menthods #3895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions sqlx-sqlite/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ use futures_core::future::BoxFuture;
use futures_intrusive::sync::MutexGuard;
use futures_util::future;
use libsqlite3_sys::{
sqlite3, sqlite3_commit_hook, sqlite3_get_autocommit, sqlite3_progress_handler,
sqlite3_rollback_hook, sqlite3_update_hook, SQLITE_DELETE, SQLITE_INSERT, SQLITE_UPDATE,
sqlite3, sqlite3_commit_hook, sqlite3_db_release_memory, sqlite3_db_status,
sqlite3_get_autocommit, sqlite3_progress_handler, sqlite3_rollback_hook, sqlite3_update_hook,
SQLITE_DBSTATUS_CACHE_HIT, SQLITE_DBSTATUS_CACHE_MISS, SQLITE_DBSTATUS_CACHE_USED,
SQLITE_DBSTATUS_CACHE_USED_SHARED, SQLITE_DBSTATUS_CACHE_WRITE, SQLITE_DBSTATUS_DEFERRED_FKS,
SQLITE_DBSTATUS_LOOKASIDE_HIT, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, SQLITE_DBSTATUS_LOOKASIDE_USED,
SQLITE_DBSTATUS_SCHEMA_USED, SQLITE_DBSTATUS_STMT_USED, SQLITE_DELETE, SQLITE_INSERT,
SQLITE_OK, SQLITE_UPDATE,
};
#[cfg(feature = "preupdate-hook")]
pub use preupdate_hook::*;
Expand Down Expand Up @@ -77,6 +83,54 @@ pub enum SqliteOperation {
Unknown(i32),
}

/// Database status parameters for the sqlite3_db_status function.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SqliteDatabaseStatus {
/// Current number of bytes used by lookaside allocations
LookasideUsed,
/// Current number of bytes of pager cache used
CacheUsed,
/// Current number of bytes used by the schema
SchemaUsed,
/// Current number of bytes used by prepared statements
StmtUsed,
/// Number of lookaside malloc hits
LookasideHit,
/// Number of lookaside malloc misses due to size
LookasideMissSize,
/// Number of lookaside malloc misses due to full buffer
LookasideMissFull,
/// Number of pager cache hits
CacheHit,
/// Number of pager cache misses
CacheMiss,
/// Number of dirty cache pages written
CacheWrite,
/// Number of foreign key constraint violations detected
DeferredFks,
/// Maximum cache used in shared cache mode
CacheUsedShared,
}

impl From<SqliteDatabaseStatus> for i32 {
fn from(status: SqliteDatabaseStatus) -> Self {
match status {
SqliteDatabaseStatus::LookasideUsed => SQLITE_DBSTATUS_LOOKASIDE_USED,
SqliteDatabaseStatus::CacheUsed => SQLITE_DBSTATUS_CACHE_USED,
SqliteDatabaseStatus::SchemaUsed => SQLITE_DBSTATUS_SCHEMA_USED,
SqliteDatabaseStatus::StmtUsed => SQLITE_DBSTATUS_STMT_USED,
SqliteDatabaseStatus::LookasideHit => SQLITE_DBSTATUS_LOOKASIDE_HIT,
SqliteDatabaseStatus::LookasideMissSize => SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
SqliteDatabaseStatus::LookasideMissFull => SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
SqliteDatabaseStatus::CacheHit => SQLITE_DBSTATUS_CACHE_HIT,
SqliteDatabaseStatus::CacheMiss => SQLITE_DBSTATUS_CACHE_MISS,
SqliteDatabaseStatus::CacheWrite => SQLITE_DBSTATUS_CACHE_WRITE,
SqliteDatabaseStatus::DeferredFks => SQLITE_DBSTATUS_DEFERRED_FKS,
SqliteDatabaseStatus::CacheUsedShared => SQLITE_DBSTATUS_CACHE_USED_SHARED,
}
}
}

impl From<i32> for SqliteOperation {
fn from(value: i32) -> Self {
match value {
Expand Down Expand Up @@ -557,6 +611,52 @@ impl LockedSqliteHandle<'_> {
let ret = unsafe { sqlite3_get_autocommit(self.as_raw_handle().as_ptr()) };
ret == 0
}

/// Retrieves statistics about a database connection.
///
/// This function is used to retrieve runtime status information about a single database connection.
/// The `status` parameter determines which statistic to retrieve.
///
/// Returns a tuple containing `(current_value, highest_value_since_reset)`.
/// If `reset` is true, the highest value is reset to the current value after retrieval.
///
/// See: https://www.sqlite.org/c3ref/db_status.html
pub fn db_status(
&mut self,
status: SqliteDatabaseStatus,
reset: bool,
) -> Result<(i32, i32), Error> {
let mut current = 0i32;
let mut highest = 0i32;

let result = unsafe {
sqlite3_db_status(
self.as_raw_handle().as_ptr(),
status.into(),
&mut current,
&mut highest,
if reset { 1 } else { 0 },
)
};

if result == SQLITE_OK {
Ok((current, highest))
} else {
Err(self.guard.handle.expect_error().into())
}
}

/// Attempts to free as much heap memory as possible from the database connection.
///
/// This function causes SQLite to release some memory used by the database connection,
/// such as memory used to cache prepared statements.
///
/// Returns the number of bytes of memory released.
///
/// See: https://www.sqlite.org/c3ref/db_release_memory.html
pub fn db_release_memory(&mut self) -> i32 {
unsafe { sqlite3_db_release_memory(self.as_raw_handle().as_ptr()) }
}
}

impl Drop for ConnectionState {
Expand Down
61 changes: 60 additions & 1 deletion sqlx-sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ pub use column::SqliteColumn;
pub use connection::serialize::SqliteOwnedBuf;
#[cfg(feature = "preupdate-hook")]
pub use connection::PreupdateHookResult;
pub use connection::{LockedSqliteHandle, SqliteConnection, SqliteOperation, UpdateHookResult};
pub use connection::{
LockedSqliteHandle, SqliteConnection, SqliteDatabaseStatus, SqliteOperation, UpdateHookResult,
};
pub use database::Sqlite;
pub use error::SqliteError;
pub use options::{
Expand Down Expand Up @@ -124,6 +126,63 @@ impl_encode_for_option!(Sqlite);
#[doc(hidden)]
pub static CREATE_DB_WAL: AtomicBool = AtomicBool::new(true);

/// Sets the soft heap limit for SQLite.
///
/// This function sets a soft limit on the amount of heap memory that can be allocated by SQLite
/// across all database connections within a single process.
///
/// - `Some(limit)` sets the heap limit to the specified number of bytes
/// - `None` disables the heap limit
///
/// Returns the previous heap limit in bytes, or `None` if no limit was previously set.
///
/// # Errors
///
/// Returns an error if the limit exceeds `i64::MAX`.
///
/// See: https://www.sqlite.org/c3ref/hard_heap_limit64.html
pub fn set_soft_heap_limit(
limit: Option<std::num::NonZeroU64>,
) -> Result<Option<std::num::NonZeroU64>, Error> {
use libsqlite3_sys::sqlite3_soft_heap_limit64;

let limit_value = match limit {
Some(n) => {
let value = n.get();
if value > i64::MAX as u64 {
return Err(Error::Configuration("heap limit exceeds i64::MAX".into()));
}
value as i64
}
None => 0,
};

let previous = unsafe { sqlite3_soft_heap_limit64(limit_value) };

Ok(if previous > 0 {
Some(std::num::NonZeroU64::new(previous as u64).unwrap())
} else {
None
})
}

/// Gets the current soft heap limit for SQLite.
///
/// Returns the current heap limit in bytes, or `None` if no limit is set.
///
/// See: https://www.sqlite.org/c3ref/hard_heap_limit64.html
pub fn soft_heap_limit() -> Option<std::num::NonZeroU64> {
use libsqlite3_sys::sqlite3_soft_heap_limit64;

let current = unsafe { sqlite3_soft_heap_limit64(-1) };

if current > 0 {
Some(std::num::NonZeroU64::new(current as u64).unwrap())
} else {
None
}
}

/// UNSTABLE: for use by `sqlite-macros-core` only.
#[doc(hidden)]
pub fn describe_blocking(query: &str, database_url: &str) -> Result<Describe<Sqlite>, Error> {
Expand Down
Loading
Loading