database_mysql_dump.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <?php
  2. // $Id: database_mysql_dump.inc,v 1.20 2010/09/04 00:24:29 sun Exp $
  3. // Some older mysql client libs are missing this constant.
  4. if (!defined('MYSQLI_BINARY_FLAG')) {
  5. define('MYSQLI_BINARY_FLAG', 128);
  6. }
  7. /**
  8. * Dump active database.
  9. *
  10. * @param $filename
  11. * The filename including path to write the dump to.
  12. * @param $options
  13. * An associative array of snapshot options, as described in demo_dump().
  14. */
  15. function demo_dump_db($filename, $options = array()) {
  16. // Make sure we have permission to save our backup file.
  17. $directory = dirname($filename);
  18. if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
  19. return FALSE;
  20. }
  21. if ($fp = fopen($filename, 'wb')) {
  22. $header = array();
  23. $header[] = '-- Demo module database dump';
  24. $header[] = '-- Version ' . DEMO_DUMP_VERSION;
  25. $header[] = '-- http://drupal.org/project/demo';
  26. $header[] = '--';
  27. $header[] = '-- Database: ' . _demo_get_database();
  28. $header[] = '-- Date: ' . format_date(REQUEST_TIME, 'small');
  29. $header[] = '-- Server version: ' . db_query('SELECT version()')->fetchField();
  30. $header[] = '-- PHP version: ' . PHP_VERSION;
  31. $header[] = '-- Drupal version: ' . VERSION;
  32. // Avoid auto value for zero values (required for user id 0).
  33. $header[] = '';
  34. $header[] = 'SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";';
  35. // Temporarily disable foreign key checks for the time of import.
  36. $header[] = 'SET FOREIGN_KEY_CHECKS = 0;';
  37. $header[] = '';
  38. // Set collations for the import. PMA and mysqldump use conditional comments
  39. // to exclude MySQL <4.1, but D6 requires >=4.1.
  40. $header[] = 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;';
  41. $header[] = 'SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;';
  42. $header[] = 'SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION;';
  43. $header[] = 'SET NAMES utf8;';
  44. $header[] = '';
  45. fwrite($fp, implode("\n", $header));
  46. foreach ($options['tables'] as $table => $dump_options) {
  47. if (!_demo_table_is_view($table)) {
  48. if ($dump_options['schema']) {
  49. _demo_dump_table_schema($fp, $table);
  50. }
  51. if ($dump_options['data']) {
  52. _demo_dump_table_data($fp, $table);
  53. }
  54. }
  55. }
  56. $footer = array();
  57. $footer[] = '';
  58. // Re-enable foreign key checks.
  59. $footer[] = 'SET FOREIGN_KEY_CHECKS = 1;';
  60. // Revert collations for potential subsequent database queries not belonging
  61. // to this module.
  62. // @todo Double-check this behavior according to the results of
  63. // http://drupal.org/node/772678
  64. $footer[] = 'SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT;';
  65. $footer[] = 'SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS;';
  66. $footer[] = 'SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION;';
  67. $footer[] = '';
  68. $footer[] = '';
  69. fwrite($fp, implode("\n", $footer));
  70. fclose($fp);
  71. return TRUE;
  72. }
  73. return FALSE;
  74. }
  75. /**
  76. * Returns the name of the active database.
  77. */
  78. function _demo_get_database() {
  79. $database = array_keys(db_query('SHOW TABLES')->fetchAssoc());
  80. $database = preg_replace('/^Tables_in_/i', '', $database[0]);
  81. return $database;
  82. }
  83. /**
  84. * Enumerate database tables.
  85. */
  86. function _demo_enum_tables() {
  87. return db_query('SHOW TABLES')->fetchCol();
  88. }
  89. /**
  90. * Dump table schema.
  91. *
  92. * @param $fp
  93. * The file handle of the output file.
  94. * @param $table
  95. * A table name to export the schema for.
  96. */
  97. function _demo_dump_table_schema($fp, $table) {
  98. $output = "\n";
  99. $output .= "--\n";
  100. $output .= "-- Table structure for table '$table'\n";
  101. $output .= "--\n\n";
  102. $data = db_query("SHOW CREATE TABLE `$table`")->fetchAssoc();
  103. $status = db_query('SHOW TABLE STATUS LIKE :table', array(':table' => $table))->fetchAssoc();
  104. // Column keys in $status start with a lower-case letter in PDO and with a
  105. // upper-case letter otherwise. We convert all to lower-case.
  106. foreach ($status as $key => $value) {
  107. $key_lower = strtolower($key);
  108. if ($key[0] != $key_lower[0]) {
  109. $status[$key_lower] = $value;
  110. unset($status[$key]);
  111. }
  112. }
  113. // Add IF NOT EXISTS to CREATE TABLE, replace double quotes with MySQL quotes.
  114. $output .= preg_replace(
  115. array('/^CREATE TABLE/', '/"/'),
  116. array('CREATE TABLE IF NOT EXISTS', '`'),
  117. $data['create table']
  118. );
  119. // @todo Rethink the following code. Perhaps try to strip + parse the existing
  120. // table definition (after leading ")" on last line) and merge anything
  121. // missing into it, and re-append it again. There are too many differences
  122. // between MySQL 5.0 and 5.1+, and PHP mysql(i) and pdo_mysql extensions.
  123. // PDO is missing the table engine.
  124. if (!strpos($output, ' ENGINE=')) {
  125. $output .= ' ENGINE=' . $status['engine'];
  126. }
  127. // Always add charset and collation info to table definitions.
  128. // SHOW CREATE TABLE does not contain collation information, if the collation
  129. // is equal to the default collation of the connection. Since dumps can be
  130. // moved across servers, we need to ensure correct collations.
  131. // Note that [DEFAULT] CHARSET or [DEFAULT] CHARACTER SET is always contained
  132. // on MySQL 5.1, even if it is equal to the default.
  133. // This addition assumes that a collation specified for a table is taken over
  134. // for the table's columns. The MySQL manual does not state whether this is
  135. // the case, but manual tests confirmed that it works that way.
  136. // Like Drupal core, we need to enforce UTF8 as character set and
  137. // utf8_general_ci as default database collation, if not overridden via
  138. // settings.php.
  139. if (!strpos($output, 'COLLATE=')) {
  140. // Only if the collation contains a underscore, the first string up to the
  141. // first underscore is the character set.
  142. // @see PMA_exportDBCreate()
  143. if (strpos($status['collation'], '_')) {
  144. $collate = 'COLLATE=' . $status['collation'];
  145. }
  146. // If there is a character set defined already, just append the collation.
  147. if (strpos($output, 'CHARSET') || strpos($output, 'CHARACTER SET')) {
  148. // @todo This may also hit column definitions instead of the table
  149. // definition only. Should technically also be case-insensitive.
  150. $output = preg_replace('@((?:DEFAULT )?(?:CHARSET|CHARACTER SET) \w+)@', '$1 ' . $collate, $output);
  151. }
  152. else {
  153. $output .= ' DEFAULT CHARSET=utf8 ' . $collate;
  154. }
  155. }
  156. // Add the table comment, if any.
  157. if (!preg_match('@^\) .*COMMENT.+$@', $output) && !empty($status['comment'])) {
  158. // On PHP 5.2.6/Win32 with PDO MySQL 5.0 with InnoDB, the table comment has
  159. // a trailing "; InnoDB free: 84992 kB".
  160. $status['comment'] = preg_replace('@; InnoDB free: .+$@', '', $status['comment']);
  161. $output .= " COMMENT='" . $status['comment'] . "'";
  162. }
  163. // @todo Depends on whether we dump data and table existence on import.
  164. // if (!empty($status['auto_increment'])) {
  165. // $output .= ' AUTO_INCREMENT=' . $status['auto_increment'];
  166. // }
  167. $output .= ";\n";
  168. fwrite($fp, $output);
  169. }
  170. /**
  171. * Dump table data.
  172. *
  173. * This code has largely been stolen from the phpMyAdmin project.
  174. *
  175. * @param $fp
  176. * The file handle of the output file.
  177. * @param $table
  178. * A table name to export the data for.
  179. */
  180. function _demo_dump_table_data($fp, $table) {
  181. $output = "\n";
  182. $output .= "--\n";
  183. $output .= "-- Dumping data for table '$table'\n";
  184. $output .= "--\n\n";
  185. // Dump table data.
  186. $result = db_query("SELECT * FROM `$table`", array(), array('fetch' => PDO::FETCH_ASSOC));
  187. // Get table fields.
  188. if ($fields = _demo_get_fields($result)) {
  189. // Disable indices to speed up import.
  190. $output .= "/*!40000 ALTER TABLE $table DISABLE KEYS */;\n";
  191. // Escape backslashes, PHP code, special chars
  192. $search = array('\\', "'", "\x00", "\x0a", "\x0d", "\x1a");
  193. $replace = array('\\\\', "''", '\0', '\n', '\r', '\Z');
  194. $insert_cmd = "INSERT INTO `$table` VALUES\n";
  195. $insert_buffer = '';
  196. $current_row = 0;
  197. $query_size = 0;
  198. foreach ($result as $row) {
  199. $current_row++;
  200. $values = array();
  201. $field = 0;
  202. foreach ($row as $value) {
  203. // NULL
  204. if (!isset($value) || is_null($value)) {
  205. $values[] = 'NULL';
  206. }
  207. // A number
  208. // timestamp is numeric on some MySQL 4.1, BLOBs are sometimes numeric
  209. else if ($fields[$field]->numeric && !$fields[$field]->timestamp && !$fields[$field]->blob) {
  210. $values[] = $value;
  211. }
  212. // A true BLOB
  213. // - mysqldump only generates hex data when the --hex-blob
  214. // option is used, for fields having the binary attribute
  215. // no hex is generated
  216. // - a TEXT field returns type blob but a real blob
  217. // returns also the 'binary' flag
  218. else if ($fields[$field]->binary && $fields[$field]->blob) {
  219. // Empty blobs need to be different, but '0' is also empty :-(
  220. if (empty($value) && $value != '0') {
  221. $values[] = "''";
  222. }
  223. else {
  224. $values[] = '0x' . bin2hex($value);
  225. }
  226. }
  227. // Something else -> treat as a string
  228. else {
  229. $values[] = "'" . str_replace($search, $replace, $value) . "'";
  230. }
  231. $field++;
  232. }
  233. if ($current_row == 1) {
  234. $insert_buffer = $insert_cmd . '(' . implode(', ', $values) . ')';
  235. }
  236. else {
  237. $insert_buffer = '(' . implode(', ', $values) . ')';
  238. // Check if buffer size exceeds 50KB.
  239. if ($query_size + strlen($insert_buffer) > 50000) {
  240. // Flush to disc and start new buffer.
  241. fwrite($fp, $output . ";\n");
  242. $output = '';
  243. $current_row = 1;
  244. $query_size = 0;
  245. $insert_buffer = $insert_cmd . $insert_buffer;
  246. }
  247. }
  248. $query_size += strlen($insert_buffer);
  249. $output .= ($current_row == 1 ? '' : ",\n") . $insert_buffer;
  250. }
  251. if ($current_row > 0) {
  252. $output .= ";\n";
  253. }
  254. // Enable indices again.
  255. $output .= "/*!40000 ALTER TABLE $table ENABLE KEYS */;\n";
  256. }
  257. fwrite($fp, $output);
  258. }
  259. /**
  260. * Return table fields and their properties.
  261. */
  262. function _demo_get_fields($result) {
  263. $fields = array();
  264. switch (db_driver()) {
  265. case 'mysql':
  266. $i = 0;
  267. while ($meta = $result->getColumnMeta($i)) {
  268. settype($meta, 'object');
  269. // pdo_mysql does not add a native type for INT fields.
  270. if (isset($meta->native_type)) {
  271. // Enhance the field definition for mysql-extension compatibilty.
  272. $meta->numeric = (strtolower($meta->native_type) == 'short');
  273. $meta->blob = (strtolower($meta->native_type) == 'blob');
  274. // Add custom properties.
  275. $meta->timestamp = (strtolower($meta->native_type) == 'long');
  276. }
  277. else {
  278. $meta->numeric = $meta->blob = $meta->timestamp = FALSE;
  279. }
  280. $meta->binary = (array_search('not_null', $meta->flags));
  281. $fields[] = $meta;
  282. $i++;
  283. }
  284. break;
  285. }
  286. return $fields;
  287. }
  288. /**
  289. * Determine whether the given table is a VIEW.
  290. */
  291. function _demo_table_is_view($table) {
  292. static $tables = array();
  293. if (!isset($tables[$table])) {
  294. $status = db_query('SHOW TABLE STATUS LIKE :table', array(':table' => $table))->fetchAssoc();
  295. $tables[$table] = (strtoupper(substr($status['comment'], 0, 4)) == 'VIEW');
  296. }
  297. return $tables[$table];
  298. }