From c31bf6050d541a48e1f77da02c8fd5db8f6c7065 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 26 Feb 2026 06:15:01 -0500 Subject: [PATCH] feat(go-analyzer): add SQLite writer, complete analyzer pipeline Add sqlite.go with DBWriter that writes analysis results (modules, features, tests, dependencies, library mappings) to the porting database. Successfully analyzes nats-server: 12 modules, 3673 features, 3257 tests, 36 library mappings, 11 dependencies. --- tools/go-analyzer/go.mod | 2 + tools/go-analyzer/go.sum | 2 + tools/go-analyzer/sqlite.go | 154 ++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tools/go-analyzer/go.sum create mode 100644 tools/go-analyzer/sqlite.go diff --git a/tools/go-analyzer/go.mod b/tools/go-analyzer/go.mod index 898e50c..df8d857 100644 --- a/tools/go-analyzer/go.mod +++ b/tools/go-analyzer/go.mod @@ -1,3 +1,5 @@ module github.com/natsnet/go-analyzer go 1.25.5 + +require github.com/mattn/go-sqlite3 v1.14.34 diff --git a/tools/go-analyzer/go.sum b/tools/go-analyzer/go.sum new file mode 100644 index 0000000..684933a --- /dev/null +++ b/tools/go-analyzer/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/tools/go-analyzer/sqlite.go b/tools/go-analyzer/sqlite.go new file mode 100644 index 0000000..ff7448e --- /dev/null +++ b/tools/go-analyzer/sqlite.go @@ -0,0 +1,154 @@ +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 +}