fix(core): a file renamed just as its create lands keeps its access (B174)

This commit is contained in:
nvrl committed 2026-10-04 22:15:01 +02:00
1 parent 378667fb37
commit 1612a9d07a
5 files changed
+83 -4

No files matched your search

+18 -2
View File
@@ -95,11 +95,27 @@ impl Db {
/// Resolve a persisted node's effective access through its nearest recorded
/// share root. `None` means the node itself is not persisted, which callers
/// must not treat as owned: stale handles need to fail closed.
///
/// A stand-in ([`local_uid`](super::local_uid)) finds its row by local id,
/// so it still does once its create has landed and the row has the uid
/// Drive gave it. The tree learns that uid a moment later, and a rename in
/// between was refused (`docs/BUGS.md` B174).
pub fn effective_node_access(&self, uid: &NodeUid) -> Result<Option<Access>> {
let conn = self.read();
let key = uid.to_string();
let key = match super::local_lid(&key) {
Some(lid) => conn
.query_row("SELECT uid FROM nodes WHERE lid = ?1", [lid], |row| {
row.get::<_, Option<String>>(0)
})
.optional()?
.flatten()
.unwrap_or(key),
None => key,
};
let exists: bool = conn.query_row(
"SELECT EXISTS(SELECT 1 FROM nodes WHERE uid = ?1 AND node_json IS NOT NULL)",
[uid.to_string()],
[&key],
|row| row.get(0),
)?;
if !exists {
@@ -126,7 +142,7 @@ impl Db {
FROM ancestors a JOIN share_access sa ON sa.root_uid = a.uid
ORDER BY a.depth ASC
LIMIT 1",
[uid.to_string()],
[&key],
|row| row.get::<_, String>(0),
)
.optional()?;
+39
View File
@@ -3243,6 +3243,45 @@ fn an_op_queued_under_a_stand_in_after_its_create_landed_goes_to_the_landed_node
assert_eq!(claimed.uid, real, "the drain sends the real uid");
}
#[test]
fn a_stand_in_keeps_its_access_once_its_create_has_landed() {
let db = Db::open_in_memory().unwrap();
db.upsert_node(&folder("parent", None, "My Files")).unwrap();
let parent = uid("parent").to_string();
let lid = db.add_local_row(&parent, "new.txt", false, 1).unwrap();
let local = local_uid(lid);
let stand_in = NodeUid::new(VolumeId::from("local"), LinkId::from(lid.to_string()));
let mut node = file("new", "parent", "new.txt", 1);
node.uid = stand_in.clone();
db.upsert_node(&node).unwrap();
let real = uid("real").to_string();
let (id, _) = db
.enqueue_op(&op_on(OP_CREATE, &local, Some(&parent)))
.unwrap();
assert_eq!(
db.effective_node_access(&stand_in).unwrap(),
Some(crate::Access::Owner)
);
db.claim_next_due_op(1).unwrap().unwrap();
db.finish_create(
id,
Some("/staging/create"),
&landing(&local, &real, &parent, "new.txt"),
|_| None,
)
.unwrap();
// The tree still names the file by its stand-in for a moment.
assert_eq!(
db.effective_node_access(&stand_in).unwrap(),
Some(crate::Access::Owner)
);
assert_eq!(
db.effective_node_access(&uid("real")).unwrap(),
Some(crate::Access::Owner)
);
}
#[test]
fn a_node_queued_under_both_its_uids_is_sent_one_op_at_a_time() {
let db = Db::open_in_memory().unwrap();