o2o-site-AEO/postgres-init/migrations/0005_flatten_schemas.sql
Mina Choi 64ce467f21 [refactor] postgres-init,solution: DB 구조 재편 — 스키마 해체 · 공용 콘텐츠 한 벌 · 마이그레이션 체계
도메인별 스키마(company·place·fact·local·site·job)를 걷어내고 public 한 벌로 폈다.
스키마 한정자가 붙은 순간부터 ORM·raw SQL·테스트 픽스처가 각자 그 이름을 들고 다녀야 했다.

- 공용 콘텐츠를 한 테이블로 되돌린다. spots·region_stories 를 따로 파 놓고 보니
  같은 성격이 세 곳으로 갈라져 있었다 — `area_contents` 가 처음부터 content_type 으로
  종류를 가르는 설계였고 그걸 쓰면 됐다. 관계(거리·숨김)만 `place_area_refs` 로 남긴다.
- migrations/ + scripts/migrate.py: `init.sql` 은 **DB 를 처음 만들 때만** 돈다. 파일에
  컬럼을 더해도 이미 데이터가 든 DB 에는 반영되지 않는다 — 실제로 TourAPI 가 주변 정보를
  받아 와도 저장할 곳이 없어 축제·맛집이 0건이었고, 화면에는 "그냥 안 나오는 것" 으로만 보였다.
  DECISIONS.md 가 예고한 그대로다("운영 DB 가 생기는 순간 다시 필요해진다").
  Alembic 을 쓰지 않는 이유는 스키마 정의가 이미 두 곳(ORM·init.sql)이라 세 번째를
  더하면 어긋날 자리가 하나 더 생기기 때문이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:08:02 +09:00

86 lines
6.3 KiB
SQL

