Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions replica.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import (
"io"
"log/slog"
"os"
"path/filepath"
"sync"
"time"

"filippo.io/age"
"github.com/superfly/ltx"

"github.com/benbjohnson/litestream/internal"
)

// Default replica settings.
Expand Down Expand Up @@ -446,6 +449,15 @@ func (r *Replica) Restore(ctx context.Context, opt RestoreOptions) (err error) {
return fmt.Errorf("no matching backup files available")
}

// Create parent directory if it doesn't exist.
var dirInfo os.FileInfo
if db := r.DB(); db != nil {
dirInfo = db.dirInfo
}
if err := internal.MkdirAll(filepath.Dir(opt.OutputPath), dirInfo); err != nil {
return fmt.Errorf("create parent directory: %w", err)
}

// Output to temp file & atomically rename.
tmpOutputPath := opt.OutputPath + ".tmp"
r.Logger().Debug("compacting into database", "path", tmpOutputPath, "n", len(rdrs))
Expand Down
8 changes: 5 additions & 3 deletions replica_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,16 @@ func TestReplica_RestoreAndReplicateAfterDataLoss(t *testing.T) {
t.Fatal(err)
}

// Restore again
// Restore to a path with non-existent parent directory to verify it gets created
restoredPath := dbDir + "/restored/db.sqlite"
restoreOpt.OutputPath = restoredPath
if err := db2.Replica.Restore(ctx, restoreOpt); err != nil {
t.Fatal(err)
}
t.Log("Step 4 complete: Second restore from backup")
t.Log("Step 4 complete: Second restore from backup to path with non-existent parent")

// Step 5: Verify the new data (value=2) exists in restored database
sqldb3 := testingutil.MustOpenSQLDB(t, dbPath)
sqldb3 := testingutil.MustOpenSQLDB(t, restoredPath)
defer sqldb3.Close()

var count int
Expand Down