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)
|
||||
if unknown:
|
||||
raise ValueError(f"неизвестные поля заметки: {', '.join(sorted(unknown))}")
|
||||
cur = get_note(conn, key) or {"key": key}
|
||||
cur.update(fields)
|
||||
cur["updated_at"], cur["updated_by"] = db.now(), updated_by
|
||||
cols = ["key"] + NOTE_FIELDS + ["updated_at", "updated_by"]
|
||||
conn.execute(f"INSERT INTO issue_note({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))}) "
|
||||
"ON CONFLICT(key) DO UPDATE SET " + ", ".join(f"{c} = excluded.{c}" for c in cols[1:]),
|
||||
[cur.get(c) for c in cols])
|
||||
with db.transaction(conn): # чтение, слияние и запись — атомарно (соединение общее для потоков)
|
||||
cur = get_note(conn, key) or {"key": key}
|
||||
cur.update(fields)
|
||||
cur["updated_at"], cur["updated_by"] = db.now(), updated_by
|
||||
cols = ["key"] + NOTE_FIELDS + ["updated_at", "updated_by"]
|
||||
conn.execute(f"INSERT INTO issue_note({', '.join(cols)}) VALUES ({', '.join('?' * len(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)
|
||||
|
||||
|
||||
def rename_note(conn, old_key, new_key) -> None:
|
||||
"""После создания задачи: заметка с временного ключа переезжает на настоящий (старая запись под новым ключом,
|
||||
если вдруг была, уступает место)."""
|
||||
if get_note(conn, old_key) is None:
|
||||
if old_key == new_key:
|
||||
return
|
||||
conn.execute("DELETE FROM issue_note WHERE key = ?", (new_key,))
|
||||
conn.execute("UPDATE issue_note SET key = ? WHERE key = ?", (new_key, old_key))
|
||||
with db.transaction(conn):
|
||||
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 mirror.list_issues(conn, {"assignee": "ivanov"})[0]["work_kind"] is None
|
||||
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