rqlite/rqlite · error

invalid SQLite data

Error message

invalid SQLite data

What it means

Store restores a node (e.g., via /boot) by receiving SQLite data, writing it to a temp file, and validating it with sql.IsValidSQLiteFile before installing it. If the uploaded bytes are not a valid SQLite database (bad header/format, truncated transfer, compressed data sent uncompressed), the restore is aborted with 'invalid SQLite data' and the node's existing database is left untouched.

Source

Thrown at store/store.go:2012

	defer os.Remove(f.Name())
	defer f.Close()

	cw := progress.NewCountingWriter(f)
	cm := progress.StartCountingMonitor(func(n int64) {
		s.logger.Printf("boot process installed %d bytes (%s)", n, humanize.Bytes(uint64(n)))
	}, cw)
	n, err := func() (int64, error) {
		defer cm.StopAndWait()
		defer f.Close()
		return io.Copy(cw, r)
	}()
	if err != nil {
		return n, err
	}

	// Confirm the data is a valid SQLite database.
	if !sql.IsValidSQLiteFile(f.Name()) {
		return n, fmt.Errorf("invalid SQLite data")
	}

	// Raft won't snapshot unless there is at least one unsnapshotted log entry,
	// so prep that now before we do anything destructive.
	if af, err := s.Noop("boot"); err != nil {
		return n, err
	} else if err := af.Error(); err != nil {
		return n, err
	}

	// Swap in new database file.
	if err := s.db.Swap(f.Name(), s.dbConf.FKConstraints, true); err != nil {
		return n, fmt.Errorf("error swapping database file: %v", err)
	}

	// Swapping in a new database unregisters any registered CDC hooks, so signal that it
	// needs to be reregistered on the next change.
	s.cdcRegistered.Unset()

View on GitHub (pinned to 7586a4d1bd)

Solutions

  1. Upload an actual SQLite database file (from /db/backup without fmt=sql), not a SQL text dump.
  2. Match compression flags: add ?compress to the boot request if the payload is gzipped, or send uncompressed data without it.
  3. Validate the file locally before upload: `file backup.db` should say 'SQLite 3 database'.
  4. Re-download the backup if truncated; compare checksums with the source.

Example fix

# before
curl -X POST http://node:4001/boot --data-binary @backup.sql.gz
# after
curl -X POST 'http://node:4001/boot?compress' --data-binary @backup.db.gz
Defensive patterns

Strategy: validation

Validate before calling

hdr := make([]byte, 16)
f, _ := os.Open(restoreFile)
f.Read(hdr)
if string(hdr[:15]) != "SQLite format 3" {
    return errors.New("not a SQLite database; use /db/backup without fmt=sql")
}

Type guard

func isValidSQLiteFileHeader(b []byte) bool {
    return len(b) >= 16 && string(b[:15]) == "SQLite format 3"
}

Try / catch

resp, err := http.Post(bootURL, "application/octet-stream", body)
if resp.StatusCode != 200 && strings.Contains(readBody(resp), "invalid SQLite data") {
    // wrong format: re-export with /db/backup, recheck compress flag
}

Prevention

When it happens

Trigger: POSTing to /boot with a body that is not a genuine SQLite file: an SQL text dump instead of a .db file, a gzip-compressed file uploaded without ?compress flag (or vice versa), truncated/corrupt download, or the wrong file entirely.

Common situations: Operators backing up with ?fmt=sql and then trying to boot from the SQL dump; curl scripts misplacing the ?compress parameter; interrupted downloads reused as restore sources; backups from non-SQLite sources.

Related errors


AI-assisted analysis of rqlite/rqlite@7586a4d1bd (2026-09-03). Data as JSON: /api/errors/c6295e2a4f7731f3. Report an issue: GitHub.