chore: 로컬 운영 및 검증 스크립트 bin 디렉터리를 Git 추적에서 제외
bin 디렉터리를 무시 목록에 추가하고 기존 추적 파일을 인덱스에서 제거한다. 로컬 스크립트 파일은 유지한다.
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
/.htaccess
|
/.htaccess
|
||||||
data/
|
data/
|
||||||
test/
|
test/
|
||||||
|
/tests/
|
||||||
|
/bin/
|
||||||
sirgle/
|
sirgle/
|
||||||
test.php
|
test.php
|
||||||
*.key
|
*.key
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
// 실제 DB 대신 SQL 응답을 제공해 이력 등록의 순서와 쓰기 범위를 검증한다.
|
|
||||||
if (PHP_SAPI !== 'cli') exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
define('_GNUBOARD_', true);
|
|
||||||
define('G5_TABLE_PREFIX', 'inspection_test_');
|
|
||||||
define('G5_TIME_YMDHIS', '2026-09-07 00:00:00');
|
|
||||||
define('G5_PATH', sys_get_temp_dir() . '/g5-existing-' . uniqid());
|
|
||||||
mkdir(G5_PATH);
|
|
||||||
mkdir(G5_PATH . '/migrations');
|
|
||||||
$g5 = array('config_table' => 'inspection_test_config');
|
|
||||||
$records = array();
|
|
||||||
$queries = array();
|
|
||||||
$columns = array('present');
|
|
||||||
$lock_available = true;
|
|
||||||
$history_readable = true;
|
|
||||||
|
|
||||||
class MigrationInspectionRows
|
|
||||||
{
|
|
||||||
public $rows;
|
|
||||||
public function __construct($rows) { $this->rows = $rows; }
|
|
||||||
}
|
|
||||||
function sql_real_escape_string($value) { return addslashes($value); }
|
|
||||||
function sql_error_info() { return '검사용 오류'; }
|
|
||||||
function get_microtime() { return microtime(true); }
|
|
||||||
function sql_fetch_array($result) { return array_shift($result->rows); }
|
|
||||||
function sql_num_rows($result) { return count($result->rows); }
|
|
||||||
function sql_fetch($sql, $error = true) {
|
|
||||||
$result = sql_query($sql, $error);
|
|
||||||
return $result ? sql_fetch_array($result) : false;
|
|
||||||
}
|
|
||||||
function sql_query($sql, $error = true) {
|
|
||||||
global $queries, $records, $columns, $lock_available, $history_readable;
|
|
||||||
$queries[] = $sql;
|
|
||||||
if (strpos($sql, 'SHOW TABLES') === 0) return new MigrationInspectionRows(array(array('table' => 'inspection_test_migrations')));
|
|
||||||
if (strpos($sql, 'SHOW COLUMNS') === 0) {
|
|
||||||
$rows = array();
|
|
||||||
foreach ($columns as $column) $rows[] = array('Field' => $column);
|
|
||||||
return new MigrationInspectionRows($rows);
|
|
||||||
}
|
|
||||||
if (strpos($sql, 'SELECT migration_id') === 0) return $history_readable ? new MigrationInspectionRows(array_values($records)) : false;
|
|
||||||
if (strpos($sql, 'SELECT COUNT(*)') === 0) return new MigrationInspectionRows(array(array('total' => count($records))));
|
|
||||||
if (strpos($sql, 'SELECT GET_LOCK') === 0) return new MigrationInspectionRows(array(array('acquired' => $lock_available ? 1 : 0)));
|
|
||||||
if (strpos($sql, 'SELECT RELEASE_LOCK') === 0 || strpos($sql, 'CREATE TABLE IF NOT EXISTS `inspection_test_migrations`') === 0) return true;
|
|
||||||
if (strpos($sql, 'INSERT INTO `inspection_test_migrations`') === 0) {
|
|
||||||
preg_match("/migration_id = '([^']+)'/", $sql, $id);
|
|
||||||
preg_match("/checksum = '([^']+)'/", $sql, $checksum);
|
|
||||||
$records[$id[1]] = array('migration_id' => $id[1], 'checksum' => $checksum[1], 'status' => 'success', 'error_message' => '', 'applied_at' => G5_TIME_YMDHIS);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
fwrite(STDERR, '[실패] 허용하지 않은 SQL: ' . $sql . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
function inspection_check($condition, $message) {
|
|
||||||
if (!$condition) { fwrite(STDERR, '[실패] ' . $message . "\n"); exit(1); }
|
|
||||||
}
|
|
||||||
function inspection_cleanup() {
|
|
||||||
foreach (glob(G5_PATH . '/migrations/*') as $file) unlink($file);
|
|
||||||
rmdir(G5_PATH . '/migrations');
|
|
||||||
rmdir(G5_PATH);
|
|
||||||
}
|
|
||||||
register_shutdown_function('inspection_cleanup');
|
|
||||||
require dirname(dirname(__FILE__)) . '/lib/migration.lib.php';
|
|
||||||
$ids = array('20200101_001_existing', '20200102_001_missing', '20200103_001_later');
|
|
||||||
foreach ($ids as $index => $id) {
|
|
||||||
$column = $index === 1 ? 'missing' : 'present';
|
|
||||||
file_put_contents(G5_PATH . '/migrations/' . $id . '.sql', "-- @if-column-missing {{config_table}} $column\nALTER TABLE `{{config_table}}` ADD `$column` int;\n");
|
|
||||||
}
|
|
||||||
$statuses = g5_migration_status();
|
|
||||||
inspection_check($statuses[0]['status'] === 'unrecorded' && $statuses[1]['status'] === 'pending' && $statuses[2]['status'] === 'unrecorded', '기존·미적용 상태 구분');
|
|
||||||
foreach ($queries as $query) inspection_check(preg_match('/^(SHOW|SELECT) /', $query), '상태 조회에서 DB 쓰기 발생');
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check($result['success'] && $result['skipped'] === 1 && count($records) === 1 && isset($records[$ids[0]]), '미적용 선행 항목을 넘어 등록함');
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check($result['skipped'] === 0 && count($records) === 1, '반복 등록');
|
|
||||||
$statuses = g5_migration_status();
|
|
||||||
inspection_check($statuses[0]['status'] === 'success' && $statuses[2]['status'] === 'unrecorded', '부분 이력 이후 검사 누락');
|
|
||||||
$records[$ids[0]]['checksum'] = 'changed';
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check(!$result['success'] && count($records) === 1, '체크섬 변경 무시');
|
|
||||||
$records[$ids[0]]['status'] = 'failed';
|
|
||||||
$columns[] = 'missing';
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check($result['skipped'] === 0 && $records[$ids[0]]['status'] === 'failed', '실패 이력을 덮어씀');
|
|
||||||
$records = array();
|
|
||||||
$lock_available = false;
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check(!$result['success'] && !$records, '잠금 실패 후 등록');
|
|
||||||
$lock_available = true;
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check($result['success'] && $result['skipped'] === 3 && count($records) === 3, '재검사 후 기존 항목 등록 실패');
|
|
||||||
$history_readable = false;
|
|
||||||
$result = g5_migration_run('', true);
|
|
||||||
inspection_check(!$result['success'] && count($records) === 3, '이력 조회 실패 후 덮어씀');
|
|
||||||
echo "기존 상태 조회·순차 이력 등록·재검사·실패/체크섬/잠금 보호 검사가 통과했습니다.\n";
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
if (PHP_SAPI !== 'cli') {
|
|
||||||
exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
$root = dirname(dirname(__FILE__));
|
|
||||||
chdir($root);
|
|
||||||
|
|
||||||
define('G5_IS_CLI', true);
|
|
||||||
$_SERVER['SCRIPT_FILENAME'] = $root . '/index.php';
|
|
||||||
$_SERVER['SCRIPT_NAME'] = '/index.php';
|
|
||||||
$_SERVER['REQUEST_URI'] = '/';
|
|
||||||
$_SERVER['SERVER_NAME'] = 'localhost';
|
|
||||||
$_SERVER['SERVER_PORT'] = 80;
|
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
||||||
$_SERVER['REQUEST_METHOD'] = 'CLI';
|
|
||||||
|
|
||||||
require_once $root . '/common.php';
|
|
||||||
require_once G5_LIB_PATH . '/migration.lib.php';
|
|
||||||
|
|
||||||
$test_suffix = substr(md5(getmypid() . microtime()), 0, 10);
|
|
||||||
$test_config_table = G5_TABLE_PREFIX . 'migration_test_config_' . $test_suffix;
|
|
||||||
$test_auth_table = G5_TABLE_PREFIX . 'migration_test_auth_' . $test_suffix;
|
|
||||||
$test_memo_table = G5_TABLE_PREFIX . 'migration_test_memo_' . $test_suffix;
|
|
||||||
$test_member_table = G5_TABLE_PREFIX . 'migration_test_member_' . $test_suffix;
|
|
||||||
$test_board_file_table = G5_TABLE_PREFIX . 'migration_test_board_file_' . $test_suffix;
|
|
||||||
$test_social_profile_table = G5_TABLE_PREFIX . 'migration_test_social_' . $test_suffix;
|
|
||||||
$test_content_table = G5_TABLE_PREFIX . 'migration_test_content_' . $test_suffix;
|
|
||||||
$test_board_table = G5_TABLE_PREFIX . 'migration_test_board_' . $test_suffix;
|
|
||||||
$test_write_prefix = G5_TABLE_PREFIX . 'migration_test_write_' . $test_suffix . '_';
|
|
||||||
$test_write_table = $test_write_prefix . 'sample';
|
|
||||||
$test_cart_table = G5_TABLE_PREFIX . 'migration_test_cart_' . $test_suffix;
|
|
||||||
$test_item_table = G5_TABLE_PREFIX . 'migration_test_item_' . $test_suffix;
|
|
||||||
$test_shop_default_table = G5_TABLE_PREFIX . 'migration_test_default_' . $test_suffix;
|
|
||||||
$test_inicis_pay_table = G5_TABLE_PREFIX . 'migration_test_inicis_' . $test_suffix;
|
|
||||||
$test_inicis_pay_event_table = G5_TABLE_PREFIX . 'migration_test_inicis_event_' . $test_suffix;
|
|
||||||
$test_absent_prefix = G5_TABLE_PREFIX . 'migration_test_absent_' . $test_suffix . '_';
|
|
||||||
$test_tables = array($test_config_table, $test_auth_table, $test_memo_table, $test_member_table, $test_board_file_table, $test_social_profile_table, $test_content_table, $test_board_table, $test_write_table, $test_cart_table, $test_item_table, $test_shop_default_table, $test_inicis_pay_table, $test_inicis_pay_event_table);
|
|
||||||
|
|
||||||
function g5_migration_test_cleanup()
|
|
||||||
{
|
|
||||||
global $test_tables;
|
|
||||||
foreach ($test_tables as $table) {
|
|
||||||
sql_query("DROP TABLE IF EXISTS `" . str_replace('`', '``', $table) . "`", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function g5_migration_test_fail($message)
|
|
||||||
{
|
|
||||||
fwrite(STDERR, '[실패] ' . $message . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function g5_migration_test_execute($migration)
|
|
||||||
{
|
|
||||||
if (isset($migration['error'])) {
|
|
||||||
g5_migration_test_fail($migration['error']);
|
|
||||||
}
|
|
||||||
foreach ($migration['statements'] as $statement) {
|
|
||||||
if (!g5_migration_should_run_statement($statement['conditions'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$result = g5_migration_execute_statement($statement);
|
|
||||||
if ($result['error'] !== '') {
|
|
||||||
g5_migration_test_fail($result['error']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register_shutdown_function('g5_migration_test_cleanup');
|
|
||||||
g5_migration_test_cleanup();
|
|
||||||
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_config_table}` (
|
|
||||||
`cf_googl_shorturl_apikey` varchar(50) NOT NULL DEFAULT '',
|
|
||||||
`cf_twitter_secret` varchar(100) NOT NULL DEFAULT '',
|
|
||||||
`cf_kakao_js_apikey` varchar(100) NOT NULL DEFAULT '',
|
|
||||||
`cf_member_icon_height` int(11) NOT NULL DEFAULT '0',
|
|
||||||
`cf_google_clientid` varchar(100) NOT NULL DEFAULT '',
|
|
||||||
`cf_member_img_width` int(11) NOT NULL DEFAULT '0'
|
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_auth_table}` (`au_menu` varchar(20) NOT NULL DEFAULT '') ENGINE=MyISAM DEFAULT CHARSET=utf8", false) ||
|
|
||||||
!sql_query("CREATE TABLE `{$test_memo_table}` (`me_id` int(11) NOT NULL, PRIMARY KEY (`me_id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8", false) ||
|
|
||||||
!sql_query("CREATE TABLE `{$test_member_table}` (`mb_marketing_agree` tinyint(1) NOT NULL DEFAULT '0') ENGINE=MyISAM DEFAULT CHARSET=utf8", false) ||
|
|
||||||
!sql_query("CREATE TABLE `{$test_board_file_table}` (`bf_fileurl` varchar(255) NOT NULL DEFAULT '') ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_content_table}` (
|
|
||||||
`co_id` varchar(20) NOT NULL DEFAULT '',
|
|
||||||
`co_subject` varchar(255) NOT NULL DEFAULT '',
|
|
||||||
`co_seo_title` varchar(200) NOT NULL DEFAULT '',
|
|
||||||
PRIMARY KEY (`co_id`)
|
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_board_table}` (`bo_table` varchar(20) NOT NULL DEFAULT '') ENGINE=MyISAM DEFAULT CHARSET=utf8", false) ||
|
|
||||||
!sql_query("CREATE TABLE `{$test_write_table}` (`wr_id` int(11) NOT NULL AUTO_INCREMENT, `wr_content` text NOT NULL, `wr_seo_title` varchar(200) NOT NULL DEFAULT '', PRIMARY KEY (`wr_id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8", false) ||
|
|
||||||
!sql_query("INSERT INTO `{$test_board_table}` SET bo_table = 'sample'", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_cart_table}` (
|
|
||||||
`ct_id` int(11) NOT NULL AUTO_INCREMENT, `it_id` varchar(20) NOT NULL DEFAULT '',
|
|
||||||
`it_name` varchar(255) NOT NULL DEFAULT '',
|
|
||||||
`it_sc_method` tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (`ct_id`)
|
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_item_table}` (
|
|
||||||
`it_id` varchar(20) NOT NULL DEFAULT '', `it_sc_type` tinyint(4) NOT NULL DEFAULT '0',
|
|
||||||
`it_sc_method` tinyint(4) NOT NULL DEFAULT '0', `it_sc_price` int(11) NOT NULL DEFAULT '0',
|
|
||||||
`it_sc_minimum` int(11) NOT NULL DEFAULT '0', `it_sc_qty` int(11) NOT NULL DEFAULT '0',
|
|
||||||
PRIMARY KEY (`it_id`)
|
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_shop_default_table}` (`de_inicis_pro_reconcile_use` tinyint(4) NOT NULL DEFAULT '0') ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
if (!sql_query("CREATE TABLE `{$test_inicis_pay_table}` (
|
|
||||||
`ip_id` int(11) NOT NULL AUTO_INCREMENT, `ip_oid` varchar(64) NOT NULL DEFAULT '',
|
|
||||||
`ip_environment` varchar(10) NOT NULL DEFAULT '', PRIMARY KEY (`ip_id`), UNIQUE KEY `ip_oid` (`ip_oid`)
|
|
||||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8", false)) {
|
|
||||||
g5_migration_test_fail(sql_error_info());
|
|
||||||
}
|
|
||||||
|
|
||||||
$g5['config_table'] = $test_config_table;
|
|
||||||
$g5['auth_table'] = $test_auth_table;
|
|
||||||
$g5['memo_table'] = $test_memo_table;
|
|
||||||
$g5['member_table'] = $test_member_table;
|
|
||||||
$g5['board_file_table'] = $test_board_file_table;
|
|
||||||
$g5['social_profile_table'] = $test_social_profile_table;
|
|
||||||
$g5['content_table'] = $test_content_table;
|
|
||||||
$g5['board_table'] = $test_board_table;
|
|
||||||
$g5['write_prefix'] = $test_write_prefix;
|
|
||||||
$g5['g5_shop_cart_table'] = $test_cart_table;
|
|
||||||
$g5['g5_shop_item_table'] = $test_item_table;
|
|
||||||
$g5['g5_shop_default_table'] = $test_shop_default_table;
|
|
||||||
$g5['g5_shop_inicis_pay_table'] = $test_inicis_pay_table;
|
|
||||||
$g5['g5_shop_inicis_pay_event_table'] = $test_inicis_pay_event_table;
|
|
||||||
$isolated_keys = array(
|
|
||||||
'g5_shop_order_data_table', 'g5_shop_inicis_log_table'
|
|
||||||
);
|
|
||||||
foreach ($isolated_keys as $index => $key) {
|
|
||||||
$g5[$key] = $test_absent_prefix . $index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 복합 ALTER의 첫 컬럼만 없는 역방향 부분 적용 상태에서도 과거 마이그레이션이 중단되지 않아야 한다.
|
|
||||||
$inspection_cache = array();
|
|
||||||
$social_migration = g5_migration_parse_file(G5_PATH . '/migrations/20180330_001_social_login.sql');
|
|
||||||
if (g5_migration_needs_no_execution($social_migration, $inspection_cache)) {
|
|
||||||
g5_migration_test_fail('부분 적용된 소셜 스키마를 실행 불필요로 판단했습니다.');
|
|
||||||
}
|
|
||||||
g5_migration_test_execute(g5_migration_parse_file(G5_PATH . '/migrations/20140331_001_shop_cart_shipping.sql'));
|
|
||||||
g5_migration_test_execute(g5_migration_parse_file(G5_PATH . '/migrations/20180330_001_social_login.sql'));
|
|
||||||
g5_migration_test_execute(g5_migration_parse_file(G5_PATH . '/migrations/20260723_001_inicis_pro.sql'));
|
|
||||||
$inspection_cache = array();
|
|
||||||
if (!g5_migration_needs_no_execution($social_migration, $inspection_cache)) {
|
|
||||||
g5_migration_test_fail('이미 적용된 소셜 스키마를 실행 불필요로 판단하지 못했습니다.');
|
|
||||||
}
|
|
||||||
$write_migration = g5_migration_parse_file(G5_PATH . '/migrations/20191202_002_board_write_seo.sql');
|
|
||||||
if (!g5_migration_needs_no_execution($write_migration, $inspection_cache)) {
|
|
||||||
g5_migration_test_fail('기존 게시판 SEO 컬럼을 확인하지 못했습니다.');
|
|
||||||
}
|
|
||||||
sql_query("ALTER TABLE `{$test_write_table}` DROP COLUMN wr_seo_title", false);
|
|
||||||
$inspection_cache = array();
|
|
||||||
if (g5_migration_needs_no_execution($write_migration, $inspection_cache)) {
|
|
||||||
g5_migration_test_fail('게시판 SEO 컬럼 누락을 실행 불필요로 판단했습니다.');
|
|
||||||
}
|
|
||||||
g5_migration_test_execute($write_migration);
|
|
||||||
$inspection_cache = array();
|
|
||||||
$unconditional_migration = g5_migration_parse_file(G5_PATH . '/migrations/20260904_002_reconcile_partial_schema.sql');
|
|
||||||
$backfill_migration = g5_migration_parse_file(G5_PATH . '/migrations/20260904_003_backfill_legacy_data.sql');
|
|
||||||
if (g5_migration_needs_no_execution($unconditional_migration, $inspection_cache) || g5_migration_needs_no_execution($backfill_migration, $inspection_cache)) {
|
|
||||||
g5_migration_test_fail('조건 없는 스키마 변경 또는 데이터 보정을 완료로 추정했습니다.');
|
|
||||||
}
|
|
||||||
$absent_table = $test_absent_prefix . 'inspection';
|
|
||||||
if (g5_migration_inspect_object('column', $absent_table, 'missing', $inspection_cache) !== null) {
|
|
||||||
g5_migration_test_fail('조회 오류를 객체 없음과 구분하지 못했습니다.');
|
|
||||||
}
|
|
||||||
if (!g5_migration_column_exists($test_cart_table, 'it_sc_type') ||
|
|
||||||
!g5_migration_column_exists($test_config_table, 'cf_social_login_use') ||
|
|
||||||
!g5_migration_column_exists($test_config_table, 'cf_member_img_size') ||
|
|
||||||
!g5_migration_column_exists($test_shop_default_table, 'de_inicis_pro_alert_use')) {
|
|
||||||
g5_migration_test_fail('역방향 부분 적용 스키마에서 과거 복합 변경의 기준 컬럼을 복구하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$schema_migration = g5_migration_parse_file(G5_PATH . '/migrations/20260904_002_reconcile_partial_schema.sql');
|
|
||||||
g5_migration_test_execute($schema_migration);
|
|
||||||
if (!g5_migration_column_exists($test_config_table, 'cf_google_secret') ||
|
|
||||||
!g5_migration_column_exists($test_config_table, 'cf_member_img_width') ||
|
|
||||||
!g5_migration_column_exists($test_config_table, 'cf_member_img_height')) {
|
|
||||||
g5_migration_test_fail('부분 적용 설정 테이블의 누락 컬럼을 복구하지 못했습니다.');
|
|
||||||
}
|
|
||||||
if (!g5_migration_column_exists($test_cart_table, 'it_sc_method') ||
|
|
||||||
!g5_migration_column_exists($test_shop_default_table, 'de_inicis_pro_monitor_message') ||
|
|
||||||
!g5_migration_column_exists($test_inicis_pay_table, 'ip_pg_status') ||
|
|
||||||
!g5_migration_index_exists($test_inicis_pay_table, 'ip_refund_required')) {
|
|
||||||
g5_migration_test_fail('부분 적용 쇼핑몰 테이블의 누락 컬럼 또는 인덱스를 복구하지 못했습니다.');
|
|
||||||
}
|
|
||||||
$write_column = sql_fetch("SHOW COLUMNS FROM `{$test_write_table}` LIKE 'wr_seo_title'", false);
|
|
||||||
if (!isset($write_column['Type']) || strtolower($write_column['Type']) !== 'varchar(255)') {
|
|
||||||
g5_migration_test_fail('게시판 글 SEO 컬럼의 최종 자료형을 보정하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
sql_query("INSERT INTO `{$test_config_table}` SET cf_member_img_size = 0, cf_member_img_width = 0, cf_member_img_height = 0", false);
|
|
||||||
sql_query("INSERT INTO `{$test_content_table}` SET co_id = 'company', co_subject = '회사 소개', co_seo_title = ''", false);
|
|
||||||
sql_query("INSERT INTO `{$test_item_table}` SET it_id = 'item-1', it_sc_type = 2, it_sc_method = 1, it_sc_price = 3000, it_sc_minimum = 50000, it_sc_qty = 2", false);
|
|
||||||
sql_query("INSERT INTO `{$test_cart_table}` SET it_id = 'item-1'", false);
|
|
||||||
|
|
||||||
$data_migration = g5_migration_parse_file(G5_PATH . '/migrations/20260904_003_backfill_legacy_data.sql');
|
|
||||||
g5_migration_test_execute($data_migration);
|
|
||||||
|
|
||||||
$config_row = sql_fetch("SELECT cf_member_img_size, cf_member_img_width, cf_member_img_height FROM `{$test_config_table}` LIMIT 1", false);
|
|
||||||
if ((int) $config_row['cf_member_img_size'] !== 50000 || (int) $config_row['cf_member_img_width'] !== 60 || (int) $config_row['cf_member_img_height'] !== 60) {
|
|
||||||
g5_migration_test_fail('회원 이미지 설정 기본값을 보정하지 못했습니다.');
|
|
||||||
}
|
|
||||||
$content_row = sql_fetch("SELECT co_seo_title FROM `{$test_content_table}` WHERE co_id = 'company'", false);
|
|
||||||
if (!isset($content_row['co_seo_title']) || $content_row['co_seo_title'] === '') {
|
|
||||||
g5_migration_test_fail('콘텐츠 SEO 제목을 보정하지 못했습니다.');
|
|
||||||
}
|
|
||||||
$cart_row = sql_fetch("SELECT it_sc_type, it_sc_method, it_sc_price, it_sc_minimum, it_sc_qty FROM `{$test_cart_table}` WHERE it_id = 'item-1'", false);
|
|
||||||
if ((int) $cart_row['it_sc_type'] !== 2 || (int) $cart_row['it_sc_method'] !== 1 || (int) $cart_row['it_sc_price'] !== 3000 || (int) $cart_row['it_sc_minimum'] !== 50000 || (int) $cart_row['it_sc_qty'] !== 2) {
|
|
||||||
g5_migration_test_fail('장바구니 배송비 데이터를 보정하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$migrations = g5_migration_discover();
|
|
||||||
$migration_ids = array();
|
|
||||||
$dependency_records = array();
|
|
||||||
foreach ($migrations as $migration) {
|
|
||||||
if (isset($migration['error'])) {
|
|
||||||
g5_migration_test_fail($migration['error']);
|
|
||||||
}
|
|
||||||
$migration_ids[] = $migration['id'];
|
|
||||||
$dependency_records[$migration['id']] = array('status' => 'success', 'checksum' => $migration['checksum']);
|
|
||||||
}
|
|
||||||
$sorted_ids = $migration_ids;
|
|
||||||
sort($sorted_ids, SORT_STRING);
|
|
||||||
if ($migration_ids !== $sorted_ids) {
|
|
||||||
g5_migration_test_fail('전체 마이그레이션이 오래된 순서로 정렬되지 않았습니다.');
|
|
||||||
}
|
|
||||||
$last_migration = $migrations[count($migrations) - 1];
|
|
||||||
unset($dependency_records[$migrations[0]['id']]);
|
|
||||||
if (g5_migration_validate_target_dependencies($migrations, $dependency_records, $last_migration['id']) === '') {
|
|
||||||
g5_migration_test_fail('누락된 선행 마이그레이션을 차단하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "기존 상태 읽기 전용 검사·실행 순서·선행 조건·부분 적용 스키마·기존 데이터 보정 검사가 통과했습니다.\n";
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
if (PHP_SAPI !== 'cli') exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
|
|
||||||
$root = dirname(dirname(__FILE__));
|
|
||||||
$install_files = array($root.'/install/gnuboard5.sql', $root.'/install/gnuboard5shop.sql', $root.'/adm/sql_write.sql');
|
|
||||||
$migration_files = glob($root.'/migrations/*.sql');
|
|
||||||
$install_sql = '';
|
|
||||||
foreach ($install_files as $install_file) {
|
|
||||||
$contents = @file_get_contents($install_file);
|
|
||||||
if ($contents === false) {
|
|
||||||
fwrite(STDERR, '신규 설치 SQL을 읽을 수 없습니다: '.basename($install_file)."\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$install_sql .= "\n".$contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
$table_map = array(
|
|
||||||
'{{auth_table}}'=>'g5_auth', '{{board_file_table}}'=>'g5_board_file', '{{config_table}}'=>'g5_config',
|
|
||||||
'{{content_table}}'=>'g5_content', '{{login_table}}'=>'g5_login', '{{member_table}}'=>'g5_member',
|
|
||||||
'{{member_auto_login_table}}'=>'g5_member_auto_login', '{{memo_table}}'=>'g5_memo',
|
|
||||||
'{{qa_config_table}}'=>'g5_qa_config', '{{social_profile_table}}'=>'g5_member_social_profiles',
|
|
||||||
'{{visit_table}}'=>'g5_visit', '{{write_table}}'=>'__TABLE_NAME__',
|
|
||||||
'{{g5_shop_cart_table}}'=>'g5_shop_cart', '{{g5_shop_coupon_log_table}}'=>'g5_shop_coupon_log',
|
|
||||||
'{{g5_shop_coupon_table}}'=>'g5_shop_coupon', '{{g5_shop_coupon_zone_table}}'=>'g5_shop_coupon_zone',
|
|
||||||
'{{g5_shop_default_table}}'=>'g5_shop_default', '{{g5_shop_inicis_log_table}}'=>'g5_shop_inicis_log',
|
|
||||||
'{{g5_shop_inicis_pay_event_table}}'=>'g5_shop_inicis_pay_event',
|
|
||||||
'{{g5_shop_inicis_pay_table}}'=>'g5_shop_inicis_pay',
|
|
||||||
'{{g5_shop_order_cancel_log_table}}'=>'g5_shop_order_cancel_log',
|
|
||||||
'{{g5_shop_order_data_table}}'=>'g5_shop_order_data', '{{g5_shop_order_table}}'=>'g5_shop_order',
|
|
||||||
'{{g5_shop_post_log_table}}'=>'g5_shop_order_post_log'
|
|
||||||
);
|
|
||||||
|
|
||||||
$install_tables = array();
|
|
||||||
if (preg_match_all('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`([^`]+)`\s*\((.*?)\)\s*ENGINE/is', $install_sql, $matches, PREG_SET_ORDER)) {
|
|
||||||
foreach ($matches as $match) $install_tables[$match[1]] = $match[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
function g5_schema_normalize_definition($definition)
|
|
||||||
{
|
|
||||||
$definition = preg_replace('/\s+AFTER\s+`?[a-zA-Z0-9_]+`?\s*$/i', '', trim($definition));
|
|
||||||
$definition = preg_replace('/\s+FIRST\s*$/i', '', $definition);
|
|
||||||
$definition = str_replace('`', '', strtolower($definition));
|
|
||||||
return preg_replace('/\s+/', ' ', $definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
function g5_schema_check_objects($file, $table, $sql, $install_tables, &$errors, &$latest_definitions)
|
|
||||||
{
|
|
||||||
if (!isset($install_tables[$table])) {
|
|
||||||
$errors[] = $file.': 신규 설치 SQL에 `'.$table.'` 테이블이 없습니다.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$body = $install_tables[$table];
|
|
||||||
if (preg_match_all('/\bADD(?:\s+COLUMN)?\s+`([a-zA-Z0-9_]+)`/i', $sql, $columns)) {
|
|
||||||
foreach ($columns[1] as $column) {
|
|
||||||
if (!preg_match('/`'.preg_quote($column, '/').'`\s+[a-z]/i', $body))
|
|
||||||
$errors[] = $file.': `'.$table.'`.`'.$column.'` 컬럼이 신규 설치 SQL에 없습니다.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (preg_match_all('/(?:ADD(?:\s+COLUMN)?|MODIFY(?:\s+COLUMN)?|CHANGE(?:\s+COLUMN)?\s+`[a-zA-Z0-9_]+`)\s+`([a-zA-Z0-9_]+)`\s+(.*?)(?=,\s*(?:ADD|MODIFY|CHANGE|DROP|PRIMARY|UNIQUE|KEY|INDEX)\b|$)/is', $sql, $definitions, PREG_SET_ORDER)) {
|
|
||||||
foreach ($definitions as $definition) {
|
|
||||||
$latest_definitions[$table][$definition[1]] = g5_schema_normalize_definition($definition[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (preg_match_all('/\bADD\s+(?:UNIQUE\s+)?(?:KEY|INDEX)\s+`([^`]+)`/i', $sql, $indexes)) {
|
|
||||||
foreach ($indexes[1] as $index) {
|
|
||||||
if (!preg_match('/(?:UNIQUE\s+)?KEY\s+`'.preg_quote($index, '/').'`/i', $body))
|
|
||||||
$errors[] = $file.': `'.$table.'`.`'.$index.'` 인덱스가 신규 설치 SQL에 없습니다.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$errors = array();
|
|
||||||
$latest_definitions = array();
|
|
||||||
foreach ((array) $migration_files as $migration_file) {
|
|
||||||
$sql = @file_get_contents($migration_file);
|
|
||||||
if ($sql === false) {
|
|
||||||
$errors[] = basename($migration_file).': 파일을 읽을 수 없습니다.';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (preg_match_all('/ALTER\s+TABLE\s+`(\{\{[a-zA-Z0-9_]+\}\})`\s+(.*?);/is', $sql, $alters, PREG_SET_ORDER)) {
|
|
||||||
foreach ($alters as $alter) {
|
|
||||||
if (!isset($table_map[$alter[1]])) {
|
|
||||||
$errors[] = basename($migration_file).': 검사할 수 없는 자리표시자입니다: '.$alter[1];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
g5_schema_check_objects(basename($migration_file), $table_map[$alter[1]], $alter[2], $install_tables, $errors, $latest_definitions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (preg_match_all('/CREATE\s+TABLE\s+`(\{\{[a-zA-Z0-9_]+\}\})`\s*\((.*?)\)\s*ENGINE/is', $sql, $creates, PREG_SET_ORDER)) {
|
|
||||||
foreach ($creates as $create) {
|
|
||||||
if (!isset($table_map[$create[1]]) || !isset($install_tables[$table_map[$create[1]]])) {
|
|
||||||
$errors[] = basename($migration_file).': 신규 설치 SQL에서 생성 대상 테이블을 찾을 수 없습니다: '.$create[1];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$table = $table_map[$create[1]];
|
|
||||||
if (preg_match_all('/`([a-zA-Z0-9_]+)`\s+[a-z]/i', $create[2], $columns)) {
|
|
||||||
foreach ($columns[1] as $column) {
|
|
||||||
if (!preg_match('/`'.preg_quote($column, '/').'`\s+[a-z]/i', $install_tables[$table]))
|
|
||||||
$errors[] = basename($migration_file).': `'.$table.'`.`'.$column.'` 컬럼이 신규 설치 SQL에 없습니다.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($latest_definitions as $table => $columns) {
|
|
||||||
foreach ($columns as $column => $definition) {
|
|
||||||
if (!preg_match('/^\s*`'.preg_quote($column, '/').'`\s+(.+?),?\s*$/mi', $install_tables[$table], $match)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$install_definition = g5_schema_normalize_definition($match[1]);
|
|
||||||
if ($definition !== $install_definition) {
|
|
||||||
$errors[] = '`'.$table.'`.`'.$column.'` 최종 정의가 신규 설치 SQL과 다릅니다: '.$definition.' != '.$install_definition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errors) {
|
|
||||||
foreach ($errors as $error) fwrite(STDERR, '[실패] '.$error."\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
echo "마이그레이션의 테이블·컬럼·인덱스와 최종 컬럼 정의가 신규 설치 SQL과 일치합니다.\n";
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
if (PHP_SAPI !== 'cli') {
|
|
||||||
exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
$root = dirname(dirname(__FILE__));
|
|
||||||
$scan_paths = array('');
|
|
||||||
$excluded = array(
|
|
||||||
'lib/migration.lib.php',
|
|
||||||
'lib/PHPExcel/',
|
|
||||||
'data/',
|
|
||||||
'install/',
|
|
||||||
'migrations/',
|
|
||||||
'bin/check-migration-runner.php',
|
|
||||||
'bin/check-shop-install.php'
|
|
||||||
);
|
|
||||||
$lifecycle_ddl_files = array(
|
|
||||||
'adm/board_delete.inc.php',
|
|
||||||
'lib/shop_install.lib.php'
|
|
||||||
);
|
|
||||||
$errors = array();
|
|
||||||
|
|
||||||
foreach ($scan_paths as $scan_path) {
|
|
||||||
$base = $root . '/' . $scan_path;
|
|
||||||
if (!is_dir($base)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base));
|
|
||||||
foreach ($iterator as $file) {
|
|
||||||
$pathname = $file->getPathname();
|
|
||||||
if (!$file->isFile() || strtolower(pathinfo($pathname, PATHINFO_EXTENSION)) !== 'php') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$relative = str_replace($root . '/', '', str_replace('\\', '/', $pathname));
|
|
||||||
$skip = false;
|
|
||||||
foreach ($excluded as $exclude) {
|
|
||||||
if ($relative === $exclude || strpos($relative, $exclude) === 0) {
|
|
||||||
$skip = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($skip) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array($relative, $lifecycle_ddl_files)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = file_get_contents($pathname);
|
|
||||||
$contents = preg_replace('/^.*\$schema_create.*$/m', '', $contents);
|
|
||||||
if (preg_match_all('/\b(?:ALTER\s+TABLE|CREATE\s+TABLE|DROP\s+TABLE|RENAME\s+(?:TABLE|TO)|TRUNCATE\s+TABLE)\b/i', $contents, $matches, PREG_OFFSET_CAPTURE)) {
|
|
||||||
foreach ($matches[0] as $match) {
|
|
||||||
$line = substr_count(substr($contents, 0, $match[1]), "\n") + 1;
|
|
||||||
$errors[] = $relative . ':' . $line . ': ' . preg_replace('/\s+/', ' ', trim($match[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errors) {
|
|
||||||
foreach ($errors as $error) {
|
|
||||||
fwrite(STDERR, '[실패] ' . $error . "\n");
|
|
||||||
}
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "일반 실행 경로에 DDL 문이 없습니다.\n";
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
if (PHP_SAPI !== 'cli') {
|
|
||||||
exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
$root = dirname(dirname(__FILE__));
|
|
||||||
chdir($root);
|
|
||||||
define('G5_IS_CLI', true);
|
|
||||||
$_SERVER['SCRIPT_FILENAME'] = $root . '/index.php';
|
|
||||||
$_SERVER['SCRIPT_NAME'] = '/index.php';
|
|
||||||
$_SERVER['REQUEST_URI'] = '/';
|
|
||||||
$_SERVER['SERVER_NAME'] = 'localhost';
|
|
||||||
$_SERVER['SERVER_PORT'] = 80;
|
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
||||||
$_SERVER['REQUEST_METHOD'] = 'CLI';
|
|
||||||
|
|
||||||
require_once $root . '/common.php';
|
|
||||||
require_once G5_LIB_PATH . '/migration.lib.php';
|
|
||||||
require_once G5_LIB_PATH . '/shop_install.lib.php';
|
|
||||||
|
|
||||||
function g5_shop_install_test_default_values($source)
|
|
||||||
{
|
|
||||||
preg_match_all("/\\b(de_[a-z0-9_]+)\\s*=\\s*'([^']*)'/i", $source, $matches, PREG_SET_ORDER);
|
|
||||||
$values = array();
|
|
||||||
foreach ($matches as $match) {
|
|
||||||
$values[$match[1]] = $match[2];
|
|
||||||
}
|
|
||||||
return $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
$install_source = @file_get_contents(G5_PATH . '/install/install_db.php');
|
|
||||||
$shop_install_source = @file_get_contents(G5_LIB_PATH . '/shop_install.lib.php');
|
|
||||||
if ($install_source === false || $shop_install_source === false ||
|
|
||||||
!preg_match('/insert into `\{\$g5_shop_prefix\}default`\s+set(.*?)\n\s+";/s', $install_source, $install_defaults) ||
|
|
||||||
!preg_match('/\$sql = "INSERT INTO `\{\$table\}` SET(.*?)";\n\s+return sql_query/s', $shop_install_source, $shop_defaults)) {
|
|
||||||
fwrite(STDERR, "[실패] 기본 설치와 후설치의 쇼핑몰 설정을 비교할 수 없습니다.\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
preg_match_all('/\b(de_[a-z0-9_]+)\s*=/i', $install_defaults[1], $install_fields);
|
|
||||||
preg_match_all('/\b(de_[a-z0-9_]+)\s*=/i', $shop_defaults[1], $shop_fields);
|
|
||||||
$install_fields = array_values(array_unique($install_fields[1]));
|
|
||||||
$shop_fields = array_values(array_unique($shop_fields[1]));
|
|
||||||
sort($install_fields, SORT_STRING);
|
|
||||||
sort($shop_fields, SORT_STRING);
|
|
||||||
if ($install_fields !== $shop_fields) {
|
|
||||||
fwrite(STDERR, "[실패] 기본 설치와 후설치의 쇼핑몰 기본 설정 필드가 다릅니다.\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$install_values = g5_shop_install_test_default_values($install_defaults[1]);
|
|
||||||
$shop_values = g5_shop_install_test_default_values($shop_defaults[1]);
|
|
||||||
$install_variables = array(
|
|
||||||
'$ssimg_width' => '160', '$ssimg_height' => '160', '$simg_width' => '215', '$simg_height' => '215',
|
|
||||||
'$mimg_width' => '230', '$mimg_height' => '230', '$mmimg_width' => '300', '$mmimg_height' => '300',
|
|
||||||
'$msimg_width' => '80', '$msimg_height' => '80', '$list_img_width' => '225', '$list_img_height' => '225'
|
|
||||||
);
|
|
||||||
foreach ($install_values as $field => $value) {
|
|
||||||
$value = strtr($value, $install_variables);
|
|
||||||
if (!isset($shop_values[$field]) || $shop_values[$field] !== $value) {
|
|
||||||
fwrite(STDERR, '[실패] 기본 설치와 후설치의 쇼핑몰 기본 설정값이 다릅니다: ' . $field . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$suffix = substr(md5(getmypid() . microtime()), 0, 10);
|
|
||||||
$prefix = G5_TABLE_PREFIX . 'shop_install_test_' . $suffix . '_';
|
|
||||||
$data_path = sys_get_temp_dir() . '/g5-shop-install-' . $suffix;
|
|
||||||
$config_file = $data_path . '/dbconfig.php';
|
|
||||||
$tables = g5_shop_install_table_names($prefix);
|
|
||||||
|
|
||||||
function g5_shop_install_test_cleanup()
|
|
||||||
{
|
|
||||||
global $tables, $prefix, $data_path;
|
|
||||||
foreach ($tables as $name) {
|
|
||||||
sql_query("DROP TABLE IF EXISTS `" . str_replace('`', '``', $prefix . $name) . "`", false);
|
|
||||||
}
|
|
||||||
if (is_dir($data_path)) {
|
|
||||||
$dirs = array('banner', 'common', 'event', 'item');
|
|
||||||
foreach ($dirs as $dir) {
|
|
||||||
$path = $data_path . '/' . $dir;
|
|
||||||
if (is_dir($path)) {
|
|
||||||
$files = scandir($path);
|
|
||||||
foreach ($files as $file) {
|
|
||||||
if ($file !== '.' && $file !== '..') {
|
|
||||||
@unlink($path . '/' . $file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@rmdir($path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@unlink($data_path . '/dbconfig.php');
|
|
||||||
@rmdir($data_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function g5_shop_install_test_fail($message)
|
|
||||||
{
|
|
||||||
fwrite(STDERR, '[실패] ' . $message . "\n");
|
|
||||||
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, "<?php\n// 기존 설정 보존 표식\n?>") === false) {
|
|
||||||
g5_shop_install_test_fail('격리 테스트 설정 파일을 준비하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = g5_shop_install_run(array(
|
|
||||||
'allow_enabled' => true,
|
|
||||||
'prefix' => $prefix,
|
|
||||||
'data_path' => $data_path,
|
|
||||||
'config_file' => $config_file
|
|
||||||
));
|
|
||||||
if (!$result['success']) {
|
|
||||||
g5_shop_install_test_fail($result['error']);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($tables as $name) {
|
|
||||||
if (!g5_migration_table_exists($prefix . $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('쇼핑몰 기본 설정이 올바르게 생성되지 않았습니다.');
|
|
||||||
}
|
|
||||||
for ($index = 1; $index <= 5; $index++) {
|
|
||||||
preg_match_all('/./us', $default['de_sms_cont' . $index], $characters);
|
|
||||||
$bytes = 0;
|
|
||||||
foreach ($characters[0] as $character) {
|
|
||||||
$bytes += strlen($character) > 1 ? 2 : 1;
|
|
||||||
}
|
|
||||||
if ($bytes > 80) {
|
|
||||||
g5_shop_install_test_fail('기본 SMS 문구가 80바이트를 초과합니다: de_sms_cont' . $index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$config = @file_get_contents($config_file);
|
|
||||||
if ($config === false || strpos($config, '기존 설정 보존 표식') === false || strpos($config, "define('G5_USE_SHOP', true)") === false || strpos($config, $prefix) === false) {
|
|
||||||
g5_shop_install_test_fail('기존 DB 설정을 보존하면서 쇼핑몰 설정을 추가하지 못했습니다.');
|
|
||||||
}
|
|
||||||
$retry = g5_shop_install_run(array(
|
|
||||||
'allow_enabled' => true,
|
|
||||||
'prefix' => $prefix,
|
|
||||||
'data_path' => $data_path,
|
|
||||||
'config_file' => $config_file
|
|
||||||
));
|
|
||||||
if ($retry['success']) {
|
|
||||||
g5_shop_install_test_fail('이미 설치된 쇼핑몰의 중복 설치를 차단하지 못했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '쇼핑몰 SQL ' . $checked_statements . "개 미설치 건너뛰기·후설치 후 업그레이드·설정 보존·중복 실행 차단 검사가 통과했습니다.\n";
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
if (PHP_SAPI !== 'cli') {
|
|
||||||
exit("CLI에서만 실행할 수 있습니다.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
$root = dirname(dirname(__FILE__));
|
|
||||||
chdir($root);
|
|
||||||
|
|
||||||
define('G5_IS_CLI', true);
|
|
||||||
$_SERVER['SCRIPT_FILENAME'] = $root . '/index.php';
|
|
||||||
$_SERVER['SCRIPT_NAME'] = '/index.php';
|
|
||||||
$_SERVER['REQUEST_URI'] = '/';
|
|
||||||
$_SERVER['SERVER_NAME'] = 'localhost';
|
|
||||||
$_SERVER['SERVER_PORT'] = 80;
|
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
||||||
$_SERVER['REQUEST_METHOD'] = 'CLI';
|
|
||||||
|
|
||||||
require_once $root . '/common.php';
|
|
||||||
require_once G5_LIB_PATH . '/migration.lib.php';
|
|
||||||
|
|
||||||
$command = isset($argv[1]) ? $argv[1] : 'status';
|
|
||||||
|
|
||||||
if ($command === 'status') {
|
|
||||||
$statuses = g5_migration_status();
|
|
||||||
foreach ($statuses as $status) {
|
|
||||||
if (isset($status['error'])) {
|
|
||||||
fwrite(STDERR, '[오류] ' . $status['error'] . "\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$label = g5_migration_status_label($status['status'], $status['checksum_changed']);
|
|
||||||
echo '[' . $label . '] ' . $status['id'] . ' ' . $status['description'] . "\n";
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($command !== 'migrate' && $command !== 'record-existing') {
|
|
||||||
fwrite(STDERR, "사용법: php bin/db-migrate.php status | migrate [마이그레이션_ID] | record-existing\n");
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$target_id = $command === 'migrate' && isset($argv[2]) && $argv[2] !== '--all' ? $argv[2] : '';
|
|
||||||
$result = g5_migration_run($target_id, $command === 'record-existing');
|
|
||||||
if (!$result['success']) {
|
|
||||||
foreach ($result['errors'] as $error) {
|
|
||||||
fwrite(STDERR, '[실패] ' . $error . "\n");
|
|
||||||
}
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '적용: ' . $result['applied'] . ', 기존 스키마 확인: ' . $result['skipped'] . "\n";
|
|
||||||
Reference in New Issue
Block a user