Skip to content

Commit 3c7d0e9

Browse files
art049claude
andcommitted
Replace in-memory database with SQLite
- Add SQLite driver dependency (github.com/mattn/go-sqlite3) - Create initDB() function to initialize SQLite database with schema - Update all API handlers to use SQL queries instead of in-memory slice - Seed database with initial album data on first run - Update benchmark setup to initialize database for testing - Database file: albums.db stores persistent album data 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 769218c commit 3c7d0e9

File tree

5 files changed

+111
-18
lines changed

5 files changed

+111
-18
lines changed

api.go

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,66 @@
11
package api
22

33
import (
4+
"database/sql"
45
"net/http"
56

67
"github.com/gin-gonic/gin"
8+
_ "github.com/mattn/go-sqlite3"
79
)
810

911
// album represents data about a record album.
1012
type album struct {
11-
ID string `json:"id"`
13+
ID int64 `json:"id"`
1214
Title string `json:"title"`
1315
Artist string `json:"artist"`
1416
Price float64 `json:"price"`
1517
}
1618

17-
// albums slice to seed record album data.
18-
var albums = []album{
19-
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
20-
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
21-
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
19+
var db *sql.DB
20+
21+
func initDB() error {
22+
var err error
23+
db, err = sql.Open("sqlite3", "./albums.db")
24+
if err != nil {
25+
return err
26+
}
27+
28+
createTableSQL := `CREATE TABLE IF NOT EXISTS albums (
29+
id INTEGER PRIMARY KEY AUTOINCREMENT,
30+
title TEXT NOT NULL,
31+
artist TEXT NOT NULL,
32+
price REAL NOT NULL
33+
);`
34+
35+
_, err = db.Exec(createTableSQL)
36+
if err != nil {
37+
return err
38+
}
39+
40+
// Check if table is empty and seed with initial data
41+
var count int
42+
err = db.QueryRow("SELECT COUNT(*) FROM albums").Scan(&count)
43+
if err != nil {
44+
return err
45+
}
46+
47+
if count == 0 {
48+
seedData := []album{
49+
{Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
50+
{Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
51+
{Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
52+
}
53+
54+
for _, a := range seedData {
55+
_, err = db.Exec("INSERT INTO albums (title, artist, price) VALUES (?, ?, ?)",
56+
a.Title, a.Artist, a.Price)
57+
if err != nil {
58+
return err
59+
}
60+
}
61+
}
62+
63+
return nil
2264
}
2365

2466
// setupRouter configures and returns the Gin router with all routes
@@ -32,12 +74,35 @@ func setupRouter() *gin.Engine {
3274
}
3375

3476
func main() {
77+
if err := initDB(); err != nil {
78+
panic(err)
79+
}
80+
defer db.Close()
81+
3582
router := setupRouter()
3683
router.Run("localhost:8080")
3784
}
3885

3986
// getAlbums responds with the list of all albums as JSON.
4087
func getAlbums(c *gin.Context) {
88+
rows, err := db.Query("SELECT id, title, artist, price FROM albums")
89+
if err != nil {
90+
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
91+
return
92+
}
93+
defer rows.Close()
94+
95+
var albums []album
96+
for rows.Next() {
97+
var a album
98+
err := rows.Scan(&a.ID, &a.Title, &a.Artist, &a.Price)
99+
if err != nil {
100+
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
101+
return
102+
}
103+
albums = append(albums, a)
104+
}
105+
41106
c.IndentedJSON(http.StatusOK, albums)
42107
}
43108

@@ -51,8 +116,22 @@ func postAlbums(c *gin.Context) {
51116
return
52117
}
53118

54-
// Add the new album to the slice.
55-
albums = append(albums, newAlbum)
119+
// Insert the new album into the database and get the generated ID.
120+
result, err := db.Exec("INSERT INTO albums (title, artist, price) VALUES (?, ?, ?)",
121+
newAlbum.Title, newAlbum.Artist, newAlbum.Price)
122+
if err != nil {
123+
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
124+
return
125+
}
126+
127+
// Get the auto-generated ID
128+
id, err := result.LastInsertId()
129+
if err != nil {
130+
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
131+
return
132+
}
133+
newAlbum.ID = id
134+
56135
c.IndentedJSON(http.StatusCreated, newAlbum)
57136
}
58137

@@ -61,13 +140,17 @@ func postAlbums(c *gin.Context) {
61140
func getAlbumByID(c *gin.Context) {
62141
id := c.Param("id")
63142

64-
// Loop through the list of albums, looking for
65-
// an album whose ID value matches the parameter.
66-
for _, a := range albums {
67-
if a.ID == id {
68-
c.IndentedJSON(http.StatusOK, a)
69-
return
70-
}
143+
var a album
144+
err := db.QueryRow("SELECT id, title, artist, price FROM albums WHERE id = ?", id).
145+
Scan(&a.ID, &a.Title, &a.Artist, &a.Price)
146+
147+
if err == sql.ErrNoRows {
148+
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
149+
return
150+
} else if err != nil {
151+
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
152+
return
71153
}
72-
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
154+
155+
c.IndentedJSON(http.StatusOK, a)
73156
}

api_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ func BenchmarkGetAlbumByIDNotFound(b *testing.B) {
2525

2626
func BenchmarkPostAlbumsValid(b *testing.B) {
2727
newAlbum := album{
28-
ID: "4",
2928
Title: "Kind of Blue",
3029
Artist: "Miles Davis",
3130
Price: 29.99,
@@ -37,7 +36,7 @@ func BenchmarkPostAlbumsValid(b *testing.B) {
3736
}
3837

3938
func BenchmarkPostAlbumsInvalidJSON(b *testing.B) {
40-
invalidJSON := `{"id": "5", "title": "Invalid Album", "artist": "Test Artist", "price": "invalid_price"}`
39+
invalidJSON := `{"title": "Invalid Album", "artist": "Test Artist", "price": "invalid_price"}`
4140
req, _ := http.NewRequest("POST", "/albums", strings.NewReader(invalidJSON))
4241
req.Header.Set("Content-Type", "application/json")
4342
benchmarkRequest(b, req)

benchmark_utils.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func setupBenchmarkRouter() *gin.Engine {
3131
gin.SetMode(gin.ReleaseMode)
3232
// Discard all output during benchmarks to only preserve benchmark output
3333
gin.DefaultWriter = io.Discard
34+
35+
// Initialize database if not already initialized
36+
if db == nil {
37+
if err := initDB(); err != nil {
38+
panic(err)
39+
}
40+
}
41+
3442
return setupRouter()
3543
}
3644

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
2424
github.com/leodido/go-urn v1.4.0 // indirect
2525
github.com/mattn/go-isatty v0.0.20 // indirect
26+
github.com/mattn/go-sqlite3 v1.14.32 // indirect
2627
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2728
github.com/modern-go/reflect2 v1.0.2 // indirect
2829
github.com/pelletier/go-toml/v2 v2.2.2 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
3838
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
3939
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
4040
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
41+
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
42+
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
4143
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
4244
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
4345
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

0 commit comments

Comments
 (0)