package main import ( "context" "database/sql" "flag" "fmt" "os" "path/filepath" "time" _ "github.com/mattn/go-sqlite3" "codexis/db" "codexis/indexer" ) const dbDir = ".codexis" const dbFileName = "index.db" func main() { force := flag.Bool("force", false, "Force full re-index (ignore file hashes)") output := flag.String("o", "", "Output database path (default: /.codexis.db)") flag.Parse() root := "." if flag.NArg() > 0 { root = flag.Arg(0) } absRoot, err := filepath.Abs(root) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } dbDirPath := filepath.Join(absRoot, dbDir) if err := os.MkdirAll(dbDirPath, 0755); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } dbPath := filepath.Join(dbDirPath, dbFileName) if *output != "" { dbPath = *output } if err := run(absRoot, dbPath, *force); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } func run(root, dbPath string, force bool) error { ctx := context.Background() sqlDB, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=on") if err != nil { return fmt.Errorf("opening database: %w", err) } defer sqlDB.Close() // Create schema if err := createSchema(ctx, sqlDB); err != nil { return fmt.Errorf("creating schema: %w", err) } queries := db.New(sqlDB) idx := indexer.New(queries, root, force) start := time.Now() fmt.Fprintf(os.Stderr, "Indexing %s...\n", root) stats, err := idx.Index(ctx) if err != nil { return fmt.Errorf("indexing: %w", err) } elapsed := time.Since(start) fmt.Fprintf(os.Stderr, "Done in %s\n", elapsed.Round(time.Millisecond)) fmt.Fprintf(os.Stderr, " Files: %d total, %d indexed, %d unchanged\n", stats.FilesTotal, stats.FilesIndexed, stats.FilesSkipped) fmt.Fprintf(os.Stderr, " Symbols: %d\n", stats.SymbolsTotal) fmt.Fprintf(os.Stderr, " Output: %s\n", dbPath) return nil } func createSchema(ctx context.Context, sqlDB *sql.DB) error { schema := ` CREATE TABLE IF NOT EXISTS files ( id INTEGER PRIMARY KEY, path TEXT NOT NULL UNIQUE, language TEXT NOT NULL, package TEXT, hash TEXT NOT NULL, indexed_at DATETIME DEFAULT CURRENT_TIMESTAMP ); 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, kind TEXT NOT NULL CHECK(kind IN ( 'function', 'method', 'class', 'type', 'interface', 'constant', 'variable', 'constructor' )), line INTEGER NOT NULL, line_end INTEGER, col INTEGER, col_end INTEGER, exported BOOLEAN, parent_id INTEGER REFERENCES symbols(id), UNIQUE(file_id, name, kind, line) ); CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name); CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind); CREATE INDEX IF NOT EXISTS idx_symbols_file_line ON symbols(file_id, line); CREATE INDEX IF NOT EXISTS idx_symbols_parent ON symbols(parent_id); CREATE INDEX IF NOT EXISTS idx_symbols_exported ON symbols(exported, kind); CREATE INDEX IF NOT EXISTS idx_files_path ON files(path); CREATE INDEX IF NOT EXISTS idx_files_language ON files(language); CREATE INDEX IF NOT EXISTS idx_files_package ON files(package); ` _, err := sqlDB.ExecContext(ctx, schema) return err }