Navegador

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

Keyboard Shortcuts

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