Add SQLite database implementation plan
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
# SQLite Database Support Implementation Plan
|
||||||
|
|
||||||
|
**Goal:** Add SQLite database support using `sqlite-simple`, opening a database file on startup with WAL mode and foreign keys enabled.
|
||||||
|
|
||||||
|
**Architecture:** A single new module `Sis.Database` provides `openDatabase`, which opens/creates a SQLite file, enables WAL journal mode, and enables foreign keys. The server opens the DB on startup via a `--db-path` CLI option. No tables or queries yet.
|
||||||
|
|
||||||
|
**Tech Stack:** Haskell, sqlite-simple, sqlite-simple isn't yet in the project, optparse-applicative
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add sqlite-simple dependency
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `package.yaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add sqlite-simple to library dependencies**
|
||||||
|
|
||||||
|
Add `sqlite-simple` to the `library` → `dependencies` section of `package.yaml`, in alphabetical order. The dependency block currently ends with `- orb`. Add before that line:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- sqlite-simple
|
||||||
|
```
|
||||||
|
|
||||||
|
The relevant section should read:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
library:
|
||||||
|
source-dirs: src
|
||||||
|
dependencies:
|
||||||
|
- orb
|
||||||
|
- sqlite-simple
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Regenerate cabal file**
|
||||||
|
|
||||||
|
Run: `./hs hpack`
|
||||||
|
Expected: exits 0, `sis-server.cabal` is regenerated with `sqlite-simple` in the library build-depends.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify it builds**
|
||||||
|
|
||||||
|
Run: `./hs stack build`
|
||||||
|
Expected: successful build (though `sqlite-simple` may need to be fetched/built). If the build fails with "could not find module", the dependency may need to be in `extra-deps` in `stack.yaml`. If so, add it there.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add package.yaml sis-server.cabal
|
||||||
|
git commit -m "deps: add sqlite-simple"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Create Sis.Database module
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/Sis/Database.hs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the module**
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
{- | SQLite database support for Sis.
|
||||||
|
|
||||||
|
Opens (and creates if missing) a SQLite database with WAL journal
|
||||||
|
mode and foreign keys enabled.
|
||||||
|
-}
|
||||||
|
module Sis.Database (
|
||||||
|
openDatabase,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Database.SQLite.Simple qualified as SQL
|
||||||
|
|
||||||
|
-- | Open (or create) a SQLite database at the given path.
|
||||||
|
--
|
||||||
|
-- Enables WAL journal mode for concurrent read performance and
|
||||||
|
-- enables foreign key enforcement.
|
||||||
|
openDatabase :: FilePath -> IO SQL.Connection
|
||||||
|
openDatabase path = do
|
||||||
|
conn <- SQL.open path
|
||||||
|
SQL.execute_ conn "PRAGMA journal_mode=WAL"
|
||||||
|
SQL.execute_ conn "PRAGMA foreign_keys=ON"
|
||||||
|
pure conn
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build to verify compilation**
|
||||||
|
|
||||||
|
Run: `./hs stack build`
|
||||||
|
Expected: builds successfully.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/Sis/Database.hs
|
||||||
|
git commit -m "feat: add Sis.Database module with openDatabase"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Wire --db-path CLI option and open database on startup
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/Main.hs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add import and --db-path option**
|
||||||
|
|
||||||
|
In `app/Main.hs`, add the import:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
import Sis.Database qualified as Sis
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `Options` record, add a new field:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
data Options = Options
|
||||||
|
{ optPort :: Int
|
||||||
|
, optStaticDir :: FilePath
|
||||||
|
, optDbPath :: FilePath
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In `optionsParser`, add the `optDbPath` parser after `optStaticDir`:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
<*> Opt.strOption
|
||||||
|
( Opt.long "db-path"
|
||||||
|
<> Opt.metavar "PATH"
|
||||||
|
<> Opt.help "Path to the SQLite database file"
|
||||||
|
<> Opt.value "data/sis.db"
|
||||||
|
<> Opt.showDefault
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Open database in main**
|
||||||
|
|
||||||
|
In `main`, after `opts <- ...` and before `let waiApp`, add:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
_db <- Sis.openDatabase (optDbPath opts)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the `_db` prefix suppresses the unused-binding warning. The connection is opened but not yet used — that comes in a later task when tables and routes are added.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build and verify compilation**
|
||||||
|
|
||||||
|
Run: `./hs stack build`
|
||||||
|
Expected: builds successfully with no warnings (all `-Werror`).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/Main.hs
|
||||||
|
git commit -m "feat: add --db-path CLI option and open SQLite db on startup"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Integration smoke test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- No new files
|
||||||
|
|
||||||
|
- [ ] **Step 1: Start the server and verify database creation**
|
||||||
|
|
||||||
|
Run: `./scripts/run`
|
||||||
|
|
||||||
|
Expected output includes `[sis] listening on port 8080`.
|
||||||
|
|
||||||
|
In another terminal, verify the database file was created:
|
||||||
|
|
||||||
|
Run: `ls -la data/sis.db`
|
||||||
|
Expected: file exists.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Shut down and restart — verify no error on existing DB**
|
||||||
|
|
||||||
|
Stop the server (Ctrl+C), then restart:
|
||||||
|
|
||||||
|
Run: `./scripts/run`
|
||||||
|
|
||||||
|
Expected: starts successfully, no errors. The existing `data/sis.db` is re-opened without issue.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Stop the server, clean up, and commit**
|
||||||
|
|
||||||
|
Stop the server.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm data/sis.db
|
||||||
|
git add -A && git status # just to confirm no lingering changes
|
||||||
|
git commit -m "test: smoke test SQLite startup, DB file creation, and re-open" --allow-empty
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the `--allow-empty` flag is used since this task is a manual verification step with no code changes. If you prefer to skip committing manual verification, you can omit this commit.
|
||||||
Reference in New Issue
Block a user