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 }