package main import ( "database/sql" "fmt" "os" _ "github.com/mattn/go-sqlite3" ) // OpenDB opens or creates the SQLite database and applies the schema. func OpenDB(dbPath, schemaPath string) (*sql.DB, error) { db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=ON") if err != nil { return nil, fmt.Errorf("opening database: %w", err) } schema, err := os.ReadFile(schemaPath) if err != nil { return nil, fmt.Errorf("reading schema: %w", err) } if _, err := db.Exec(string(schema)); err != nil { return nil, fmt.Errorf("applying schema: %w", err) } return db, nil } // DBWriter writes analysis results to the SQLite database. type DBWriter struct { db *sql.DB } // NewDBWriter creates a new DBWriter. func NewDBWriter(db *sql.DB) *DBWriter { return &DBWriter{db: db} } // WriteAll writes all analysis results to the database in a single transaction. func (w *DBWriter) WriteAll(result *AnalysisResult) error { tx, err := w.db.Begin() if err != nil { return fmt.Errorf("beginning transaction: %w", err) } defer tx.Rollback() moduleIDs := make(map[string]int64) featureIDs := make(map[string]int64) for _, mod := range result.Modules { modID, err := w.insertModule(tx, &mod) if err != nil { return fmt.Errorf("inserting module %s: %w", mod.Name, err) } moduleIDs[mod.Name] = modID for _, feat := range mod.Features { featID, err := w.insertFeature(tx, modID, &feat) if err != nil { return fmt.Errorf("inserting feature %s: %w", feat.Name, err) } featureIDs[mod.Name+":"+feat.Name] = featID } for _, test := range mod.Tests { var featureID *int64 if test.FeatureName != "" { if fid, ok := featureIDs[mod.Name+":"+test.FeatureName]; ok { featureID = &fid } } if err := w.insertTest(tx, modID, featureID, &test); err != nil { return fmt.Errorf("inserting test %s: %w", test.Name, err) } } } for _, dep := range result.Dependencies { sourceID, ok := moduleIDs[dep.SourceModule] if !ok { continue } targetID, ok := moduleIDs[dep.TargetModule] if !ok { continue } if err := w.insertDependency(tx, "module", sourceID, "module", targetID, dep.DependencyKind); err != nil { return fmt.Errorf("inserting dependency %s->%s: %w", dep.SourceModule, dep.TargetModule, err) } } for _, imp := range result.Imports { if imp.IsStdlib { continue } if err := w.insertLibrary(tx, &imp); err != nil { return fmt.Errorf("inserting library %s: %w", imp.ImportPath, err) } } return tx.Commit() } func (w *DBWriter) insertModule(tx *sql.Tx, mod *Module) (int64, error) { res, err := tx.Exec( `INSERT INTO modules (name, description, go_package, go_file, go_line_count, status) VALUES (?, ?, ?, ?, ?, 'not_started')`, mod.Name, mod.Description, mod.GoPackage, mod.GoFile, mod.GoLineCount, ) if err != nil { return 0, err } return res.LastInsertId() } func (w *DBWriter) insertFeature(tx *sql.Tx, moduleID int64, feat *Feature) (int64, error) { res, err := tx.Exec( `INSERT INTO features (module_id, name, description, go_file, go_class, go_method, go_line_number, go_line_count, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'not_started')`, moduleID, feat.Name, feat.Description, feat.GoFile, feat.GoClass, feat.GoMethod, feat.GoLineNumber, feat.GoLineCount, ) if err != nil { return 0, err } return res.LastInsertId() } func (w *DBWriter) insertTest(tx *sql.Tx, moduleID int64, featureID *int64, test *TestFunc) error { _, err := tx.Exec( `INSERT INTO unit_tests (module_id, feature_id, name, description, go_file, go_class, go_method, go_line_number, go_line_count, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'not_started')`, moduleID, featureID, test.Name, test.Description, test.GoFile, test.GoClass, test.GoMethod, test.GoLineNumber, test.GoLineCount, ) return err } func (w *DBWriter) insertDependency(tx *sql.Tx, srcType string, srcID int64, tgtType string, tgtID int64, kind string) error { _, err := tx.Exec( `INSERT OR IGNORE INTO dependencies (source_type, source_id, target_type, target_id, dependency_kind) VALUES (?, ?, ?, ?, ?)`, srcType, srcID, tgtType, tgtID, kind, ) return err } func (w *DBWriter) insertLibrary(tx *sql.Tx, imp *ImportInfo) error { _, err := tx.Exec( `INSERT OR IGNORE INTO library_mappings (go_import_path, go_library_name, status) VALUES (?, ?, 'not_mapped')`, imp.ImportPath, imp.ImportPath, ) return err } // OpenDBNoSchema opens an existing SQLite database without applying schema. // It verifies that the required tables exist. func OpenDBNoSchema(dbPath string) (*sql.DB, error) { db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=ON") if err != nil { return nil, fmt.Errorf("opening database: %w", err) } // Verify required tables exist for _, table := range []string{"modules", "features", "unit_tests", "dependencies"} { var name string err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name=?", table).Scan(&name) if err != nil { db.Close() return nil, fmt.Errorf("required table %q not found: %w", table, err) } } return db, nil } // CallGraphStats holds summary statistics from a call-graph update. type CallGraphStats struct { TestsAnalyzed int TestsLinked int DependencyRows int FeatureIDsSet int } // UpdateCallGraph writes call-graph analysis results to the database incrementally. func (w *DBWriter) UpdateCallGraph(result *AnalysisResult) (*CallGraphStats, error) { stats := &CallGraphStats{} // Load module name→ID mapping moduleIDs := make(map[string]int64) rows, err := w.db.Query("SELECT id, name FROM modules") if err != nil { return nil, fmt.Errorf("querying modules: %w", err) } for rows.Next() { var id int64 var name string if err := rows.Scan(&id, &name); err != nil { rows.Close() return nil, err } moduleIDs[name] = id } rows.Close() // Load feature DB IDs: "module_name:go_method:go_class" → id type featureKey struct { moduleName string goMethod string goClass string } featureDBIDs := make(map[featureKey]int64) rows, err = w.db.Query(` SELECT f.id, m.name, f.go_method, COALESCE(f.go_class, '') FROM features f JOIN modules m ON f.module_id = m.id `) if err != nil { return nil, fmt.Errorf("querying features: %w", err) } for rows.Next() { var id int64 var modName, goMethod, goClass string if err := rows.Scan(&id, &modName, &goMethod, &goClass); err != nil { rows.Close() return nil, err } featureDBIDs[featureKey{modName, goMethod, goClass}] = id } rows.Close() // Load test DB IDs: "module_name:go_method" → id testDBIDs := make(map[string]int64) rows, err = w.db.Query(` SELECT ut.id, m.name, ut.go_method FROM unit_tests ut JOIN modules m ON ut.module_id = m.id `) if err != nil { return nil, fmt.Errorf("querying unit_tests: %w", err) } for rows.Next() { var id int64 var modName, goMethod string if err := rows.Scan(&id, &modName, &goMethod); err != nil { rows.Close() return nil, err } testDBIDs[modName+":"+goMethod] = id } rows.Close() // Begin transaction tx, err := w.db.Begin() if err != nil { return nil, fmt.Errorf("beginning transaction: %w", err) } defer tx.Rollback() // Clear old call-graph data if _, err := tx.Exec("DELETE FROM dependencies WHERE source_type='unit_test' AND dependency_kind='calls'"); err != nil { return nil, fmt.Errorf("clearing old dependencies: %w", err) } if _, err := tx.Exec("UPDATE unit_tests SET feature_id = NULL"); err != nil { return nil, fmt.Errorf("clearing old feature_ids: %w", err) } // Prepare statements insertDep, err := tx.Prepare("INSERT OR IGNORE INTO dependencies (source_type, source_id, target_type, target_id, dependency_kind) VALUES ('unit_test', ?, 'feature', ?, 'calls')") if err != nil { return nil, fmt.Errorf("preparing insert dependency: %w", err) } defer insertDep.Close() updateFeatureID, err := tx.Prepare("UPDATE unit_tests SET feature_id = ? WHERE id = ?") if err != nil { return nil, fmt.Errorf("preparing update feature_id: %w", err) } defer updateFeatureID.Close() // Process each module's tests for _, mod := range result.Modules { for _, test := range mod.Tests { stats.TestsAnalyzed++ testDBID, ok := testDBIDs[mod.Name+":"+test.GoMethod] if !ok { continue } // Insert dependency rows for linked features if len(test.LinkedFeatures) > 0 { stats.TestsLinked++ } for _, fi := range test.LinkedFeatures { feat := mod.Features[fi] featDBID, ok := featureDBIDs[featureKey{mod.Name, feat.GoMethod, feat.GoClass}] if !ok { continue } if _, err := insertDep.Exec(testDBID, featDBID); err != nil { return nil, fmt.Errorf("inserting dependency for test %s: %w", test.GoMethod, err) } stats.DependencyRows++ } // Set feature_id for best match if test.BestFeatureIdx >= 0 { feat := mod.Features[test.BestFeatureIdx] featDBID, ok := featureDBIDs[featureKey{mod.Name, feat.GoMethod, feat.GoClass}] if !ok { continue } if _, err := updateFeatureID.Exec(featDBID, testDBID); err != nil { return nil, fmt.Errorf("updating feature_id for test %s: %w", test.GoMethod, err) } stats.FeatureIDsSet++ } } } if err := tx.Commit(); err != nil { return nil, fmt.Errorf("committing transaction: %w", err) } return stats, nil }