Repository navigation
SQLite Transactions #40
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ pub enum StoreError { | |
| Read(String), | ||
| // A write to the storage backend failed. | ||
| Write(String), | ||
| // A Compare-And-Swap (CAS) optimistic concurrency conflict occurred. | ||
| CasConflict(String), | ||
| // Data failed to be encoded into protobuf format. | ||
| Encode(String), | ||
| // Data was retrieved but could not be decoded, indicating corruption. | ||
|
|
@@ -31,6 +33,7 @@ impl std::fmt::Display for StoreError { | |
| match self { | ||
| StoreError::Read(msg) => write!(f, "storage read error: {msg}"), | ||
| StoreError::Write(msg) => write!(f, "storage write error: {msg}"), | ||
| StoreError::CasConflict(msg) => write!(f, "CAS conflict: {msg}"), | ||
| StoreError::Encode(msg) => write!(f, "storage encode error: {msg}"), | ||
| StoreError::Decode(msg) => write!(f, "stored data is corrupt: {msg}"), | ||
| StoreError::Task(msg) => write!(f, "storage task error: {msg}"), | ||
|
|
@@ -74,10 +77,22 @@ pub trait Store: Send + Sync { | |
| async fn put_view(&self, repo_id: String, view_id: Vec<u8>, view: View) -> StoreResult<()>; | ||
|
|
||
| async fn get_op_heads(&self, repo_id: &str) -> StoreResult<Option<Vec<Vec<u8>>>>; | ||
| async fn update_op_heads( | ||
| // Updates op heads by removing `ids_to_remove` (if present) and appending `id_to_add`. | ||
| // Validates that `ids_to_remove` are currently active to prevent stale client overwrites. | ||
| async fn update_op_heads_append_new_remove_old( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of two separate update_op_heads_append_new_remove_old and update_op_heads_compare_and_swap methods, I think you can keep a single update_op_heads: This would first read the current_opheads. If expected_op_heads is set and is different from current_opheads it returns an error. Otherwise it applies new_opheads = current_opheads - ids_to_remove + ids_to_add. It should fail if new_opheads is empty. Otherwise it updates opheads = new_opheads. The whole thing needs to be atomic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing it this way makes it possible to update op heads to some arbitrary set of new opheads, and it can easily be used by the reconciliation logic.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to have 2 different function, both atomic transactions. I think the functionality will be the exact same, and I would prefer to have the reconcile vs normal update call the separate database function, rather than call the same one with different arguments, and have it resolve inside with an if statement. I agree that changing to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm.. I thought jj's upstream OpHeadsStore trait allowed passing any number of operations to add, but that is not the case; it allows exactly 1 operation to add: https://github.com/jj-vcs/jj/blob/e9f48b3e4c1c5e816fbbfbfd06c154a6a96b0215/lib/src/op_heads_store.rs#L60 It is ok to keep
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok sounds, good. I will leave it as is. no changes made. |
||
| &self, | ||
| repo_id: String, | ||
| old_ids: &[Vec<u8>], | ||
| ids_to_remove: &[Vec<u8>], | ||
| id_to_add: Vec<u8>, | ||
| ) -> StoreResult<Vec<Vec<u8>>>; | ||
|
|
||
| // Atomically compares the full set of currently active op heads against `expected_exact_heads` | ||
| // and replaces them entirely with `[new_id]`. Fails immediately with `StoreError::CasConflict` | ||
| // if the active set does not match `expected_exact_heads`. Used during server-side reconciliation. | ||
| async fn update_op_heads_compare_and_swap( | ||
| &self, | ||
| repo_id: String, | ||
| expected_exact_heads: &[Vec<u8>], | ||
| new_id: Vec<u8>, | ||
| ) -> StoreResult<Vec<Vec<u8>>>; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a bit of a confusion due to the naming... 'append_row' makes me think that this is purely an additive operation, and 'old_ids' seemed like it was meant for write race detection. Maybe ids_to_remove, and id_to_add would have made this a bit clearer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. changed the name to update_op_heads_append_new_remove_old
changed variable names to ids_to_remove and id_to_add (singular)