Stop writing the schema version on database open

This commit is contained in:
Yiorgis Gozadinos 2026-06-05 10:32:30 +03:00
parent e70f271ac7
commit b6bbea3d64
No known key found for this signature in database
3 changed files with 33 additions and 18 deletions

View file

@ -1,6 +1,10 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
### Fixed
- Opening a database no longer writes to it: reads no longer rewrite the stored embedding settings or change the stored version, and the version is never downgraded.
## [0.54.0] - 2026-06-04 ## [0.54.0] - 2026-06-04
### Added ### Added

View file

@ -12,6 +12,7 @@ import lancedb
import pyarrow as pa import pyarrow as pa
from lancedb.index import FTS, BTree, IvfPq from lancedb.index import FTS, BTree, IvfPq
from lancedb.pydantic import LanceModel, Vector from lancedb.pydantic import LanceModel, Vector
from packaging.version import parse
from pydantic import Field from pydantic import Field
from haiku.rag.config import AppConfig, Config from haiku.rag.config import AppConfig, Config
@ -509,7 +510,7 @@ class Store:
await self.set_haiku_version(metadata.version("haiku.rag-slim")) await self.set_haiku_version(metadata.version("haiku.rag-slim"))
async def _check_migrations(self) -> None: async def _check_migrations(self) -> None:
"""Check if migrations are pending and error or update version accordingly. """Raise if migrations are pending. Opening never writes the version.
Raises: Raises:
MigrationRequiredError: If migrations are pending. MigrationRequiredError: If migrations are pending.
@ -529,10 +530,6 @@ class Store:
"Run 'haiku-rag migrate' to upgrade." "Run 'haiku-rag migrate' to upgrade."
) )
# No pending migrations - update version silently if needed (writable only)
if not self._read_only and db_version != current_version:
await self.set_haiku_version(current_version)
async def migrate(self) -> list[str]: async def migrate(self) -> list[str]:
"""Run pending database migrations. """Run pending database migrations.
@ -551,8 +548,9 @@ class Store:
applied = await run_pending_upgrades(self, db_version) applied = await run_pending_upgrades(self, db_version)
# Update version after successful migration # Advance the schema marker only forward — never downgrade a database
if applied or db_version != current_version: # opened with an older build than last stamped it.
if parse(current_version) > parse(db_version):
await self.set_haiku_version(current_version) await self.set_haiku_version(current_version)
return applied return applied

View file

@ -38,21 +38,19 @@ class TestMigrationCheck:
pass pass
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_version_bump_without_pending_migrations_updates_silently( async def test_open_does_not_change_version(self, temp_db_path):
self, temp_db_path """Opening a database never writes the version, even when it differs.
):
"""When version is outdated but no migrations pending, update version silently.""" A version newer than all upgrade steps has no pending migrations, so the
open succeeds; the stored version must be left untouched (no downgrade,
no bump) because opening is a pure read.
"""
async with Store(temp_db_path, create=True) as store: async with Store(temp_db_path, create=True) as store:
# Set an older version that has no pending migrations
# (newer than all current upgrade steps)
await store.set_haiku_version("100.0.0") await store.set_haiku_version("100.0.0")
# Re-open - should update version silently, no error # Re-open writable - no error, version unchanged
async with Store(temp_db_path) as store: async with Store(temp_db_path) as store:
# Version should now be current assert await store.get_haiku_version() == "100.0.0"
version = await store.get_haiku_version()
expected = metadata.version("haiku.rag-slim")
assert version == expected
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_pending_migrations_raises_error(self, temp_db_path): async def test_pending_migrations_raises_error(self, temp_db_path):
@ -160,6 +158,21 @@ class TestMigrateMethod:
applied = await store.migrate() applied = await store.migrate()
assert applied == [] assert applied == []
@pytest.mark.asyncio
async def test_migrate_does_not_downgrade_future_version(self, temp_db_path):
"""migrate() must not move the version backwards.
Running an older build's migrate against a DB stamped with a newer
version (no pending upgrades) leaves the stored version untouched.
"""
async with Store(temp_db_path, create=True) as store:
await store.set_haiku_version("100.0.0")
async with Store(temp_db_path, skip_migration_check=True) as store:
applied = await store.migrate()
assert applied == []
assert await store.get_haiku_version() == "100.0.0"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_migrate_raises_read_only_error(self, temp_db_path): async def test_migrate_raises_read_only_error(self, temp_db_path):
"""Store.migrate() should raise ReadOnlyError in read-only mode.""" """Store.migrate() should raise ReadOnlyError in read-only mode."""