-- 0005 · 스키마를 해체하고 이름으로 소속을 말한다
--
-- ★ 왜 (2026-09-09)
-- 스키마를 나누는 이유는 셋뿐이다 — ① 같은 이름을 여러 번 쓰려고 ② 권한을 덩어리로 주려고
-- ③ 읽는 사람에게 경계를 보여주려고. 이 프로젝트에선 ①②가 성립하지 않는다:
-- · `web4ai_db` 는 이 제품 전용 database 다. 다른 프로젝트는 다른 database 에 있고,
-- 19개 테이블 이름이 전부 다르다 — 충돌할 상대가 애초에 없다.
-- · 접속 계정이 하나다. 스키마별 GRANT 를 준 적이 없고, 실제 권한 분리는 DB 밖에서
-- 포트로 한다(솔루션 9800 / 어드민 9801, ADMIN_API_BIND=127.0.0.1).
-- ③만 남는데, 그 값을 치르는 방식이 틀렸다:
-- · 19개 중 9개가 스키마 이름을 다시 말한다(place.places · fact.facts · site.sites ·
-- local.local_contents · job.jobs …). 폴더 이름이 파일 이름에 이미 들어 있으면
-- 그 폴더는 정보를 더하지 못한다.
-- · **결정적으로, 경계가 조인 방향과 반대다.** `places.place_id` 를 참조하는 테이블이
-- 11개인데 그중 `place` 스키마 안에 있는 건 **하나도 없다**. 관계를 잇는 컬럼이
-- 매번 경계를 넘는다면 그건 경계가 아니다. 특히 업장↔지역 관계 테이블은
-- 존재 자체가 두 스키마를 잇는 것이라 어디에 둬도 틀린다.
--
-- 그래서 스키마를 없애고 **접두어가 소속을 말한다.**
-- place_* 한 업장의 것 · site_* 한 사이트의 것 · users/jobs 아무에게도 안 속함
-- area_contents 만 접두어가 없다 — **그게 공유물이라는 표시다.**
-- 이 규칙이 서면 오늘 같은 복제 사고는 이름만 봐도 걸린다(place_ 붙은 테이블에 축제가
-- 들어가 있으면 그 자체로 틀린 것이다).
--
-- ★ 데이터는 옮기지 않는다. RENAME 뿐이라 잃는 행이 없다.
-- ── 사람 · 큐 (아무에게도 안 속한다) ──────────────────────────────────────
ALTER TABLE IF EXISTS company.users SET SCHEMA public;
ALTER TABLE IF EXISTS job.jobs SET SCHEMA public;
-- ── 업장 ────────────────────────────────────────────────────────────────
ALTER TABLE IF EXISTS place.places SET SCHEMA public;
ALTER TABLE IF EXISTS place.place_links SET SCHEMA public;
ALTER TABLE IF EXISTS place.units SET SCHEMA public;
ALTER TABLE IF EXISTS place.media SET SCHEMA public;
ALTER TABLE IF EXISTS fact.facts SET SCHEMA public;
ALTER TABLE IF EXISTS fact.faqs SET SCHEMA public;
-- 이름이 무엇에 속하는지, 그리고 무엇을 담는지 말하게 한다.
ALTER TABLE IF EXISTS public.place_links RENAME TO place_channels; -- 네이버·야놀자 등 채널
ALTER TABLE IF EXISTS public.units RENAME TO place_units; -- 객실·메뉴·프로그램
ALTER TABLE IF EXISTS public.media RENAME TO place_photos; -- 실제로 사진만 담는다
ALTER TABLE IF EXISTS public.facts RENAME TO place_facts;
ALTER TABLE IF EXISTS public.faqs RENAME TO place_faqs;
-- ── 지역 (여럿이 나눠 쓴다) ──────────────────────────────────────────────
ALTER TABLE IF EXISTS local.local_contents SET SCHEMA public;
ALTER TABLE IF EXISTS local.place_contents SET SCHEMA public;
-- 접두어 없는 유일한 테이블. 공유물이라는 표시다.
ALTER TABLE IF EXISTS public.local_contents RENAME TO area_contents;
-- 값이 아니라 **관계**다 — 이름이 그걸 말해야 한다.
ALTER TABLE IF EXISTS public.place_contents RENAME TO place_area_refs;
-- ── 사이트 ──────────────────────────────────────────────────────────────
ALTER TABLE IF EXISTS site.sites SET SCHEMA public;
ALTER TABLE IF EXISTS site.site_contents SET SCHEMA public;
ALTER TABLE IF EXISTS site.site_versions SET SCHEMA public;
ALTER TABLE IF EXISTS site.publish_logs SET SCHEMA public;
-- 섹션의 '내용'이다. site_contents 는 무엇의 콘텐츠인지 말하지 않는다.
ALTER TABLE IF EXISTS public.site_contents RENAME TO site_sections;
ALTER TABLE IF EXISTS public.publish_logs RENAME TO site_publish_logs;
-- ── 아직 남기는 것 ──────────────────────────────────────────────────────
-- 빈 테이블(place_aliases · routes · nearby_links · ai_check_results)과 이관 잔재
-- (place_contents_legacy)는 0006 에서 판단한다. 한 파일에 두 가지 일을 넣지 않는다.
ALTER TABLE IF EXISTS place.place_aliases SET SCHEMA public;
ALTER TABLE IF EXISTS local.routes SET SCHEMA public;
ALTER TABLE IF EXISTS local.nearby_links SET SCHEMA public;
ALTER TABLE IF EXISTS site.ai_check_results SET SCHEMA public;
ALTER TABLE IF EXISTS local.place_contents_legacy SET SCHEMA public;
ALTER TABLE IF EXISTS public.routes RENAME TO place_routes;
ALTER TABLE IF EXISTS public.place_aliases RENAME TO place_names;
-- ai_check_results 는 place_id 가 키다 — 사이트가 아니라 **업장**이 AI 검색에 나오는지를
-- 재는 것이라, 사이트를 안 만들어도 잴 수 있다. site_ 가 아니라 place_ 다.
ALTER TABLE IF EXISTS public.ai_check_results RENAME TO place_ai_checks;
-- ── 빈 스키마 정리 ──────────────────────────────────────────────────────
-- ★ CASCADE 를 쓰지 않는다. 옮기다 빠뜨린 테이블이 있으면 여기서 실패해야 한다 —
-- CASCADE 면 그 테이블이 조용히 함께 지워진다.
DROP SCHEMA IF EXISTS company RESTRICT;
DROP SCHEMA IF EXISTS place RESTRICT;
DROP SCHEMA IF EXISTS fact RESTRICT;
DROP SCHEMA IF EXISTS local RESTRICT;
DROP SCHEMA IF EXISTS site RESTRICT;
DROP SCHEMA IF EXISTS job RESTRICT;