From 575a7aa4170a0584ba34daf102c267fe14b64e97 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Wed, 20 May 2026 17:02:24 +0200 Subject: [PATCH] feat: add hot path indexes for watch list and cache queries --- internal/database/database_test.go | 40 +++++++++++++++++++ .../migrations/021_add_hot_path_indexes.sql | 22 ++++++++++ 2 files changed, 62 insertions(+) create mode 100644 internal/database/database_test.go create mode 100644 internal/database/migrations/021_add_hot_path_indexes.sql diff --git a/internal/database/database_test.go b/internal/database/database_test.go new file mode 100644 index 0000000..03b7c96 --- /dev/null +++ b/internal/database/database_test.go @@ -0,0 +1,40 @@ +package database + +import ( + "database/sql" + "testing" + + _ "github.com/mattn/go-sqlite3" +) + +func TestRunMigrationsCreatesHotPathIndexes(t *testing.T) { + sqlDB, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatalf("open sqlite: %v", err) + } + defer sqlDB.Close() + sqlDB.SetMaxOpenConns(1) + + if err := RunMigrations(sqlDB); err != nil { + t.Fatalf("RunMigrations: %v", err) + } + + for _, indexName := range []string{ + "idx_watch_list_entry_user_updated_at", + "idx_watch_list_entry_user_status_updated_at_desc", + "idx_watch_list_entry_status_updated_at_anime_id", + "idx_continue_watching_anime_id", + "idx_jikan_cache_expires_at_datetime", + } { + t.Run(indexName, func(t *testing.T) { + var count int + err := sqlDB.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = ?`, indexName).Scan(&count) + if err != nil { + t.Fatalf("query index: %v", err) + } + if count != 1 { + t.Fatalf("index %s count = %d, want 1", indexName, count) + } + }) + } +} diff --git a/internal/database/migrations/021_add_hot_path_indexes.sql b/internal/database/migrations/021_add_hot_path_indexes.sql new file mode 100644 index 0000000..8010b09 --- /dev/null +++ b/internal/database/migrations/021_add_hot_path_indexes.sql @@ -0,0 +1,22 @@ +-- +goose Up +CREATE INDEX IF NOT EXISTS idx_watch_list_entry_user_updated_at +ON watch_list_entry(user_id, updated_at DESC); + +CREATE INDEX IF NOT EXISTS idx_watch_list_entry_user_status_updated_at_desc +ON watch_list_entry(user_id, status, updated_at DESC); + +CREATE INDEX IF NOT EXISTS idx_watch_list_entry_status_updated_at_anime_id +ON watch_list_entry(status, updated_at DESC, anime_id); + +CREATE INDEX IF NOT EXISTS idx_continue_watching_anime_id +ON continue_watching_entry(anime_id); + +CREATE INDEX IF NOT EXISTS idx_jikan_cache_expires_at_datetime +ON jikan_cache(datetime(expires_at)); + +-- +goose Down +DROP INDEX IF EXISTS idx_jikan_cache_expires_at_datetime; +DROP INDEX IF EXISTS idx_continue_watching_anime_id; +DROP INDEX IF EXISTS idx_watch_list_entry_status_updated_at_anime_id; +DROP INDEX IF EXISTS idx_watch_list_entry_user_status_updated_at_desc; +DROP INDEX IF EXISTS idx_watch_list_entry_user_updated_at;