Keep Today and Later reads available during planning writes #700

Merged
timmy merged 1 commits from timmy/699-readable-planning-stores into main 2026-08-13 02:24:46 +00:00
4 changed files with 51 additions and 4 deletions

View File

@ -22,10 +22,14 @@ class LaterStore:
self.operation_limit = operation_limit
self.operation_retention_seconds = operation_retention_seconds
self.clock = clock
self._initialize()
def _connect(self) -> sqlite3.Connection:
def _initialize(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
connection = sqlite3.connect(self.path, timeout=self.timeout)
if connection.execute("PRAGMA user_version").fetchone()[0] >= 1:
connection.close()
return
connection.execute("PRAGMA journal_mode=WAL")
connection.execute(
"""
@ -63,8 +67,12 @@ class LaterStore:
"UPDATE later_operations SET created_at = ? WHERE created_at IS NULL",
(self.clock(),),
)
connection.execute("PRAGMA user_version = 1")
connection.commit()
return connection
connection.close()
def _connect(self) -> sqlite3.Connection:
return sqlite3.connect(self.path, timeout=self.timeout)
def _record_operation(self, connection: sqlite3.Connection, login: str, operation_id: str) -> None:
now = self.clock()

View File

@ -30,10 +30,14 @@ class TodayStore:
self.operation_retention_seconds = operation_retention_seconds
self.recap_limit = recap_limit
self.clock = clock
self._initialize()
def _connect(self) -> sqlite3.Connection:
def _initialize(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
connection = sqlite3.connect(self.path, timeout=self.timeout)
if connection.execute("PRAGMA user_version").fetchone()[0] >= 1:
connection.close()
return
connection.execute("PRAGMA journal_mode=WAL")
connection.execute(
"""
@ -101,8 +105,12 @@ class TodayStore:
)
"""
)
connection.execute("PRAGMA user_version = 1")
connection.commit()
return connection
connection.close()
def _connect(self) -> sqlite3.Connection:
return sqlite3.connect(self.path, timeout=self.timeout)
def _record_operation(self, connection: sqlite3.Connection, login: str, operation_id: str) -> None:
now = self.clock()

View File

@ -65,6 +65,23 @@ def test_deferrals_are_durable_revisioned_idempotent_and_account_scoped(tmp_path
) == {"revision": 2, "records": {}}
def test_initialized_later_reads_remain_available_during_a_planning_write(tmp_path):
path = tmp_path / "later.sqlite3"
store = LaterStore(path, timeout=0.05)
expected = store.apply(
"timmy", "seed", "defer", "issue:r:1:",
wake_at="2026-08-10T09:00:00.000Z",
)
writer = sqlite3.connect(path)
writer.execute("BEGIN IMMEDIATE")
try:
assert LaterStore(path, timeout=0.05).get("timmy") == expected
finally:
writer.rollback()
writer.close()
def test_batch_applies_in_one_ordered_idempotent_unit(tmp_path):
store = LaterStore(tmp_path / "later.sqlite3")
operations = [

View File

@ -67,6 +67,20 @@ def test_operations_are_durable_ordered_idempotent_and_account_scoped(tmp_path):
}
def test_initialized_today_reads_remain_available_during_a_planning_write(tmp_path):
path = tmp_path / "today.sqlite3"
store = TodayStore(path, timeout=0.05)
expected = store.apply("timmy", "seed", "add", "issue:r:1:")
writer = sqlite3.connect(path)
writer.execute("BEGIN IMMEDIATE")
try:
assert TodayStore(path, timeout=0.05).get("timmy") == expected
finally:
writer.rollback()
writer.close()
def test_capacity_and_estimates_are_durable_account_scoped_and_follow_item_identity(tmp_path):
path = tmp_path / "today.sqlite3"
store = TodayStore(path, limit=3)