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 2 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
116 changes: 114 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_soft_heap_limit64, 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,64 @@ impl LockedSqliteHandle<'_> {
let ret = unsafe { sqlite3_get_autocommit(self.as_raw_handle().as_ptr()) };
ret == 0
}

/// Sets the soft heap limit for the current thread.
///
/// This function sets a soft limit on the amount of heap memory that can be allocated by SQLite.
/// The limit is in bytes. If `limit` is zero, the heap limit is disabled.
///
/// Returns the previous heap limit. If the heap limit was never set, returns 0.
///
/// See: https://www.sqlite.org/c3ref/hard_heap_limit64.html
pub fn soft_heap_limit(&mut self, limit: i64) -> i64 {
unsafe { sqlite3_soft_heap_limit64(limit) }
Copy link
Collaborator

@abonander abonander Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The very first line of the linked documentation says:

These interfaces impose limits on the amount of heap memory that will be by all database connections within a single process.

So saying it sets it for the current thread, or having it as a method on the connection. is very misleading.

It should be a free function, or perhaps an associated method of the Sqlite type. Probably a free function.

Passing a negative number just gets the heap limit and doesn't set it, which also needs to be documented.

A more Rust-y API would be something like limit: Option<NonZeroU64>, returning an error if it's greater than i64::MAX (or using a wrapper type with fallible constructors), and providing a separate getter function, but I don't know if we need to go that far. I'd kind of like to sleep on it, though.

}

/// 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/release_memory.html
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This links to the wrong page. It should be: 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
4 changes: 3 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
Loading