NewSQLiteSessionStore creates a new SQLite session store backed by a file at path. If migrations fail (other than a version mismatch or a filesystem open failure) the existing database is moved aside to .bak and a fresh one is created.
(ctx context.Context, path string)
| 462 | // open failure) the existing database is moved aside to <path>.bak and a |
| 463 | // fresh one is created. |
| 464 | func NewSQLiteSessionStore(ctx context.Context, path string) (Store, error) { |
| 465 | store, err := openAndMigrateSQLiteStore(ctx, path) |
| 466 | if err != nil { |
| 467 | // Don't attempt recovery for version mismatch - the user needs to upgrade, |
| 468 | // not silently lose their data by starting fresh. |
| 469 | if errors.Is(err, ErrNewerDatabase) { |
| 470 | return nil, err |
| 471 | } |
| 472 | |
| 473 | // Don't attempt recovery if we couldn't even open/create the database file |
| 474 | // (e.g., permission denied, read-only filesystem, missing directory). |
| 475 | // The backup+retry dance can't fix a filesystem-level problem, and would just |
| 476 | // wrap the real error in a confusing "migration failed even after database reset" |
| 477 | // message. |
| 478 | if sqliteutil.IsCantOpenError(err) { |
| 479 | return nil, err |
| 480 | } |
| 481 | |
| 482 | // If migrations failed, try to recover by backing up the database and starting fresh |
| 483 | slog.WarnContext(ctx, "Failed to open session store, attempting recovery", "error", err) |
| 484 | |
| 485 | backupErr := backupDatabase(path) |
| 486 | if backupErr != nil { |
| 487 | // Return the original error if backup failed |
| 488 | slog.ErrorContext(ctx, "Failed to backup database for recovery", "error", backupErr) |
| 489 | return nil, fmt.Errorf("migration failed: %w (backup also failed: %w)", err, backupErr) |
| 490 | } |
| 491 | |
| 492 | // Try again with a fresh database |
| 493 | store, err = openAndMigrateSQLiteStore(ctx, path) |
| 494 | if err != nil { |
| 495 | return nil, fmt.Errorf("migration failed even after database reset: %w", err) |
| 496 | } |
| 497 | |
| 498 | slog.InfoContext(ctx, "Successfully recovered session store with fresh database") |
| 499 | } |
| 500 | |
| 501 | return store, nil |
| 502 | } |
| 503 | |
| 504 | // NewSQLiteSessionStoreFromDB wraps an already-open *sql.DB in a session store, |
| 505 | // running the bootstrap schema and migrations against it. The caller retains |