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

/source/solar/Solar/Sql/Adapter/Mysql.php

https://github.com/btweedy/foresmo
PHP | 464 lines | 182 code | 49 blank | 233 comment | 25 complexity | 6c8ff413206a7bc3a5c2b38fb57286a7 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * Class for MySQL behaviors.
  5. *
  6. * @category Solar
  7. *
  8. * @package Solar_Sql
  9. *
  10. * @author Paul M. Jones <pmjones@solarphp.com>
  11. *
  12. * @license http://opensource.org/licenses/bsd-license.php BSD
  13. *
  14. * @version $Id: Mysql.php 3988 2009-09-04 13:51:51Z pmjones $
  15. *
  16. */
  17. class Solar_Sql_Adapter_Mysql extends Solar_Sql_Adapter
  18. {
  19. /**
  20. *
  21. * Map of Solar generic types to RDBMS native types used when creating
  22. * portable tables.
  23. *
  24. * @var array
  25. *
  26. */
  27. protected $_solar_native = array(
  28. 'bool' => 'BOOLEAN',
  29. 'char' => 'CHAR',
  30. 'varchar' => 'VARCHAR',
  31. 'smallint' => 'SMALLINT',
  32. 'int' => 'INTEGER',
  33. 'bigint' => 'BIGINT',
  34. 'numeric' => 'DECIMAL',
  35. 'float' => 'DOUBLE',
  36. 'clob' => 'LONGTEXT',
  37. 'date' => 'DATE',
  38. 'time' => 'TIME',
  39. 'timestamp' => 'DATETIME'
  40. );
  41. /**
  42. *
  43. * Map of native RDBMS types to Solar generic types used when reading
  44. * table column information.
  45. *
  46. * Note that fetchTableCols() will programmatically convert TINYINT(1) to
  47. * 'bool' independent of this map.
  48. *
  49. * @var array
  50. *
  51. * @see fetchTableCols()
  52. *
  53. */
  54. protected $_native_solar = array(
  55. // numeric
  56. 'smallint' => 'smallint',
  57. 'int' => 'int',
  58. 'integer' => 'int',
  59. 'bigint' => 'bigint',
  60. 'dec' => 'numeric',
  61. 'decimal' => 'numeric',
  62. 'double' => 'float',
  63. // date & time
  64. 'date' => 'date',
  65. 'datetime' => 'timestamp',
  66. 'timestamp' => 'int',
  67. 'time' => 'time',
  68. // string
  69. 'national char' => 'char',
  70. 'nchar' => 'char',
  71. 'char' => 'char',
  72. 'binary' => 'char',
  73. 'national varchar' => 'varchar',
  74. 'nvarchar' => 'varchar',
  75. 'varchar' => 'varchar',
  76. 'varbinary' => 'varchar',
  77. // clob
  78. 'longtext' => 'clob',
  79. 'longblob' => 'clob',
  80. );
  81. /**
  82. *
  83. * The PDO adapter type.
  84. *
  85. * @var string
  86. *
  87. */
  88. protected $_pdo_type = 'mysql';
  89. /**
  90. *
  91. * The quote character before an entity name (table, index, etc).
  92. *
  93. * @var string
  94. *
  95. */
  96. protected $_ident_quote_prefix = '`';
  97. /**
  98. *
  99. * The quote character after an entity name (table, index, etc).
  100. *
  101. * @var string
  102. *
  103. */
  104. protected $_ident_quote_suffix = '`';
  105. /**
  106. *
  107. * Creates a PDO-style DSN.
  108. *
  109. * For example, "mysql:host=127.0.0.1;dbname=test"
  110. *
  111. * @param array $info An array with host, post, name, etc. keys.
  112. *
  113. * @return string A PDO-style DSN.
  114. *
  115. */
  116. protected function _buildDsn($info)
  117. {
  118. // the dsn info
  119. $dsn = array();
  120. // socket, or host-and-port? (can't use both.)
  121. if (! empty($info['sock'])) {
  122. // use a socket
  123. $dsn[] = 'unix_socket=' . $info['sock'];
  124. } else {
  125. // use host and port
  126. if (! empty($info['host'])) {
  127. $dsn[] = 'host=' . $info['host'];
  128. }
  129. if (! empty($info['port'])) {
  130. $dsn[] = 'port=' . $info['port'];
  131. }
  132. }
  133. // database name
  134. if (! empty($info['name'])) {
  135. $dsn[] = 'dbname=' . $info['name'];
  136. }
  137. // done
  138. return $this->_pdo_type . ':' . implode(';', $dsn);
  139. }
  140. /**
  141. *
  142. * Returns a list of all tables in the database.
  143. *
  144. * @return array All table names in the database.
  145. *
  146. */
  147. protected function _fetchTableList()
  148. {
  149. return $this->fetchCol('SHOW TABLES');
  150. }
  151. /**
  152. *
  153. * Returns an array describing the columns in a table.
  154. *
  155. * @param string $table The table name to fetch columns for.
  156. *
  157. * @return array An array of table column information.
  158. *
  159. */
  160. protected function _fetchTableCols($table)
  161. {
  162. // mysql> DESCRIBE table_name;
  163. // +--------------+--------------+------+-----+---------+-------+
  164. // | Field | Type | Null | Key | Default | Extra |
  165. // +--------------+--------------+------+-----+---------+-------+
  166. // | id | int(11) | | PRI | 0 | |
  167. // | created | varchar(19) | YES | MUL | NULL | |
  168. // | updated | varchar(19) | YES | MUL | NULL | |
  169. // | name | varchar(127) | | UNI | | |
  170. // | owner_handle | varchar(32) | YES | MUL | NULL | |
  171. // | subj | varchar(255) | YES | | NULL | |
  172. // | prefs | longtext | YES | | NULL | |
  173. // +--------------+--------------+------+-----+---------+-------+
  174. // strip non-word characters to try and prevent SQL injections,
  175. // then quote it to avoid reserved-word issues
  176. $table = preg_replace('/[^\w]/', '', $table);
  177. $table = $this->quoteName($table);
  178. // where the description will be stored
  179. $descr = array();
  180. // get the column descriptions
  181. $cols = $this->fetchAll("DESCRIBE $table");
  182. if (! $cols) {
  183. throw $this->_exception('ERR_QUERY_FAILED');
  184. }
  185. // loop through the result rows; each describes a column.
  186. foreach ($cols as $val) {
  187. $name = $val['field'];
  188. // override $type to find tinyint(1) as boolean
  189. if (strtolower($val['type']) == 'tinyint(1)') {
  190. $type = 'bool';
  191. $size = null;
  192. $scope = null;
  193. } else {
  194. list($type, $size, $scope) = $this->_getTypeSizeScope($val['type']);
  195. }
  196. // save the column description
  197. $descr[$name] = array(
  198. 'name' => $name,
  199. 'type' => $type,
  200. 'size' => ($size ? (int) $size : null),
  201. 'scope' => ($scope ? (int) $scope : null),
  202. 'default' => $this->_getDefault($val['default']),
  203. 'require' => (bool) ($val['null'] != 'YES'),
  204. 'primary' => (bool) ($val['key'] == 'PRI'),
  205. 'autoinc' => (bool) (strpos($val['extra'], 'auto_increment') !== false),
  206. );
  207. // don't keep "size" for integers
  208. if (substr($type, -3) == 'int') {
  209. $descr[$name]['size'] = null;
  210. }
  211. }
  212. // done!
  213. return $descr;
  214. }
  215. /**
  216. *
  217. * Returns an array of index information for a table.
  218. *
  219. * @param string $table The table name to fetch indexes for.
  220. *
  221. * @return array An array of table indexes.
  222. *
  223. */
  224. protected function _fetchIndexInfo($table)
  225. {
  226. // strip non-word characters to try and prevent SQL injections,
  227. // then quote it to avoid reserved-word issues
  228. $table = preg_replace('/[^\w]/', '', $table);
  229. $table = $this->quoteName($table);
  230. // where the index info will be stored
  231. $info = array();
  232. // get all indexed columns
  233. $list = $this->fetchAll("SHOW INDEXES IN $table");
  234. if (! $list) {
  235. // no indexes
  236. return array();
  237. }
  238. // collect indexes
  239. foreach ($list as $item) {
  240. // index name?
  241. $name = $item['key_name'];
  242. // skip primary-key indexes
  243. if ($name == 'PRIMARY') {
  244. continue;
  245. }
  246. // unique?
  247. if ($item['non_unique']) {
  248. $info[$name]['type'] = 'normal';
  249. } else {
  250. $info[$name]['type'] = 'unique';
  251. }
  252. // cols?
  253. $info[$name]['cols'][] = $item['column_name'];
  254. }
  255. // done!
  256. return $info;
  257. }
  258. /**
  259. *
  260. * Given a native column SQL default value, finds a PHP literal value.
  261. *
  262. * SQL NULLs are converted to PHP nulls. Non-literal values (such as
  263. * keywords and functions) are also returned as null.
  264. *
  265. * @param string $default The column default SQL value.
  266. *
  267. * @return scalar A literal PHP value.
  268. *
  269. */
  270. protected function _getDefault($default)
  271. {
  272. $upper = strtoupper($default);
  273. if ($upper == 'NULL' || $upper == 'CURRENT_TIMESTAMP') {
  274. // the only non-literal allowed by MySQL is "CURRENT_TIMESTAMP"
  275. return null;
  276. } else {
  277. // return the literal default
  278. return $default;
  279. }
  280. }
  281. /**
  282. *
  283. * Builds a CREATE TABLE command string.
  284. *
  285. * @param string $name The table name to create.
  286. *
  287. * @param string $cols The column definitions.
  288. *
  289. * @return string A CREATE TABLE command string.
  290. *
  291. */
  292. protected function _sqlCreateTable($name, $cols)
  293. {
  294. $stmt = parent::_sqlCreateTable($name, $cols);
  295. $stmt .= " TYPE=InnoDB"; // for transactions
  296. $stmt .= " DEFAULT CHARSET=utf8 COLLATE=utf8_bin"; // for UTF8
  297. return $stmt;
  298. }
  299. /**
  300. *
  301. * Drops an index.
  302. *
  303. * @param string $table The table of the index.
  304. *
  305. * @param string $name The full index name.
  306. *
  307. * @return void
  308. *
  309. */
  310. protected function _dropIndex($table, $name)
  311. {
  312. $table = $this->quoteName($table);
  313. $name = $this->quoteName($name);
  314. return $this->query("DROP INDEX $name ON $table");
  315. }
  316. /**
  317. *
  318. * Creates a sequence, optionally starting at a certain number.
  319. *
  320. * @param string $name The sequence name to create.
  321. *
  322. * @param int $start The first sequence number to return.
  323. *
  324. * @return void
  325. *
  326. */
  327. protected function _createSequence($name, $start = 1)
  328. {
  329. $start -= 1;
  330. $name = $this->quoteName($name);
  331. $this->query("CREATE TABLE $name (id INT NOT NULL) TYPE=InnoDB");
  332. return $this->query("INSERT INTO $name (id) VALUES ($start)");
  333. }
  334. /**
  335. *
  336. * Drops a sequence.
  337. *
  338. * @param string $name The sequence name to drop.
  339. *
  340. * @return void
  341. *
  342. */
  343. protected function _dropSequence($name)
  344. {
  345. $name = $this->quoteName($name);
  346. return $this->query("DROP TABLE IF EXISTS $name");
  347. }
  348. /**
  349. *
  350. * Gets a sequence number; creates the sequence if it does not exist.
  351. *
  352. * @param string $name The sequence name.
  353. *
  354. * @return int The next sequence number.
  355. *
  356. */
  357. protected function _nextSequence($name)
  358. {
  359. $cmd = "UPDATE " . $this->quoteName($name)
  360. . " SET id = LAST_INSERT_ID(id+1)";
  361. // first, try to increment the sequence number, assuming
  362. // the table exists.
  363. try {
  364. $this->query($cmd);
  365. } catch (Exception $e) {
  366. // error when updating the sequence.
  367. // assume we need to create it, then
  368. // try to increment again.
  369. $this->_createSequence($name);
  370. $this->query($cmd);
  371. }
  372. // get the sequence number
  373. return $this->lastInsertId();
  374. }
  375. /**
  376. *
  377. * Modifies the sequence name.
  378. *
  379. * MySQL doesn't have sequences, so this adapter uses a table instead.
  380. * This means we have to deconflict between "real" tables and tables being
  381. * used for sequences, so this method appends "__s" to the sequnce name.
  382. *
  383. * @param string $name The requested sequence name.
  384. *
  385. * @return string The modified sequence name.
  386. *
  387. */
  388. protected function _modSequenceName($name)
  389. {
  390. return $name . '__s';
  391. }
  392. /**
  393. *
  394. * Given a column definition, modifies the auto-increment and primary-key
  395. * clauses in place.
  396. *
  397. * @param string &$coldef The column definition as it is now.
  398. *
  399. * @param bool $autoinc Whether or not this is an auto-increment column.
  400. *
  401. * @param bool $primary Whether or not this is a primary-key column.
  402. *
  403. * @return void
  404. *
  405. */
  406. protected function _modAutoincPrimary(&$coldef, $autoinc, $primary)
  407. {
  408. if ($autoinc) {
  409. $coldef .= " AUTO_INCREMENT";
  410. }
  411. if ($primary) {
  412. $coldef .= " PRIMARY KEY";
  413. }
  414. }
  415. }