fix(examples): use DSN params for PRAGMAs in library examples (#938)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cory LaNou
2025-12-30 08:19:49 -06:00
committed by GitHub
parent 0ba567d083
commit e8653d70b6
3 changed files with 31 additions and 36 deletions

View File

@@ -9,11 +9,28 @@ The Litestream library API is not considered stable and may change between
versions. The CLI interface is more stable for production use. Use the library
API at your own risk, and pin to specific versions.
Note (macOS): macOS uses per-process locks for SQLite, not per-handle locks.
If you open the same database with two different SQLite driver implementations
in the same process and close one of them, you can hit locking issues. Prefer
using the same driver for your app and Litestream (these examples use
`modernc.org/sqlite`).
**Note (POSIX platforms):** All POSIX platforms (Linux, macOS, BSD, etc.) use
per-process locks for SQLite, not per-handle locks. If you open the same
database with two different SQLite driver implementations in the same process
and close one of them, you can hit locking issues. You **must** use
`modernc.org/sqlite` for your app since Litestream uses it internally.
## Important Constraints
When using Litestream as a library, be aware of these critical requirements:
1. **Required Driver**: You must use `modernc.org/sqlite`. Litestream uses this
driver internally, and mixing drivers causes lock conflicts on POSIX systems.
2. **Lifecycle Management**: You cannot call `litestream.DB.Close()` or
`Replica.Stop(true)` while your application still has open database
connections. Either close all your app's database connections first, or only
close Litestream when your process is shutting down.
3. **PRAGMA Configuration**: Use DSN parameters (e.g.,
`?_pragma=busy_timeout(5000)`) instead of `PRAGMA` statements via
`ExecContext`. An `sql.DB` is a connection pool, and `ExecContext` only
applies the PRAGMA to one random connection from the pool.
## Examples
@@ -94,10 +111,10 @@ if err := store.Open(ctx); err != nil { ... }
defer store.Close(context.Background())
// 7. Open your app's SQLite connection for normal database operations
sqlDB, err := sql.Open("sqlite", "/path/to/db.sqlite")
// Use DSN params for PRAGMAs to ensure they apply to all connections in the pool
dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(wal)", "/path/to/db.sqlite")
sqlDB, err := sql.Open("sqlite", dsn)
if err != nil { ... }
if _, err := sqlDB.ExecContext(ctx, `PRAGMA journal_mode = wal;`); err != nil { ... }
if _, err := sqlDB.ExecContext(ctx, `PRAGMA busy_timeout = 5000;`); err != nil { ... }
```
## Restore Pattern

View File

@@ -98,20 +98,9 @@ func run(ctx context.Context) error {
}
}
func openAppDB(ctx context.Context, path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, err
}
if _, err := db.ExecContext(ctx, `PRAGMA journal_mode = wal;`); err != nil {
_ = db.Close()
return nil, err
}
if _, err := db.ExecContext(ctx, `PRAGMA busy_timeout = 5000;`); err != nil {
_ = db.Close()
return nil, err
}
return db, nil
func openAppDB(_ context.Context, path string) (*sql.DB, error) {
dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(wal)", path)
return sql.Open("sqlite", dsn)
}
func initSchema(ctx context.Context, db *sql.DB) error {

View File

@@ -168,20 +168,9 @@ func restoreIfNotExists(ctx context.Context, client *s3.ReplicaClient, dbPath st
return nil
}
func openAppDB(ctx context.Context, path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, err
}
if _, err := db.ExecContext(ctx, `PRAGMA journal_mode = wal;`); err != nil {
_ = db.Close()
return nil, err
}
if _, err := db.ExecContext(ctx, `PRAGMA busy_timeout = 5000;`); err != nil {
_ = db.Close()
return nil, err
}
return db, nil
func openAppDB(_ context.Context, path string) (*sql.DB, error) {
dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(wal)", path)
return sql.Open("sqlite", dsn)
}
func initSchema(ctx context.Context, db *sql.DB) error {