| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- /**
- * Database Migrations
- *
- * Schema versioning and migration support.
- */
- import { SqliteDatabase } from './sqlite-adapter';
- /**
- * Current schema version
- */
- export const CURRENT_SCHEMA_VERSION = 5;
- /**
- * Migration definition
- */
- interface Migration {
- version: number;
- description: string;
- up: (db: SqliteDatabase) => void;
- }
- /**
- * All migrations in order
- *
- * Note: Version 1 is the initial schema, handled by schema.sql
- * Future migrations go here.
- */
- const migrations: Migration[] = [
- {
- version: 2,
- description: 'Add project metadata, provenance tracking, and unresolved ref context',
- up: (db) => {
- db.exec(`
- CREATE TABLE IF NOT EXISTS project_metadata (
- key TEXT PRIMARY KEY,
- value TEXT NOT NULL,
- updated_at INTEGER NOT NULL
- );
- ALTER TABLE unresolved_refs ADD COLUMN file_path TEXT NOT NULL DEFAULT '';
- ALTER TABLE unresolved_refs ADD COLUMN language TEXT NOT NULL DEFAULT 'unknown';
- ALTER TABLE edges ADD COLUMN provenance TEXT DEFAULT NULL;
- CREATE INDEX IF NOT EXISTS idx_unresolved_file_path ON unresolved_refs(file_path);
- CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
- `);
- },
- },
- {
- version: 3,
- description: 'Add lower(name) expression index for memory-efficient case-insensitive lookups',
- up: (db) => {
- db.exec(`
- CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name));
- `);
- },
- },
- {
- version: 4,
- description:
- 'Drop redundant idx_edges_source / idx_edges_target (covered by source_kind / target_kind composites)',
- up: (db) => {
- db.exec(`
- DROP INDEX IF EXISTS idx_edges_source;
- DROP INDEX IF EXISTS idx_edges_target;
- `);
- },
- },
- {
- version: 5,
- description:
- 'Add nodes.return_type — normalized return/result type for receiver-type inference (C++ singletons/factories, #645)',
- up: (db) => {
- db.exec(`
- ALTER TABLE nodes ADD COLUMN return_type TEXT;
- `);
- },
- },
- ];
- /**
- * Get the current schema version from the database
- */
- export function getCurrentVersion(db: SqliteDatabase): number {
- try {
- const row = db
- .prepare('SELECT MAX(version) as version FROM schema_versions')
- .get() as { version: number | null } | undefined;
- return row?.version ?? 0;
- } catch {
- // Table doesn't exist yet
- return 0;
- }
- }
- /**
- * Record a migration as applied
- */
- function recordMigration(db: SqliteDatabase, version: number, description: string): void {
- db.prepare(
- 'INSERT INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)'
- ).run(version, Date.now(), description);
- }
- /**
- * Run all pending migrations
- */
- export function runMigrations(db: SqliteDatabase, fromVersion: number): void {
- const pending = migrations.filter((m) => m.version > fromVersion);
- if (pending.length === 0) {
- return;
- }
- // Sort by version
- pending.sort((a, b) => a.version - b.version);
- // Run each migration in a transaction
- for (const migration of pending) {
- db.transaction(() => {
- migration.up(db);
- recordMigration(db, migration.version, migration.description);
- })();
- }
- }
- /**
- * Check if the database needs migration
- */
- export function needsMigration(db: SqliteDatabase): boolean {
- const current = getCurrentVersion(db);
- return current < CURRENT_SCHEMA_VERSION;
- }
- /**
- * Get list of pending migrations
- */
- export function getPendingMigrations(db: SqliteDatabase): Migration[] {
- const current = getCurrentVersion(db);
- return migrations
- .filter((m) => m.version > current)
- .sort((a, b) => a.version - b.version);
- }
- /**
- * Get migration history from database
- */
- export function getMigrationHistory(
- db: SqliteDatabase
- ): Array<{ version: number; appliedAt: number; description: string | null }> {
- const rows = db
- .prepare('SELECT version, applied_at, description FROM schema_versions ORDER BY version')
- .all() as Array<{ version: number; applied_at: number; description: string | null }>;
- return rows.map((row) => ({
- version: row.version,
- appliedAt: row.applied_at,
- description: row.description,
- }));
- }
|