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>
93 lines
3.4 KiB
Rust
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(())
|
|
}
|