Test #465
@@ -1,42 +1,5 @@
|
||||
-- 서버 배포 전에 적용한다. KO는 임시 기본 분류이며 JA 소재와 연결 이벤트 언어는 운영 담당자가 수동 확인한다.
|
||||
SET @schema_name := DATABASE();
|
||||
|
||||
SET @lang_column_exists := (
|
||||
SELECT COUNT(1)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = @schema_name
|
||||
AND table_name = 'sub_banner'
|
||||
AND column_name = 'lang'
|
||||
);
|
||||
|
||||
SET @add_lang_column_sql := IF(
|
||||
@lang_column_exists = 0,
|
||||
'ALTER TABLE sub_banner ADD COLUMN lang VARCHAR(10) NULL COMMENT ''서브 배너 노출 언어'' AFTER is_active',
|
||||
'SELECT ''sub_banner.lang already exists'' AS message'
|
||||
);
|
||||
|
||||
PREPARE add_lang_column_stmt FROM @add_lang_column_sql;
|
||||
EXECUTE add_lang_column_stmt;
|
||||
DEALLOCATE PREPARE add_lang_column_stmt;
|
||||
|
||||
UPDATE sub_banner
|
||||
SET lang = 'KO'
|
||||
WHERE lang IS NULL;
|
||||
|
||||
SET @lang_column_nullable := (
|
||||
SELECT IS_NULLABLE
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = @schema_name
|
||||
AND table_name = 'sub_banner'
|
||||
AND column_name = 'lang'
|
||||
);
|
||||
|
||||
SET @alter_lang_column_sql := IF(
|
||||
@lang_column_nullable = 'YES',
|
||||
'ALTER TABLE sub_banner MODIFY COLUMN lang VARCHAR(10) NOT NULL DEFAULT ''KO'' COMMENT ''서브 배너 노출 언어 (KO 기본, EN/JA 등록 가능)''',
|
||||
'SELECT ''sub_banner.lang already normalized'' AS message'
|
||||
);
|
||||
|
||||
PREPARE alter_lang_column_stmt FROM @alter_lang_column_sql;
|
||||
EXECUTE alter_lang_column_stmt;
|
||||
DEALLOCATE PREPARE alter_lang_column_stmt;
|
||||
-- lang 컬럼이 없는 기존 테이블에 한 번만 실행한다. 이미 존재하면 실행하지 않는다.
|
||||
ALTER TABLE sub_banner
|
||||
ADD COLUMN lang VARCHAR(10) NOT NULL DEFAULT 'KO'
|
||||
COMMENT '서브 배너 노출 언어 (KO 기본, EN/JA 등록 가능)' AFTER is_active;
|
||||
|
||||
@@ -146,4 +146,4 @@ POST와 PUT은 `multipart/form-data`다. `image` 파트는 파일이며, `reques
|
||||
|
||||
관리자 CRUD·소프트 삭제·두 추천 API 통합을 구현하고 최종 전체 2,682개 테스트와 lint·bootJar·독립 리뷰를 통과했다. 실제 HTTP/DB 및 단계별 실행 증거는 [plan-task.md](plan-task.md)에 있다. DDL은 독립 H2에 적용해 검증했지만 native MySQL 및 운영 DB에는 적용하지 않았다. 구현·검증 완료를 운영 배포 완료로 간주하지 않는다.
|
||||
|
||||
서버 배포 전에 신규 환경은 [create-sub-banner.sql](create-sub-banner.sql), 기존 테이블은 [20261102_add_sub_banner_lang.sql](20261102_add_sub_banner_lang.sql)을 적용해 `lang VARCHAR(10) NOT NULL DEFAULT 'KO'`를 준비해야 한다. 기존 `is_active` 등 선행 스키마도 필요하다. 언어 migration은 컬럼 존재 확인 → nullable 추가 → NULL의 KO backfill → NOT NULL/default 순서다. KO는 기존 행의 언어를 추론한 결과가 아니므로 운영 담당자가 JA 소재를 수동 분류하고 연결 이벤트 언어와 일치하는지 검토해야 한다. 외부 링크는 원문 그대로이며 자동 언어 분류·링크 파서는 없다. 통합 추천 조회도 새 컬럼을 사용하므로 migration 전에 서버 코드를 배포하지 않는다. SQL의 운영 적용·native MySQL 검증·배포는 이번 작업에서 수행하지 않는다.
|
||||
서버 배포 전에 신규 환경은 [create-sub-banner.sql](create-sub-banner.sql), 기존 테이블은 [20261102_add_sub_banner_lang.sql](20261102_add_sub_banner_lang.sql)을 적용해 `lang VARCHAR(10) NOT NULL DEFAULT 'KO'`를 준비해야 한다. 기존 `is_active` 등 선행 스키마도 필요하다. 이관 파일은 동적 SQL 없는 단일 `ALTER TABLE`이며 **lang 컬럼이 없는 기존 테이블에 한 번만 실행한다. 이미 컬럼이 있거나 신규 CREATE를 적용한 DB에서는 실행하지 않는다.** 기존 행은 컬럼 추가 시 KO 기본값을 가지며 별도 UPDATE는 필요하지 않다. KO는 기존 행의 언어를 추론한 결과가 아니므로 운영 담당자가 JA 소재를 수동 분류하고 연결 이벤트 언어와 일치하는지 검토해야 한다. 외부 링크는 원문 그대로이며 자동 언어 분류·링크 파서는 없다. 통합 추천 조회도 새 컬럼을 사용하므로 migration 전에 서버 코드를 배포하지 않는다. SQL의 운영 적용·native MySQL 검증·배포는 이번 작업에서 수행하지 않는다.
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
| 문서 항목 | 내용 |
|
||||
|---|---|
|
||||
| 상태 | P7 언어 후속 구현·전체 검증·독립 리뷰 완료, 미배포 |
|
||||
| 상태 | P7 언어 기능·P8 정적 이관 SQL 검증 및 리뷰 완료, 미배포 |
|
||||
| 작성일 | 2026-10-29 |
|
||||
| 요구사항 기준 | [prd.md](prd.md) |
|
||||
| API 기준 | [api-contract.md](api-contract.md), 실제 구현·HTTP 검증 계약 |
|
||||
| 현재 Phase | Phase 2~7 완료 |
|
||||
| 현재 Phase | Phase 2~8 완료 |
|
||||
| 현재 활성 Goal | 없음 |
|
||||
| 다음 Goal | 없음 |
|
||||
|
||||
@@ -588,7 +588,7 @@ export TMPDIR=/tmp/soda-banner-impl.CfAkhD NODE_COMPILE_CACHE=/tmp/soda-banner-i
|
||||
./gradlew test ktlintCheck bootJar --continue --console=plain
|
||||
```
|
||||
|
||||
**DDL requirements:** both SQL artifacts define `lang VARCHAR(10) NOT NULL DEFAULT 'KO'` with a column comment. The migration safely adds a missing column, backfills NULL rows to KO, then enforces NOT NULL/default, following the existing project SQL pattern. Before production application, warn that KO is only a safe placeholder: operations must manually identify JA banners and verify language consistency against linked events. No event link parser or automatic classification is allowed. Production DB/DDL execution is not part of this goal.
|
||||
**DDL requirements:** both SQL artifacts define `lang VARCHAR(10) NOT NULL DEFAULT 'KO'` with a column comment. DEC-013 supersedes the original guarded migration with one static ALTER TABLE for an existing table without lang. Existing rows receive KO; do not rerun when lang exists. Before production application, operations must manually identify JA banners and verify language consistency against linked events. No event link parser or automatic classification is allowed. Production DB/DDL execution is not part of this goal.
|
||||
|
||||
### Phase 7 Gate `P7-GATE`
|
||||
|
||||
@@ -616,3 +616,32 @@ export TMPDIR=/tmp/soda-banner-impl.CfAkhD NODE_COMPILE_CACHE=/tmp/soda-banner-i
|
||||
- 원본 `/tmp/soda-banner-ulw.HDkpg4/full-verification.log`, `final-verification.log` 및 구현자 임시 증거는 작업 종료 시 정리한다. 재현 테스트 소스, 최신 build reports, 본 검증 기록과 [공동 리뷰](reviews/banner-language-review.md)를 남긴다.
|
||||
- 독립 Oracle 리뷰는 명세·코드·시나리오 APPROVE, 후속 공백 delta 및 최종 검증 Gate도 APPROVE했다. 미해결 발견 사항 없음. `P7-T1/P7-GATE` 완료이며 운영 DDL·배포는 별도다.
|
||||
- 서브 배너 구현·SQL·테스트 커밋 `c4c61e36`, hook/메시지 검사 통과. 소유 임시 자료·QA 프로세스 정리와 build 산출물 보존은 공동 리뷰의 종료 증거에 기록했다. 운영 DDL·배포·push는 하지 않았다.
|
||||
|
||||
## Phase 8: 정적 이관 SQL
|
||||
|
||||
### Task 8.1 단일 ALTER TABLE 전환 (`P8-T1`)
|
||||
|
||||
- Objective: 저장 프로시저·동적 SQL 없이 기존 lang 미존재 테이블에 KO 기본 언어 컬럼을 추가한다.
|
||||
- 시작 조건: DEC-013 사용자 요청, 기존 SQL 및 MySQL ALTER 문법 확인.
|
||||
- Files: `docs/20261029_가로형서브배너/20261102_add_sub_banner_lang.sql`, `prd.md`, `api-contract.md`, `plan-task.md`.
|
||||
- 범위 밖: 앱 코드·신규 CREATE 변경, 이미 존재하는 컬럼 정정, 자동 재실행, 운영 DB 접속·실행.
|
||||
- TDD 예외: 애플리케이션 로직 변경 없이 배포 SQL 표현을 단순화한다. H2에서 기존 MySQL PREPARE가 실패하는 것을 기능 RED로 가장하지 않는다.
|
||||
- 대체 검증: 실제 SQL 파일을 독립 H2 MySQL 모드에 적용하는 임시 JDBC assertion으로 기존 행 보존/KO 초기값, 새 행 기본값, JA/EN 보존, NULL 거부, 재실행 실패 후 값 불변을 확인한다. native MySQL 실행 증거와 구분한다.
|
||||
- [x] 단일 정적 SQL 및 실행 전제/재실행 제한을 관련 계약에 반영했다.
|
||||
- [x] 독립 DB 확인, 문서 대조와 `git diff --check`를 수행했다.
|
||||
|
||||
### Phase 8 Gate (`P8-GATE`)
|
||||
|
||||
- 완료 증거: S1 기존 행/컬럼, S2 신규 기본값·명시 언어·NULL 거부, S3 재실행 실패·값 보존이 통과하고 scoped 리뷰가 승인한다.
|
||||
- [x] 실제 SQL 검증·독립 리뷰 APPROVE를 기록하고 소유 임시 자원을 정리했다. 애플리케이션 변경이 없어 Gradle 전체 suite는 반복하지 않았다.
|
||||
|
||||
### Phase 8 검증 기록 — 2026-11-03
|
||||
|
||||
- MySQL 8.0 공식 [ALTER TABLE 문법](https://dev.mysql.com/doc/refman/8.0/en/alter-table.html)의 정적 `ADD COLUMN column_definition [AFTER col_name]`를 확인했다. 직접 fetch는 403으로 실패해 검색 결과의 공식 문법을 확인했다. MySQL에 MariaDB의 `ADD COLUMN IF NOT EXISTS` 문법을 가정하지 않았다.
|
||||
- 실제 변경한 SQL 파일을 캐시된 H2 2.1.214의 MySQL 모드에서 임시 Java/JDBC driver로 실행했다. 명령: `java --class-path /Users/klaus/.gradle/caches/modules-2/files-2.1/com.h2database/h2/2.1.214/d5c2005c9e3279201e12d4776c948578b16bf8b2/h2-2.1.214.jar /tmp/soda-banner-sql.CIhdCg/VerifyMigration.java docs/20261029_가로형서브배너/20261102_add_sub_banner_lang.sql`. exit 0, 모든 assertion PASS.
|
||||
- S1: 기존 활성·비활성 두 행의 ID/이미지/활성값 보존, 두 행의 새 lang=KO, VARCHAR(10)/NOT NULL 확인.
|
||||
- S2: 새 행의 언어 생략 KO, 명시적 JA/EN 저장 유지, 명시적 NULL 거부(SQLState 23502) 확인.
|
||||
- S3: 같은 SQL 재실행은 duplicate column(SQLState 42S21)으로 실패하고 JA/EN/KO 값이 바뀌지 않음을 확인했다. 이는 재실행 허용이 아니라 한 번만 실행해야 한다는 계약 검증이다.
|
||||
- Native MySQL·운영 DB에는 실행하지 않았으며, H2 결과를 MySQL 실행 증거로 표현하지 않는다. 앱 소스·CREATE SQL 변경이 없어 Gradle 테스트/빌드/린트는 반복하지 않았다. 임시 driver는 결과 기록 후 정리한다.
|
||||
- 독립 scoped 리뷰 `ses_e630f286affevZ6Rc77rxuL3Dn`: APPROVE, 문서화된 1회 실행 전제 아래 데이터 손실 회귀·차단 사항 없음. 기존 Phase 7의 동적 SQL 검토 기록은 당시 이력이며 현재 계약은 DEC-013/P8이다.
|
||||
- Teardown: Java 검증 프로세스 잔존 없음 확인, 소유 `/tmp/soda-banner-sql.CIhdCg`와 `/tmp/soda-sql-review.439u9H`의 내용·실경로를 확인한 뒤 삭제하고 부재를 확인했다. 임시 notepad도 삭제했다. 공유 H2/Gradle 캐시는 보존했다.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 문서 상태 | Phase 7 언어 필터 후속 구현·전체 검증·독립 리뷰 완료, 미배포 |
|
||||
| 문서 상태 | Phase 7 언어 기능·Phase 8 정적 이관 SQL 검증 및 리뷰 완료, 미배포 |
|
||||
| 작성일 | 2026-10-29 |
|
||||
| 최종 수정일 | 2026-11-03 |
|
||||
| 대상 기능 | 가로형 서브 배너 백엔드 및 영속성 |
|
||||
@@ -145,6 +145,7 @@
|
||||
| 2026-10-29 | DEC-010 | 사용자 후속 확정·정정 | `is_active BOOLEAN NOT NULL DEFAULT TRUE`를 추가하고 DELETE는 false 갱신으로 변경한다. 삭제된 행은 모든 소비자 조회에서 제외하며 응답·권한·기간·S3 정책은 유지한다. 앞선 하드 삭제 완료 기록은 당시 이력으로 보존한다. | "is_active를 두고 소프트 삭제로 하자" |
|
||||
| 2026-10-29 | DEC-011 | 사용자 후속 확정·정정 | 별도 앱 GET을 제거하고 두 추천 GET에 `data.subBanners`를 추가한다. 두 GET 모두 비로그인 접근을 유지하고 앱 프롬프트도 함께 변경한다. 관리자 CRUD·소프트 삭제·기간·응답 항목은 유지한다. | 두 추천 API 통합 및 앱 프롬프트 수정 요청 |
|
||||
| 2026-11-02 | DEC-012 | 사용자 후속 확정 | 서브 배너에 기존 `Lang` enum 필드를 추가한다. 관리자 생성 누락은 KO, 수정 누락은 유지, 명시적 null·비문자열·invalid는 HTTP 400이며 upload/write 전 검증한다. 관리자 목록은 언어 전체 조회, 공개 두 추천은 회원 국가 기준 JP→JA/그 외→KO로 필터링한다. public DTO는 유지한다. | 승인된 최소 이벤트/서브 배너 언어 후속 수정 |
|
||||
| 2026-11-03 | DEC-013 | 사용자 후속 변경 | 이관 파일을 SET/PREPARE/EXECUTE 없는 정적 ALTER TABLE 한 문장으로 변경한다. lang 컬럼이 없는 기존 테이블에 한 번만 적용하며 이미 존재하면 실행하지 않는다. 컬럼 정의와 KO 기본값은 유지한다. | 프로시저 없이 순수 SQL로 변경 요청 |
|
||||
|
||||
## 후속 사용자 요구사항: 관리자 노출 기간
|
||||
|
||||
@@ -161,6 +162,6 @@
|
||||
- 관리자 create에서 `lang`을 생략하면 KO로 저장한다. update에서 생략하면 기존 언어를 보존한다. 명시적 null, JSON 비문자열 또는 유효하지 않은 코드는 HTTP 400으로 거부하고 S3 upload와 DB write 이전에 검증한다.
|
||||
- 관리자 목록은 기존 placement 조건 외 언어 필터를 추가하지 않으며 응답마다 `lang`을 반환한다. public `SubBannerResponse`에는 `lang`을 노출하지 않는다.
|
||||
- 공개 조회 service는 member를 받아 기존 `MemberContentPreferenceService.resolveCountryCode(member)`로 JP→JA, 그 외→KO를 선택하고 이를 필수 Lang으로 repository에 전달한다. 두 추천 facade는 기존 member를 service에 전달한다.
|
||||
- 기존 테이블 CREATE SQL에는 `lang VARCHAR(10) NOT NULL DEFAULT 'KO'`를 포함한다. 별도 신규 migration SQL은 기존 테이블에 언어 컬럼을 안전하게 추가하고 null 값을 KO로 backfill한 뒤 NOT NULL DEFAULT KO를 적용한다.
|
||||
- 기존 테이블 CREATE SQL에는 `lang VARCHAR(10) NOT NULL DEFAULT 'KO'`를 포함한다. 별도 migration은 DEC-013에 따라 lang 컬럼이 없는 기존 테이블에 같은 정의를 단일 정적 ALTER TABLE로 추가한다. 기존 행은 KO 기본값을 가지며 자동 존재 검사·재실행 건너뛰기는 제공하지 않는다. 이미 lang이 있으면 실행하지 않는다.
|
||||
- KO backfill은 기존 행의 언어를 자동 추론하지 않는다. 운영 적용 전에 배너 소재를 검토해 JA 배너와 연결 이벤트의 언어를 운영 담당자가 수동 분류·정합성 확인해야 한다. 운영 DB에 DDL을 실행하지 않는다.
|
||||
- 제외: 앱/관리자 UI, event link parsing, event detail/adult 정책 변경, 새 dependency, public DTO 변경, 운영 DB·DDL 실행.
|
||||
|
||||
Reference in New Issue
Block a user