schema.sql 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. -- CodeGraph SQLite Schema
  2. -- Version 1
  3. -- Schema version tracking
  4. CREATE TABLE IF NOT EXISTS schema_versions (
  5. version INTEGER PRIMARY KEY,
  6. applied_at INTEGER NOT NULL,
  7. description TEXT
  8. );
  9. -- Insert initial version
  10. INSERT INTO schema_versions (version, applied_at, description)
  11. VALUES (1, strftime('%s', 'now') * 1000, 'Initial schema');
  12. -- =============================================================================
  13. -- Core Tables
  14. -- =============================================================================
  15. -- Nodes: Code symbols (functions, classes, variables, etc.)
  16. CREATE TABLE IF NOT EXISTS nodes (
  17. id TEXT PRIMARY KEY,
  18. kind TEXT NOT NULL,
  19. name TEXT NOT NULL,
  20. qualified_name TEXT NOT NULL,
  21. file_path TEXT NOT NULL,
  22. language TEXT NOT NULL,
  23. start_line INTEGER NOT NULL,
  24. end_line INTEGER NOT NULL,
  25. start_column INTEGER NOT NULL,
  26. end_column INTEGER NOT NULL,
  27. docstring TEXT,
  28. signature TEXT,
  29. visibility TEXT,
  30. is_exported INTEGER DEFAULT 0,
  31. is_async INTEGER DEFAULT 0,
  32. is_static INTEGER DEFAULT 0,
  33. is_abstract INTEGER DEFAULT 0,
  34. decorators TEXT, -- JSON array
  35. type_parameters TEXT, -- JSON array
  36. return_type TEXT, -- normalized return/result type name (e.g. C++ method return, for receiver-type inference)
  37. updated_at INTEGER NOT NULL
  38. );
  39. -- Edges: Relationships between nodes
  40. CREATE TABLE IF NOT EXISTS edges (
  41. id INTEGER PRIMARY KEY AUTOINCREMENT,
  42. source TEXT NOT NULL,
  43. target TEXT NOT NULL,
  44. kind TEXT NOT NULL,
  45. metadata TEXT, -- JSON object
  46. line INTEGER,
  47. col INTEGER,
  48. provenance TEXT DEFAULT NULL,
  49. FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
  50. FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
  51. );
  52. -- Files: Tracked source files.
  53. -- `generated` is the index-time verdict from extraction/generated-detection.ts:
  54. -- the filename convention (*.pb.go, *.g.dart, …) OR a generation banner in the
  55. -- file's header. Go's convention is a CONTENT marker, so a generated
  56. -- `payroll.go` beside hand-written use-cases is invisible to the path check
  57. -- alone (#1500) — deciding it here means ranking never reads file headers per
  58. -- request. Migration v9 adds the column to existing databases; rows keep the
  59. -- 0 default until the next full index, so readers treat it as a hint that
  60. -- only ever ADDS to the path signal, never overrides it.
  61. CREATE TABLE IF NOT EXISTS files (
  62. path TEXT PRIMARY KEY,
  63. content_hash TEXT NOT NULL,
  64. language TEXT NOT NULL,
  65. size INTEGER NOT NULL,
  66. modified_at INTEGER NOT NULL,
  67. indexed_at INTEGER NOT NULL,
  68. node_count INTEGER DEFAULT 0,
  69. errors TEXT, -- JSON array
  70. generated INTEGER NOT NULL DEFAULT 0
  71. );
  72. -- Unresolved References: References that need resolution after full indexing.
  73. -- status lifecycle: rows are inserted 'pending' by extraction; a completed
  74. -- resolution pass either deletes a row (resolved) or marks it 'failed'
  75. -- (attempted, no match — kept so a later sync can retry it when a changed
  76. -- file introduces a symbol that could satisfy it, #1240). name_tail is the
  77. -- last segment of reference_name ('util.greet' → 'greet'), written when a
  78. -- row is marked failed, so the retry lookup matches new node names against
  79. -- dotted refs too. Rows follow their from_node via ON DELETE CASCADE, so
  80. -- re-extracting or deleting a file clears its stale rows in any status.
  81. CREATE TABLE IF NOT EXISTS unresolved_refs (
  82. id INTEGER PRIMARY KEY AUTOINCREMENT,
  83. from_node_id TEXT NOT NULL,
  84. reference_name TEXT NOT NULL,
  85. reference_kind TEXT NOT NULL,
  86. line INTEGER NOT NULL,
  87. col INTEGER NOT NULL,
  88. candidates TEXT, -- JSON array
  89. file_path TEXT NOT NULL DEFAULT '',
  90. language TEXT NOT NULL DEFAULT 'unknown',
  91. status TEXT NOT NULL DEFAULT 'pending',
  92. name_tail TEXT NOT NULL DEFAULT '',
  93. FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
  94. );
  95. -- =============================================================================
  96. -- Indexes for Query Performance
  97. -- =============================================================================
  98. -- Node indexes
  99. CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
  100. CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
  101. CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name);
  102. CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes(file_path);
  103. CREATE INDEX IF NOT EXISTS idx_nodes_language ON nodes(language);
  104. CREATE INDEX IF NOT EXISTS idx_nodes_file_line ON nodes(file_path, start_line);
  105. CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name));
  106. -- Full-text search index on node names, docstrings, and signatures
  107. CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
  108. id,
  109. name,
  110. qualified_name,
  111. docstring,
  112. signature,
  113. content='nodes',
  114. content_rowid='rowid'
  115. );
  116. -- Triggers to keep FTS index in sync
  117. CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
  118. INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
  119. VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring, NEW.signature);
  120. END;
  121. CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
  122. INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
  123. VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring, OLD.signature);
  124. END;
  125. CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
  126. INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
  127. VALUES ('delete', OLD.rowid, OLD.id, OLD.name, OLD.qualified_name, OLD.docstring, OLD.signature);
  128. INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
  129. VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring, NEW.signature);
  130. END;
  131. -- Prose-word → symbol-name lookup for the prompt hook's graph-derived gate.
  132. -- One row per (segment, name): segment is a lowercased word of a symbol name
  133. -- ("OrderStateMachine" → order, state, machine — see identifier-segments.ts),
  134. -- which lets natural-language prompt words be verified against the graph in
  135. -- any language whose technical nouns are Latin script. File nodes are
  136. -- excluded — a file's basename duplicates the symbols inside it and skews the
  137. -- singleton-vs-cluster rarity statistics. FTS can't serve this lookup (its
  138. -- tokenizer keeps camelCase names as single tokens), so segments are
  139. -- materialized on the node write path.
  140. -- Deletions leave orphan rows ON PURPOSE: rows are PROPOSALS, always
  141. -- re-verified against nodes before being surfaced (CodeGraph.getSegmentMatches),
  142. -- and a full index clears the table at its start. Populated lazily on old
  143. -- databases (empty until the next index/sync heals it).
  144. CREATE TABLE IF NOT EXISTS name_segment_vocab (
  145. segment TEXT NOT NULL,
  146. name TEXT NOT NULL,
  147. PRIMARY KEY (segment, name)
  148. ) WITHOUT ROWID;
  149. -- Edge indexes.
  150. -- idx_edges_source / idx_edges_target are intentionally omitted —
  151. -- the (source, kind) and (target, kind) composites below cover the
  152. -- corresponding source-only / target-only lookups via SQLite's
  153. -- left-prefix scan, so the narrow indexes are dead weight on writes.
  154. -- Migration v4 drops them on existing databases.
  155. CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
  156. CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source, kind);
  157. CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target, kind);
  158. -- Edge identity uniqueness. An edge IS uniquely (source, target, kind, line,
  159. -- col); insertEdge uses `INSERT OR IGNORE`, but without something UNIQUE to
  160. -- conflict on it behaved like a plain INSERT, so two passes emitting the same
  161. -- edge produced byte-identical duplicate rows that inflated counts and flowed
  162. -- into callers/impact (#1034). IFNULL folds the nullable line/col so
  163. -- coordinate-less edges (synthesized / file-level) dedup too — SQLite treats
  164. -- each NULL as distinct otherwise. Migration v6 dedups existing rows + adds
  165. -- this on older databases.
  166. CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_identity
  167. ON edges(source, target, kind, IFNULL(line, -1), IFNULL(col, -1));
  168. -- File indexes.
  169. -- idx_files_generated is PARTIAL: the generated set is a small minority of any
  170. -- repo, so a lookup that intersects a bounded candidate list with it stays
  171. -- proportional to the generated files, not to the repo.
  172. CREATE INDEX IF NOT EXISTS idx_files_language ON files(language);
  173. CREATE INDEX IF NOT EXISTS idx_files_modified_at ON files(modified_at);
  174. CREATE INDEX IF NOT EXISTS idx_files_generated ON files(path) WHERE generated = 1;
  175. -- Unresolved refs indexes
  176. CREATE INDEX IF NOT EXISTS idx_unresolved_from_node ON unresolved_refs(from_node_id);
  177. CREATE INDEX IF NOT EXISTS idx_unresolved_name ON unresolved_refs(reference_name);
  178. CREATE INDEX IF NOT EXISTS idx_unresolved_file_path ON unresolved_refs(file_path);
  179. CREATE INDEX IF NOT EXISTS idx_unresolved_from_name ON unresolved_refs(from_node_id, reference_name);
  180. CREATE INDEX IF NOT EXISTS idx_unresolved_status ON unresolved_refs(status);
  181. CREATE INDEX IF NOT EXISTS idx_unresolved_failed_tail ON unresolved_refs(name_tail) WHERE status = 'failed';
  182. CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
  183. -- Project metadata for version/provenance tracking
  184. CREATE TABLE IF NOT EXISTS project_metadata (
  185. key TEXT PRIMARY KEY,
  186. value TEXT NOT NULL,
  187. updated_at INTEGER NOT NULL
  188. );