From d9f90e3b46fb08e624c76b6413d74036d99dd07c Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Tue, 24 Feb 2026 17:06:38 +0100 Subject: [PATCH] fix(shop): resolve ambiguous column and missing unique constraint in RecordPurchase The ON CONFLICT upsert referenced unqualified "bought" which PostgreSQL rejected as ambiguous, and the table lacked the UNIQUE constraint needed for ON CONFLICT. Adds a unique index on (character_id, shop_item_id) via migration 0003 and qualifies the column as shop_items_bought.bought. --- server/channelserver/repo_shop.go | 3 +- server/channelserver/repo_shop_test.go | 32 +++++++++++++++---- server/migrations/sql/0001_init.sql | 3 ++ .../sql/0003_shop_items_bought_unique.sql | 5 +++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 server/migrations/sql/0003_shop_items_bought_unique.sql 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);