From d1ec0d8889ef21f416c86baa62c337e86f301042 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:01:27 +0200 Subject: [PATCH 1/2] maintenance: add FTS optimize to clean and expand doctor DB diagnostics --- internal/cli/app.go | 114 +++++++++++++++++++++++++++------ internal/cli/commands.go | 7 +- internal/doctor/doctor.go | 92 +++++++++++++++++++++++++- internal/doctor/doctor_test.go | 2 +- internal/store/store.go | 68 ++++++++++++++++++++ 5 files changed, 256 insertions(+), 27 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index db2600e..d88edb5 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -103,14 +103,42 @@ func hasHelpFlag(args []string) bool { return false } -func runDoctor(stdout io.Writer, args []string) error { +func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args []string) error { fs := flag.NewFlagSet("doctor", flag.ContinueOnError) fs.SetOutput(io.Discard) fix := fs.Bool("fix", false, "apply non-destructive fixes") - if err := fs.Parse(args); err != nil { + repoRootFlag := fs.String("repo-root", "", "repository root to inspect (optional)") + repoRootArg := "" + parseArgs := args + if len(args) > 0 && !strings.HasPrefix(args[0], "-") { + repoRootArg = args[0] + parseArgs = args[1:] + } + if err := fs.Parse(parseArgs); err != nil { return err } - report, err := doctor.RunWithFix(*fix) + repoRoot := strings.TrimSpace(*repoRootFlag) + if repoRoot == "" { + repoRoot = strings.TrimSpace(repoRootArg) + } + if repoRoot == "" && config.IsRepoDBDir(cfg.DBDir) { + repoRoot = "." + } + + dbPath := "" + if repoRoot != "" { + canonical, err := store.CanonicalRepoPath(repoRoot) + if err != nil { + return err + } + p, err := dbPathForRepo(cfg, repoRoot, canonical) + if err != nil { + return err + } + dbPath = p + } + + report, err := doctor.RunWithFix(*fix, dbPath) if err != nil { return err } @@ -1021,13 +1049,20 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s fs := flag.NewFlagSet("clean", flag.ContinueOnError) fs.SetOutput(io.Discard) vacuum := fs.Bool("vacuum", false, "run VACUUM on databases") + ftsOptimize := fs.Bool("fts-optimize", false, "run FTS optimize on databases (symbol_fts)") repoRootFlag := fs.String("repo-root", "", "repository root to clean") - if err := fs.Parse(args); err != nil { + repoRootArg := "" + parseArgs := args + if len(args) > 0 && !strings.HasPrefix(args[0], "-") { + repoRootArg = args[0] + parseArgs = args[1:] + } + if err := fs.Parse(parseArgs); err != nil { return err } repoRoot := strings.TrimSpace(*repoRootFlag) - if repoRoot == "" && fs.NArg() > 0 { - repoRoot = fs.Arg(0) + if repoRoot == "" { + repoRoot = strings.TrimSpace(repoRootArg) } if repoRoot == "" && config.IsRepoDBDir(cfg.DBDir) { repoRoot = "." @@ -1036,13 +1071,19 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s type dbResult struct { Path string `json:"path"` Action string `json:"action"` + SizeBefore int64 `json:"size_before_bytes"` + SizeAfter int64 `json:"size_after_bytes"` ReclaimedBytes int64 `json:"reclaimed_bytes"` + Vacuumed bool `json:"vacuumed,omitempty"` + FTSOptimized bool `json:"fts_optimized,omitempty"` + FTSOptimizeMS int64 `json:"fts_optimize_ms,omitempty"` CanonicalRepo string `json:"canonical_repo,omitempty"` Error string `json:"error,omitempty"` } report := map[string]any{ - "vacuum": *vacuum, - "dbs": []dbResult{}, + "vacuum": *vacuum, + "fts_optimize": *ftsOptimize, + "dbs": []dbResult{}, } results := make([]dbResult, 0) var reclaimed int64 @@ -1058,24 +1099,37 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s } res := dbResult{Path: dbPath, CanonicalRepo: canonical} before := fileSize(dbPath) + res.SizeBefore = before s, err := store.OpenWithOptions(dbPath, store.OpenOptions{PerformanceProfile: cfg.DBPerformanceProfile}) if err != nil { return err } defer s.Close() + if *ftsOptimize { + dur, err := s.OptimizeFTS(ctx) + if err != nil { + return err + } + res.FTSOptimized = true + res.FTSOptimizeMS = dur.Milliseconds() + } if *vacuum { if err := s.Vacuum(ctx); err != nil { return err } - after := fileSize(dbPath) - if before > after { - res.ReclaimedBytes = before - after - reclaimed += res.ReclaimedBytes - } + res.Vacuumed = true res.Action = "vacuumed" + } else if *ftsOptimize { + res.Action = "fts_optimized" } else { res.Action = "inspected" } + after := fileSize(dbPath) + res.SizeAfter = after + if before > after { + res.ReclaimedBytes = before - after + reclaimed += res.ReclaimedBytes + } results = append(results, res) report["dbs"] = results report["reclaimed_bytes"] = reclaimed @@ -1095,7 +1149,7 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s } dbPath := filepath.Join(cfg.DBDir, entry.Name()) sizeBefore := fileSize(dbPath) - res := dbResult{Path: dbPath} + res := dbResult{Path: dbPath, SizeBefore: sizeBefore} s, err := store.OpenWithOptions(dbPath, store.OpenOptions{PerformanceProfile: cfg.DBPerformanceProfile}) if err != nil { res.Action = "skipped" @@ -1138,6 +1192,24 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s results = append(results, res) continue } + if !*vacuum { + res.Action = "kept" + } + if *ftsOptimize { + dur, err := s.OptimizeFTS(ctx) + if err != nil { + _ = s.Close() + res.Action = "skipped" + res.Error = err.Error() + results = append(results, res) + continue + } + res.FTSOptimized = true + res.FTSOptimizeMS = dur.Milliseconds() + if !*vacuum && res.Action == "kept" { + res.Action = "fts_optimized" + } + } if *vacuum { if err := s.Vacuum(ctx); err != nil { _ = s.Close() @@ -1146,16 +1218,16 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s results = append(results, res) continue } - after := fileSize(dbPath) - if sizeBefore > after { - res.ReclaimedBytes = sizeBefore - after - reclaimed += res.ReclaimedBytes - } + res.Vacuumed = true res.Action = "vacuumed" - } else { - res.Action = "kept" } _ = s.Close() + sizeAfter := fileSize(dbPath) + res.SizeAfter = sizeAfter + if sizeBefore > sizeAfter { + res.ReclaimedBytes = sizeBefore - sizeAfter + reclaimed += res.ReclaimedBytes + } results = append(results, res) } diff --git a/internal/cli/commands.go b/internal/cli/commands.go index 582dd18..7f6861e 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -256,10 +256,11 @@ func newCommandList() []*command { description: "run diagnostics", usageLines: []string{ " doctor", + " add --repo-root PATH to inspect a repo DB", " add --fix for non-destructive autofixes", }, run: func(ctx context.Context, cfg config.Config, stdout, stderr io.Writer, invokedName string, args []string) error { - return runDoctor(stdout, args) + return runDoctor(ctx, cfg, stdout, args) }, }, { @@ -324,13 +325,15 @@ func newCommandList() []*command { { name: "clean", description: "clean index data", - usageLines: []string{" clean [repo-path] [--vacuum]"}, + usageLines: []string{" clean [repo-path] [--vacuum] [--fts-optimize]"}, flags: []commandFlag{ {name: "--vacuum", description: "VACUUM the database after cleanup"}, + {name: "--fts-optimize", description: "run FTS optimize (symbol_fts)"}, }, examples: []string{ "codegraph clean .", "codegraph clean . --vacuum", + "codegraph clean . --fts-optimize", }, run: func(ctx context.Context, cfg config.Config, stdout, stderr io.Writer, invokedName string, args []string) error { return runClean(ctx, cfg, stdout, args) diff --git a/internal/doctor/doctor.go b/internal/doctor/doctor.go index 287eca4..24bcb2a 100644 --- a/internal/doctor/doctor.go +++ b/internal/doctor/doctor.go @@ -1,17 +1,21 @@ package doctor import ( + "context" + "database/sql" "errors" "fmt" "os" "os/exec" "path/filepath" "runtime" + "strings" "github.com/isink17/codegraph/internal/appname" "github.com/isink17/codegraph/internal/config" "github.com/isink17/codegraph/internal/gotool" "github.com/isink17/codegraph/internal/platform" + "github.com/isink17/codegraph/internal/store" ) type Report struct { @@ -22,15 +26,23 @@ type Report struct { CacheDir string `json:"cache_dir"` CodegraphOnPath bool `json:"codegraph_on_path"` CodegraphPath string `json:"codegraph_path,omitempty"` + SQLiteDriver string `json:"sqlite_driver"` + DB *DBInfo `json:"db,omitempty"` AppliedFixes []string `json:"applied_fixes,omitempty"` Recommendations []string `json:"recommendations,omitempty"` } +type DBInfo struct { + Path string `json:"path"` + SizeBytes int64 `json:"size_bytes"` + Pragmas store.DBPragmas `json:"pragmas"` +} + func Run() (Report, error) { - return RunWithFix(false) + return RunWithFix(false, "") } -func RunWithFix(fix bool) (Report, error) { +func RunWithFix(fix bool, dbPath string) (Report, error) { paths, err := platform.DefaultPaths() if err != nil { return Report{}, err @@ -44,7 +56,9 @@ func RunWithFix(fix bool) (Report, error) { binaryPath, lookErr := exec.LookPath("codegraph") onPath := lookErr == nil if lookErr != nil && !errors.Is(lookErr, exec.ErrNotFound) { - return Report{}, lookErr + // Be conservative: doctor should still run even if the PATH lookup found something unusable. + onPath = false + binaryPath = "" } appliedFixes := []string{} if fix { @@ -81,6 +95,16 @@ func RunWithFix(fix bool) (Report, error) { recommendations = append(recommendations, "verify after reopening shell: "+gotool.VerifyCommandHint(appname.BinaryName)) } + var dbInfo *DBInfo + if strings.TrimSpace(dbPath) != "" { + info, err := inspectDB(context.Background(), dbPath) + if err != nil { + recommendations = append(recommendations, "repo DB inspect failed: "+err.Error()) + } else { + dbInfo = info + } + } + return Report{ GOOS: runtime.GOOS, ConfigPath: filepath.Clean(configPath), @@ -89,6 +113,8 @@ func RunWithFix(fix bool) (Report, error) { CacheDir: paths.CacheDir, CodegraphOnPath: onPath, CodegraphPath: binaryPath, + SQLiteDriver: store.SQLiteDriverName(), + DB: dbInfo, AppliedFixes: appliedFixes, Recommendations: recommendations, }, nil @@ -101,3 +127,63 @@ func firstPathHint() string { } return hints[0] } + +func inspectDB(ctx context.Context, dbPath string) (*DBInfo, error) { + st, err := os.Stat(dbPath) + if err != nil { + return nil, err + } + db, err := sql.Open(store.SQLiteDriverName(), dbPath) + if err != nil { + return nil, err + } + defer db.Close() + var pragmas store.DBPragmas + if err := db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&pragmas.SQLiteVersion); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA journal_mode`).Scan(&pragmas.JournalMode); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA synchronous`).Scan(&pragmas.Synchronous); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA temp_store`).Scan(&pragmas.TempStore); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA auto_vacuum`).Scan(&pragmas.AutoVacuum); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&pragmas.PageSize); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&pragmas.BusyTimeoutMS); err != nil { + return nil, err + } + var foreignKeys int64 + if err := db.QueryRowContext(ctx, `PRAGMA foreign_keys`).Scan(&foreignKeys); err != nil { + return nil, err + } + pragmas.ForeignKeys = foreignKeys != 0 + if err := db.QueryRowContext(ctx, `PRAGMA wal_autocheckpoint`).Scan(&pragmas.WalAutocheckpoint); err != nil { + return nil, err + } + if err := db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&pragmas.UserVersion); err != nil { + return nil, err + } + var symbolFTSName string + err = db.QueryRowContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name='symbol_fts'`).Scan(&symbolFTSName) + if err == nil && symbolFTSName == "symbol_fts" { + pragmas.SymbolFTSPresent = true + } else if errors.Is(err, sql.ErrNoRows) { + pragmas.SymbolFTSPresent = false + } else if err != nil { + return nil, err + } + + return &DBInfo{ + Path: filepath.Clean(dbPath), + SizeBytes: st.Size(), + Pragmas: pragmas, + }, nil +} diff --git a/internal/doctor/doctor_test.go b/internal/doctor/doctor_test.go index 2f43890..f05298b 100644 --- a/internal/doctor/doctor_test.go +++ b/internal/doctor/doctor_test.go @@ -58,7 +58,7 @@ func TestRunReportsCodegraphMissingFromPath(t *testing.T) { func TestRunWithFixCreatesConfig(t *testing.T) { home := filepath.Join(t.TempDir(), "codegraph-home") t.Setenv("CODEGRAPH_HOME", home) - report, err := RunWithFix(true) + report, err := RunWithFix(true, "") if err != nil { t.Fatalf("RunWithFix(true) error = %v", err) } diff --git a/internal/store/store.go b/internal/store/store.go index dabdde1..bd1dcfe 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -503,6 +503,74 @@ func (s *Store) Vacuum(ctx context.Context) error { return err } +func (s *Store) OptimizeFTS(ctx context.Context) (time.Duration, error) { + start := time.Now() + _, err := s.db.ExecContext(ctx, `INSERT INTO symbol_fts(symbol_fts) VALUES('optimize')`) + return time.Since(start), err +} + +type DBPragmas struct { + SQLiteVersion string `json:"sqlite_version"` + JournalMode string `json:"journal_mode"` + Synchronous string `json:"synchronous"` + TempStore string `json:"temp_store"` + AutoVacuum int64 `json:"auto_vacuum"` + PageSize int64 `json:"page_size"` + BusyTimeoutMS int64 `json:"busy_timeout_ms"` + ForeignKeys bool `json:"foreign_keys"` + WalAutocheckpoint int64 `json:"wal_autocheckpoint"` + UserVersion int64 `json:"user_version"` + SymbolFTSPresent bool `json:"symbol_fts_present"` +} + +func (s *Store) DBPragmas(ctx context.Context) (DBPragmas, error) { + var out DBPragmas + + if err := s.db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&out.SQLiteVersion); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA journal_mode`).Scan(&out.JournalMode); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA synchronous`).Scan(&out.Synchronous); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA temp_store`).Scan(&out.TempStore); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA auto_vacuum`).Scan(&out.AutoVacuum); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&out.PageSize); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&out.BusyTimeoutMS); err != nil { + return DBPragmas{}, err + } + var foreignKeys int64 + if err := s.db.QueryRowContext(ctx, `PRAGMA foreign_keys`).Scan(&foreignKeys); err != nil { + return DBPragmas{}, err + } + out.ForeignKeys = foreignKeys != 0 + if err := s.db.QueryRowContext(ctx, `PRAGMA wal_autocheckpoint`).Scan(&out.WalAutocheckpoint); err != nil { + return DBPragmas{}, err + } + if err := s.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&out.UserVersion); err != nil { + return DBPragmas{}, err + } + + var symbolFTSName string + err := s.db.QueryRowContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name='symbol_fts'`).Scan(&symbolFTSName) + if err == nil && symbolFTSName == "symbol_fts" { + out.SymbolFTSPresent = true + } else if errors.Is(err, sql.ErrNoRows) { + out.SymbolFTSPresent = false + } else if err != nil { + return DBPragmas{}, err + } + return out, nil +} + func (s *Store) ExistingFiles(ctx context.Context, repoID int64) (map[string]FileRecord, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, path, language, size_bytes, mtime_unix_ns, content_sha256, is_deleted From 58b2eddb864a9659c84f2b80fffa1cd78aba8c06 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:18:48 +0200 Subject: [PATCH 2/2] doctor/clean: reuse store pragma inspection and dedupe repo-root parsing --- internal/cli/app.go | 48 ++++++++++++++++++++++----------------- internal/doctor/doctor.go | 42 ++-------------------------------- internal/store/store.go | 26 ++++++++++++--------- 3 files changed, 44 insertions(+), 72 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index d88edb5..d0eeb4c 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -103,11 +103,7 @@ func hasHelpFlag(args []string) bool { return false } -func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args []string) error { - fs := flag.NewFlagSet("doctor", flag.ContinueOnError) - fs.SetOutput(io.Discard) - fix := fs.Bool("fix", false, "apply non-destructive fixes") - repoRootFlag := fs.String("repo-root", "", "repository root to inspect (optional)") +func parseOptionalRepoRootArg(fs *flag.FlagSet, args []string, repoRootFlag *string, defaultRepoRoot string) (string, error) { repoRootArg := "" parseArgs := args if len(args) > 0 && !strings.HasPrefix(args[0], "-") { @@ -115,14 +111,31 @@ func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args [] parseArgs = args[1:] } if err := fs.Parse(parseArgs); err != nil { - return err + return "", err } repoRoot := strings.TrimSpace(*repoRootFlag) if repoRoot == "" { repoRoot = strings.TrimSpace(repoRootArg) } - if repoRoot == "" && config.IsRepoDBDir(cfg.DBDir) { - repoRoot = "." + if repoRoot == "" { + repoRoot = defaultRepoRoot + } + return repoRoot, nil +} + +func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args []string) error { + fs := flag.NewFlagSet("doctor", flag.ContinueOnError) + fs.SetOutput(io.Discard) + fix := fs.Bool("fix", false, "apply non-destructive fixes") + repoRootFlag := fs.String("repo-root", "", "repository root to inspect (optional)") + + defaultRepoRoot := "" + if config.IsRepoDBDir(cfg.DBDir) { + defaultRepoRoot = "." + } + repoRoot, err := parseOptionalRepoRootArg(fs, args, repoRootFlag, defaultRepoRoot) + if err != nil { + return err } dbPath := "" @@ -1051,22 +1064,15 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s vacuum := fs.Bool("vacuum", false, "run VACUUM on databases") ftsOptimize := fs.Bool("fts-optimize", false, "run FTS optimize on databases (symbol_fts)") repoRootFlag := fs.String("repo-root", "", "repository root to clean") - repoRootArg := "" - parseArgs := args - if len(args) > 0 && !strings.HasPrefix(args[0], "-") { - repoRootArg = args[0] - parseArgs = args[1:] + + defaultRepoRoot := "" + if config.IsRepoDBDir(cfg.DBDir) { + defaultRepoRoot = "." } - if err := fs.Parse(parseArgs); err != nil { + repoRoot, err := parseOptionalRepoRootArg(fs, args, repoRootFlag, defaultRepoRoot) + if err != nil { return err } - repoRoot := strings.TrimSpace(*repoRootFlag) - if repoRoot == "" { - repoRoot = strings.TrimSpace(repoRootArg) - } - if repoRoot == "" && config.IsRepoDBDir(cfg.DBDir) { - repoRoot = "." - } type dbResult struct { Path string `json:"path"` diff --git a/internal/doctor/doctor.go b/internal/doctor/doctor.go index 24bcb2a..cf4fa46 100644 --- a/internal/doctor/doctor.go +++ b/internal/doctor/doctor.go @@ -138,46 +138,8 @@ func inspectDB(ctx context.Context, dbPath string) (*DBInfo, error) { return nil, err } defer db.Close() - var pragmas store.DBPragmas - if err := db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&pragmas.SQLiteVersion); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA journal_mode`).Scan(&pragmas.JournalMode); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA synchronous`).Scan(&pragmas.Synchronous); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA temp_store`).Scan(&pragmas.TempStore); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA auto_vacuum`).Scan(&pragmas.AutoVacuum); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&pragmas.PageSize); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&pragmas.BusyTimeoutMS); err != nil { - return nil, err - } - var foreignKeys int64 - if err := db.QueryRowContext(ctx, `PRAGMA foreign_keys`).Scan(&foreignKeys); err != nil { - return nil, err - } - pragmas.ForeignKeys = foreignKeys != 0 - if err := db.QueryRowContext(ctx, `PRAGMA wal_autocheckpoint`).Scan(&pragmas.WalAutocheckpoint); err != nil { - return nil, err - } - if err := db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&pragmas.UserVersion); err != nil { - return nil, err - } - var symbolFTSName string - err = db.QueryRowContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name='symbol_fts'`).Scan(&symbolFTSName) - if err == nil && symbolFTSName == "symbol_fts" { - pragmas.SymbolFTSPresent = true - } else if errors.Is(err, sql.ErrNoRows) { - pragmas.SymbolFTSPresent = false - } else if err != nil { + pragmas, err := store.QueryDBPragmas(ctx, db) + if err != nil { return nil, err } diff --git a/internal/store/store.go b/internal/store/store.go index bd1dcfe..3a3d764 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -524,43 +524,47 @@ type DBPragmas struct { } func (s *Store) DBPragmas(ctx context.Context) (DBPragmas, error) { + return QueryDBPragmas(ctx, s.db) +} + +func QueryDBPragmas(ctx context.Context, db *sql.DB) (DBPragmas, error) { var out DBPragmas - if err := s.db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&out.SQLiteVersion); err != nil { + if err := db.QueryRowContext(ctx, `SELECT sqlite_version()`).Scan(&out.SQLiteVersion); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA journal_mode`).Scan(&out.JournalMode); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA journal_mode`).Scan(&out.JournalMode); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA synchronous`).Scan(&out.Synchronous); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA synchronous`).Scan(&out.Synchronous); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA temp_store`).Scan(&out.TempStore); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA temp_store`).Scan(&out.TempStore); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA auto_vacuum`).Scan(&out.AutoVacuum); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA auto_vacuum`).Scan(&out.AutoVacuum); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&out.PageSize); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&out.PageSize); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&out.BusyTimeoutMS); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&out.BusyTimeoutMS); err != nil { return DBPragmas{}, err } var foreignKeys int64 - if err := s.db.QueryRowContext(ctx, `PRAGMA foreign_keys`).Scan(&foreignKeys); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA foreign_keys`).Scan(&foreignKeys); err != nil { return DBPragmas{}, err } out.ForeignKeys = foreignKeys != 0 - if err := s.db.QueryRowContext(ctx, `PRAGMA wal_autocheckpoint`).Scan(&out.WalAutocheckpoint); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA wal_autocheckpoint`).Scan(&out.WalAutocheckpoint); err != nil { return DBPragmas{}, err } - if err := s.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&out.UserVersion); err != nil { + if err := db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&out.UserVersion); err != nil { return DBPragmas{}, err } var symbolFTSName string - err := s.db.QueryRowContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name='symbol_fts'`).Scan(&symbolFTSName) + err := db.QueryRowContext(ctx, `SELECT name FROM sqlite_master WHERE type='table' AND name='symbol_fts'`).Scan(&symbolFTSName) if err == nil && symbolFTSName == "symbol_fts" { out.SymbolFTSPresent = true } else if errors.Is(err, sql.ErrNoRows) {