benbjohnson/litestream · error

get table list: %w

Error message

get table list: %w

What it means

After opening the database, shrinkDatabase calls c.getTableList(db), which queries sqlite_master for user tables (excluding sqlite_% and load_test). Any error from that query or from scanning rows is wrapped as `get table list: %w`. This indicates the schema read failed — usually a database-level problem such as corruption or a locked/busy database.

Source

Thrown at cmd/litestream-test/shrink.go:77

func (c *ShrinkCommand) shrinkDatabase(ctx context.Context) error {
	initialSize, err := getDatabaseSize(c.DB)
	if err != nil {
		return fmt.Errorf("get initial size: %w", err)
	}

	slog.Info("Initial database size",
		"size_mb", initialSize/1024/1024,
	)

	db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL")
	if err != nil {
		return fmt.Errorf("open database: %w", err)
	}
	defer db.Close()

	tables, err := c.getTableList(db)
	if err != nil {
		return fmt.Errorf("get table list: %w", err)
	}

	slog.Info("Found tables", "count", len(tables))

	totalDeleted := int64(0)
	for _, table := range tables {
		deleted, err := c.deleteFromTable(db, table)
		if err != nil {
			slog.Error("Failed to delete from table", "table", table, "error", err)
			continue
		}
		totalDeleted += deleted
		slog.Info("Deleted rows from table",
			"table", table,
			"rows_deleted", deleted,
		)
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the file is a real SQLite database: `file <path>` or `sqlite3 <path> '.tables'`
  2. Close or wait for other processes holding locks on the database, then retry
  3. Add a busy timeout to the DSN (e.g. &_busy_timeout=5000) if concurrent access is expected
  4. Check the wrapped inner error for 'not a database' vs 'database is locked' and act accordingly

Example fix

// before
db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL")
// after
db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL&_busy_timeout=5000")
Defensive patterns

Strategy: retry

Validate before calling

// confirm the file is a valid SQLite database before invoking
if header, err := os.ReadFile(dbPath); err == nil && len(header) >= 16 {
    if string(header[:16]) != "SQLite format 3\x00" {
        return fmt.Errorf("%s is not a SQLite database", dbPath)
    }
}

Try / catch

if err := runShrink(args); err != nil {
    if strings.Contains(err.Error(), "get table list") {
        if strings.Contains(err.Error(), "locked") {
            time.Sleep(2 * time.Second)
            return runShrink(args)
        }
    }
    return err
}

Prevention

When it happens

Trigger: The opened file is not a valid SQLite database (sql.Open succeeded lazily; the first real query fails with 'file is not a database'); the database is locked by another writer with no busy timeout; disk I/O error while reading sqlite_master.

Common situations: Pointing the tool at a non-SQLite file (e.g. an LTX file, WAL fragment, or text file renamed to .db); another process holds an exclusive lock during heavy load testing; reading a database written by a newer SQLite with an unsupported file format.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/db67e56e8f4408d0. Report an issue: GitHub.