diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer/cache.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/cache.py | 56 |
1 files changed, 33 insertions, 23 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/cache.py b/packages/meshbay-node/src/meshbay_node/indexer/cache.py index 31b9b15..c167db9 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/cache.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/cache.py @@ -32,21 +32,25 @@ log = logging.getLogger(__name__) _SCHEMA = """ CREATE TABLE IF NOT EXISTS files ( - path TEXT PRIMARY KEY, - mtime REAL NOT NULL, - size INTEGER NOT NULL, - hash TEXT NOT NULL, - type TEXT NOT NULL, - added_at INTEGER NOT NULL + path TEXT PRIMARY KEY, + mtime REAL NOT NULL, + size INTEGER NOT NULL, + hash TEXT NOT NULL, + type TEXT NOT NULL, + added_at INTEGER NOT NULL, + hash_version INTEGER NOT NULL DEFAULT 1 ); """ +_MIGRATE_V2 = "ALTER TABLE files ADD COLUMN hash_version INTEGER NOT NULL DEFAULT 1" + @dataclass class CachedEntry: hash: str type: str added_at: int + hash_version: int = 1 class IndexCache: @@ -60,6 +64,10 @@ class IndexCache: self._db_path.parent.mkdir(parents=True, exist_ok=True) self._db = await aiosqlite.connect(str(self._db_path)) await self._db.executescript(_SCHEMA) + try: + await self._db.execute(_MIGRATE_V2) + except Exception: + pass # column already exists await self._db.commit() async def close(self) -> None: @@ -74,34 +82,36 @@ class IndexCache: async def __aexit__(self, *_): await self.close() - async def lookup(self, path: str, size: int, mtime: float) -> CachedEntry | None: + async def lookup(self, path: str, size: int, mtime: float, + hash_version: int = 1) -> CachedEntry | None: """ - A cache hit requires an EXACT match on both size and mtime. A mtime - touched without a content change is a false negative (an unnecessary - rehash) — accepted, since the alternative (trusting a stale hash) is - a silent wrong answer instead of an occasional wasted read. + A cache hit requires an EXACT match on size, mtime AND hash_version. + A v1 cached hash won't serve a v2 lookup for the same path — the file + is re-hashed with the new algorithm instead. """ async with self._db.execute( - "SELECT hash, type, added_at FROM files " - "WHERE path = ? AND size = ? AND mtime = ?", - (path, size, mtime)) as cur: + "SELECT hash, type, added_at, hash_version FROM files " + "WHERE path = ? AND size = ? AND mtime = ? AND hash_version = ?", + (path, size, mtime, hash_version)) as cur: row = await cur.fetchone() - return CachedEntry(hash=row[0], type=row[1], added_at=row[2]) if row else None + return CachedEntry(hash=row[0], type=row[1], added_at=row[2], + hash_version=row[3]) if row else None async def put(self, path: str, size: int, mtime: float, hash: str, - type: str, added_at: int) -> None: + type: str, added_at: int, hash_version: int = 1) -> None: """ - Written only once a file has been hashed in full — never partway - through — so a crash mid-hash leaves no stale/partial row behind: the - next scan simply finds no cache entry and hashes the file again. + Written only once a file has been hashed — never partway through — so + a crash mid-hash leaves no stale/partial row behind: the next scan + simply finds no cache entry and hashes the file again. """ await self._db.execute( - "INSERT INTO files (path, mtime, size, hash, type, added_at) " - "VALUES (?, ?, ?, ?, ?, ?) " + "INSERT INTO files (path, mtime, size, hash, type, added_at, hash_version) " + "VALUES (?, ?, ?, ?, ?, ?, ?) " "ON CONFLICT(path) DO UPDATE SET " "mtime = excluded.mtime, size = excluded.size, hash = excluded.hash, " - "type = excluded.type, added_at = excluded.added_at", - (path, mtime, size, hash, type, added_at)) + "type = excluded.type, added_at = excluded.added_at, " + "hash_version = excluded.hash_version", + (path, mtime, size, hash, type, added_at, hash_version)) await self._db.commit() # ── Maintenance (node admin UI "prune index cache") ────────────────────── |