Skip to main content

SourceFingerprint

Struct SourceFingerprint 

Source
pub struct SourceFingerprint {
    pub main_len: u64,
    pub change_counter: Option<u32>,
    pub wal: Option<WalFileHeader>,
    pub wal_len: u64,
}
Expand description

R858-B18 — everything about a source database’s files that must hold still for a copy of them to be a point-in-time image, sampled with no engine open and no lock taken.

This is the other half of reading a foreign-written database. A ReadOnly open (CoreWalSeam::open_reader) gets us in the door without stealing the whole-file lock, but it buys no shared locking protocol with upstream C SQLite: turso locks whole files with fcntl, C SQLite uses byte-range locks plus the -shm WAL index, and neither engine observes the other’s. So a foreign checkpoint landing in the middle of our copy can fold WAL frames into the main file we have already half-read, and the result is a torn image that still passes PRAGMA integrity_check.

Rather than reimplement SQLite’s reader protocol (registering a read-mark in the -shm WAL index — a research project and a permanent compatibility liability against an engine we do not control), raw_consistent_copy_live uses textbook optimistic validation: sample this before the copy, sample it again after, and accept the copy only if nothing moved. That converts “may silently read torn state” into “detects torn state and refuses”, needs no cooperation from the foreign engine, and costs two stats and a 132-byte read per attempt.

§Why these fields

  • wal (salt + checkpoint_seq) moves on every WAL reset, in both fold regimes — fresh randomness on a writer restart, salt1 incrementing on an in-process autocheckpoint (both measured; see WalSalt).
  • main_len moves when a checkpoint grows the main database.
  • change_counter (main header bytes 24..28) moves on every write to the main file — i.e. on every checkpoint — even one that leaves its length alone. It is meaningful here only because the foreign writer is C SQLite: turso does not maintain this field (it stays 1 in every journal mode, verified — see snapshot.rs’s two-gate rationale), which is exactly why the WAL salt carries the weight and this one is corroboration.
  • wal_len is recorded for the report but deliberately not part of the accept/reject test — see Self::stable_across, which is the comparison to use. There is no PartialEq on this type on purpose: a bare == would silently include wal_len and refuse every copy taken while the application was merely writing.

Fields§

§main_len: u64

Length of the main database file.

§change_counter: Option<u32>

The main header’s change counter, or None when the file is too short to carry a SQLite header at all (a database whose page 1 still lives only in the WAL). Unknown-and-unknown compares equal, which is safe because main_len participates in the same comparison.

§wal: Option<WalFileHeader>

The -wal header, or None when there is no WAL sidecar.

§wal_len: u64

Length of the -wal file (0 when absent).

Implementations§

Source§

impl SourceFingerprint

Source

pub fn read(db_path: &str) -> Result<Self>

Sample the fingerprint of the database at db_path. Touches nothing: two metadata calls plus a 28-byte and a 32-byte read.

Source

pub fn stable_across(&self, after: &Self) -> bool

True when nothing that can tear a copy moved between self (sampled before) and after (sampled after). This is the accept test in raw_consistent_copy_live, and it is narrower than field equality on purpose.

§What can tear the copy, and what cannot

The copy reads the main file, then replays WAL frames 1..=max_frame captured when the seam opened. Against that algorithm:

  • A checkpoint tears it. It rewrites pages of the main file and resets the WAL, so our already-read main bytes and our frame reads can straddle the fold — replaying pre-fold frames over post-fold pages rolls pages backwards. Caught: a checkpoint bumps change_counter and/or main_len, and a WAL restart re-rolls the salt and the sequence (measured in both regimes, examples/foreign_checkpoint_probe.rs probes A and E).
  • A plain append does NOT tear it. SQLite only ever appends frames within a generation, and only a reset (which re-rolls salt1) lets it overwrite an existing frame. So frames 1..=max_frame are immutable for as long as the salt holds, and a writer that commits during our copy just means our image is a slightly earlier point in time — which is what a point-in-time copy is.

Which is why wal_len and the frame count are excluded. Including them buys no additional safety and costs a refusal on every copy taken while the application is writing at all — turning a working backup into one that only succeeds against an idle database. R858-B18 measured that difference rather than assuming it; see probe H.

Trait Implementations§

Source§

impl Clone for SourceFingerprint

Source§

fn clone(&self) -> SourceFingerprint

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for SourceFingerprint

Source§

impl Debug for SourceFingerprint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Fruit for T
where T: Send + Downcast,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more