mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 08:34:19 +01:00
add optional upper_bound_inclusive parameter to some checkpoint modes
- will be used in sync-engine protocol
This commit is contained in:
@@ -1439,8 +1439,12 @@ impl Pager {
|
|||||||
trace!(?state);
|
trace!(?state);
|
||||||
match state {
|
match state {
|
||||||
CheckpointState::Checkpoint => {
|
CheckpointState::Checkpoint => {
|
||||||
let res =
|
let res = return_if_io!(wal.borrow_mut().checkpoint(
|
||||||
return_if_io!(wal.borrow_mut().checkpoint(self, CheckpointMode::Passive));
|
self,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None
|
||||||
|
}
|
||||||
|
));
|
||||||
self.checkpoint_state
|
self.checkpoint_state
|
||||||
.replace(CheckpointState::SyncDbFile { res });
|
.replace(CheckpointState::SyncDbFile { res });
|
||||||
}
|
}
|
||||||
@@ -1487,7 +1491,9 @@ impl Pager {
|
|||||||
self.io.wait_for_completion(c)?;
|
self.io.wait_for_completion(c)?;
|
||||||
}
|
}
|
||||||
if !wal_auto_checkpoint_disabled {
|
if !wal_auto_checkpoint_disabled {
|
||||||
self.wal_checkpoint(CheckpointMode::Passive)?;
|
self.wal_checkpoint(CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,23 +67,31 @@ impl CheckpointResult {
|
|||||||
pub enum CheckpointMode {
|
pub enum CheckpointMode {
|
||||||
/// Checkpoint as many frames as possible without waiting for any database readers or writers to finish, then sync the database file if all frames in the log were checkpointed.
|
/// Checkpoint as many frames as possible without waiting for any database readers or writers to finish, then sync the database file if all frames in the log were checkpointed.
|
||||||
/// Passive never blocks readers or writers, only ensures (like all modes do) that there are no other checkpointers.
|
/// Passive never blocks readers or writers, only ensures (like all modes do) that there are no other checkpointers.
|
||||||
Passive,
|
///
|
||||||
|
/// Optional upper_bound_inclusive parameter can be set in order to checkpoint frames with number no larger than the parameter
|
||||||
|
Passive { upper_bound_inclusive: Option<u64> },
|
||||||
/// This mode blocks until there is no database writer and all readers are reading from the most recent database snapshot. It then checkpoints all frames in the log file and syncs the database file. This mode blocks new database writers while it is pending, but new database readers are allowed to continue unimpeded.
|
/// This mode blocks until there is no database writer and all readers are reading from the most recent database snapshot. It then checkpoints all frames in the log file and syncs the database file. This mode blocks new database writers while it is pending, but new database readers are allowed to continue unimpeded.
|
||||||
Full,
|
Full,
|
||||||
/// This mode works the same way as `Full` with the addition that after checkpointing the log file it blocks (calls the busy-handler callback) until all readers are reading from the database file only. This ensures that the next writer will restart the log file from the beginning. Like `Full`, this mode blocks new database writer attempts while it is pending, but does not impede readers.
|
/// This mode works the same way as `Full` with the addition that after checkpointing the log file it blocks (calls the busy-handler callback) until all readers are reading from the database file only. This ensures that the next writer will restart the log file from the beginning. Like `Full`, this mode blocks new database writer attempts while it is pending, but does not impede readers.
|
||||||
Restart,
|
Restart,
|
||||||
/// This mode works the same way as `Restart` with the addition that it also truncates the log file to zero bytes just prior to a successful return.
|
/// This mode works the same way as `Restart` with the addition that it also truncates the log file to zero bytes just prior to a successful return.
|
||||||
Truncate,
|
///
|
||||||
|
/// Extra parameter can be set in order to perform conditional TRUNCATE: database will be checkpointed and truncated only if max_frames equals to the parameter value
|
||||||
|
/// this behaviour used by sync-engine which consolidate WAL before checkpoint and needs to be sure that no frames will be missed
|
||||||
|
Truncate { upper_bound_inclusive: Option<u64> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CheckpointMode {
|
impl CheckpointMode {
|
||||||
fn should_restart_log(&self) -> bool {
|
fn should_restart_log(&self) -> bool {
|
||||||
matches!(self, CheckpointMode::Truncate | CheckpointMode::Restart)
|
matches!(
|
||||||
|
self,
|
||||||
|
CheckpointMode::Truncate { .. } | CheckpointMode::Restart
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/// All modes other than Passive require a complete backfilling of all available frames
|
/// All modes other than Passive require a complete backfilling of all available frames
|
||||||
/// from `shared.nbackfills + 1 -> shared.max_frame`
|
/// from `shared.nbackfills + 1 -> shared.max_frame`
|
||||||
fn require_all_backfilled(&self) -> bool {
|
fn require_all_backfilled(&self) -> bool {
|
||||||
!matches!(self, CheckpointMode::Passive)
|
!matches!(self, CheckpointMode::Passive { .. })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,7 +717,7 @@ impl CheckpointLocks {
|
|||||||
// readers or writers. It acquires the checkpoint lock to ensure that no other
|
// readers or writers. It acquires the checkpoint lock to ensure that no other
|
||||||
// concurrent checkpoint happens, and acquires the exclusive read lock 0
|
// concurrent checkpoint happens, and acquires the exclusive read lock 0
|
||||||
// to ensure that no readers read from a partially checkpointed db file.
|
// to ensure that no readers read from a partially checkpointed db file.
|
||||||
CheckpointMode::Passive => {
|
CheckpointMode::Passive { .. } => {
|
||||||
let read0 = &mut shared.read_locks[0];
|
let read0 = &mut shared.read_locks[0];
|
||||||
if !read0.write() {
|
if !read0.write() {
|
||||||
shared.checkpoint_lock.unlock();
|
shared.checkpoint_lock.unlock();
|
||||||
@@ -735,7 +743,7 @@ impl CheckpointLocks {
|
|||||||
}
|
}
|
||||||
Ok(Self::Writer { ptr })
|
Ok(Self::Writer { ptr })
|
||||||
}
|
}
|
||||||
CheckpointMode::Restart | CheckpointMode::Truncate => {
|
CheckpointMode::Restart | CheckpointMode::Truncate { .. } => {
|
||||||
// like all modes, we must acquire an exclusive checkpoint lock and lock on read 0
|
// like all modes, we must acquire an exclusive checkpoint lock and lock on read 0
|
||||||
// to prevent a reader from reading a partially checkpointed db file.
|
// to prevent a reader from reading a partially checkpointed db file.
|
||||||
let read0 = &mut shared.read_locks[0];
|
let read0 = &mut shared.read_locks[0];
|
||||||
@@ -1513,7 +1521,25 @@ impl WalFile {
|
|||||||
}
|
}
|
||||||
// acquire the appropriate exclusive locks depending on the checkpoint mode
|
// acquire the appropriate exclusive locks depending on the checkpoint mode
|
||||||
self.acquire_proper_checkpoint_guard(mode)?;
|
self.acquire_proper_checkpoint_guard(mode)?;
|
||||||
self.ongoing_checkpoint.max_frame = self.determine_max_safe_checkpoint_frame();
|
let mut max_frame = self.determine_max_safe_checkpoint_frame();
|
||||||
|
|
||||||
|
if let CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: Some(upper_bound),
|
||||||
|
} = mode
|
||||||
|
{
|
||||||
|
if max_frame > upper_bound {
|
||||||
|
tracing::info!("abort checkpoint because latest frame in WAL is greater than upper_bound in TRUNCATE mode: {max_frame} != {upper_bound}");
|
||||||
|
return Err(LimboError::Busy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: Some(upper_bound),
|
||||||
|
} = mode
|
||||||
|
{
|
||||||
|
max_frame = max_frame.min(upper_bound);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ongoing_checkpoint.max_frame = max_frame;
|
||||||
self.ongoing_checkpoint.min_frame = nbackfills + 1;
|
self.ongoing_checkpoint.min_frame = nbackfills + 1;
|
||||||
let to_checkpoint = {
|
let to_checkpoint = {
|
||||||
let shared = self.get_shared();
|
let shared = self.get_shared();
|
||||||
@@ -1676,7 +1702,7 @@ impl WalFile {
|
|||||||
.max_frame
|
.max_frame
|
||||||
.saturating_sub(self.ongoing_checkpoint.min_frame - 1);
|
.saturating_sub(self.ongoing_checkpoint.min_frame - 1);
|
||||||
|
|
||||||
if matches!(mode, CheckpointMode::Truncate) {
|
if matches!(mode, CheckpointMode::Truncate { .. }) {
|
||||||
// sqlite always returns zeros for truncate mode
|
// sqlite always returns zeros for truncate mode
|
||||||
CheckpointResult::default()
|
CheckpointResult::default()
|
||||||
} else if frames_checkpointed == 0
|
} else if frames_checkpointed == 0
|
||||||
@@ -1858,7 +1884,7 @@ impl WalFile {
|
|||||||
self.min_frame = 0;
|
self.min_frame = 0;
|
||||||
|
|
||||||
// For TRUNCATE mode: shrink the WAL file to 0 B
|
// For TRUNCATE mode: shrink the WAL file to 0 B
|
||||||
if matches!(mode, CheckpointMode::Truncate) {
|
if matches!(mode, CheckpointMode::Truncate { .. }) {
|
||||||
let c = Completion::new_trunc(|_| {
|
let c = Completion::new_trunc(|_| {
|
||||||
tracing::trace!("WAL file truncated to 0 B");
|
tracing::trace!("WAL file truncated to 0 B");
|
||||||
});
|
});
|
||||||
@@ -1892,11 +1918,13 @@ impl WalFile {
|
|||||||
fn acquire_proper_checkpoint_guard(&mut self, mode: CheckpointMode) -> Result<()> {
|
fn acquire_proper_checkpoint_guard(&mut self, mode: CheckpointMode) -> Result<()> {
|
||||||
let needs_new_guard = !matches!(
|
let needs_new_guard = !matches!(
|
||||||
(&self.checkpoint_guard, mode),
|
(&self.checkpoint_guard, mode),
|
||||||
(Some(CheckpointLocks::Read0 { .. }), CheckpointMode::Passive,)
|
(
|
||||||
| (
|
Some(CheckpointLocks::Read0 { .. }),
|
||||||
Some(CheckpointLocks::Writer { .. }),
|
CheckpointMode::Passive { .. },
|
||||||
CheckpointMode::Restart | CheckpointMode::Truncate,
|
) | (
|
||||||
),
|
Some(CheckpointLocks::Writer { .. }),
|
||||||
|
CheckpointMode::Restart | CheckpointMode::Truncate { .. },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if needs_new_guard {
|
if needs_new_guard {
|
||||||
// Drop any existing guard
|
// Drop any existing guard
|
||||||
@@ -2035,7 +2063,10 @@ impl WalFileShared {
|
|||||||
/// writing frames into the start of the log file.
|
/// writing frames into the start of the log file.
|
||||||
fn restart_wal_header(&mut self, io: &Arc<dyn IO>, mode: CheckpointMode) -> Result<()> {
|
fn restart_wal_header(&mut self, io: &Arc<dyn IO>, mode: CheckpointMode) -> Result<()> {
|
||||||
turso_assert!(
|
turso_assert!(
|
||||||
matches!(mode, CheckpointMode::Restart | CheckpointMode::Truncate),
|
matches!(
|
||||||
|
mode,
|
||||||
|
CheckpointMode::Restart | CheckpointMode::Truncate { .. }
|
||||||
|
),
|
||||||
"CheckpointMode must be Restart or Truncate"
|
"CheckpointMode must be Restart or Truncate"
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
@@ -2140,7 +2171,13 @@ pub mod test {
|
|||||||
let stat = std::fs::metadata(&walpath).unwrap();
|
let stat = std::fs::metadata(&walpath).unwrap();
|
||||||
let meta_before = std::fs::metadata(&walpath).unwrap();
|
let meta_before = std::fs::metadata(&walpath).unwrap();
|
||||||
let bytes_before = meta_before.len();
|
let bytes_before = meta_before.len();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Truncate);
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
drop(wal);
|
drop(wal);
|
||||||
|
|
||||||
assert_eq!(pager.wal_frame_count().unwrap(), 0);
|
assert_eq!(pager.wal_frame_count().unwrap(), 0);
|
||||||
@@ -2338,7 +2375,13 @@ pub mod test {
|
|||||||
let (res1, max_before) = {
|
let (res1, max_before) = {
|
||||||
let pager = conn1.pager.borrow();
|
let pager = conn1.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
let res = run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive);
|
let res = run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
let maxf = unsafe {
|
let maxf = unsafe {
|
||||||
(&*db.maybe_shared_wal.read().as_ref().unwrap().get())
|
(&*db.maybe_shared_wal.read().as_ref().unwrap().get())
|
||||||
.max_frame
|
.max_frame
|
||||||
@@ -2367,7 +2410,13 @@ pub mod test {
|
|||||||
// Second passive checkpoint should finish
|
// Second passive checkpoint should finish
|
||||||
let pager = conn1.pager.borrow();
|
let pager = conn1.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
let res2 = run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive);
|
let res2 = run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res2.num_backfilled, res2.num_attempted,
|
res2.num_backfilled, res2.num_attempted,
|
||||||
"Second checkpoint completes remaining frames"
|
"Second checkpoint completes remaining frames"
|
||||||
@@ -2514,7 +2563,13 @@ pub mod test {
|
|||||||
let checkpoint_result = {
|
let checkpoint_result = {
|
||||||
let pager = conn_writer.pager.borrow();
|
let pager = conn_writer.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive)
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -2557,7 +2612,13 @@ pub mod test {
|
|||||||
{
|
{
|
||||||
let pager = conn.pager.borrow();
|
let pager = conn.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
let _result = run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive);
|
let _result = run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that read mark 1 (default reader) was updated to max_frame
|
// check that read mark 1 (default reader) was updated to max_frame
|
||||||
@@ -2703,7 +2764,13 @@ pub mod test {
|
|||||||
let result1 = {
|
let result1 = {
|
||||||
let pager = conn_writer.pager.borrow();
|
let pager = conn_writer.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive)
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
assert_eq!(result1.num_backfilled, r1_frame);
|
assert_eq!(result1.num_backfilled, r1_frame);
|
||||||
|
|
||||||
@@ -2714,7 +2781,13 @@ pub mod test {
|
|||||||
let result2 = {
|
let result2 = {
|
||||||
let pager = conn_writer.pager.borrow();
|
let pager = conn_writer.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive)
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
assert_eq!(result1.num_backfilled + result2.num_backfilled, r2_frame);
|
assert_eq!(result1.num_backfilled + result2.num_backfilled, r2_frame);
|
||||||
|
|
||||||
@@ -2757,7 +2830,13 @@ pub mod test {
|
|||||||
{
|
{
|
||||||
let pager = conn.pager.borrow();
|
let pager = conn.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Truncate);
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size after truncate
|
// Check file size after truncate
|
||||||
@@ -2812,7 +2891,13 @@ pub mod test {
|
|||||||
{
|
{
|
||||||
let pager = conn.pager.borrow();
|
let pager = conn.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Truncate);
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size after truncate
|
// Check file size after truncate
|
||||||
@@ -2844,7 +2929,13 @@ pub mod test {
|
|||||||
{
|
{
|
||||||
let pager = conn.pager.borrow();
|
let pager = conn.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive);
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// delete the WAL file so we can read right from db and assert
|
// delete the WAL file so we can read right from db and assert
|
||||||
// that everything was backfilled properly
|
// that everything was backfilled properly
|
||||||
@@ -2933,7 +3024,13 @@ pub mod test {
|
|||||||
{
|
{
|
||||||
let pager = conn1.pager.borrow();
|
let pager = conn1.pager.borrow();
|
||||||
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
let mut wal = pager.wal.as_ref().unwrap().borrow_mut();
|
||||||
run_checkpoint_until_done(&mut *wal, &pager, CheckpointMode::Passive);
|
run_checkpoint_until_done(
|
||||||
|
&mut *wal,
|
||||||
|
&pager,
|
||||||
|
CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a read transaction on conn2
|
// Start a read transaction on conn2
|
||||||
|
|||||||
@@ -402,7 +402,9 @@ fn query_pragma(
|
|||||||
LimboError::ParseError(format!("Unknown Checkpoint Mode: {e}"))
|
LimboError::ParseError(format!("Unknown Checkpoint Mode: {e}"))
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
_ => CheckpointMode::Passive,
|
_ => CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
program.alloc_registers(2);
|
program.alloc_registers(2);
|
||||||
|
|||||||
@@ -1345,9 +1345,13 @@ pub unsafe extern "C" fn sqlite3_wal_checkpoint_v2(
|
|||||||
let db: &mut sqlite3 = &mut *db;
|
let db: &mut sqlite3 = &mut *db;
|
||||||
let db = db.inner.lock().unwrap();
|
let db = db.inner.lock().unwrap();
|
||||||
let chkptmode = match mode {
|
let chkptmode = match mode {
|
||||||
SQLITE_CHECKPOINT_PASSIVE => CheckpointMode::Passive,
|
SQLITE_CHECKPOINT_PASSIVE => CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
SQLITE_CHECKPOINT_RESTART => CheckpointMode::Restart,
|
SQLITE_CHECKPOINT_RESTART => CheckpointMode::Restart,
|
||||||
SQLITE_CHECKPOINT_TRUNCATE => CheckpointMode::Truncate,
|
SQLITE_CHECKPOINT_TRUNCATE => CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
},
|
||||||
SQLITE_CHECKPOINT_FULL => CheckpointMode::Full,
|
SQLITE_CHECKPOINT_FULL => CheckpointMode::Full,
|
||||||
_ => return SQLITE_MISUSE, // Unsupported mode
|
_ => return SQLITE_MISUSE, // Unsupported mode
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashSet, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use rand::{RngCore, SeedableRng};
|
use rand::{RngCore, SeedableRng};
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
use rusqlite::types::Value;
|
use rusqlite::types::Value;
|
||||||
use turso_core::types::WalFrameInfo;
|
use turso_core::{types::WalFrameInfo, CheckpointMode, LimboError, StepResult};
|
||||||
|
|
||||||
use crate::common::{limbo_exec_rows, rng_from_time, TempDatabase};
|
use crate::common::{limbo_exec_rows, rng_from_time, TempDatabase};
|
||||||
|
|
||||||
@@ -458,3 +458,83 @@ fn test_wal_api_revert_pages() {
|
|||||||
vec![] as Vec<Vec<Value>>,
|
vec![] as Vec<Vec<Value>>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wal_upper_bound_passive() {
|
||||||
|
let db = TempDatabase::new_empty(false);
|
||||||
|
let writer = db.connect_limbo();
|
||||||
|
|
||||||
|
writer
|
||||||
|
.execute("create table test(id integer primary key, value text)")
|
||||||
|
.unwrap();
|
||||||
|
let watermark0 = writer.wal_frame_count().unwrap();
|
||||||
|
writer
|
||||||
|
.execute("insert into test values (1, 'hello')")
|
||||||
|
.unwrap();
|
||||||
|
let watermark1 = writer.wal_frame_count().unwrap();
|
||||||
|
writer
|
||||||
|
.execute("insert into test values (2, 'turso')")
|
||||||
|
.unwrap();
|
||||||
|
let watermark2 = writer.wal_frame_count().unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
vec![
|
||||||
|
turso_core::types::Value::Integer(1),
|
||||||
|
turso_core::types::Value::Text(turso_core::types::Text::new("hello")),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
turso_core::types::Value::Integer(2),
|
||||||
|
turso_core::types::Value::Text(turso_core::types::Text::new("turso")),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (prefix, watermark) in [(0, watermark0), (1, watermark1), (2, watermark2)] {
|
||||||
|
let mode = CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: Some(watermark),
|
||||||
|
};
|
||||||
|
writer.checkpoint(mode).unwrap();
|
||||||
|
|
||||||
|
let db_path_copy = format!("{}-{}-copy", db.path.to_str().unwrap(), watermark);
|
||||||
|
std::fs::copy(&db.path, db_path_copy.clone()).unwrap();
|
||||||
|
let db_copy = TempDatabase::new_with_existent(&PathBuf::from(db_path_copy), false);
|
||||||
|
let conn = db_copy.connect_limbo();
|
||||||
|
let mut stmt = conn.prepare("select * from test").unwrap();
|
||||||
|
let mut rows: Vec<Vec<turso_core::types::Value>> = Vec::new();
|
||||||
|
loop {
|
||||||
|
let result = stmt.step();
|
||||||
|
match result {
|
||||||
|
Ok(StepResult::Row) => {
|
||||||
|
rows.push(stmt.row().unwrap().get_values().cloned().collect())
|
||||||
|
}
|
||||||
|
Ok(StepResult::IO) => db_copy.io.run_once().unwrap(),
|
||||||
|
Ok(StepResult::Done) => break,
|
||||||
|
result => panic!("unexpected step result: {result:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(rows, expected[0..prefix]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wal_upper_bound_truncate() {
|
||||||
|
let db = TempDatabase::new_empty(false);
|
||||||
|
let writer = db.connect_limbo();
|
||||||
|
|
||||||
|
writer
|
||||||
|
.execute("create table test(id integer primary key, value text)")
|
||||||
|
.unwrap();
|
||||||
|
writer
|
||||||
|
.execute("insert into test values (1, 'hello')")
|
||||||
|
.unwrap();
|
||||||
|
let watermark = writer.wal_frame_count().unwrap();
|
||||||
|
writer
|
||||||
|
.execute("insert into test values (2, 'turso')")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mode = CheckpointMode::Truncate {
|
||||||
|
upper_bound_inclusive: Some(watermark),
|
||||||
|
};
|
||||||
|
assert!(matches!(
|
||||||
|
writer.checkpoint(mode).err().unwrap(),
|
||||||
|
LimboError::Busy
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@@ -287,7 +287,9 @@ fn test_wal_checkpoint() -> anyhow::Result<()> {
|
|||||||
for i in 0..iterations {
|
for i in 0..iterations {
|
||||||
let insert_query = format!("INSERT INTO test VALUES ({i})");
|
let insert_query = format!("INSERT INTO test VALUES ({i})");
|
||||||
do_flush(&conn, &tmp_db)?;
|
do_flush(&conn, &tmp_db)?;
|
||||||
conn.checkpoint(CheckpointMode::Passive)?;
|
conn.checkpoint(CheckpointMode::Passive {
|
||||||
|
upper_bound_inclusive: None,
|
||||||
|
})?;
|
||||||
run_query(&tmp_db, &conn, &insert_query)?;
|
run_query(&tmp_db, &conn, &insert_query)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user