diff --git a/server/channelserver/repo_shop.go b/server/channelserver/repo_shop.go index fd32c8a43..04f1c18f1 100644 --- a/server/channelserver/repo_shop.go +++ b/server/channelserver/repo_shop.go @@ -28,8 +28,7 @@ func (r *ShopRepository) GetShopItems(shopType uint8, shopID uint32, charID uint func (r *ShopRepository) RecordPurchase(charID, shopItemID, quantity uint32) error { _, err := r.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought) VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id) - DO UPDATE SET bought = bought + $3 - WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2 + DO UPDATE SET bought = shop_items_bought.bought + $3 `, charID, shopItemID, quantity) return err } diff --git a/server/channelserver/repo_shop_test.go b/server/channelserver/repo_shop_test.go index 03a882c93..9a84b1070 100644 --- a/server/channelserver/repo_shop_test.go +++ b/server/channelserver/repo_shop_test.go @@ -57,14 +57,32 @@ func TestRepoShopGetShopItems(t *testing.T) { } } -func TestRepoShopRecordPurchaseAmbiguousColumn(t *testing.T) { - repo, _, charID := setupShopRepo(t) +func TestRepoShopRecordPurchaseInsertAndUpdate(t *testing.T) { + repo, db, charID := setupShopRepo(t) - // RecordPurchase uses ON CONFLICT with unqualified "bought" column reference, - // which PostgreSQL rejects as ambiguous. This test documents the existing bug. - err := repo.RecordPurchase(charID, 1, 3) - if err == nil { - t.Fatal("Expected error from ambiguous column reference in RecordPurchase SQL, but got nil") + // First purchase inserts a new row + if err := repo.RecordPurchase(charID, 1, 3); err != nil { + t.Fatalf("RecordPurchase (insert) failed: %v", err) + } + + var bought int + if err := db.QueryRow("SELECT bought FROM shop_items_bought WHERE character_id=$1 AND shop_item_id=$2", charID, 1).Scan(&bought); err != nil { + t.Fatalf("Verification query failed: %v", err) + } + if bought != 3 { + t.Errorf("Expected bought=3, got: %d", bought) + } + + // Second purchase updates (adds to) the existing row + if err := repo.RecordPurchase(charID, 1, 2); err != nil { + t.Fatalf("RecordPurchase (update) failed: %v", err) + } + + if err := db.QueryRow("SELECT bought FROM shop_items_bought WHERE character_id=$1 AND shop_item_id=$2", charID, 1).Scan(&bought); err != nil { + t.Fatalf("Verification query failed: %v", err) + } + if bought != 5 { + t.Errorf("Expected bought=5 (3+2), got: %d", bought) } } diff --git a/server/migrations/sql/0001_init.sql b/server/migrations/sql/0001_init.sql index eec83adef..c5ac2b225 100644 --- a/server/migrations/sql/0001_init.sql +++ b/server/migrations/sql/0001_init.sql @@ -1220,6 +1220,9 @@ CREATE TABLE public.shop_items_bought ( bought integer ); +CREATE UNIQUE INDEX IF NOT EXISTS shop_items_bought_character_item_unique + ON public.shop_items_bought (character_id, shop_item_id); + -- -- Name: shop_items_id_seq; Type: SEQUENCE; Schema: public; Owner: - diff --git a/server/migrations/sql/0003_shop_items_bought_unique.sql b/server/migrations/sql/0003_shop_items_bought_unique.sql new file mode 100644 index 000000000..52f0fb6c4 --- /dev/null +++ b/server/migrations/sql/0003_shop_items_bought_unique.sql @@ -0,0 +1,5 @@ +-- Add unique constraint required for ON CONFLICT upsert in RecordPurchase. +-- Uses CREATE UNIQUE INDEX which supports IF NOT EXISTS, avoiding errors +-- when the baseline schema (0001) already includes the constraint. +CREATE UNIQUE INDEX IF NOT EXISTS shop_items_bought_character_item_unique + ON public.shop_items_bought (character_id, shop_item_id);