From 8cab7ed5a42a3952e71f472ab6f9d51f73f48bcd Mon Sep 17 00:00:00 2001 From: zhujinyong Date: Wed, 2 Aug 2023 13:59:50 +0800 Subject: [PATCH] + Tool for migrating MySQL to DMDB. --- tools/mysql2dm.php | 333 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 tools/mysql2dm.php diff --git a/tools/mysql2dm.php b/tools/mysql2dm.php new file mode 100644 index 0000000000..b19dca06fd --- /dev/null +++ b/tools/mysql2dm.php @@ -0,0 +1,333 @@ +#!/usr/bin/env php +db = new stdclass(); +$config->default = new stdclass(); +function getWebRoot() {} +include '../config/my.php'; + +if(!file_exists('dmconfig.php')) +{ + print("Please create the config file named 'dmconfig.php' in this directory. Config example:"); + print <<driver = 'dm'; +\$dmConfig->host = '10.0.7.242'; +\$dmConfig->port = '5236'; +\$dmConfig->name = 'lybiz85'; +\$dmConfig->user = 'SYSDBA'; +\$dmConfig->encoding = 'UTF8'; +\$dmConfig->prefix = 'zt_'; +\$dmConfig->password = 'SYSDBA001'; + + +EOT; + exit; +} + +echo "开始迁移MySQL数据到达梦数据库\n\n"; + +include 'dmconfig.php'; + +function formatSQL($sql) +{ + $fieldsBegin = stripos($sql, 'select'); + $fieldsEnd = stripos($sql, 'from'); + $fields = substr($sql, $fieldsBegin+6, $fieldsEnd-$fieldsBegin-6); + $fieldList = preg_split("/,(?![^(]+\))/", $fields); + foreach($fieldList as $key => $field) + { + $aliasPos = stripos($field, ' AS '); + $subField = substr($field, 0, $aliasPos); + if(stripos($field, 'SUM(') === 0) $subField = substr($subField, 4, -1); + + $fieldParts = preg_split("/\+(?![^(]+\))/", $subField); + foreach($fieldParts as $pkey => $fieldPart) + { + $originField = trim($fieldPart); + if(stripos($originField, 'if(') === false) continue; + $fieldParts[$pkey] = formatDmIfFunction($originField); + } + $fieldList[$key] = str_replace($subField, implode(' + ', $fieldParts), $field); + } + $fields = implode(',', $fieldList); + return substr($sql, 0, $fieldsBegin+6) . $fields . substr($sql, $fieldsEnd); +} + +function formatDmIfFunction($field) +{ + preg_match('/if\(.+\)+/i', $field, $matches); + + $if = $matches[0]; + if(substr_count($if, '(') == 1) + { + $pos = strpos($if, ')'); + $if = substr($if, 0, $pos+1); + } + + /* fix sum(if(..., 1, 0)) , count(if(..., 1, 0)) */ + if(substr($if, strlen($if)-2) == '))' and (stripos($field, 'sum(') == 0 or stripos($field, 'count(') == 0)) $if = substr($if, 0, strlen($if)-1); + + $parts = explode(',', substr($if, 3, strlen($if)-4)); // remove 'if(' and ')' + $case = 'CASE WHEN ' . implode(',', array_slice($parts, 0, count($parts)-2)) . ' THEN ' . $parts[count($parts)-2] . ' ELSE ' . $parts[count($parts)-1] . ' END'; + $field = str_ireplace($if, $case, $field); + + return $field; +} + +$mysql = new dbh($config->db); + +$stmt = $mysql->query('SHOW TABLES'); +$data = $stmt->fetchAll(PDO::FETCH_ASSOC); +$tables = array(); +$views = array(); + +$indexes = array(); //普通索引 +$tablePrimary = array(); //表的主键 + +foreach($data as $table) +{ + $table = current($table); + + /* Table. */ + if(strpos($table, 'zt_') === 0) + { + $stmt = $mysql->prepare('DESC ' . $table); + $stmt->execute(); + $fields = $stmt->fetchAll(PDO::FETCH_ASSOC); + + /* Table. */ + $tables[$table] = array(); + foreach($fields as $field) + { + $type = strtolower(explode('(', $field['Type'])[0]); + $type = trim(str_replace('unsigned', '', $type)); + switch($type) + { + case 'int': + case 'mediumint': + case 'smallint': + case 'tinyint': + $type = 'integer'; + break; + case 'enum': + case 'varchar': + case 'char': + $type = 'varchar(255)'; + break; + case 'mediumtext': + case 'longtext': + $type = 'text'; + break; + } + + $tables[$table][$field['Field']] = array( + 'type' => $type, + 'name' => $field['Field'], + 'null' => $field['Null'] == 'YES', + 'default' => $field['Default'], + //'isPrimary' => $field['Key'] == 'PRI', // 不需要从DESC读取索引 + 'autoIncrement' => $field['Extra'] == 'auto_increment', + ); + } + + /* Index. */ + $stmt = $mysql->prepare('SHOW INDEX FROM ' . $table); + $stmt->execute(); + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if($result) + { + foreach($result as $item) + { + $keyName = $item['Key_name']; + + /* 主键索引在创建表的时候一起创建。 */ + if($keyName == 'PRIMARY') + { + if(!isset($tablePrimary[$table])) $tablePrimary[$table] = array(); + $tablePrimary[$table][(int)$item['Seq_in_index']-1] = $item['Column_name']; + continue; + } + + /* 其他索引单独创建。 */ + if(!isset($indexes[$table . $keyName])) $indexes[$table . $keyName] = array('table' => $table, 'name' => $keyName, 'type' => $item['Index_type'], 'isUnique' => !$item['Non_unique'], 'cols' => array()); + + /* 达梦的全文索引只支持一个字段。 */ + if($item['Index_type'] == 'FULLTEXT' && $item['Column_name'] != 'title') continue; + + $indexes[$table . $keyName]['cols'][(int)$item['Seq_in_index']-1] = $item['Column_name']; + } + } + } + /* View. */ + elseif(strpos($table, 'ztv_') === 0) + { + $stmt = $mysql->prepare('SHOW CREATE VIEW ' . $table); + $stmt->execute(); + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + $pos = stripos($result[0]['Create View'], ' as '); + $sql = substr($result[0]['Create View'], $pos + 4); + $sql = str_replace('`', '"', $sql); + $views[$table] = array( + 'sql' => formatSQL($sql), + ); + } +} + +echo "初始化表空间和用户\n"; +$dm = new dbh($dmConfig, false); + +/* Delete. */ +$tableSpace = strtoupper($dmConfig->name); +$res = $dm->query("SELECT * FROM dba_users WHERE username = '{$dmConfig->name}'")->fetchAll(); +if(!empty($res)) +{ + $dropUser = "DROP USER \"{$dmConfig->name}\" CASCADE"; + $dm->rawQuery($dropUser); +} + +$tableSpace = strtoupper($dmConfig->name); +$res = $dm->query("SELECT * FROM dba_data_files WHERE TABLESPACE_NAME = '$tableSpace'")->fetchAll(); +if(!empty($res)) +{ + $dropTableSpace = "DROP TABLESPACE \"$tableSpace\""; + + $dm->rawQuery($dropTableSpace); +} + +/* Create. */ +$createTableSpace = "CREATE TABLESPACE \"$tableSpace\" DATAFILE '{$dmConfig->name}.DBF' size 150 AUTOEXTEND ON"; +$createUser = "CREATE USER \"{$dmConfig->name}\" IDENTIFIED by {$dmConfig->password} DEFAULT TABLESPACE \"{$dmConfig->name}\" DEFAULT INDEX TABLESPACE \"{$dmConfig->name}\""; + +$dm->query($createTableSpace); +$dm->query($createUser); + +$createSchema = "CREATE SCHEMA \"{$dmConfig->name}\" AUTHORIZATION \"{$dmConfig->name}\""; +$dm->query($createSchema); + +echo "生成达梦数据表结构\n"; +$dm = new dbh($dmConfig); + +$identityTables = array(); //标记有自增字段的表,插入数据的时候会判断 + +/* 插入表结构。 */ +foreach($tables as $table => $fields) +{ + $sql = "CREATE TABLE IF NOT EXISTS \"$table\" (\n"; + + foreach(array_values($fields) as $key => $field) + { + $sql .= '"' . $field['name'] . '" ' . $field['type'] . ($field['null'] ? ' NULL ' : ' NOT NULL '); + if($field['default'] !== NULL) $sql .= " DEFAULT '{$field['default']}' "; + + if($field['autoIncrement']) + { + $sql .= " IDENTITY(1, 1) "; + $identityTables[$table] = $table; + } + + $sql .= $key == count(array_values($fields))-1 && !isset($tablePrimary[$table]) ? "\n" : ",\n"; + } + + if(isset($tablePrimary[$table])) + { + $primary = array(); + ksort($tablePrimary[$table]); + foreach($tablePrimary[$table] as $col) $primary[] = '"' . $col . '"'; + + $sql .= "NOT CLUSTER PRIMARY KEY (" . implode(',', $primary) . ")"; // 只有NOT CLUSTER可以删除 + } + + $sql .= "\n)\n"; + + $dm->rawQuery($sql); +} + +echo "生成达梦表索引\n"; +foreach($indexes as $index) +{ + $name = strtolower($index['table']) . '_' . $index['name']; + foreach($index['cols'] as $col) + { + $fields = array(); + ksort($index['cols']); + foreach($index['cols'] as $col) + { + $fields[] = '"' . $col . '"'; + } + } + + $sql = 'CREATE '; + if($index['isUnique']) // 唯一索引 + { + $sql .= ' UNIQUE '; + } + elseif($index['type'] == 'FULLTEXT') // 全文索引 + { + $sql .= ' CONTEXT '; + } + + $sql .= ' INDEX "' . $name . '" ON `' . $index['table'] . '` (' . implode(',', $fields) . ')'; + + $dm->rawQuery($sql); +} + +echo "生成达梦视图\n"; +/* 插入视图结构。 */ +foreach($views as $view => $info) +{ + $sql = str_replace('convert(', '(', $info['sql']); + $sql = str_replace('using utf8mb3', '', $sql); + $sql = str_replace('using utf8_general_ci', '', $sql); + $dm->rawQuery("CREATE OR REPLACE VIEW \"$view\" AS " . $sql); +} + +echo "开始插入数据,请耐心等待..."; +foreach($tables as $table => $fields) +{ + $rows = $mysql->query("SELECT * FROM `$table`")->fetchAll(PDO::FETCH_ASSOC); + if(empty($rows)) continue; + + $fs = array(); + foreach($fields as $field) $fs[] = '"' . $field['name'] . '"'; + $fs = implode(',', $fs); + + $template = "INSERT INTO \"$table\"($fs) VALUES"; + + $step = 500; + $begin = 0; + $data = array(); + for($key = $begin; $key < count($rows); $key++) + { + $row = $rows[$key]; + $values = array(); + foreach($fields as $field) + { + $value = str_replace("'", "''''", $row[$field['name']]); + $value = "'{$value}'"; + if($value == "'0000-00-00 00:00:00'" || $value == "'0000-00-00'") + { + $null = $tables[$table][$field['name']]['null']; + $value = $null ? 'NULL': '1970-01-01'; + } + $values[] = $value; + } + $values = '(' . implode(',', $values) . ')'; + $data[] = $values; + } + + $sql = $template . implode(',', $data) . ';'; + + if(isset($identityTables[$table])) $sql = "SET IDENTITY_INSERT $table ON;" . $sql; + + $dm->rawQuery($sql); +} + +echo "\n迁移成功,修改my.php测试一下吧\n\n";