Skip to content

feat: read-only open — OpenOptions::read_only (#429) - #464

Merged
adityamukho merged 2 commits into
v3from
feat/429-read-only-open
Oct 7, 2026
Merged

adityamukho merged 2 commits into
v3from
feat/429-read-only-open

Conversation

@adityamukho

@adityamukho adityamukho commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #429 (Rust API; exposing open_with_options with read_only, page_cache_size and allow_unlocked in the bindings is #465).

What

OpenOptions::read_only(true) opens a database so that nothing done through the handle writes to the .graph file or its WAL.

  • Shared lock. The file is locked with try_lock_shared, so any number of read-only handles can hold it, in this process or in others. A read-only handle and a read-write handle refuse each other with the existing STG-025 or STG-026, with the same retry budget. While a reader is open, no writer can change the file or the WAL under it. The in-process registry (OPEN_PATHS) now records either one writer or a count of readers, so STG-025 still means "this process holds a conflicting handle". allow_unlocked admits many readers or one writer, never both.
  • Same view, nothing written:
    • A WAL from an earlier session is applied in memory. It is not checkpointed or deleted, and no WAL is created.
    • A format v7 file is read into memory as uncheckpointed facts, not migrated. Its migration happens on the next read-write open. Memory is O(facts) for such files only.
    • An empty file (or a torn initial meta) opens as an empty database and is not initialised.
    • A torn page 0 after a migration is read through the backup meta and not restored.
    • A missing file is the new STG-042, and nothing is created.
  • Refusals. transact, retract, begin_write, checkpoint and rebuild_indexes fail with the new API-014 before a tx count is taken. Queries, cursors, fact_log, verify, prepare, UDFs and rule registration (memory only) work. Dropping the handle does not checkpoint. The same refusals apply to read_only on an in-memory database.
  • Backstop. The shared-mode backend refuses write_page and sync with the new INT-056. If a check is ever missed, the call fails rather than writing.
  • page_cache_size already serves every committed page read, so it applies unchanged. The docs now say how to keep the whole file in memory (file_size / 4096 pages).

Design: docs/superpowers/specs/2026-10-07-read-only-open-design.md

Tests

1329 total (1320 passing, 9 ignored), up 20.

  • tests/read_only_open_test.rs (13 tests):
    • On a file with checkpointed facts and a pending WAL, a read-only handle gives the same queries, tx_count and fact log as a read-write one.
    • Reads change no bytes or mtime and create no WAL.
    • API-014 for each write, also on an in-memory database and through the builder.
    • STG-042 for a missing file; an empty file stays 0 bytes.
    • Readers share a file, and a reader and a writer refuse each other (STG-025).
    • A file without write permission opens read-only.
    • The page cache works at 1 page and at the whole file.
  • tests/golden_corpus_test.rs opens every golden file (v7 and v8, with pending and stale WALs) read-only first. It checks the manifest's tx_count and queries, and that neither the file nor its WAL changes.
  • File backend unit tests:
    • A shared backend refuses writes and syncs (INT-056).
    • The registry counts readers, and the unlocked claim admits readers or one writer.
    • Across processes, a reader in a child process allows a reader here and refuses a writer here (STG-026).
  • Storage unit tests: a read-only open of a v7 file at every point of an interrupted migration (clean and torn writes) sees every fact and writes nothing.

Docs updated: CHANGELOG, ROADMAP, README, CLAUDE.md, TEST_COVERAGE, ERROR_REFERENCE (STG-042, API-014, INT-056, plus notes on STG-025/026). The wiki is left for the v3.0.0 doc sync, as with the earlier v3 PRs.

🤖 Generated with Claude Code

https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn

adityamukho and others added 2 commits October 7, 2026 09:52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
A read-only handle writes nothing to the .graph file or its WAL and
shows the same data a read-write open would: the WAL is applied in
memory but neither checkpointed nor deleted, a v7 file is read into
memory instead of migrated, an empty file is an empty database, and a
torn page 0 after migration is read through the backup meta without
being restored. A missing file is the new STG-042.

The file is locked in shared mode (try_lock_shared), so read-only
handles coexist in one process or several, and exclude a read-write
open (and are excluded by one) with the existing STG-025/STG-026. The
in-process registry now records one writer or a count of readers.

transact, retract, begin_write, checkpoint and rebuild_indexes fail with
the new API-014 before a tx count is taken; queries, cursors, fact_log,
verify and rule registration work, and drop does not checkpoint. As a
backstop, the shared-mode backend refuses page writes and syncs with the
new INT-056.

Bindings (open_with_options with read_only, page_cache_size and
allow_unlocked) live in the binding repositories and follow separately.

Design: docs/superpowers/specs/2026-10-07-read-only-open-design.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.54545% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/storage/backend/file.rs 93.02% 3 Missing ⚠️
src/db.rs 93.33% 2 Missing ⚠️
src/storage/persistent_facts.rs 97.29% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant