Skip to main content

reifydb_sqlite/
memory.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::os::raw::c_int;
5
6use reifydb_value::{byte_size::ByteSize, count::Count};
7use rusqlite::{
8	Connection,
9	ffi::{
10		SQLITE_DBSTATUS_CACHE_HIT, SQLITE_DBSTATUS_CACHE_MISS, SQLITE_DBSTATUS_CACHE_USED, SQLITE_OK,
11		sqlite3_db_status, sqlite3_memory_used,
12	},
13};
14
15pub fn global_memory_used() -> ByteSize {
16	// SAFETY: takes no arguments and dereferences nothing; SQLite guards the counter internally.
17	let used = unsafe { sqlite3_memory_used() };
18	ByteSize::from_bytes(used.max(0) as u64)
19}
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
22pub struct ConnectionCacheSweep {
23	pub used: ByteSize,
24	pub hits: Count,
25	pub misses: Count,
26}
27
28pub fn sweep_connection_cache(conn: &Connection) -> ConnectionCacheSweep {
29	ConnectionCacheSweep {
30		used: ByteSize::from_bytes(db_status(conn, SQLITE_DBSTATUS_CACHE_USED, false)),
31		hits: Count::new(db_status(conn, SQLITE_DBSTATUS_CACHE_HIT, true)),
32		misses: Count::new(db_status(conn, SQLITE_DBSTATUS_CACHE_MISS, true)),
33	}
34}
35
36fn db_status(conn: &Connection, op: c_int, reset: bool) -> u64 {
37	let mut current: c_int = 0;
38	let mut highwater: c_int = 0;
39	// SAFETY: the handle is a live sqlite3* for the borrow of `conn`, and both out-params are owned locals.
40	let rc = unsafe { sqlite3_db_status(conn.handle(), op, &mut current, &mut highwater, reset as c_int) };
41	if rc == SQLITE_OK {
42		current.max(0) as u64
43	} else {
44		0
45	}
46}
47
48#[cfg(test)]
49mod tests {
50	use rusqlite::Connection;
51
52	use super::sweep_connection_cache;
53
54	#[test]
55	fn sweep_resets_hit_and_miss_counters_but_not_used() {
56		// The sweep is take-and-reset so partial sweeps across a pool stay additive: every hit and
57		// miss is handed out exactly once. `used` is instantaneous and must survive the sweep.
58		let conn = Connection::open_in_memory().expect("open in-memory db");
59		conn.execute_batch(
60			"CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT); \
61			 INSERT INTO t VALUES (1, 'a'), (2, 'b');",
62		)
63		.expect("seed table");
64		let count: i64 = conn.query_row("SELECT COUNT(*) FROM t", [], |row| row.get(0)).expect("count");
65		assert_eq!(count, 2);
66
67		let first = sweep_connection_cache(&conn);
68		assert!(
69			first.hits.as_u64() + first.misses.as_u64() > 0,
70			"reading a table must touch the page cache, got {first:?}"
71		);
72		assert!(first.used.as_bytes() > 0, "pages held by the connection must report as used");
73
74		let second = sweep_connection_cache(&conn);
75		assert_eq!(second.hits.as_u64(), 0, "hits must have been taken by the first sweep");
76		assert_eq!(second.misses.as_u64(), 0, "misses must have been taken by the first sweep");
77		assert!(second.used.as_bytes() > 0, "used is instantaneous and must not be reset by sweeping");
78	}
79}