fix(hub): транзакции в set_note и rename_note, сохранение заметки при равных ключах
rename_note: при old_key == new_key ранний выход (DELETE раньше стирал единственную заметку); DELETE и UPDATE выполняются в транзакции. set_note: чтение, слияние и запись в одной транзакции (соединение общее для потоков). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
parent
e981d62620
commit
3394ddcf4c
@ -95,20 +95,24 @@ def set_note(conn, key, fields: dict, updated_by: str) -> dict:
|
|||||||
unknown = set(fields) - set(NOTE_FIELDS)
|
unknown = set(fields) - set(NOTE_FIELDS)
|
||||||
if unknown:
|
if unknown:
|
||||||
raise ValueError(f"неизвестные поля заметки: {', '.join(sorted(unknown))}")
|
raise ValueError(f"неизвестные поля заметки: {', '.join(sorted(unknown))}")
|
||||||
cur = get_note(conn, key) or {"key": key}
|
with db.transaction(conn): # чтение, слияние и запись — атомарно (соединение общее для потоков)
|
||||||
cur.update(fields)
|
cur = get_note(conn, key) or {"key": key}
|
||||||
cur["updated_at"], cur["updated_by"] = db.now(), updated_by
|
cur.update(fields)
|
||||||
cols = ["key"] + NOTE_FIELDS + ["updated_at", "updated_by"]
|
cur["updated_at"], cur["updated_by"] = db.now(), updated_by
|
||||||
conn.execute(f"INSERT INTO issue_note({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))}) "
|
cols = ["key"] + NOTE_FIELDS + ["updated_at", "updated_by"]
|
||||||
"ON CONFLICT(key) DO UPDATE SET " + ", ".join(f"{c} = excluded.{c}" for c in cols[1:]),
|
conn.execute(f"INSERT INTO issue_note({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))}) "
|
||||||
[cur.get(c) for c in cols])
|
"ON CONFLICT(key) DO UPDATE SET " + ", ".join(f"{c} = excluded.{c}" for c in cols[1:]),
|
||||||
|
[cur.get(c) for c in cols])
|
||||||
return get_note(conn, key)
|
return get_note(conn, key)
|
||||||
|
|
||||||
|
|
||||||
def rename_note(conn, old_key, new_key) -> None:
|
def rename_note(conn, old_key, new_key) -> None:
|
||||||
"""После создания задачи: заметка с временного ключа переезжает на настоящий (старая запись под новым ключом,
|
"""После создания задачи: заметка с временного ключа переезжает на настоящий (старая запись под новым ключом,
|
||||||
если вдруг была, уступает место)."""
|
если вдруг была, уступает место)."""
|
||||||
if get_note(conn, old_key) is None:
|
if old_key == new_key:
|
||||||
return
|
return
|
||||||
conn.execute("DELETE FROM issue_note WHERE key = ?", (new_key,))
|
with db.transaction(conn):
|
||||||
conn.execute("UPDATE issue_note SET key = ? WHERE key = ?", (new_key, old_key))
|
if get_note(conn, old_key) is None:
|
||||||
|
return
|
||||||
|
conn.execute("DELETE FROM issue_note WHERE key = ?", (new_key,))
|
||||||
|
conn.execute("UPDATE issue_note SET key = ? WHERE key = ?", (new_key, old_key))
|
||||||
|
|||||||
@ -69,3 +69,26 @@ def test_list_issues_filters():
|
|||||||
assert [i["key"] for i in mirror.list_issues(conn, {"q": "A-2"})] == ["A-2"]
|
assert [i["key"] for i in mirror.list_issues(conn, {"q": "A-2"})] == ["A-2"]
|
||||||
assert mirror.list_issues(conn, {"assignee": "ivanov"})[0]["work_kind"] is None
|
assert mirror.list_issues(conn, {"assignee": "ivanov"})[0]["work_kind"] is None
|
||||||
assert len(mirror.list_issues(conn, None, limit=1)) == 1
|
assert len(mirror.list_issues(conn, None, limit=1)) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_note_same_key_keeps_note():
|
||||||
|
conn = db.connect(":memory:")
|
||||||
|
mirror.apply_state(conn, [issue("A-1")], [])
|
||||||
|
mirror.set_note(conn, "A-1", {"fact_status": "В работе"}, "x")
|
||||||
|
mirror.rename_note(conn, "A-1", "A-1")
|
||||||
|
assert mirror.get_note(conn, "A-1")["fact_status"] == "В работе"
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_note_missing_is_noop():
|
||||||
|
conn = db.connect(":memory:")
|
||||||
|
mirror.rename_note(conn, "new:404", "A-9") # заметки нет — не падает
|
||||||
|
assert mirror.get_note(conn, "new:404") is None
|
||||||
|
assert mirror.get_note(conn, "A-9") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_note_partial_keeps_other_fields():
|
||||||
|
conn = db.connect(":memory:")
|
||||||
|
mirror.set_note(conn, "A-1", {"work_kind": "разработка", "done_text": "сделано"}, "x")
|
||||||
|
got = mirror.set_note(conn, "A-1", {"fact_status": "Готово"}, "y")
|
||||||
|
assert got["work_kind"] == "разработка" and got["done_text"] == "сделано"
|
||||||
|
assert got["fact_status"] == "Готово" and got["updated_by"] == "y"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user