nogic 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nogic/__init__.py +3 -0
- nogic/api/__init__.py +23 -0
- nogic/api/client.py +390 -0
- nogic/commands/__init__.py +1 -0
- nogic/commands/init.py +125 -0
- nogic/commands/login.py +75 -0
- nogic/commands/projects.py +138 -0
- nogic/commands/reindex.py +117 -0
- nogic/commands/status.py +165 -0
- nogic/commands/sync.py +72 -0
- nogic/commands/telemetry_cmd.py +65 -0
- nogic/commands/watch.py +167 -0
- nogic/config.py +157 -0
- nogic/ignore.py +109 -0
- nogic/main.py +58 -0
- nogic/parsing/__init__.py +22 -0
- nogic/parsing/js_extractor.py +674 -0
- nogic/parsing/parser.py +220 -0
- nogic/parsing/python_extractor.py +484 -0
- nogic/parsing/types.py +80 -0
- nogic/storage/__init__.py +14 -0
- nogic/storage/relationships.py +322 -0
- nogic/storage/schema.py +154 -0
- nogic/storage/symbols.py +203 -0
- nogic/telemetry.py +142 -0
- nogic/ui.py +60 -0
- nogic/watcher/__init__.py +7 -0
- nogic/watcher/monitor.py +80 -0
- nogic/watcher/storage.py +185 -0
- nogic/watcher/sync.py +879 -0
- nogic-0.0.1.dist-info/METADATA +201 -0
- nogic-0.0.1.dist-info/RECORD +35 -0
- nogic-0.0.1.dist-info/WHEEL +4 -0
- nogic-0.0.1.dist-info/entry_points.txt +2 -0
- nogic-0.0.1.dist-info/licenses/LICENSE +21 -0
nogic/parsing/types.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data classes for extracted code elements.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from tree_sitter import Node
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ExtractedFunction:
|
|
14
|
+
"""Extracted function or method information."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
qualified_name: str
|
|
18
|
+
start_line: int
|
|
19
|
+
end_line: int
|
|
20
|
+
signature: str
|
|
21
|
+
docstring: str | None = None
|
|
22
|
+
decorators: list[str] = field(default_factory=list)
|
|
23
|
+
is_async: bool = False
|
|
24
|
+
is_method: bool = False
|
|
25
|
+
class_name: str | None = None
|
|
26
|
+
parameters: list[str] = field(default_factory=list)
|
|
27
|
+
return_type: str | None = None
|
|
28
|
+
source_code: str = ""
|
|
29
|
+
node: "Node | None" = field(default=None, repr=False)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ExtractedClass:
|
|
34
|
+
"""Extracted class information."""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
qualified_name: str
|
|
38
|
+
start_line: int
|
|
39
|
+
end_line: int
|
|
40
|
+
docstring: str | None = None
|
|
41
|
+
decorators: list[str] = field(default_factory=list)
|
|
42
|
+
bases: list[str] = field(default_factory=list)
|
|
43
|
+
methods: list[ExtractedFunction] = field(default_factory=list)
|
|
44
|
+
source_code: str = ""
|
|
45
|
+
node: "Node | None" = field(default=None, repr=False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ExtractedCall:
|
|
50
|
+
"""Extracted function call information."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
line: int
|
|
54
|
+
caller_qualified_name: str
|
|
55
|
+
receiver: str | None = None
|
|
56
|
+
arguments: list[str] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ExtractedImport:
|
|
61
|
+
"""Extracted import statement."""
|
|
62
|
+
|
|
63
|
+
module: str
|
|
64
|
+
name: str | None = None
|
|
65
|
+
alias: str | None = None
|
|
66
|
+
is_wildcard: bool = False
|
|
67
|
+
line: int = 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ParseResult:
|
|
72
|
+
"""Container for all extracted elements from a file."""
|
|
73
|
+
|
|
74
|
+
file_path: str
|
|
75
|
+
language: str
|
|
76
|
+
functions: list[ExtractedFunction] = field(default_factory=list)
|
|
77
|
+
classes: list[ExtractedClass] = field(default_factory=list)
|
|
78
|
+
imports: list[ExtractedImport] = field(default_factory=list)
|
|
79
|
+
calls: list[ExtractedCall] = field(default_factory=list)
|
|
80
|
+
error: str | None = None
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage module for SQLite database operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .schema import create_schema, SCHEMA_VERSION
|
|
6
|
+
from .symbols import SymbolStorageService
|
|
7
|
+
from .relationships import RelationshipStorageService
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"create_schema",
|
|
11
|
+
"SCHEMA_VERSION",
|
|
12
|
+
"SymbolStorageService",
|
|
13
|
+
"RelationshipStorageService",
|
|
14
|
+
]
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Relationship storage service for managing code relationships in SQLite.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sqlite3
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from nogic.parsing.types import ExtractedCall, ExtractedImport, ExtractedClass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class StoredImport:
|
|
15
|
+
"""A file-level import retrieved from the database."""
|
|
16
|
+
|
|
17
|
+
id: int
|
|
18
|
+
source_file_id: int
|
|
19
|
+
target_file_id: Optional[int]
|
|
20
|
+
module_name: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class StoredSymbolImport:
|
|
25
|
+
"""A symbol-level import retrieved from the database."""
|
|
26
|
+
|
|
27
|
+
id: int
|
|
28
|
+
source_file_id: int
|
|
29
|
+
module_name: str
|
|
30
|
+
symbol_names: list[str]
|
|
31
|
+
import_type: str
|
|
32
|
+
alias: Optional[str]
|
|
33
|
+
line: Optional[int]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class StoredCall:
|
|
38
|
+
"""A call relationship retrieved from the database."""
|
|
39
|
+
|
|
40
|
+
id: int
|
|
41
|
+
caller_id: str
|
|
42
|
+
callee_id: Optional[str]
|
|
43
|
+
callee_name: str
|
|
44
|
+
call_type: str
|
|
45
|
+
receiver: Optional[str]
|
|
46
|
+
line: Optional[int]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class StoredInheritance:
|
|
51
|
+
"""An inheritance relationship retrieved from the database."""
|
|
52
|
+
|
|
53
|
+
id: int
|
|
54
|
+
child_id: str
|
|
55
|
+
parent_name: str
|
|
56
|
+
parent_id: Optional[str]
|
|
57
|
+
type: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RelationshipStorageService:
|
|
61
|
+
"""Service for storing and querying code relationships."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, conn: sqlite3.Connection):
|
|
64
|
+
self.conn = conn
|
|
65
|
+
self.conn.row_factory = sqlite3.Row
|
|
66
|
+
|
|
67
|
+
# --- Import Storage ---
|
|
68
|
+
|
|
69
|
+
def store_import(
|
|
70
|
+
self,
|
|
71
|
+
source_file_id: int,
|
|
72
|
+
imp: ExtractedImport,
|
|
73
|
+
target_file_id: Optional[int] = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Store an import relationship."""
|
|
76
|
+
# Store file-level import
|
|
77
|
+
self.conn.execute(
|
|
78
|
+
"""
|
|
79
|
+
INSERT OR IGNORE INTO imports (source_file_id, target_file_id, module_name)
|
|
80
|
+
VALUES (?, ?, ?)
|
|
81
|
+
""",
|
|
82
|
+
(source_file_id, target_file_id, imp.module),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Determine import type
|
|
86
|
+
if imp.is_wildcard:
|
|
87
|
+
import_type = "wildcard"
|
|
88
|
+
symbol_names = ["*"]
|
|
89
|
+
elif imp.name:
|
|
90
|
+
import_type = "named"
|
|
91
|
+
symbol_names = [imp.name]
|
|
92
|
+
else:
|
|
93
|
+
import_type = "namespace"
|
|
94
|
+
symbol_names = [imp.module.split(".")[-1]]
|
|
95
|
+
|
|
96
|
+
# Store symbol-level import
|
|
97
|
+
self.conn.execute(
|
|
98
|
+
"""
|
|
99
|
+
INSERT INTO symbol_imports (source_file_id, module_name, symbol_names, import_type, alias, line)
|
|
100
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
101
|
+
""",
|
|
102
|
+
(
|
|
103
|
+
source_file_id,
|
|
104
|
+
imp.module,
|
|
105
|
+
json.dumps(symbol_names),
|
|
106
|
+
import_type,
|
|
107
|
+
imp.alias,
|
|
108
|
+
imp.line,
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def delete_file_imports(self, file_id: int) -> int:
|
|
113
|
+
"""Delete all imports for a file. Returns count deleted."""
|
|
114
|
+
count1 = self.conn.execute(
|
|
115
|
+
"DELETE FROM imports WHERE source_file_id = ?", (file_id,)
|
|
116
|
+
).rowcount
|
|
117
|
+
count2 = self.conn.execute(
|
|
118
|
+
"DELETE FROM symbol_imports WHERE source_file_id = ?", (file_id,)
|
|
119
|
+
).rowcount
|
|
120
|
+
return count1 + count2
|
|
121
|
+
|
|
122
|
+
def get_file_imports(self, file_id: int) -> list[StoredImport]:
|
|
123
|
+
"""Get all file-level imports for a file."""
|
|
124
|
+
rows = self.conn.execute(
|
|
125
|
+
"SELECT * FROM imports WHERE source_file_id = ?", (file_id,)
|
|
126
|
+
).fetchall()
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
StoredImport(
|
|
130
|
+
id=row["id"],
|
|
131
|
+
source_file_id=row["source_file_id"],
|
|
132
|
+
target_file_id=row["target_file_id"],
|
|
133
|
+
module_name=row["module_name"],
|
|
134
|
+
)
|
|
135
|
+
for row in rows
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
def get_symbol_imports(self, file_id: int) -> list[StoredSymbolImport]:
|
|
139
|
+
"""Get all symbol-level imports for a file."""
|
|
140
|
+
rows = self.conn.execute(
|
|
141
|
+
"SELECT * FROM symbol_imports WHERE source_file_id = ?", (file_id,)
|
|
142
|
+
).fetchall()
|
|
143
|
+
|
|
144
|
+
return [
|
|
145
|
+
StoredSymbolImport(
|
|
146
|
+
id=row["id"],
|
|
147
|
+
source_file_id=row["source_file_id"],
|
|
148
|
+
module_name=row["module_name"],
|
|
149
|
+
symbol_names=json.loads(row["symbol_names"]),
|
|
150
|
+
import_type=row["import_type"],
|
|
151
|
+
alias=row["alias"],
|
|
152
|
+
line=row["line"],
|
|
153
|
+
)
|
|
154
|
+
for row in rows
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
# --- Call Storage ---
|
|
158
|
+
|
|
159
|
+
def store_call(
|
|
160
|
+
self,
|
|
161
|
+
call: ExtractedCall,
|
|
162
|
+
caller_symbol_id: str,
|
|
163
|
+
callee_symbol_id: Optional[str] = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Store a call relationship."""
|
|
166
|
+
# Determine call type
|
|
167
|
+
if call.receiver:
|
|
168
|
+
call_type = "method"
|
|
169
|
+
else:
|
|
170
|
+
call_type = "function"
|
|
171
|
+
|
|
172
|
+
self.conn.execute(
|
|
173
|
+
"""
|
|
174
|
+
INSERT INTO calls (caller_id, callee_id, callee_name, call_type, receiver, line)
|
|
175
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
176
|
+
""",
|
|
177
|
+
(
|
|
178
|
+
caller_symbol_id,
|
|
179
|
+
callee_symbol_id,
|
|
180
|
+
call.name,
|
|
181
|
+
call_type,
|
|
182
|
+
call.receiver,
|
|
183
|
+
call.line,
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def delete_caller_calls(self, caller_id: str) -> int:
|
|
188
|
+
"""Delete all calls from a specific caller symbol."""
|
|
189
|
+
cursor = self.conn.execute(
|
|
190
|
+
"DELETE FROM calls WHERE caller_id = ?", (caller_id,)
|
|
191
|
+
)
|
|
192
|
+
return cursor.rowcount
|
|
193
|
+
|
|
194
|
+
def delete_file_calls(self, file_id: int) -> int:
|
|
195
|
+
"""Delete all calls from symbols in a file."""
|
|
196
|
+
cursor = self.conn.execute(
|
|
197
|
+
"""
|
|
198
|
+
DELETE FROM calls WHERE caller_id IN (
|
|
199
|
+
SELECT id FROM symbols WHERE file_id = ?
|
|
200
|
+
)
|
|
201
|
+
""",
|
|
202
|
+
(file_id,),
|
|
203
|
+
)
|
|
204
|
+
return cursor.rowcount
|
|
205
|
+
|
|
206
|
+
def get_calls_from(self, caller_id: str) -> list[StoredCall]:
|
|
207
|
+
"""Get all calls made by a symbol."""
|
|
208
|
+
rows = self.conn.execute(
|
|
209
|
+
"SELECT * FROM calls WHERE caller_id = ?", (caller_id,)
|
|
210
|
+
).fetchall()
|
|
211
|
+
|
|
212
|
+
return [self._row_to_call(row) for row in rows]
|
|
213
|
+
|
|
214
|
+
def get_calls_to(self, callee_id: str) -> list[StoredCall]:
|
|
215
|
+
"""Get all calls to a symbol."""
|
|
216
|
+
rows = self.conn.execute(
|
|
217
|
+
"SELECT * FROM calls WHERE callee_id = ?", (callee_id,)
|
|
218
|
+
).fetchall()
|
|
219
|
+
|
|
220
|
+
return [self._row_to_call(row) for row in rows]
|
|
221
|
+
|
|
222
|
+
def get_calls_by_name(self, name: str) -> list[StoredCall]:
|
|
223
|
+
"""Get all calls to a function/method by name (including unresolved)."""
|
|
224
|
+
rows = self.conn.execute(
|
|
225
|
+
"SELECT * FROM calls WHERE callee_name = ?", (name,)
|
|
226
|
+
).fetchall()
|
|
227
|
+
|
|
228
|
+
return [self._row_to_call(row) for row in rows]
|
|
229
|
+
|
|
230
|
+
def _row_to_call(self, row: sqlite3.Row) -> StoredCall:
|
|
231
|
+
"""Convert a database row to a StoredCall."""
|
|
232
|
+
return StoredCall(
|
|
233
|
+
id=row["id"],
|
|
234
|
+
caller_id=row["caller_id"],
|
|
235
|
+
callee_id=row["callee_id"],
|
|
236
|
+
callee_name=row["callee_name"],
|
|
237
|
+
call_type=row["call_type"],
|
|
238
|
+
receiver=row["receiver"],
|
|
239
|
+
line=row["line"],
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# --- Inheritance Storage ---
|
|
243
|
+
|
|
244
|
+
def store_inheritance(
|
|
245
|
+
self,
|
|
246
|
+
child_symbol_id: str,
|
|
247
|
+
cls: ExtractedClass,
|
|
248
|
+
parent_symbol_ids: Optional[dict[str, str]] = None,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Store inheritance relationships for a class."""
|
|
251
|
+
parent_symbol_ids = parent_symbol_ids or {}
|
|
252
|
+
|
|
253
|
+
for base_name in cls.bases:
|
|
254
|
+
parent_id = parent_symbol_ids.get(base_name)
|
|
255
|
+
|
|
256
|
+
self.conn.execute(
|
|
257
|
+
"""
|
|
258
|
+
INSERT INTO inheritance (child_id, parent_name, parent_id, type)
|
|
259
|
+
VALUES (?, ?, ?, ?)
|
|
260
|
+
""",
|
|
261
|
+
(
|
|
262
|
+
child_symbol_id,
|
|
263
|
+
base_name,
|
|
264
|
+
parent_id,
|
|
265
|
+
"extends", # Python only has extends, not implements
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def delete_child_inheritance(self, child_id: str) -> int:
|
|
270
|
+
"""Delete all inheritance relationships for a child class."""
|
|
271
|
+
cursor = self.conn.execute(
|
|
272
|
+
"DELETE FROM inheritance WHERE child_id = ?", (child_id,)
|
|
273
|
+
)
|
|
274
|
+
return cursor.rowcount
|
|
275
|
+
|
|
276
|
+
def delete_file_inheritance(self, file_id: int) -> int:
|
|
277
|
+
"""Delete all inheritance relationships for classes in a file."""
|
|
278
|
+
cursor = self.conn.execute(
|
|
279
|
+
"""
|
|
280
|
+
DELETE FROM inheritance WHERE child_id IN (
|
|
281
|
+
SELECT id FROM symbols WHERE file_id = ? AND kind = 'class'
|
|
282
|
+
)
|
|
283
|
+
""",
|
|
284
|
+
(file_id,),
|
|
285
|
+
)
|
|
286
|
+
return cursor.rowcount
|
|
287
|
+
|
|
288
|
+
def get_class_parents(self, child_id: str) -> list[StoredInheritance]:
|
|
289
|
+
"""Get all parent classes for a class."""
|
|
290
|
+
rows = self.conn.execute(
|
|
291
|
+
"SELECT * FROM inheritance WHERE child_id = ?", (child_id,)
|
|
292
|
+
).fetchall()
|
|
293
|
+
|
|
294
|
+
return [self._row_to_inheritance(row) for row in rows]
|
|
295
|
+
|
|
296
|
+
def get_class_children(self, parent_id: str) -> list[StoredInheritance]:
|
|
297
|
+
"""Get all child classes for a class."""
|
|
298
|
+
rows = self.conn.execute(
|
|
299
|
+
"SELECT * FROM inheritance WHERE parent_id = ?", (parent_id,)
|
|
300
|
+
).fetchall()
|
|
301
|
+
|
|
302
|
+
return [self._row_to_inheritance(row) for row in rows]
|
|
303
|
+
|
|
304
|
+
def _row_to_inheritance(self, row: sqlite3.Row) -> StoredInheritance:
|
|
305
|
+
"""Convert a database row to a StoredInheritance."""
|
|
306
|
+
return StoredInheritance(
|
|
307
|
+
id=row["id"],
|
|
308
|
+
child_id=row["child_id"],
|
|
309
|
+
parent_name=row["parent_name"],
|
|
310
|
+
parent_id=row["parent_id"],
|
|
311
|
+
type=row["type"],
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# --- Bulk Operations ---
|
|
315
|
+
|
|
316
|
+
def delete_all_file_relationships(self, file_id: int) -> dict[str, int]:
|
|
317
|
+
"""Delete all relationships for a file. Returns counts by type."""
|
|
318
|
+
return {
|
|
319
|
+
"imports": self.delete_file_imports(file_id),
|
|
320
|
+
"calls": self.delete_file_calls(file_id),
|
|
321
|
+
"inheritance": self.delete_file_inheritance(file_id),
|
|
322
|
+
}
|
nogic/storage/schema.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema for local code intelligence storage.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sqlite3
|
|
6
|
+
|
|
7
|
+
SCHEMA_VERSION = 2 # Increment when schema changes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_schema(conn: sqlite3.Connection) -> None:
|
|
11
|
+
"""Create all database tables for code intelligence storage."""
|
|
12
|
+
conn.executescript(
|
|
13
|
+
"""
|
|
14
|
+
-- Schema version tracking
|
|
15
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
16
|
+
version INTEGER PRIMARY KEY
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- Files table (already exists in watcher/storage.py, but included for completeness)
|
|
20
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
21
|
+
id INTEGER PRIMARY KEY,
|
|
22
|
+
path TEXT UNIQUE NOT NULL,
|
|
23
|
+
content_hash TEXT NOT NULL,
|
|
24
|
+
last_modified REAL NOT NULL,
|
|
25
|
+
synced_at REAL,
|
|
26
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- Sync events table (already exists)
|
|
30
|
+
CREATE TABLE IF NOT EXISTS sync_events (
|
|
31
|
+
id INTEGER PRIMARY KEY,
|
|
32
|
+
file_id INTEGER NOT NULL,
|
|
33
|
+
event_type TEXT NOT NULL,
|
|
34
|
+
content_hash TEXT NOT NULL,
|
|
35
|
+
timestamp REAL NOT NULL,
|
|
36
|
+
FOREIGN KEY (file_id) REFERENCES files(id)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- Symbols table (unified for all types: class, function, method, variable)
|
|
40
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
41
|
+
id TEXT PRIMARY KEY, -- "path/file.py::ClassName::method"
|
|
42
|
+
name TEXT NOT NULL,
|
|
43
|
+
file_id INTEGER NOT NULL,
|
|
44
|
+
kind TEXT NOT NULL, -- class/function/method/variable/etc
|
|
45
|
+
parent_symbol_id TEXT, -- For methods: their class symbol id
|
|
46
|
+
exported INTEGER DEFAULT 0,
|
|
47
|
+
line_start INTEGER,
|
|
48
|
+
line_end INTEGER,
|
|
49
|
+
signature TEXT,
|
|
50
|
+
docstring TEXT,
|
|
51
|
+
extra_data TEXT, -- JSON for decorators, params, etc
|
|
52
|
+
FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE,
|
|
53
|
+
FOREIGN KEY (parent_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
-- File-level imports (which files import which)
|
|
57
|
+
CREATE TABLE IF NOT EXISTS imports (
|
|
58
|
+
id INTEGER PRIMARY KEY,
|
|
59
|
+
source_file_id INTEGER NOT NULL,
|
|
60
|
+
target_file_id INTEGER, -- NULL if external module
|
|
61
|
+
module_name TEXT NOT NULL, -- The imported module path
|
|
62
|
+
UNIQUE(source_file_id, module_name),
|
|
63
|
+
FOREIGN KEY (source_file_id) REFERENCES files(id) ON DELETE CASCADE,
|
|
64
|
+
FOREIGN KEY (target_file_id) REFERENCES files(id) ON DELETE CASCADE
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
-- Symbol-level imports (what symbols are imported from where)
|
|
68
|
+
CREATE TABLE IF NOT EXISTS symbol_imports (
|
|
69
|
+
id INTEGER PRIMARY KEY,
|
|
70
|
+
source_file_id INTEGER NOT NULL,
|
|
71
|
+
module_name TEXT NOT NULL,
|
|
72
|
+
symbol_names TEXT NOT NULL, -- JSON array of imported symbol names
|
|
73
|
+
import_type TEXT NOT NULL, -- 'named', 'default', 'wildcard', 'namespace'
|
|
74
|
+
alias TEXT, -- Alias if renamed
|
|
75
|
+
line INTEGER,
|
|
76
|
+
FOREIGN KEY (source_file_id) REFERENCES files(id) ON DELETE CASCADE
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
-- Call edges (function/method calls)
|
|
80
|
+
CREATE TABLE IF NOT EXISTS calls (
|
|
81
|
+
id INTEGER PRIMARY KEY,
|
|
82
|
+
caller_id TEXT NOT NULL, -- Symbol id of the caller
|
|
83
|
+
callee_id TEXT, -- Symbol id of callee (NULL if unresolved)
|
|
84
|
+
callee_name TEXT NOT NULL, -- Name of the called function/method
|
|
85
|
+
call_type TEXT NOT NULL, -- 'function', 'method', 'constructor'
|
|
86
|
+
receiver TEXT, -- For method calls: the receiver expression
|
|
87
|
+
line INTEGER,
|
|
88
|
+
FOREIGN KEY (caller_id) REFERENCES symbols(id) ON DELETE CASCADE,
|
|
89
|
+
FOREIGN KEY (callee_id) REFERENCES symbols(id) ON DELETE SET NULL
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
-- Inheritance relationships
|
|
93
|
+
CREATE TABLE IF NOT EXISTS inheritance (
|
|
94
|
+
id INTEGER PRIMARY KEY,
|
|
95
|
+
child_id TEXT NOT NULL, -- Symbol id of the child class
|
|
96
|
+
parent_name TEXT NOT NULL, -- Name of the parent (may be unresolved)
|
|
97
|
+
parent_id TEXT, -- Symbol id of parent (NULL if external)
|
|
98
|
+
type TEXT NOT NULL, -- 'extends' or 'implements'
|
|
99
|
+
FOREIGN KEY (child_id) REFERENCES symbols(id) ON DELETE CASCADE,
|
|
100
|
+
FOREIGN KEY (parent_id) REFERENCES symbols(id) ON DELETE SET NULL
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
-- Indexes for efficient querying
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_files_status ON files(status);
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_file_id ON symbols(file_id);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_parent ON symbols(parent_symbol_id);
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_imports_source ON imports(source_file_id);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_imports_target ON imports(target_file_id);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_symbol_imports_source ON symbol_imports(source_file_id);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_calls_caller ON calls(caller_id);
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_calls_callee ON calls(callee_id);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_calls_name ON calls(callee_name);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_inheritance_child ON inheritance(child_id);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_inheritance_parent ON inheritance(parent_id);
|
|
118
|
+
"""
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_schema_version(conn: sqlite3.Connection) -> int:
|
|
123
|
+
"""Get the current schema version from database."""
|
|
124
|
+
try:
|
|
125
|
+
cursor = conn.execute("SELECT version FROM schema_version LIMIT 1")
|
|
126
|
+
row = cursor.fetchone()
|
|
127
|
+
return row[0] if row else 0
|
|
128
|
+
except sqlite3.OperationalError:
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def set_schema_version(conn: sqlite3.Connection, version: int) -> None:
|
|
133
|
+
"""Set the schema version in database."""
|
|
134
|
+
conn.execute("DELETE FROM schema_version")
|
|
135
|
+
conn.execute("INSERT INTO schema_version (version) VALUES (?)", (version,))
|
|
136
|
+
conn.commit()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def migrate_schema(conn: sqlite3.Connection) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Migrate schema to latest version if needed.
|
|
142
|
+
Returns True if migration was performed.
|
|
143
|
+
"""
|
|
144
|
+
current_version = get_schema_version(conn)
|
|
145
|
+
|
|
146
|
+
if current_version >= SCHEMA_VERSION:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# For now, we recreate schema on version mismatch
|
|
150
|
+
# In production, you'd want proper migrations
|
|
151
|
+
create_schema(conn)
|
|
152
|
+
set_schema_version(conn, SCHEMA_VERSION)
|
|
153
|
+
|
|
154
|
+
return True
|