@@ -29,7 +29,7 @@ classes:
2929 - name : sqlitedb
3030 type : class
3131 matlab_path : " +did/+implementations/sqlitedb.m"
32- matlab_last_sync_hash : " 8bea92c "
32+ matlab_last_sync_hash : " da83fa3 "
3333 status : ported
3434 python_path : " did/implementations/sqlitedb.py"
3535 python_class : " SQLiteDB"
@@ -133,6 +133,60 @@ classes:
133133 new tests, including the acceptance case, the "one fetch shared
134134 across members" cache guarantee, and the handler-refused failure).
135135
136+ 2026-10-23: synced through DID-matlab decd155 and da83fa3 (PRs #206,
137+ #207), the slow-drive SQLITE_READONLY work. MATLAB (1) sets
138+ busy_timeout=30000 in open_db, (2) retries a BUSY / READONLY failure in
139+ run_sql_noOpen with backoffs 0.1, 0.2, 0.5, 1, 2, 4 s, (3) closes and
140+ reopens the connection before each READONLY retry (a poisoned handle,
141+ SQLITE_READONLY_DBMOVED, which waiting never clears), and (4) prints
142+ filesystem diagnostics to stderr when the retries are exhausted.
143+ Ported: _open_db passes timeout=30.0 to sqlite3.connect (the same
144+ busy_timeout); do_run_sql_query retries on the same schedule, reopening
145+ first for READONLY; _is_retryable_sql_error, _is_readonly_error and
146+ _collect_readonly_diagnostics mirror the three new MATLAB helpers; the
147+ debug-gated progress messages are printed when self.debug is set.
148+ The three helpers are protected in MATLAB and so carry no member
149+ entries here. _is_retryable_sql_error and _is_readonly_error are
150+ staticmethods (MATLAB's take ~ for the object). The diagnostics run the
151+ same four probes -- db file and time, -journal/-wal/-shm siblings with
152+ sizes, the file's write permission (os.access for fileattrib's
153+ UserWrite), and a temp-file write probe of the directory -- but print
154+ the time as ISO-8601 UTC rather than MATLAB's local datestr.
155+
156+ DEVIATION -- where the retry may run. mksqlite autocommits every
157+ statement, so in MATLAB a failed statement is the whole unit of work
158+ and re-running it (even after a reopen) is safe. SQLiteDB groups each
159+ add/remove/branch operation into one sqlite3 transaction. Rolling back
160+ or reopening mid-transaction would silently discard the statements
161+ already pending, and a retried tail would then commit a partial
162+ operation. So do_run_sql_query retries only when the connection had no
163+ uncommitted work before the statement; inside a transaction the error
164+ is reported (with diagnostics) and raised at once, and the caller's
165+ existing rollback runs.
166+
167+ DEVIATION -- the commit is retried too. Because Python's writes reach
168+ the disk at COMMIT rather than at each statement, that is where a
169+ slow-drive failure lands here. Every commit now goes through
170+ _commit(), which retries a BUSY / READONLY COMMIT on the same schedule
171+ while the transaction is still open. It never reopens -- that would
172+ lose the transaction -- so a genuinely poisoned handle (DBMOVED) is not
173+ recovered at COMMIT: the error is raised and the operation rolled back.
174+ Replaying a whole operation after a reopen would cover that case, but
175+ _do_add_doc also copies ingested files, so it is not obviously
176+ idempotent; not attempted.
177+
178+ DEVIATION -- classification. Like MATLAB, BUSY vs READONLY is judged
179+ from the error message (sqlite3.Error.sqlite_errorcode would be exact
180+ but is 3.11+ only, and this package supports 3.10). Unlike MATLAB, only
181+ sqlite3.Error instances qualify, so an unrelated Python exception whose
182+ text says "read only" is never retried.
183+
184+ Not ported: MATLAB's run_sql_noOpen also prints every failing query
185+ and its bound values to stderr. That predates these commits, and
186+ Python never did it; only the new diagnostics block is printed here.
187+ Coverage: tests/test_sqlitedb_transient_errors.py (real lock and
188+ read-only-handle failures, with time.sleep replaced).
189+
136190 properties :
137191 - name : FileDir
138192 python_name : ~ # not ported
@@ -174,6 +228,10 @@ classes:
174228 Python: do_run_sql_query(query_str, params=()).
175229 Python uses parameterized queries. Synchronized 2026-03-15.
176230
231+ Carries MATLAB run_sql_noOpen's BUSY / READONLY retry (decd155,
232+ da83fa3), restricted to statements run with no transaction open;
233+ see the entry-level sync_notes, 2026-10-23.
234+
177235 - name : do_add_doc
178236 python_name : _do_add_doc
179237 out_of_sync : false
@@ -571,6 +629,10 @@ classes:
571629 combined "open_db / close_db" entry 2026-08-28 so each method
572630 resolves on its own.
573631
632+ Both set a 30 s busy timeout (decd155): MATLAB with pragma
633+ busy_timeout=30000, Python with sqlite3.connect(timeout=30.0),
634+ which is the same setting. Python's previous default was 5 s.
635+
574636 - name : close_db
575637 python_name : _close_db
576638 decision_log : >
0 commit comments