From b3a2bf3dc604d589aac670a97f3a61f8ed9847f5 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:53:52 +0200 Subject: [PATCH 1/2] clean/doctor: add explicit ANALYZE, WAL checkpoint, incremental vacuum; add doctor --deep integrity checks --- internal/cli/app.go | 119 ++++++++++++++++++++++++++++++++----- internal/cli/commands.go | 8 ++- internal/doctor/doctor.go | 121 +++++++++++++++++++++++++++++++++----- internal/store/store.go | 58 ++++++++++++++++++ 4 files changed, 277 insertions(+), 29 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index ad94542..9d93065 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -127,6 +127,7 @@ func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args [] fs := flag.NewFlagSet("doctor", flag.ContinueOnError) fs.SetOutput(io.Discard) fix := fs.Bool("fix", false, "apply non-destructive fixes") + deep := fs.Bool("deep", false, "run deeper (potentially slow) diagnostics, including integrity_check") repoRootFlag := fs.String("repo-root", "", "repository root to inspect (optional)") defaultRepoRoot := "" @@ -151,7 +152,7 @@ func runDoctor(ctx context.Context, cfg config.Config, stdout io.Writer, args [] dbPath = p } - report, err := doctor.RunWithFix(*fix, dbPath) + report, err := doctor.RunWithOptions(doctor.Options{Fix: *fix, DBPath: dbPath, Deep: *deep}) if err != nil { return err } @@ -1063,6 +1064,9 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s 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)") + analyze := fs.Bool("analyze", false, "run ANALYZE on databases") + walCheckpointTruncate := fs.Bool("wal-checkpoint-truncate", false, "run PRAGMA wal_checkpoint(TRUNCATE) on databases") + incrementalVacuum := fs.Bool("incremental-vacuum", false, "run PRAGMA incremental_vacuum (requires auto_vacuum=INCREMENTAL)") repoRootFlag := fs.String("repo-root", "", "repository root to clean") defaultRepoRoot := "" @@ -1075,21 +1079,31 @@ 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"` + 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"` + Analyzed bool `json:"analyzed,omitempty"` + AnalyzeMS int64 `json:"analyze_ms,omitempty"` + WALCheckpoint *store.WalCheckpointResult `json:"wal_checkpoint,omitempty"` + IncVacuumed bool `json:"incremental_vacuumed,omitempty"` + IncVacuumMS int64 `json:"incremental_vacuum_ms,omitempty"` + IncVacuumBeforeFreelist int64 `json:"incremental_vacuum_before_freelist_pages,omitempty"` + IncVacuumAfterFreelist int64 `json:"incremental_vacuum_after_freelist_pages,omitempty"` + CanonicalRepo string `json:"canonical_repo,omitempty"` + Error string `json:"error,omitempty"` } report := map[string]any{ - "vacuum": *vacuum, - "fts_optimize": *ftsOptimize, - "dbs": []dbResult{}, + "vacuum": *vacuum, + "fts_optimize": *ftsOptimize, + "analyze": *analyze, + "wal_checkpoint_truncate": *walCheckpointTruncate, + "incremental_vacuum": *incrementalVacuum, + "dbs": []dbResult{}, } results := make([]dbResult, 0) var reclaimed int64 @@ -1111,6 +1125,14 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s return err } defer s.Close() + if *analyze { + dur, err := s.Analyze(ctx) + if err != nil { + return err + } + res.Analyzed = true + res.AnalyzeMS = dur.Milliseconds() + } if *ftsOptimize { dur, err := s.OptimizeFTS(ctx) if err != nil { @@ -1119,12 +1141,35 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s res.FTSOptimized = true res.FTSOptimizeMS = dur.Milliseconds() } + if *walCheckpointTruncate { + walRes, err := s.WalCheckpointTruncate(ctx) + if err != nil { + return err + } + res.WALCheckpoint = &walRes + } + if *incrementalVacuum { + beforePages, afterPages, dur, err := s.IncrementalVacuumAll(ctx) + if err != nil { + return err + } + res.IncVacuumed = true + res.IncVacuumMS = dur.Milliseconds() + res.IncVacuumBeforeFreelist = beforePages + res.IncVacuumAfterFreelist = afterPages + } if *vacuum { if err := s.Vacuum(ctx); err != nil { return err } res.Vacuumed = true res.Action = "vacuumed" + } else if *incrementalVacuum { + res.Action = "incremental_vacuumed" + } else if *walCheckpointTruncate { + res.Action = "wal_checkpointed" + } else if *analyze { + res.Action = "analyzed" } else if *ftsOptimize { res.Action = "fts_optimized" } else { @@ -1201,6 +1246,21 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s if !*vacuum { res.Action = "kept" } + if *analyze { + dur, err := s.Analyze(ctx) + if err != nil { + _ = s.Close() + res.Action = "skipped" + res.Error = err.Error() + results = append(results, res) + continue + } + res.Analyzed = true + res.AnalyzeMS = dur.Milliseconds() + if !*vacuum && !*ftsOptimize && res.Action == "kept" { + res.Action = "analyzed" + } + } if *ftsOptimize { dur, err := s.OptimizeFTS(ctx) if err != nil { @@ -1216,6 +1276,37 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s res.Action = "fts_optimized" } } + if *walCheckpointTruncate { + walRes, err := s.WalCheckpointTruncate(ctx) + if err != nil { + _ = s.Close() + res.Action = "skipped" + res.Error = err.Error() + results = append(results, res) + continue + } + res.WALCheckpoint = &walRes + if !*vacuum && !*ftsOptimize && !*analyze && res.Action == "kept" { + res.Action = "wal_checkpointed" + } + } + if *incrementalVacuum { + beforePages, afterPages, dur, err := s.IncrementalVacuumAll(ctx) + if err != nil { + _ = s.Close() + res.Action = "skipped" + res.Error = err.Error() + results = append(results, res) + continue + } + res.IncVacuumed = true + res.IncVacuumMS = dur.Milliseconds() + res.IncVacuumBeforeFreelist = beforePages + res.IncVacuumAfterFreelist = afterPages + if !*vacuum && !*ftsOptimize && !*analyze && !*walCheckpointTruncate && res.Action == "kept" { + res.Action = "incremental_vacuumed" + } + } if *vacuum { if err := s.Vacuum(ctx); err != nil { _ = s.Close() diff --git a/internal/cli/commands.go b/internal/cli/commands.go index 7f6861e..53b0f60 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -258,6 +258,7 @@ func newCommandList() []*command { " doctor", " add --repo-root PATH to inspect a repo DB", " add --fix for non-destructive autofixes", + " add --deep for slower DB checks (integrity_check)", }, run: func(ctx context.Context, cfg config.Config, stdout, stderr io.Writer, invokedName string, args []string) error { return runDoctor(ctx, cfg, stdout, args) @@ -325,15 +326,20 @@ func newCommandList() []*command { { name: "clean", description: "clean index data", - usageLines: []string{" clean [repo-path] [--vacuum] [--fts-optimize]"}, + usageLines: []string{" clean [repo-path] [--vacuum] [--fts-optimize] [--analyze] [--wal-checkpoint-truncate] [--incremental-vacuum]"}, flags: []commandFlag{ {name: "--vacuum", description: "VACUUM the database after cleanup"}, {name: "--fts-optimize", description: "run FTS optimize (symbol_fts)"}, + {name: "--analyze", description: "run ANALYZE"}, + {name: "--wal-checkpoint-truncate", description: "run PRAGMA wal_checkpoint(TRUNCATE)"}, + {name: "--incremental-vacuum", description: "run PRAGMA incremental_vacuum (requires auto_vacuum=INCREMENTAL)"}, }, examples: []string{ "codegraph clean .", "codegraph clean . --vacuum", "codegraph clean . --fts-optimize", + "codegraph clean . --analyze", + "codegraph clean . --wal-checkpoint-truncate", }, 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 cf4fa46..9155f94 100644 --- a/internal/doctor/doctor.go +++ b/internal/doctor/doctor.go @@ -19,17 +19,18 @@ import ( ) type Report struct { - GOOS string `json:"goos"` - ConfigPath string `json:"config_path"` - ConfigExists bool `json:"config_exists"` - DataDir string `json:"data_dir"` - 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"` + GOOS string `json:"goos"` + ConfigPath string `json:"config_path"` + ConfigExists bool `json:"config_exists"` + DataDir string `json:"data_dir"` + 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"` + Deep *DeepInfo `json:"deep,omitempty"` + AppliedFixes []string `json:"applied_fixes,omitempty"` + Recommendations []string `json:"recommendations,omitempty"` } type DBInfo struct { @@ -38,11 +39,32 @@ type DBInfo struct { Pragmas store.DBPragmas `json:"pragmas"` } +type DeepInfo struct { + DB *DBDeepInfo `json:"db,omitempty"` +} + +type DBDeepInfo struct { + IntegrityOK bool `json:"integrity_ok"` + IntegrityMessages []string `json:"integrity_messages,omitempty"` + IntegrityTruncated bool `json:"integrity_truncated,omitempty"` + ForeignKeyIssues int64 `json:"foreign_key_issues,omitempty"` +} + func Run() (Report, error) { return RunWithFix(false, "") } func RunWithFix(fix bool, dbPath string) (Report, error) { + return RunWithOptions(Options{Fix: fix, DBPath: dbPath}) +} + +type Options struct { + Fix bool + DBPath string + Deep bool +} + +func RunWithOptions(opts Options) (Report, error) { paths, err := platform.DefaultPaths() if err != nil { return Report{}, err @@ -61,7 +83,7 @@ func RunWithFix(fix bool, dbPath string) (Report, error) { binaryPath = "" } appliedFixes := []string{} - if fix { + if opts.Fix { defaultCfg, err := config.Default() if err != nil { return Report{}, err @@ -96,13 +118,22 @@ func RunWithFix(fix bool, dbPath string) (Report, error) { } var dbInfo *DBInfo - if strings.TrimSpace(dbPath) != "" { - info, err := inspectDB(context.Background(), dbPath) + var deepInfo *DeepInfo + if strings.TrimSpace(opts.DBPath) != "" { + info, err := inspectDB(context.Background(), opts.DBPath) if err != nil { recommendations = append(recommendations, "repo DB inspect failed: "+err.Error()) } else { dbInfo = info } + if opts.Deep { + di, err := inspectDeepDB(context.Background(), opts.DBPath) + if err != nil { + recommendations = append(recommendations, "repo DB deep inspect failed: "+err.Error()) + } else { + deepInfo = &DeepInfo{DB: di} + } + } } return Report{ @@ -115,6 +146,7 @@ func RunWithFix(fix bool, dbPath string) (Report, error) { CodegraphPath: binaryPath, SQLiteDriver: store.SQLiteDriverName(), DB: dbInfo, + Deep: deepInfo, AppliedFixes: appliedFixes, Recommendations: recommendations, }, nil @@ -149,3 +181,64 @@ func inspectDB(ctx context.Context, dbPath string) (*DBInfo, error) { Pragmas: pragmas, }, nil } + +func inspectDeepDB(ctx context.Context, dbPath string) (*DBDeepInfo, error) { + db, err := sql.Open(store.SQLiteDriverName(), dbPath) + if err != nil { + return nil, err + } + defer db.Close() + + // integrity_check can return many rows; keep this bounded and human-usable. + const maxIntegrityMessages = 20 + integrityMessages := make([]string, 0) + truncated := false + rows, err := db.QueryContext(ctx, `PRAGMA integrity_check`) + if err != nil { + return nil, err + } + for rows.Next() { + var msg string + if err := rows.Scan(&msg); err != nil { + rows.Close() + return nil, err + } + if len(integrityMessages) < maxIntegrityMessages { + integrityMessages = append(integrityMessages, msg) + } else { + truncated = true + } + } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() + + integrityOK := len(integrityMessages) == 1 && integrityMessages[0] == "ok" + if integrityOK { + integrityMessages = nil + truncated = false + } + + var foreignKeyIssues int64 + fkRows, err := db.QueryContext(ctx, `PRAGMA foreign_key_check`) + if err != nil { + return nil, err + } + for fkRows.Next() { + foreignKeyIssues++ + } + if err := fkRows.Err(); err != nil { + fkRows.Close() + return nil, err + } + fkRows.Close() + + return &DBDeepInfo{ + IntegrityOK: integrityOK, + IntegrityMessages: integrityMessages, + IntegrityTruncated: truncated, + ForeignKeyIssues: foreignKeyIssues, + }, nil +} diff --git a/internal/store/store.go b/internal/store/store.go index 3a3d764..2551184 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -509,6 +509,56 @@ func (s *Store) OptimizeFTS(ctx context.Context) (time.Duration, error) { return time.Since(start), err } +type WalCheckpointResult struct { + Busy int64 `json:"busy"` + LogFrames int64 `json:"log_frames"` + CkptFrames int64 `json:"checkpointed_frames"` + Mode string `json:"mode"` + DurationMS int64 `json:"duration_ms"` +} + +func (s *Store) Analyze(ctx context.Context) (time.Duration, error) { + start := time.Now() + _, err := s.db.ExecContext(ctx, `ANALYZE`) + return time.Since(start), err +} + +func (s *Store) WalCheckpointTruncate(ctx context.Context) (WalCheckpointResult, error) { + start := time.Now() + var busy, logFrames, ckptFrames int64 + if err := s.db.QueryRowContext(ctx, `PRAGMA wal_checkpoint(TRUNCATE)`).Scan(&busy, &logFrames, &ckptFrames); err != nil { + return WalCheckpointResult{}, err + } + return WalCheckpointResult{ + Busy: busy, + LogFrames: logFrames, + CkptFrames: ckptFrames, + Mode: "TRUNCATE", + DurationMS: time.Since(start).Milliseconds(), + }, nil +} + +func (s *Store) IncrementalVacuumAll(ctx context.Context) (beforeFreelist, afterFreelist int64, dur time.Duration, err error) { + before, err := s.DBPragmas(ctx) + if err != nil { + return 0, 0, 0, err + } + if before.AutoVacuum != 2 { + return 0, 0, 0, fmt.Errorf("incremental vacuum requires PRAGMA auto_vacuum=2 (INCREMENTAL), got %d", before.AutoVacuum) + } + + start := time.Now() + // PRAGMA incremental_vacuum without an argument attempts to remove all pages from the freelist. + if _, err := s.db.ExecContext(ctx, `PRAGMA incremental_vacuum`); err != nil { + return 0, 0, 0, err + } + after, err := s.DBPragmas(ctx) + if err != nil { + return 0, 0, 0, err + } + return before.FreelistCount, after.FreelistCount, time.Since(start), nil +} + type DBPragmas struct { SQLiteVersion string `json:"sqlite_version"` JournalMode string `json:"journal_mode"` @@ -516,6 +566,8 @@ type DBPragmas struct { TempStore string `json:"temp_store"` AutoVacuum int64 `json:"auto_vacuum"` PageSize int64 `json:"page_size"` + PageCount int64 `json:"page_count"` + FreelistCount int64 `json:"freelist_count"` BusyTimeoutMS int64 `json:"busy_timeout_ms"` ForeignKeys bool `json:"foreign_keys"` WalAutocheckpoint int64 `json:"wal_autocheckpoint"` @@ -548,6 +600,12 @@ func QueryDBPragmas(ctx context.Context, db *sql.DB) (DBPragmas, error) { if err := db.QueryRowContext(ctx, `PRAGMA page_size`).Scan(&out.PageSize); err != nil { return DBPragmas{}, err } + if err := db.QueryRowContext(ctx, `PRAGMA page_count`).Scan(&out.PageCount); err != nil { + return DBPragmas{}, err + } + if err := db.QueryRowContext(ctx, `PRAGMA freelist_count`).Scan(&out.FreelistCount); err != nil { + return DBPragmas{}, err + } if err := db.QueryRowContext(ctx, `PRAGMA busy_timeout`).Scan(&out.BusyTimeoutMS); err != nil { return DBPragmas{}, err } From b258c19b520b2bfa93741dc65fe001aa2f3e2dd8 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:29:44 +0200 Subject: [PATCH 2/2] clean: dedupe maintenance action execution; clarify incremental_vacuum auto_vacuum mode errors --- internal/cli/app.go | 61 +++++++++++++++++++++++------------------ internal/store/store.go | 15 ++++++++-- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index 9d93065..8cb1a36 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -1108,6 +1108,35 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s results := make([]dbResult, 0) var reclaimed int64 + runAnalyze := func(ctx context.Context, s *store.Store, res *dbResult) error { + dur, err := s.Analyze(ctx) + if err != nil { + return err + } + res.Analyzed = true + res.AnalyzeMS = dur.Milliseconds() + return nil + } + runWalCheckpointTruncate := func(ctx context.Context, s *store.Store, res *dbResult) error { + walRes, err := s.WalCheckpointTruncate(ctx) + if err != nil { + return err + } + res.WALCheckpoint = &walRes + return nil + } + runIncrementalVacuumAll := func(ctx context.Context, s *store.Store, res *dbResult) error { + beforePages, afterPages, dur, err := s.IncrementalVacuumAll(ctx) + if err != nil { + return err + } + res.IncVacuumed = true + res.IncVacuumMS = dur.Milliseconds() + res.IncVacuumBeforeFreelist = beforePages + res.IncVacuumAfterFreelist = afterPages + return nil + } + if repoRoot != "" { canonical, err := store.CanonicalRepoPath(repoRoot) if err != nil { @@ -1126,12 +1155,9 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s } defer s.Close() if *analyze { - dur, err := s.Analyze(ctx) - if err != nil { + if err := runAnalyze(ctx, s, &res); err != nil { return err } - res.Analyzed = true - res.AnalyzeMS = dur.Milliseconds() } if *ftsOptimize { dur, err := s.OptimizeFTS(ctx) @@ -1142,21 +1168,14 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s res.FTSOptimizeMS = dur.Milliseconds() } if *walCheckpointTruncate { - walRes, err := s.WalCheckpointTruncate(ctx) - if err != nil { + if err := runWalCheckpointTruncate(ctx, s, &res); err != nil { return err } - res.WALCheckpoint = &walRes } if *incrementalVacuum { - beforePages, afterPages, dur, err := s.IncrementalVacuumAll(ctx) - if err != nil { + if err := runIncrementalVacuumAll(ctx, s, &res); err != nil { return err } - res.IncVacuumed = true - res.IncVacuumMS = dur.Milliseconds() - res.IncVacuumBeforeFreelist = beforePages - res.IncVacuumAfterFreelist = afterPages } if *vacuum { if err := s.Vacuum(ctx); err != nil { @@ -1247,16 +1266,13 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s res.Action = "kept" } if *analyze { - dur, err := s.Analyze(ctx) - if err != nil { + if err := runAnalyze(ctx, s, &res); err != nil { _ = s.Close() res.Action = "skipped" res.Error = err.Error() results = append(results, res) continue } - res.Analyzed = true - res.AnalyzeMS = dur.Milliseconds() if !*vacuum && !*ftsOptimize && res.Action == "kept" { res.Action = "analyzed" } @@ -1277,32 +1293,25 @@ func runClean(ctx context.Context, cfg config.Config, stdout io.Writer, args []s } } if *walCheckpointTruncate { - walRes, err := s.WalCheckpointTruncate(ctx) - if err != nil { + if err := runWalCheckpointTruncate(ctx, s, &res); err != nil { _ = s.Close() res.Action = "skipped" res.Error = err.Error() results = append(results, res) continue } - res.WALCheckpoint = &walRes if !*vacuum && !*ftsOptimize && !*analyze && res.Action == "kept" { res.Action = "wal_checkpointed" } } if *incrementalVacuum { - beforePages, afterPages, dur, err := s.IncrementalVacuumAll(ctx) - if err != nil { + if err := runIncrementalVacuumAll(ctx, s, &res); err != nil { _ = s.Close() res.Action = "skipped" res.Error = err.Error() results = append(results, res) continue } - res.IncVacuumed = true - res.IncVacuumMS = dur.Milliseconds() - res.IncVacuumBeforeFreelist = beforePages - res.IncVacuumAfterFreelist = afterPages if !*vacuum && !*ftsOptimize && !*analyze && !*walCheckpointTruncate && res.Action == "kept" { res.Action = "incremental_vacuumed" } diff --git a/internal/store/store.go b/internal/store/store.go index 2551184..a4c6ff9 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -543,8 +543,19 @@ func (s *Store) IncrementalVacuumAll(ctx context.Context) (beforeFreelist, after if err != nil { return 0, 0, 0, err } - if before.AutoVacuum != 2 { - return 0, 0, 0, fmt.Errorf("incremental vacuum requires PRAGMA auto_vacuum=2 (INCREMENTAL), got %d", before.AutoVacuum) + // SQLite auto_vacuum modes: + // 0 = NONE + // 1 = FULL + // 2 = INCREMENTAL (required for PRAGMA incremental_vacuum to reclaim pages) + switch before.AutoVacuum { + case 2: + // ok + case 0: + return 0, 0, 0, fmt.Errorf("incremental vacuum requires PRAGMA auto_vacuum=INCREMENTAL (2); database is auto_vacuum=NONE (0)") + case 1: + return 0, 0, 0, fmt.Errorf("incremental vacuum requires PRAGMA auto_vacuum=INCREMENTAL (2); database is auto_vacuum=FULL (1)") + default: + return 0, 0, 0, fmt.Errorf("incremental vacuum requires PRAGMA auto_vacuum=INCREMENTAL (2); got auto_vacuum=%d", before.AutoVacuum) } start := time.Now()