Skip to content

Commit f3c76f9

Browse files
committed
test(database): cover torn WAL tail on the engine default
Bring over the torn-tail test and replay docstring from Refs #75, adapted to engine-controlled defaults: a strict open still throws on the torn tail while the default open keeps the first insert. The docstring now says recovery replays up to the last complete commit and truncates there, dropping the torn tail and anything after a damaged record.
1 parent 337dff7 commit f3c76f9

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

‎src_py/database.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,11 @@ def __init__(
9898
9999
throw_on_wal_replay_failure: bool, optional
100100
If true, any WAL replaying failure when loading the database will throw an error.
101-
Otherwise, Lbug will silently ignore the failure and replay up to where the error
102-
occured. If None (default), the engine default is used.
101+
Otherwise (the default, as in the engine), Lbug replays the WAL up to the last
102+
complete commit before the first record it cannot read and truncates the file
103+
there. This drops the torn tail a crash leaves while a commit is being written,
104+
and anything recorded after a damaged record.
105+
If None (default), the engine default is used.
103106
104107
enable_checksums: bool, optional
105108
If true, the database will use checksums to detect corruption in the
@@ -220,7 +223,8 @@ def __getstate__(self) -> dict[str, Any]:
220223
return state
221224

222225
def _engine_kwargs(self) -> dict[str, Any]:
223-
"""Return only the explicitly set engine settings.
226+
"""
227+
Return only the explicitly set engine settings.
224228
225229
Unset (None) settings are omitted so the engine default applies,
226230
instead of this binding pinning its own copy of the default.

‎test/test_database.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,34 @@ def test_database_throw_on_wal_replay_failure_config(tmp_path: Path) -> None:
164164
assert result.get_next()[0] == 1
165165

166166

167+
def test_database_opens_past_a_torn_wal_tail_by_default(tmp_path: Path) -> None:
168+
database_path = get_db_file_path(tmp_path)
169+
with (
170+
lb.Database(database_path=database_path, auto_checkpoint=False) as db,
171+
lb.Connection(db) as conn,
172+
):
173+
conn.execute("CALL force_checkpoint_on_close=false")
174+
conn.execute("CREATE NODE TABLE T(id INT64 PRIMARY KEY)")
175+
conn.execute("CREATE (:T {id: 1})")
176+
conn.execute("CREATE (:T {id: 2})")
177+
# A crash while the last commit is written leaves its records cut short.
178+
wal_file_path = Path(str(database_path) + ".wal")
179+
with Path.open(wal_file_path, "r+b") as wal_file:
180+
wal_file.truncate(wal_file_path.stat().st_size - 8)
181+
182+
with pytest.raises(
183+
RuntimeError,
184+
match=r"Checksum verification failed|Reading past the end of the file",
185+
):
186+
lb.Database(database_path=database_path, throw_on_wal_replay_failure=True)
187+
with (
188+
lb.Database(database_path=database_path) as db,
189+
lb.Connection(db) as conn,
190+
conn.execute("MATCH (t:T) RETURN t.id") as result,
191+
):
192+
assert result.get_all() == [[1]]
193+
194+
167195
def test_database_enable_checksums_config(tmp_path: Path) -> None:
168196
database_path = get_db_file_path(tmp_path)
169197
# first construct database file with enable_checksums=True

0 commit comments

Comments
 (0)