Navegador

navegador / navegador / graph / migrations.py
Source Blame History 97 lines
0838495… lmata 1 """
0838495… lmata 2 Schema versioning and migrations for the navegador graph.
0838495… lmata 3
0838495… lmata 4 The schema version is stored as a property on a singleton :Meta node.
0838495… lmata 5 On store open, the current version is checked and any pending migrations
0838495… lmata 6 are applied sequentially.
0838495… lmata 7
0838495… lmata 8 Migration functions take a GraphStore and upgrade from version N to N+1.
0838495… lmata 9 """
0838495… lmata 10
0838495… lmata 11 import logging
0838495… lmata 12
0838495… lmata 13 from navegador.graph.store import GraphStore
0838495… lmata 14
0838495… lmata 15 logger = logging.getLogger(__name__)
0838495… lmata 16
0838495… lmata 17 CURRENT_SCHEMA_VERSION = 2
0838495… lmata 18
0838495… lmata 19 # ── Migration registry ───────────────────────────────────────────────────────
0838495… lmata 20
0838495… lmata 21 _migrations: dict[int, callable] = {}
0838495… lmata 22
0838495… lmata 23
0838495… lmata 24 def migration(from_version: int):
0838495… lmata 25 """Decorator to register a migration from `from_version` to `from_version + 1`."""
0838495… lmata 26
0838495… lmata 27 def decorator(fn):
0838495… lmata 28 _migrations[from_version] = fn
0838495… lmata 29 return fn
0838495… lmata 30
0838495… lmata 31 return decorator
0838495… lmata 32
0838495… lmata 33
0838495… lmata 34 # ── Core API ─────────────────────────────────────────────────────────────────
0838495… lmata 35
0838495… lmata 36
0838495… lmata 37 def get_schema_version(store: GraphStore) -> int:
0838495… lmata 38 """Read the current schema version from the graph (0 if unset)."""
0838495… lmata 39 result = store.query("MATCH (m:Meta {name: 'schema'}) RETURN m.version")
0838495… lmata 40 rows = result.result_set or []
0838495… lmata 41 if not rows or rows[0][0] is None:
0838495… lmata 42 return 0
0838495… lmata 43 return int(rows[0][0])
0838495… lmata 44
0838495… lmata 45
0838495… lmata 46 def set_schema_version(store: GraphStore, version: int) -> None:
0838495… lmata 47 """Write the schema version to the graph."""
0838495… lmata 48 store.query(
0838495… lmata 49 "MERGE (m:Meta {name: 'schema'}) SET m.version = $version",
0838495… lmata 50 {"version": version},
0838495… lmata 51 )
0838495… lmata 52
0838495… lmata 53
0838495… lmata 54 def migrate(store: GraphStore) -> list[int]:
0838495… lmata 55 """
0838495… lmata 56 Apply all pending migrations to bring the graph up to CURRENT_SCHEMA_VERSION.
0838495… lmata 57
0838495… lmata 58 Returns:
0838495… lmata 59 List of versions that were applied.
0838495… lmata 60 """
0838495… lmata 61 current = get_schema_version(store)
0838495… lmata 62 applied: list[int] = []
0838495… lmata 63
0838495… lmata 64 while current < CURRENT_SCHEMA_VERSION:
0838495… lmata 65 fn = _migrations.get(current)
0838495… lmata 66 if fn is None:
e5e1a3b… lmata 67 raise RuntimeError(f"No migration registered for version {current} -> {current + 1}")
0838495… lmata 68 logger.info("Applying migration %d -> %d", current, current + 1)
0838495… lmata 69 fn(store)
0838495… lmata 70 current += 1
0838495… lmata 71 set_schema_version(store, current)
0838495… lmata 72 applied.append(current)
0838495… lmata 73
0838495… lmata 74 return applied
0838495… lmata 75
0838495… lmata 76
0838495… lmata 77 def needs_migration(store: GraphStore) -> bool:
0838495… lmata 78 """Check if the graph needs migration."""
0838495… lmata 79 return get_schema_version(store) < CURRENT_SCHEMA_VERSION
0838495… lmata 80
0838495… lmata 81
0838495… lmata 82 # ── Migrations ───────────────────────────────────────────────────────────────
0838495… lmata 83
0838495… lmata 84
0838495… lmata 85 @migration(0)
0838495… lmata 86 def _migrate_0_to_1(store: GraphStore) -> None:
0838495… lmata 87 """Initial schema — set version on existing graphs."""
0838495… lmata 88 # No structural changes needed — this just stamps the version.
0838495… lmata 89 logger.info("Stamping initial schema version")
0838495… lmata 90
0838495… lmata 91
0838495… lmata 92 @migration(1)
0838495… lmata 93 def _migrate_1_to_2(store: GraphStore) -> None:
0838495… lmata 94 """Add content_hash property to File nodes for incremental ingestion."""
0838495… lmata 95 # Set content_hash to empty string on existing File nodes that lack it.
e5e1a3b… lmata 96 store.query("MATCH (f:File) WHERE f.content_hash IS NULL SET f.content_hash = ''")
0838495… lmata 97 logger.info("Added content_hash to File nodes")

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button