From b8c377da1ea9c41cec73b34a4f97910181afdc7a Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 12 Mar 2026 11:41:34 +0200 Subject: [PATCH] Prevent read-only mode from creating tables in empty directories --- CHANGELOG.md | 4 ++++ haiku_rag_slim/haiku/rag/cli.py | 7 +++++-- haiku_rag_slim/haiku/rag/store/engine.py | 15 +++++++++++---- tests/store/test_read_only.py | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b5d619..10ed91bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Fixed + +- **Read-only mode table creation**: `--read-only` no longer creates lance tables when pointed at an empty directory. `Store._init_tables()` now raises `ReadOnlyError` when tables are missing in read-only mode. + ## [0.33.2] - 2026-03-11 ### Changed diff --git a/haiku_rag_slim/haiku/rag/cli.py b/haiku_rag_slim/haiku/rag/cli.py index 3e758cd6..905e4940 100644 --- a/haiku_rag_slim/haiku/rag/cli.py +++ b/haiku_rag_slim/haiku/rag/cli.py @@ -23,7 +23,10 @@ from haiku.rag.config import ( # noqa: E402 set_config, ) from haiku.rag.logging import configure_cli_logging # noqa: E402 -from haiku.rag.store.exceptions import MigrationRequiredError # noqa: E402 +from haiku.rag.store.exceptions import ( # noqa: E402 + MigrationRequiredError, + ReadOnlyError, +) from haiku.rag.utils import is_up_to_date # noqa: E402 _cli = typer.Typer( @@ -34,7 +37,7 @@ _cli = typer.Typer( def cli(): try: _cli() - except MigrationRequiredError as e: + except (MigrationRequiredError, ReadOnlyError) as e: typer.echo(f"Error: {e}", err=True) sys.exit(1) diff --git a/haiku_rag_slim/haiku/rag/store/engine.py b/haiku_rag_slim/haiku/rag/store/engine.py index 305271cd..28bb0b62 100644 --- a/haiku_rag_slim/haiku/rag/store/engine.py +++ b/haiku_rag_slim/haiku/rag/store/engine.py @@ -333,10 +333,17 @@ class Store: def _init_tables(self): """Initialize database tables (create if they don't exist).""" - # Get list of existing tables existing_tables = self.db.table_names() + required_tables = {"documents", "chunks", "settings"} + missing_tables = required_tables - set(existing_tables) - # Create or get documents table + if missing_tables and self._read_only: + raise ReadOnlyError( + "Cannot create tables in read-only mode. " + "Use 'haiku-rag init' to create a new database." + ) + + # Create or open documents table if "documents" in existing_tables: self.documents_table = self.db.open_table("documents") else: @@ -344,7 +351,7 @@ class Store: "documents", schema=get_documents_arrow_schema() ) - # Create or get chunks table + # Create or open chunks table if "chunks" in existing_tables: self.chunks_table = self.db.open_table("chunks") else: @@ -354,7 +361,7 @@ class Store: "content_fts", replace=True, with_position=True, remove_stop_words=False ) - # Create or get settings table + # Create or open settings table if "settings" in existing_tables: self.settings_table = self.db.open_table("settings") else: diff --git a/tests/store/test_read_only.py b/tests/store/test_read_only.py index d16b1c2d..c2f0a89a 100644 --- a/tests/store/test_read_only.py +++ b/tests/store/test_read_only.py @@ -28,6 +28,21 @@ class TestReadOnlyError: class TestStoreReadOnly: + def test_store_read_only_raises_on_empty_directory(self, tmp_path): + """Opening an empty directory in read-only mode raises ReadOnlyError.""" + empty_dir = tmp_path / "empty_db" + empty_dir.mkdir() + + with pytest.raises( + ReadOnlyError, match="Cannot create tables in read-only mode" + ): + Store( + empty_dir, + read_only=True, + skip_validation=True, + skip_migration_check=True, + ) + def test_store_default_is_not_read_only(self, temp_db_path): """Store defaults to not read-only.""" store = Store(temp_db_path, create=True)