PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Migration/Adapter/Mysql.php

https://github.com/grjones/qframe
PHP | 331 lines | 172 code | 27 blank | 132 comment | 23 complexity | d090dae4b0be0edfb3ed0ddedd2e6d81 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of QFrame.
  4. *
  5. * QFrame is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * QFrame is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. * @category Migration
  19. * @package Migration
  20. * @copyright Copyright (c) 2007, 2008, 2009, 2010, 2011 Collaborative Software Foundation (CSF)
  21. * @license http://www.gnu.org/licenses/agpl-3.0.txt GNU Affero General Public License v3
  22. */
  23. /**
  24. * @category Migration
  25. * @package Migration
  26. * @copyright Copyright (c) 2007, 2008, 2009, 2010, 2011 Collaborative Software Foundation (CSF)
  27. * @license http://www.gnu.org/licenses/agpl-3.0.txt GNU Affero General Public License v3
  28. */
  29. abstract class Migration_Adapter_Mysql extends Migration_Adapter {
  30. /**
  31. * Expands a format like 2M, 2K, 2G, etc. to actual bytes
  32. *
  33. * @param string shorthand notated numeric string
  34. * @return integer
  35. */
  36. private static function expandNumericString($string) {
  37. $string = preg_replace('/K$/', '000', $string);
  38. $string = preg_replace('/M$/', '000000', $string);
  39. $string = preg_replace('/G$/', '000000000', $string);
  40. if(!is_numeric($string)) throw new Exception("Invalid number string [{$string}]");
  41. return intVal($string);
  42. }
  43. /**
  44. * Maps abtract types to the correct MySQL type
  45. *
  46. * @param string type to be mapped
  47. * @param integer (optional) limit
  48. * @return string
  49. */
  50. public function mapType($type, $limit = null) {
  51. switch(strtolower($type)) {
  52. case 'integer':
  53. switch(true) {
  54. case $limit === null:
  55. return 'INT';
  56. case $limit <= 4:
  57. return 'TINYINT';
  58. case $limit <= 6:
  59. return 'SMALLINT';
  60. case $limit <= 9:
  61. return 'MEDIUMINT';
  62. case $limit <= 11:
  63. return 'INT';
  64. default:
  65. return 'BIGINT';
  66. }
  67. case 'decimal':
  68. case 'float':
  69. case 'datetime':
  70. case 'date':
  71. case 'timestamp':
  72. case 'time':
  73. return strtoupper($type);
  74. case 'string':
  75. $limit = ($limit) ? $limit : 255;
  76. return "VARCHAR({$limit})";
  77. case 'text':
  78. if($limit !== null) $limit = self::expandNumericString($limit);
  79. switch(true) {
  80. case $limit === null:
  81. return 'TEXT';
  82. case $limit <= 255:
  83. return 'TINYTEXT';
  84. case $limit <= 65535:
  85. return 'TEXT';
  86. case $limit <= 16777215:
  87. return 'MEDIUMTEXT';
  88. default:
  89. return 'LONGTEXT';
  90. }
  91. case 'binary':
  92. if($limit !== null) $limit = self::expandNumericString($limit);
  93. switch(true) {
  94. case $limit === null:
  95. return 'BLOB';
  96. case $limit <= 255:
  97. return 'TINYBLOB';
  98. case $limit <= 65535:
  99. return 'BLOB';
  100. case $limit <= 16777215:
  101. return 'MEDIUMBLOB';
  102. default:
  103. return 'LONGBLOB';
  104. }
  105. case 'boolean':
  106. return 'TINYINT(1)';
  107. default:
  108. throw new Exception("Unknown abstract type '{$type}'");
  109. }
  110. }
  111. /**
  112. * Convert an array of column elements into the text to create that column
  113. *
  114. * @param array column parameters
  115. * @return string
  116. */
  117. public function generateColumn($column, array $options) {
  118. // check column array count to make sure there is enough data
  119. if(count($column) < 2)
  120. throw new Exception("Invalid column specifier " . '[ "' . implode('","', $column) . '" ]');
  121. // merge default column values with some sensible defaults
  122. if(count($column) == 2) $column[2] = array();
  123. $colOpts = array_merge(array(
  124. 'limit' => null,
  125. 'default' => null,
  126. 'null' => false
  127. ), $column[2]);
  128. // set up some variables so that the column string we build will make more sense
  129. $unquotedName = $column[0];
  130. $name = $this->dbAdapter->quoteIdentifier($column[0]);
  131. $type = $this->mapType($column[1], $colOpts['limit']);
  132. $default = (isset($colOpts['default'])) ? $this->dbAdapter->quote($colOpts['default']) : null;
  133. $null = (isset($colOpts['null']) && !$colOpts['null']) ? false : true;
  134. // build the column string
  135. $column = "{$name} {$type}";
  136. if($default !== null) $column .= " DEFAULT {$default}";
  137. if(!$null) $column .= ' NOT NULL';
  138. if($options['primary'] === $unquotedName && (!isset($options['auto']) || $options['auto'])) {
  139. $column .= ' AUTO_INCREMENT';
  140. }
  141. // return the built column
  142. return $column;
  143. }
  144. /**
  145. * Create a new table
  146. *
  147. * @param string table name
  148. * @param array options for creating the table
  149. * @param array array of columns (which is each an array)
  150. * @return boolean
  151. */
  152. public function createTable($name, array $options, array $columns) {
  153. // check for a 'primary' option and act appropriately based on what its value is
  154. if(!isset($options['primary'])) {
  155. array_unshift($columns, array('id', 'integer', array('null' => false)));
  156. $options['primary'] = 'id';
  157. }
  158. // check for a valid primary key
  159. if($options['primary'] && !$this->validPrimaryKey($options['primary'], $columns)) {
  160. die("Specified primary key column does not exist [{$options['primary']}]\n\n");
  161. }
  162. // if $options['primary'] is an array, join it all up, otherwise just quote it
  163. if(is_array($options['primary'])) {
  164. $adapter = $this->dbAdapter;
  165. $quotedKeys = array_map(array($adapter, 'quoteIdentifier'), $options['primary']);
  166. $primary = implode(',', $quotedKeys);
  167. }
  168. elseif($options['primary']) {
  169. $primary = $this->dbAdapter->quoteIdentifier($options['primary']);
  170. }
  171. // build up the column definitions
  172. $query = "CREATE TABLE {$this->dbAdapter->quoteIdentifier($name)} (\n";
  173. foreach($columns as $column) {
  174. $columnStrings[] = ' ' . $this->generateColumn($column, $options);
  175. }
  176. // add a "column definition" for a primary key if necessary
  177. if($options['primary']) {
  178. $columnStrings[] = " PRIMARY KEY({$primary})";
  179. }
  180. // explode column definitions into SQL, tack on the last little bit, and run the query
  181. $query .= implode(",\n", $columnStrings);
  182. $query .= "\n) ENGINE InnoDB DEFAULT CHARSET='utf8'";
  183. return $this->dbAdapter->query($query);
  184. }
  185. /**
  186. * Check to make sure that the passed in primary key is a valid column
  187. *
  188. * @param array|string primary key name
  189. * @param array column list
  190. * @return boolean
  191. */
  192. protected final function validPrimaryKey($key, $columns) {
  193. foreach($columns as $column) $columnNames[] = $column[0];
  194. if(is_array($key)) {
  195. foreach($key as $k) {
  196. if(!in_array($k, $columnNames)) return false;
  197. }
  198. }
  199. else {
  200. if(!in_array($key, $columnNames)) return false;
  201. }
  202. return true;
  203. }
  204. /**
  205. * Drop a table
  206. *
  207. * @param string table name
  208. * @return boolean
  209. */
  210. public function dropTable($table) {
  211. $query = "DROP TABLE {$this->dbAdapter->quoteIdentifier($table)}";
  212. return $this->dbAdapter->query($query);
  213. }
  214. /**
  215. * Add an index to an existing table
  216. *
  217. * @param string table we are adding the index to
  218. * @param array list of columns being indexed
  219. * @param array (optional) list of options to use when generating the index
  220. * @return boolean
  221. */
  222. public function createIndex($table, array $columns, array $options = array()) {
  223. // merge the options we got with a default set of options
  224. $options = array_merge(array(
  225. 'name' => strtolower(implode('_', $columns)) . '_index',
  226. 'unique' => false,
  227. ), $options);
  228. $unique = $options['unique'] ? 'UNIQUE' : '';
  229. // quote columns as identifiers
  230. foreach($columns as $column) $quotedColumns[] = $this->dbAdapter->quoteIdentifier($column);
  231. $quotedColumns = implode(',', $quotedColumns);
  232. // build the query
  233. $query = "CREATE {$unique} INDEX {$this->dbAdapter->quoteIdentifier($options['name'])}\n";
  234. if(isset($options['type'])) $query .= " USING {$options['type']}\n";
  235. $query .= " ON {$this->dbAdapter->quoteIdentifier($table)}({$quotedColumns})";
  236. // run the query and return
  237. return $this->dbAdapter->query($query);
  238. }
  239. /**
  240. * Drop an existing index
  241. *
  242. * @param string table we are dropping the index from
  243. * @param array|string list of columns being indexed OR explicit name of the index
  244. * @return boolean
  245. */
  246. public function dropIndex($table, $columns) {
  247. if(is_array($columns)) $columns = strtolower(implode('_', $columns)) . '_index';
  248. $name = $this->dbAdapter->quoteIdentifier($columns);
  249. $table = $this->dbAdapter->quoteIdentifier($table);
  250. return $this->dbAdapter->query("DROP INDEX {$name} ON {$table}");
  251. }
  252. /**
  253. * Add a column to an existing table
  254. *
  255. * @param string table name
  256. * @param string new column name
  257. * @param string new column type
  258. * @param array column definition
  259. * @return boolean
  260. */
  261. public function addColumn($table, $name, $type, array $options = array()) {
  262. $this->addOrModifyColumn(true, $table, $name, $type, $options);
  263. }
  264. /**
  265. * Remove a column from an existing table
  266. *
  267. * @param string table name
  268. * @param string column name
  269. * @return boolean
  270. */
  271. public function removeColumn($table, $name) {
  272. $table = $this->dbAdapter->quoteIdentifier($table);
  273. $column = $this->dbAdapter->quoteIdentifier($name);
  274. return $this->dbAdapter->query("ALTER TABLE {$table} DROP COLUMN {$column}");
  275. }
  276. /**
  277. * Alter an existing column
  278. *
  279. * @param string table name
  280. * @param string column name
  281. * @param string new column type
  282. * @param array column definition
  283. * @return boolean
  284. */
  285. public function alterColumn($table, $name, $type, array $options = array()) {
  286. $this->addOrModifyColumn(false, $table, $name, $type, $options);
  287. }
  288. /**
  289. * Add a new column or modify an existing column
  290. *
  291. * @param boolean is this a new column?
  292. * @param string table name
  293. * @param string column name
  294. * @param string new column type
  295. * @param array column definition
  296. * @return boolean
  297. */
  298. private function addOrModifyColumn($new, $table, $name, $type, array $options) {
  299. $verb = ($new) ? 'ADD' : 'MODIFY';
  300. $column = $this->generateColumn(array($name, $type, $options), array('primary' => false));
  301. $table = $this->dbAdapter->quoteIdentifier($table);
  302. return $this->dbAdapter->query("ALTER TABLE {$table} {$verb} COLUMN {$column}");
  303. }
  304. }