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'][] = "{$table_name} 테이블 생성 실패: " . 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'][] = "{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패: " . 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]; } }