From e74486e171916e55bb16b5f02b60074343821b57 Mon Sep 17 00:00:00 2001 From: whitedot Date: Mon, 7 Sep 2026 03:32:16 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=EC=87=BC=ED=95=91=EB=AA=B0=20=EB=AF=B8?= =?UTF-8?q?=EC=84=A4=EC=B9=98=20=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EC=99=80=20=ED=9B=84=EC=84=A4=EC=B9=98=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EA=B2=80=EC=A6=9D=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전체 마이그레이션의 쇼핑몰 SQL 64개를 격리된 접두어에서 검사해 미설치 상태의 건너뛰기와 후설치 후 업그레이드를 확인한다. 쇼핑몰 추가 여부에 따른 운영 절차를 문서화한다. --- bin/check-shop-install.php | 61 ++++++++++++++++++++++++++++++++++++- docs/database-migrations.md | 7 +++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/bin/check-shop-install.php b/bin/check-shop-install.php index 0df32ce9a..453ff332e 100755 --- a/bin/check-shop-install.php +++ b/bin/check-shop-install.php @@ -99,8 +99,64 @@ function g5_shop_install_test_fail($message) exit(1); } +// 모든 마이그레이션의 쇼핑몰 SQL만 격리 접두어로 실행한다. +// 공통 스키마가 섞인 파일에서도 쇼핑몰 문장의 조건을 빠짐없이 검사한다. +function g5_shop_install_test_migrations($prefix, $installed) +{ + $checked = 0; + foreach (g5_migration_discover() as $migration) { + if (isset($migration['error'])) { + g5_shop_install_test_fail($migration['error']); + } + foreach ($migration['statements'] as $statement) { + if (strpos($statement['sql'], '`' . $prefix) === false) { + continue; + } + $checked++; + if (!preg_match('/^(?:CREATE TABLE(?: IF NOT EXISTS)?|ALTER TABLE|UPDATE)\s+`' . preg_quote($prefix, '/') . '/i', $statement['sql'])) { + g5_shop_install_test_fail('격리된 쇼핑몰 테이블을 대상으로 하지 않는 SQL입니다: ' . $migration['id']); + } + $should_run = g5_migration_should_run_statement($statement['conditions']); + if (!$installed && $should_run) { + g5_shop_install_test_fail('쇼핑몰 미설치 상태에서 실행되는 SQL이 있습니다: ' . $migration['id']); + } + if ($should_run) { + $result = g5_migration_execute_statement($statement); + if ($result['error'] !== '') { + g5_shop_install_test_fail($migration['id'] . ': ' . $result['error']); + } + } + } + } + if (!$checked) { + g5_shop_install_test_fail('쇼핑몰 마이그레이션 검사 대상이 없습니다.'); + } + return $checked; +} + register_shutdown_function('g5_shop_install_test_cleanup'); g5_shop_install_test_cleanup(); + +// SQL과 조건에서 사용되는 쇼핑몰 자리표시자를 모두 임시 테이블에 연결한다. +$shop_keys = array(); +foreach (glob(G5_PATH . '/migrations/*.sql') as $migration_file) { + preg_match_all('/\{\{(g5_shop_[a-z0-9_]+_table)\}\}/', file_get_contents($migration_file), $matches); + foreach ($matches[1] as $key) { + $shop_keys[$key] = true; + } +} +$source_shop_prefix = defined('G5_SHOP_TABLE_PREFIX') ? G5_SHOP_TABLE_PREFIX : G5_TABLE_PREFIX . 'shop_'; +foreach ($shop_keys as $key => $unused) { + $original = g5_migration_replace_placeholders('{{' . $key . '}}'); + if (strpos($original, $source_shop_prefix) !== 0) { + g5_shop_install_test_fail('쇼핑몰 테이블 접두어를 격리하지 못했습니다: ' . $key); + } + $g5[$key] = $prefix . substr($original, strlen($source_shop_prefix)); +} +$checked_statements = g5_shop_install_test_migrations($prefix, false); +if (g5_shop_install_existing_tables($prefix) !== array()) { + g5_shop_install_test_fail('쇼핑몰 미설치 검사 후 테이블이 생성되었습니다.'); +} if (!@mkdir($data_path, G5_DIR_PERMISSION) || @file_put_contents($config_file, "") === false) { g5_shop_install_test_fail('격리 테스트 설정 파일을 준비하지 못했습니다.'); } @@ -120,6 +176,9 @@ foreach ($tables as $name) { g5_shop_install_test_fail('쇼핑몰 테이블이 누락되었습니다: ' . $prefix . $name); } } +// 미설치 업그레이드 뒤 후설치는 과거 이력과 관계없이 최신 설치 SQL을 사용한다. +// 쇼핑몰을 먼저 설치한 경우의 업그레이드 SQL도 최신 스키마에서 정상 처리돼야 한다. +g5_shop_install_test_migrations($prefix, true); $default = sql_fetch("SELECT de_shop_skin, de_shop_mobile_skin, de_sms_cont1, de_sms_cont2, de_sms_cont3, de_sms_cont4, de_sms_cont5 FROM `{$prefix}default` LIMIT 1", false); if (!isset($default['de_shop_skin']) || $default['de_shop_skin'] !== 'basic' || $default['de_shop_mobile_skin'] !== 'basic') { g5_shop_install_test_fail('쇼핑몰 기본 설정이 올바르게 생성되지 않았습니다.'); @@ -148,4 +207,4 @@ if ($retry['success']) { g5_shop_install_test_fail('이미 설치된 쇼핑몰의 중복 설치를 차단하지 못했습니다.'); } -echo "쇼핑몰 후설치·설정 보존·중복 실행 차단 검사가 통과했습니다.\n"; +echo '쇼핑몰 SQL ' . $checked_statements . "개 미설치 건너뛰기·후설치 후 업그레이드·설정 보존·중복 실행 차단 검사가 통과했습니다.\n"; diff --git a/docs/database-migrations.md b/docs/database-migrations.md index 93b55f1db..f515cedb8 100644 --- a/docs/database-migrations.md +++ b/docs/database-migrations.md @@ -48,6 +48,13 @@ php bin/check-runtime-ddl.php 쇼핑몰을 설치하지 않은 사이트에서는 DB 업그레이드 화면의 `쇼핑몰 설치` 버튼으로 최신 쇼핑몰 스키마를 추가할 수 있다. 같은 접두어의 쇼핑몰 테이블이 하나라도 있으면 기존 데이터 훼손을 막기 위해 설치를 중단하며, 기존 `data/dbconfig.php`의 DB 접속 정보와 토큰 키는 유지하고 쇼핑몰 설정만 추가한다. `php bin/check-shop-install.php`로 격리된 접두어에서 후설치, 설정 보존과 중복 실행 차단을 검사할 수 있다. +쇼핑몰 미설치 사이트의 업그레이드는 다음 두 방식으로 진행할 수 있다. + +- **쇼핑몰 없이 업그레이드:** `쇼핑몰 설치`를 누르지 않고 기존 상태 이력 등록 또는 마이그레이션 실행을 진행한다. 쇼핑몰 관련 SQL은 기존 쇼핑몰 테이블의 존재 조건을 확인하므로 해당 테이블이 없는 설치에서는 실행하지 않는다. 공통 스키마와 쇼핑몰 변경이 한 파일에 있어도 쇼핑몰 문장만 건너뛴다. 쇼핑몰 항목의 성공 이력이 생겨도 쇼핑몰이 설치됐다는 뜻은 아니다. +- **쇼핑몰을 추가하고 업그레이드:** `쇼핑몰 설치`로 최신 쇼핑몰 스키마를 생성한 뒤 공통 DB 마이그레이션을 진행한다. 쇼핑몰 없이 업그레이드를 먼저 완료한 뒤 후설치해도 최신 설치 SQL을 사용하므로 과거에 건너뛴 쇼핑몰 이력을 삭제하거나 다시 실행할 필요가 없다. + +이 구분은 `G5_USE_SHOP` 값만으로 판단하지 않고 실제 쇼핑몰 테이블 존재 여부를 사용한다. 기존 쇼핑몰 테이블을 남겨 둔 채 기능만 비활성화한 사이트는 미설치 사이트와 다르며, 기존 테이블의 스키마 보정 대상이 될 수 있다. `check-shop-install.php`는 전체 마이그레이션의 쇼핑몰 SQL을 검사해 미설치 상태의 모든 문장이 건너뛰어지는지, 후설치 뒤에는 오류 없이 처리되는지도 검증한다. + `check-runtime-ddl.php`는 관리자·게시판·라이브러리·쇼핑몰 등 일반 실행 경로에 DDL 문이 다시 추가되지 않았는지 검사한다. 테이블이나 컬럼 변경은 이 검사를 우회하지 말고 버전형 마이그레이션으로 작성한다. 게시판 삭제 시 게시판별 테이블을 함께 삭제하는 것처럼 사용자가 명시적으로 요청한 데이터 생명주기 작업은 마이그레이션과 구분해 제한된 허용 목록으로 관리한다. 일반 레코드 생성 과정에서 `AUTO_INCREMENT`를 초기화하는 DDL은 허용하지 않는다.