Files
rustytokens/src/index/schema.rs
T
endergebnisandCopilot f1531a64d4 Add caller/reference analysis, C# imports, and de-noised search
Agent-workflow tooling and search quality improvements:

- find_callers / find_references (by id or exact name), with caller
  attribution to the innermost enclosing symbol and import-graph narrowing
- index file (single-file re-index) and workspace glob member resolution
- search symbols: exact > prefix > fulltext > substring fallback, inline
  source and line numbers, --limit on all search paths
- search text: reads live from disk (no stale FTS snapshot), collapses
  duplicate snippets, correct multi-word phrase matching
- find dead-symbols: excludes framework-wired entry points (pytest test_*,
  #[test] tests, FastAPI/Flask routes, Celery tasks, click/typer commands)
- find references: filters the definition line and string/comment
  occurrences (incl. Rust raw strings)
- C# import edges via new extract_csharp (using directives)
- richer MCP tool descriptions; brace-in-string tokenizer fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-08 19:49:36 +02:00

93 lines
3.4 KiB
Rust

//! SQLite schema + migration (schema_version in metadata).
use anyhow::{Context, Result};
use rusqlite::Connection;
const SCHEMA_VERSION: i64 = 2;
/// Fresh-install schema at the current version. Everything is `IF NOT EXISTS`
/// so it can be re-run safely (the old version re-ran it on every `version <
/// SCHEMA_VERSION`, which broke the moment any table already existed).
const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY,
path TEXT NOT NULL UNIQUE,
content_hash TEXT NOT NULL,
language TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS symbols (
id INTEGER PRIMARY KEY,
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
name TEXT NOT NULL,
qualified_name TEXT NOT NULL,
kind TEXT NOT NULL,
signature TEXT,
line_start INTEGER NOT NULL,
line_end INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id);
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
CREATE TABLE IF NOT EXISTS imports (
id INTEGER PRIMARY KEY,
from_file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
to_file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
import_type TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_imports_from ON imports(from_file_id);
CREATE INDEX IF NOT EXISTS idx_imports_to ON imports(to_file_id);
CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
name, qualified_name, kind, signature,
content='symbols', content_rowid='id'
);
CREATE TRIGGER IF NOT EXISTS symbols_ai AFTER INSERT ON symbols BEGIN
INSERT INTO symbols_fts(rowid, name, qualified_name, kind, signature)
VALUES (new.id, new.name, new.qualified_name, new.kind, new.signature);
END;
CREATE TRIGGER IF NOT EXISTS symbols_ad AFTER DELETE ON symbols BEGIN
INSERT INTO symbols_fts(symbols_fts, rowid, name, qualified_name, kind, signature)
VALUES ('delete', old.id, old.name, old.qualified_name, old.kind, old.signature);
END;
CREATE VIRTUAL TABLE IF NOT EXISTS content_fts USING fts5(path UNINDEXED, content);
"#;
/// Incremental migrations. `MIGRATIONS[i]` upgrades from schema version `i+1`
/// to `i+2`; version 0 is a fresh database and is created via [`SCHEMA`]
/// directly.
const MIGRATIONS: &[&str] = &[
// v1 -> v2: the `mtime` column was written but never read (change detection
// is content-hash based). Drop the dead column.
"ALTER TABLE files DROP COLUMN mtime;",
];
/// Create schema if missing, or migrate to the current version.
pub fn init(conn: &mut Connection) -> Result<()> {
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL);",
)?;
let version: i64 = conn
.query_row(
"SELECT COALESCE(CAST(value AS INTEGER), 0) FROM metadata WHERE key='schema_version'",
[],
|r| r.get(0),
)
.unwrap_or(0);
if version == 0 {
conn.execute_batch(SCHEMA)?;
} else {
for (i, migration) in MIGRATIONS.iter().enumerate() {
// i-th migration upgrades version (i+1) -> (i+2).
if version <= (i as i64) + 1 {
conn.execute_batch(migration)
.with_context(|| format!("migration to schema v{} failed", i + 2))?;
}
}
}
conn.execute(
"INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?1)",
[SCHEMA_VERSION.to_string()],
)?;
Ok(())
}