zdb.class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * The zdb library of zentaopms, can be used to bakup and restore a database.
  4. *
  5. * @copyright Copyright 2009-2015 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yidong Wang <yidong@cnezsoft.com>
  8. * @package Zdb
  9. * @version $Id$
  10. * @link http://www.zentao.net
  11. */
  12. class zdb
  13. {
  14. /**
  15. * dbh
  16. *
  17. * @var object
  18. * @access public
  19. */
  20. public $dbh;
  21. /**
  22. * Construct
  23. *
  24. * @access public
  25. * @return void
  26. */
  27. public function __construct()
  28. {
  29. global $dbh;
  30. $this->dbh = $dbh;
  31. }
  32. /**
  33. * Get all tables.
  34. *
  35. * @param string $type base|view. if type is 'base', just get base table.
  36. * @access public
  37. * @return array
  38. */
  39. public function getAllTables($type = 'base')
  40. {
  41. global $config;
  42. $allTables = array();
  43. $sql = 'show full tables';
  44. if($config->db->driver == 'dm') $sql = "select OBJECT_NAME AS Tables_in_{$config->db->name}, OBJECT_TYPE as Table_type from all_objects where owner='{$config->db->name}' and OBJECT_TYPE in('TABLE','VIEW');";
  45. $stmt = $this->dbh->query($sql);
  46. while($table = $stmt->fetch(PDO::FETCH_ASSOC))
  47. {
  48. $tableType = strtolower($table['Table_type']);
  49. if($type == 'base' && $tableType != 'base table' && $tableType != 'table') continue;
  50. if($type == 'view' && $tableType != 'view') continue;
  51. $tableName = $table["Tables_in_{$config->db->name}"];
  52. $allTables[$tableName] = $tableType == 'base table' ? 'table' : $tableType;
  53. }
  54. return $allTables;
  55. }
  56. /**
  57. * Get table fields.
  58. *
  59. * @param string $table
  60. * @access public
  61. * @return array
  62. */
  63. public function getTableFields($table)
  64. {
  65. try
  66. {
  67. $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
  68. $sql = "DESC $table";
  69. $rawFields = $this->dbh->query($sql)->fetchAll();
  70. $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
  71. }
  72. catch (PDOException $e)
  73. {
  74. global $dao;
  75. $dao->sqlError($e);
  76. }
  77. $fields = array();
  78. foreach($rawFields as $field) $fields[$field->field] = $field;
  79. return $fields;
  80. }
  81. /**
  82. * Diff current table fields with a fields array.
  83. *
  84. * @param string $table
  85. * @param array $fields
  86. * @access public
  87. * @return array
  88. */
  89. public function diffTable($table, $fields)
  90. {
  91. $tableFields = $this->getTableFields($table);
  92. $diff = array_udiff_assoc($fields, $tableFields,
  93. function($a, $b)
  94. {
  95. return (array)$a == (array)$b ? 0 : 1;
  96. }
  97. );
  98. return $diff;
  99. }
  100. /**
  101. * Add a column to a table, or modify a existing column.
  102. *
  103. * @param string $table
  104. * @param object $column
  105. * @param boolean $add if true, add $column as a new column, otherwise modify a existing column to $column.
  106. * @access public
  107. * @return object
  108. */
  109. public function updateColumn($table, $column, $add = true)
  110. {
  111. $return = new stdclass();
  112. $return->result = true;
  113. $return->error = '';
  114. $query = "ALTER TABLE `$table` " . ($add ? 'ADD' : 'MODIFY COLUMN') . " `$column->field` $column->type" . ($column->null == 'NO' ? ' NOT NULL' : '') . (is_null($column->default) ? '' : " DEFAULT '$column->default'") . (empty($column->extra) ? '' : " $column->extra") . ';';
  115. try
  116. {
  117. $this->dbh->exec($query);
  118. return $return;
  119. }
  120. catch(PDOException $e)
  121. {
  122. $return->result = false;
  123. $return->error = $e->getMessage();
  124. $return->sql = $query;
  125. return $return;
  126. }
  127. }
  128. /**
  129. * Create a table with fields.
  130. *
  131. * @param string $name
  132. * @param array $fields
  133. * @access public
  134. * @return object
  135. */
  136. public function createTable($name, $fields)
  137. {
  138. $return = new stdclass();
  139. $return->result = true;
  140. $return->error = '';
  141. $createTableQuery = "CREATE TABLE `$name` (";
  142. foreach($fields as $field)
  143. {
  144. $createColumnQuery = "`$field->field` $field->type" . ($field->null == 'NO' ? ' NOT NULL' : '') . (is_null($field->default) ? '' : " DEFAULT '$field->default'") . (empty($field->extra) ? '' : " $field->extra") . ", ";
  145. if(!empty($field->key))
  146. {
  147. if($field->key === 'PRI') $createColumnQuery .= "PRIMARY KEY (`{$field->field}`), ";
  148. if($field->key === 'MUL') $createColumnQuery .= "KEY `{$field->field}` (`{$field->field}`), ";
  149. if($field->key === 'UNI') $createColumnQuery .= "UNIQUE KEY `{$field->field}` (`{$field->field}`), ";
  150. }
  151. $createTableQuery .= $createColumnQuery;
  152. }
  153. $createTableQuery = rtrim($createTableQuery, ', ');
  154. $createTableQuery .= ") ENGINE=MyISAM DEFAULT CHARSET=utf8;";
  155. try
  156. {
  157. $this->dbh->exec($createTableQuery);
  158. return $return;
  159. }
  160. catch(PDOException $e)
  161. {
  162. $return->result = false;
  163. $return->error = $e->getMessage();
  164. $return->sql = $createTableQuery;
  165. return $return;
  166. }
  167. }
  168. /**
  169. * Dump db.
  170. *
  171. * @param string $fileName
  172. * @param array $tables
  173. * @access public
  174. * @return object
  175. */
  176. public function dump($fileName, $tables = array())
  177. {
  178. /* Init the return. */
  179. $return = new stdclass();
  180. $return->result = true;
  181. $return->error = '';
  182. /* Get all tables in database. */
  183. $allTables = $this->getAllTables();
  184. /* Dump all tables when tables is empty. */
  185. if(empty($tables))
  186. {
  187. $tables = $allTables;
  188. }
  189. else
  190. {
  191. foreach($tables as $table) $tables[$table] = $allTables[$table];
  192. }
  193. /* Check file. */
  194. if(empty($fileName))
  195. {
  196. $return->result = false;
  197. $return->error = 'Has not file';
  198. return $return;
  199. }
  200. if(!is_writable(dirname($fileName)))
  201. {
  202. $return->result = false;
  203. $return->error = 'The directory is not writable';
  204. return $return;
  205. }
  206. global $config;
  207. /* Open this file. */
  208. $fp = fopen($fileName, 'w');
  209. fwrite($fp, "SET NAMES {$config->db->encoding};\n");
  210. $this->dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
  211. foreach($tables as $table => $tableType)
  212. {
  213. /* Check table exists. */
  214. if(!isset($allTables[$table])) continue;
  215. /* Create sql code. */
  216. $backupSql = "DROP " . strtoupper($tableType) . " IF EXISTS `$table`;\n";
  217. $desc = $this->dbh->query("desc `$table`")->fetchAll();
  218. if(empty($desc)) continue;
  219. $schemaSQL = $this->getSchemaSQL($table, $tableType);
  220. if($schemaSQL->result) $backupSql .= $schemaSQL->sql;
  221. fwrite($fp, $backupSql);
  222. if($tableType != 'table') continue;
  223. $nullFields = array();
  224. foreach($desc as $field) $nullFields[$field->Field] = ($field->Null == 'YES' || $field->Null == 'Y');
  225. /* Create key sql for insert. */
  226. $fields = "`" . join('`,`', array_map('addslashes', array_keys($nullFields))) . "`";
  227. $rows = $this->dbh->query("select * from `$table`");
  228. $values = array();
  229. $batchNum = 200;
  230. while($row = $rows->fetch(PDO::FETCH_ASSOC))
  231. {
  232. /* Create a value sql. */
  233. $row = array_map('addslashes', $row);
  234. $value = array();
  235. foreach($row as $fieldName => $fieldValue)
  236. {
  237. $length = strlen($fieldValue);
  238. $fieldValue = "'{$fieldValue}'";
  239. if($length == 0 and !empty($nullFields[$fieldName])) $fieldValue = 'null';
  240. $value[] = $fieldValue;
  241. }
  242. $values[] = '(' . join(',', $value) . ')';
  243. if(count($values) == $batchNum)
  244. {
  245. /* Write sql code. */
  246. fwrite($fp, "INSERT INTO `$table`($fields) VALUES " . implode(",\n", $values) . ";\n");
  247. $values = array();
  248. }
  249. }
  250. if($values) fwrite($fp, "INSERT INTO `$table`($fields) VALUES " . implode(",\n", $values) . ";\n");
  251. }
  252. /* Get all views in database. */
  253. $allViews = $this->getAllTables('view');
  254. foreach($allViews as $table => $tableType)
  255. {
  256. $createView = $this->dbh->query("show create view `$table`")->fetch(PDO::FETCH_ASSOC);
  257. if($createView && isset($createView['Create View']))
  258. {
  259. $backupSql = "DROP VIEW IF EXISTS `$table`;\n";
  260. $backupSql .= $createView['Create View'] . ";\n";
  261. }
  262. fwrite($fp, $backupSql);
  263. }
  264. fclose($fp);
  265. return $return;
  266. }
  267. /**
  268. * Import DB
  269. *
  270. * @param string $fileName
  271. * @access public
  272. * @return object
  273. */
  274. public function import($fileName)
  275. {
  276. $return = new stdclass();
  277. $return->result = true;
  278. $return->error = '';
  279. if(!file_exists($fileName))
  280. {
  281. $return->result = false;
  282. $return->error = "File is not exists";
  283. return $return;
  284. }
  285. $fp = fopen($fileName, 'r');
  286. $sql = '';
  287. $startTags = '^DROP TABLE|^CREATE TABLE|^INSERT INTO|^SET|^DROP VIEW|^CREATE .*VIEW';
  288. $isInsert = false;
  289. while(!feof($fp))
  290. {
  291. $line = fgets($fp);
  292. if(empty($line)) continue;
  293. $sqlStart = false;
  294. $sqlEnd = false;
  295. $execSQL = false;
  296. if(empty($sql) and preg_match("/{$startTags}/", $line)) $sqlStart = true;
  297. if(!$isInsert and $sqlStart and strpos($line, 'INSERT INTO') === 0) $isInsert = true;
  298. $endTag = $isInsert ? "[^\\\]\'\);$" : ";$";
  299. if(preg_match("/{$endTag}/", $line)) $sqlEnd = true;
  300. if(!$sqlEnd && $isInsert && preg_match('/\,\s*null\);$/', $line)) $sqlEnd = true;
  301. if($sqlStart && $sqlEnd) // Only one line sql. e.g. DROP TABLE IF EXISTS `blog`;
  302. {
  303. $sql = $line;
  304. $execSQL = true;
  305. }
  306. elseif($sqlStart && !$sqlEnd) // Start sql line. e.g. CREATE TABLE `zt_account` (
  307. {
  308. $sql = $line;
  309. $execSQL = false;
  310. }
  311. elseif(!$sqlStart && !$sqlEnd) // Not start and not end. e.g. `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  312. {
  313. $sql .= $line;
  314. $execSQL = false;
  315. }
  316. elseif($sqlEnd) // More line sql, and end line. e.g. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
  317. {
  318. $sql .= $line;
  319. $execSQL = true;
  320. }
  321. if($execSQL)
  322. {
  323. try
  324. {
  325. $this->dbh->exec($sql);
  326. }
  327. catch(PDOException $e)
  328. {
  329. $return->result = false;
  330. $return->error .= $e->getMessage() . "\n";
  331. }
  332. $sql = '';
  333. $isInsert = false;
  334. }
  335. }
  336. return $return;
  337. }
  338. /**
  339. * Get schema SQL.
  340. *
  341. * @param string $table
  342. * @access public
  343. * @return object
  344. */
  345. public function getSchemaSQL($table, $type = 'table')
  346. {
  347. $return = new stdclass();
  348. $return->result = true;
  349. $return->error = '';
  350. try
  351. {
  352. $sql = "SHOW CREATE $type `$table`";
  353. $createSql = $this->dbh->query($sql)->fetch(PDO::FETCH_ASSOC);
  354. $return->sql = $createSql['Create ' . ucfirst($type)] . ";\n";
  355. return $return;
  356. }
  357. catch(PDOException $e)
  358. {
  359. $return->result = false;
  360. $return->error = $e->getMessage();
  361. return $return;
  362. }
  363. }
  364. /**
  365. * Add slashes for string or string list.
  366. *
  367. * @param string|string[] $data
  368. * @return string|string[]
  369. */
  370. public function addslashes($data)
  371. {
  372. if(is_string($data)) return addslashes($data);
  373. $arrayIsList = function (array $array) : bool {
  374. if (function_exists('array_is_list')) {
  375. return array_is_list($array);
  376. }
  377. if ($array === []) {
  378. return true;
  379. }
  380. $current_key = 0;
  381. foreach ($array as $key => $noop) {
  382. if ($key !== $current_key) {
  383. return false;
  384. }
  385. ++$current_key;
  386. }
  387. return true;
  388. };
  389. if((function_exists('array_is_list') && $arrayIsList($data)) || (is_array($data) && array_keys($data) === array_keys(array_keys($data))))
  390. {
  391. $result = array();
  392. foreach($data as $item)
  393. {
  394. if(is_string($item))
  395. $result[] = addslashes($item);
  396. elseif(is_null($item))
  397. $result[] = null;
  398. else
  399. $result[] = $item;
  400. }
  401. return $result;
  402. }
  403. return $data;
  404. }
  405. }