194 lines
6.3 KiB
PHP
194 lines
6.3 KiB
PHP
<?php
|
|
if (!defined('_GNUBOARD_')) exit;
|
|
|
|
/**
|
|
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
|
|
*/
|
|
class SchemaManager
|
|
{
|
|
private $sql_file_path;
|
|
private $results;
|
|
|
|
/**
|
|
* 생성자
|
|
* @param string $sql_file_path install.sql 파일의 절대 경로
|
|
*/
|
|
public function __construct($sql_file_path)
|
|
{
|
|
if (!file_exists($sql_file_path)) {
|
|
throw new Exception($sql_file_path . ' 파일을 찾을 수 없습니다.');
|
|
}
|
|
$this->sql_file_path = $sql_file_path;
|
|
$this->results = [
|
|
'created' => [],
|
|
'existing' => [],
|
|
'updated' => [],
|
|
'failed' => [],
|
|
'errors' => [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 스키마 설치/업데이트를 실행합니다.
|
|
*/
|
|
public function execute()
|
|
{
|
|
$sql_statements = $this->parse_sql_file();
|
|
|
|
foreach ($sql_statements as $stmt) {
|
|
// CREATE TABLE 문인지 확인
|
|
if (preg_match('/^CREATE\s+TABLE/i', $stmt)) {
|
|
$schema = $this->parse_create_table_sql($stmt);
|
|
if ($schema && !empty($schema['name'])) {
|
|
$this->process_table_schema($stmt, $schema);
|
|
}
|
|
} else {
|
|
// CREATE TABLE 문이 아닌 다른 SQL 문 (e.g. INSERT, UPDATE)
|
|
sql_query($stmt, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 처리 결과를 반환합니다.
|
|
* @return array
|
|
*/
|
|
public function get_results()
|
|
{
|
|
return $this->results;
|
|
}
|
|
|
|
/**
|
|
* 테이블 스키마를 처리합니다. (생성 또는 업데이트)
|
|
* @param string $create_sql 전체 CREATE TABLE 구문
|
|
* @param array $schema 파싱된 스키마 정보
|
|
*/
|
|
private function process_table_schema($create_sql, $schema)
|
|
{
|
|
$table_name = $schema['name'];
|
|
|
|
if ($this->table_exists($table_name)) {
|
|
// 테이블이 존재하면, 컬럼 비교 및 추가
|
|
$this->results['existing'][] = $table_name;
|
|
$this->update_table_columns($table_name, $schema['columns']);
|
|
} else {
|
|
// 테이블이 존재하지 않으면, 새로 생성
|
|
if (sql_query($create_sql, false)) {
|
|
$this->results['created'][] = $table_name;
|
|
} else {
|
|
$this->results['failed'][] = $table_name;
|
|
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . sql_error();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블의 컬럼 구조를 업데이트합니다.
|
|
* @param string $table_name
|
|
* @param array $target_columns .sql 파일에 정의된 컬럼 목록
|
|
*/
|
|
private function update_table_columns($table_name, $target_columns)
|
|
{
|
|
$current_columns = $this->get_current_columns($table_name);
|
|
$added_columns_in_table = [];
|
|
|
|
foreach ($target_columns as $col_name => $col_definition) {
|
|
// 현재 테이블에 해당 컬럼이 없으면 추가
|
|
if (!isset($current_columns[$col_name])) {
|
|
$alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `{$col_name}` {$col_definition}";
|
|
if (sql_query($alter_sql, false)) {
|
|
$added_columns_in_table[] = $col_name;
|
|
} else {
|
|
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
|
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . sql_error();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($added_columns_in_table)) {
|
|
$this->results['updated'][$table_name] = $added_columns_in_table;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SQL 파일을 읽고 각 구문으로 분리합니다.
|
|
* @return array
|
|
*/
|
|
private function parse_sql_file()
|
|
{
|
|
$sql = file_get_contents($this->sql_file_path);
|
|
// 주석 제거 (SQL 주석 '--' 와 C-style '/* ... */' 주석)
|
|
$sql = preg_replace('/--.*/', '', $sql);
|
|
$sql = preg_replace('!/\*.*?\*/!s', '', $sql);
|
|
$sql = trim($sql);
|
|
|
|
// 세미콜론(;)을 기준으로 쿼리 분리
|
|
return array_filter(array_map('trim', explode(';', $sql)));
|
|
}
|
|
|
|
/**
|
|
* 테이블 존재 여부를 확인합니다.
|
|
* @param string $table_name
|
|
* @return bool
|
|
*/
|
|
private function table_exists($table_name)
|
|
{
|
|
$res = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
|
|
return sql_num_rows($res) > 0;
|
|
}
|
|
|
|
/**
|
|
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
|
|
* @param string $table_name
|
|
* @return array
|
|
*/
|
|
private function get_current_columns($table_name)
|
|
{
|
|
$res = sql_query("SHOW COLUMNS FROM `{$table_name}`", false);
|
|
$columns = [];
|
|
while ($row = sql_fetch_array($res)) {
|
|
$columns[$row['Field']] = true;
|
|
}
|
|
return $columns;
|
|
}
|
|
|
|
/**
|
|
* CREATE TABLE SQL 구문에서 테이블명과 컬럼 정의를 파싱합니다.
|
|
* @param string $query CREATE TABLE 구문
|
|
* @return array|null
|
|
*/
|
|
private function parse_create_table_sql($query)
|
|
{
|
|
$table_name = '';
|
|
if (preg_match('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?(\w+)`?/i', $query, $matches)) {
|
|
$table_name = $matches[1];
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// 괄호 안의 내용만 추출
|
|
$start = strpos($query, '(');
|
|
$end = strrpos($query, ')');
|
|
if ($start === false || $end === false) {
|
|
return ['name' => $table_name, 'columns' => []];
|
|
}
|
|
$content = substr($query, $start + 1, $end - $start - 1);
|
|
|
|
// 줄 단위로 분리
|
|
$lines = explode("\n", $content);
|
|
|
|
$columns = [];
|
|
foreach ($lines as $line) {
|
|
$line = trim($line, " \t\n\r\0\x0B,"); // 양쪽 공백과 마지막 쉼표 제거
|
|
|
|
// 컬럼 정의 라인인지 확인 (첫 단어가 `column_name` 형태)
|
|
if (preg_match('/^`(\w+)`\s+(.*)/i', $line, $match)) {
|
|
$col_name = $match[1];
|
|
$col_definition = $match[2];
|
|
$columns[$col_name] = $col_definition;
|
|
}
|
|
}
|
|
|
|
return ['name' => $table_name, 'columns' => $columns];
|
|
}
|
|
} |