-- docs/tag-ddl.sql -- W3-1 태그 + 검색 확장. 멱등(IF NOT EXISTS / DO $$ guard). search_path=dev. -- 1) 통합 태그 마스터 CREATE TABLE IF NOT EXISTS tags ( id BIGSERIAL PRIMARY KEY, name VARCHAR(20) NOT NULL, slug VARCHAR(40) NOT NULL, tag_type VARCHAR(10) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT FALSE, created_by BIGINT NULL, created_at TIMESTAMP NOT NULL DEFAULT now() ); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_tags_name_len') THEN ALTER TABLE tags ADD CONSTRAINT chk_tags_name_len CHECK (char_length(name) BETWEEN 2 AND 20); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chk_tags_type') THEN ALTER TABLE tags ADD CONSTRAINT chk_tags_type CHECK (tag_type IN ('GAME', 'JAM', 'COMMON')); END IF; END $$; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_tags_created_by') THEN ALTER TABLE tags ADD CONSTRAINT fk_tags_created_by FOREIGN KEY (created_by) REFERENCES users(id); END IF; END $$; CREATE UNIQUE INDEX IF NOT EXISTS uq_tags_slug ON tags (slug); CREATE UNIQUE INDEX IF NOT EXISTS uq_tags_name ON tags (name); CREATE INDEX IF NOT EXISTS idx_tags_type_active ON tags (tag_type, is_active); -- 2) 게임-태그 조인 (N:M) CREATE TABLE IF NOT EXISTS game_tags ( id BIGSERIAL PRIMARY KEY, game_id BIGINT NOT NULL, tag_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX IF NOT EXISTS uq_game_tags ON game_tags (game_id, tag_id); CREATE INDEX IF NOT EXISTS idx_game_tags_tag ON game_tags (tag_id); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_game_tags_game') THEN ALTER TABLE game_tags ADD CONSTRAINT fk_game_tags_game FOREIGN KEY (game_id) REFERENCES games(id); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_game_tags_tag') THEN ALTER TABLE game_tags ADD CONSTRAINT fk_game_tags_tag FOREIGN KEY (tag_id) REFERENCES tags(id); END IF; END $$; -- 3) 잼-태그 조인 (N:M, jams 는 W2-1 소관) CREATE TABLE IF NOT EXISTS jam_tags ( id BIGSERIAL PRIMARY KEY, jam_id BIGINT NOT NULL, tag_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX IF NOT EXISTS uq_jam_tags ON jam_tags (jam_id, tag_id); CREATE INDEX IF NOT EXISTS idx_jam_tags_tag ON jam_tags (tag_id); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_jam_tags_jam') THEN ALTER TABLE jam_tags ADD CONSTRAINT fk_jam_tags_jam FOREIGN KEY (jam_id) REFERENCES jams(id); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_jam_tags_tag') THEN ALTER TABLE jam_tags ADD CONSTRAINT fk_jam_tags_tag FOREIGN KEY (tag_id) REFERENCES tags(id); END IF; END $$; -- 4) 방문 로그 (dedupe 가능) CREATE TABLE IF NOT EXISTS game_views ( id BIGSERIAL PRIMARY KEY, game_id BIGINT NOT NULL, viewer_key VARCHAR(200) NOT NULL, viewed_at TIMESTAMP NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_game_views_game ON game_views (game_id); CREATE INDEX IF NOT EXISTS idx_game_views_dedupe ON game_views (game_id, viewer_key, viewed_at); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_game_views_game') THEN ALTER TABLE game_views ADD CONSTRAINT fk_game_views_game FOREIGN KEY (game_id) REFERENCES games(id); END IF; END $$; -- 5) games 비정규화 방문수 카운터 ALTER TABLE games ADD COLUMN IF NOT EXISTS view_count INTEGER NOT NULL DEFAULT 0; CREATE INDEX IF NOT EXISTS idx_games_view_count ON games (view_count);