//go:build cgo package database import ( "context" "database/sql" "errors" "fmt" "os" "time" "github.com/mattn/go-sqlite3" ) func BackupSQLite(dstPath string) (err error) { if IsPostgres() { return errors.New("sqlite backup is unavailable for PostgreSQL") } if db == nil { return errors.New("database is not initialized") } if _, err := os.Lstat(dstPath); err == nil { return fmt.Errorf("sqlite backup destination already exists: %s", dstPath) } else if !errors.Is(err, os.ErrNotExist) { return err } defer func() { if err != nil { _ = os.Remove(dstPath) } }() ctx, cancel := context.WithTimeout(context.Background(), backupSQLiteTimeout) defer cancel() sourceDB, err := db.DB() if err != nil { return err } sourceConn, err := sourceDB.Conn(ctx) if err != nil { return err } defer sourceConn.Close() destinationDB, err := sql.Open("sqlite3", dstPath) if err != nil { return err } defer destinationDB.Close() destinationConn, err := destinationDB.Conn(ctx) if err != nil { return err } defer destinationConn.Close() return sourceConn.Raw(func(sourceDriver any) error { source, ok := sourceDriver.(*sqlite3.SQLiteConn) if !ok { return fmt.Errorf("unexpected SQLite source connection type %T", sourceDriver) } return destinationConn.Raw(func(destinationDriver any) error { destination, ok := destinationDriver.(*sqlite3.SQLiteConn) if !ok { return fmt.Errorf("unexpected SQLite destination connection type %T", destinationDriver) } backup, err := destination.Backup("main", source, "main") if err != nil { return err } finished := false defer func() { if !finished { _ = backup.Finish() } }() for { done, err := backup.Step(backupSQLiteStepPages()) if err != nil { return err } if done { finished = true return backup.Finish() } select { case <-ctx.Done(): return ctx.Err() case <-time.After(10 * time.Millisecond): } } }) }) }