SQLite-backed code knowledge graph.
| 141 | |
| 142 | |
| 143 | class GraphStore: |
| 144 | """SQLite-backed code knowledge graph.""" |
| 145 | |
| 146 | def __init__(self, db_path: str | Path) -> None: |
| 147 | self.db_path = Path(db_path) |
| 148 | self.db_path.parent.mkdir(parents=True, exist_ok=True) |
| 149 | self._conn = sqlite3.connect( |
| 150 | str(self.db_path), timeout=30, check_same_thread=False, |
| 151 | isolation_level=None, # Disable implicit transactions (#135) |
| 152 | ) |
| 153 | self._conn.row_factory = sqlite3.Row |
| 154 | self._conn.execute("PRAGMA journal_mode=WAL") |
| 155 | self._conn.execute("PRAGMA busy_timeout=5000") |
| 156 | self._init_schema() |
| 157 | # Ensure schema_version is set, then run pending migrations |
| 158 | if get_schema_version(self._conn) < 1: |
| 159 | # Fresh DB — metadata table just created by _init_schema |
| 160 | self._conn.execute( |
| 161 | "INSERT OR IGNORE INTO metadata (key, value) " |
| 162 | "VALUES ('schema_version', '1')" |
| 163 | ) |
| 164 | self._conn.commit() |
| 165 | run_migrations(self._conn) |
| 166 | self._nxg_cache: nx.DiGraph | None = None |
| 167 | self._cache_lock = threading.Lock() |
| 168 | |
| 169 | def __enter__(self) -> "GraphStore": |
| 170 | return self |
| 171 | |
| 172 | def __exit__(self, exc_type, exc_val, exc_tb) -> None: |
| 173 | self.close() |
| 174 | |
| 175 | def _init_schema(self) -> None: |
| 176 | self._conn.executescript(_SCHEMA_SQL) |
| 177 | self._conn.commit() |
| 178 | |
| 179 | def _invalidate_cache(self) -> None: |
| 180 | """Invalidate the cached NetworkX graph after write operations.""" |
| 181 | with self._cache_lock: |
| 182 | self._nxg_cache = None |
| 183 | |
| 184 | def close(self) -> None: |
| 185 | self._conn.close() |
| 186 | |
| 187 | # --- Write operations --- |
| 188 | |
| 189 | def upsert_node(self, node: NodeInfo, file_hash: str = "") -> int: |
| 190 | """Insert or update a node. Returns the node ID.""" |
| 191 | now = time.time() |
| 192 | qualified = self._make_qualified(node) |
| 193 | extra = json.dumps(node.extra) if node.extra else "{}" |
| 194 | |
| 195 | self._conn.execute( |
| 196 | """INSERT INTO nodes |
| 197 | (kind, name, qualified_name, file_path, line_start, line_end, |
| 198 | language, parent_name, params, return_type, modifiers, is_test, |
| 199 | file_hash, extra, updated_at) |
| 200 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
no outgoing calls