PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/sparkplug/plugs/sparkdb/adapters/mysql.php

https://code.google.com/p/sparkplug-framework/
PHP | 692 lines | 559 code | 103 blank | 30 comment | 31 complexity | f0f91cb023f8e52d03f3703fc993e59a MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. /*
  3. Copyright 2009-2012 Sam Weiss
  4. All Rights Reserved.
  5. This file is part of Spark/Plug.
  6. Spark/Plug is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program 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. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. if (!defined('spark/plug'))
  18. {
  19. header('HTTP/1.1 403 Forbidden');
  20. exit('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You don\'t have permission to access the requested resource on this server.</p></body></html>');
  21. }
  22. // -----------------------------------------------------------------------------
  23. class MySQLFunction_metadata extends SparkDBFunction_metadata implements iSparkDBQueryFunctionMetadata
  24. {
  25. protected function tables_sql()
  26. {
  27. return 'SHOW TABLES';
  28. }
  29. protected function columns_sql($table)
  30. {
  31. return "SELECT column_name FROM information_schema.columns WHERE table_name = '{$table}'";
  32. }
  33. }
  34. // -----------------------------------------------------------------------------
  35. class MySQLFunction_create_table extends SparkDBQueryFunction implements iSparkDBQueryFunctionCreateTable
  36. {
  37. protected static $_triggers = array
  38. (
  39. self::kForeignKeyTriggerDelete => 'DELETE',
  40. self::kForeignKeyTriggerUpdate => 'UPDATE',
  41. );
  42. protected static $_actions = array
  43. (
  44. self::kForeignKeyActionNone => 'NO ACTION',
  45. self::kForeignKeyActionCascade => 'CASCADE',
  46. self::kForeignKeyActionRestrict => 'RESTRICT',
  47. self::kForeignKeyActionSetNULL => 'SET NULL',
  48. );
  49. protected $_db;
  50. protected $_table;
  51. protected $_fields;
  52. protected $_primaryKey;
  53. protected $_foreignKeys;
  54. protected $_options;
  55. public function __construct($db)
  56. {
  57. $this->_db = $db;
  58. $this->clear();
  59. }
  60. public function compile()
  61. {
  62. $fields = implode(', ', $this->_fields);
  63. if (!empty($this->_primaryKey))
  64. {
  65. $fields .= ', ' . $this->_primaryKey;
  66. }
  67. if (!empty($this->_foreignKeys))
  68. {
  69. $fields .= ', ' . implode(', ', $this->_foreignKeys);
  70. }
  71. $engine = !empty($this->_options['engine']) ? $this->_options['engine'] : 'InnoDB';
  72. $sql = "CREATE TABLE {{$this->_table}} ({$fields}) ENGINE = {$engine}";
  73. if ($charset = $this->_db->charset())
  74. {
  75. $sql .= ", DEFAULT CHARACTER SET {$charset}";
  76. }
  77. return $sql;
  78. }
  79. public function clear()
  80. {
  81. $this->_table = '';
  82. $this->_fields = array();
  83. $this->_primaryKey = NULL;
  84. $this->_foreignKeys = array();
  85. $this->_options = NULL;
  86. return $this;
  87. }
  88. public function table($name, $options = NULL)
  89. {
  90. $this->clear();
  91. $this->_table = $name;
  92. $this->_options = $options;
  93. return $this;
  94. }
  95. public function field($name, $type, $length = NULL, $default = NULL, $canBeNULL = false, $flags = NULL)
  96. {
  97. switch ($type)
  98. {
  99. case self::kFieldTypeInteger:
  100. $typeName = 'INT';
  101. $default = is_integer($default) ? "{$default}" : NULL;
  102. break;
  103. case self::kFieldTypeFloat:
  104. $typeName = 'FLOAT';
  105. $default = is_float($default) ? "{$default}" : NULL;
  106. break;
  107. case self::kFieldTypeString:
  108. $typeName = ($flags & self::kFlagFixedLength) ? 'CHAR' : 'VARCHAR';
  109. if ($length === NULL)
  110. {
  111. $length = 255;
  112. }
  113. $default = is_string($default) ? "'".$default."'" : ($canBeNULL ? NULL : "''");
  114. break;
  115. case self::kFieldTypeText:
  116. $typeName = 'MEDIUMTEXT';
  117. $default = is_string($default) ? "'".$default."'" : ($canBeNULL ? NULL : "''");
  118. break;
  119. case self::kFieldTypeDate:
  120. $typeName = 'DATETIME';
  121. $default = is_string($default) ? "'".$default."'" : ($canBeNULL ? NULL : "'0000-00-00 00:00:00'");
  122. break;
  123. case self::kFieldTypeBoolean:
  124. $typeName = 'BOOL';
  125. $default = is_bool($default) ? ($default ? '1' : '0') : NULL;
  126. break;
  127. case self::kFieldTypeBinary:
  128. $typeName = 'BLOB';
  129. $default = NULL;
  130. break;
  131. case self::kFieldTypeByte:
  132. $typeName = 'TINYINT';
  133. $default = is_integer($default) ? "{$default}" : NULL;
  134. break;
  135. case self::kFieldTypeShort:
  136. $typeName = 'SMALLINT';
  137. $default = is_integer($default) ? "{$default}" : NULL;
  138. break;
  139. case self::kFieldTypeLong:
  140. $typeName = 'INT';
  141. $default = is_integer($default) ? "{$default}" : NULL;
  142. break;
  143. }
  144. $field = $this->_db->quoteIdentifier($name) . ' ' . $typeName;
  145. if ($length !== NULL)
  146. {
  147. $field .= "({$length})";
  148. }
  149. if ($flags & self::kFlagPrimaryKey)
  150. {
  151. $field .= ' PRIMARY KEY';
  152. }
  153. if ($flags & self::kFlagAutoIncrement)
  154. {
  155. $field .= ' AUTO_INCREMENT';
  156. }
  157. if ($default !== NULL)
  158. {
  159. $field .= " DEFAULT {$default}";
  160. }
  161. if (!$canBeNULL && !($flags & self::kFlagPrimaryKey))
  162. {
  163. $field .= ' NOT NULL';
  164. }
  165. $this->_fields[] = $field;
  166. return $this;
  167. }
  168. public function primaryKey($columns)
  169. {
  170. if (!is_array($columns))
  171. {
  172. $columns = explode(',', $columns);
  173. }
  174. $columns = implode(',', array_map(array($this->_db, 'quoteIdentifier'), array_map('trim', $columns)));
  175. $this->_primaryKey = "PRIMARY KEY($columns)";
  176. }
  177. public function foreignKey($columns, $foreignTable, $foreignColumns, $actions = NULL)
  178. {
  179. if (!is_array($columns))
  180. {
  181. $columns = explode(',', $columns);
  182. }
  183. $columns = implode(',', array_map(array($this->_db, 'quoteIdentifier'), array_map('trim', $columns)));
  184. if (!is_array($foreignColumns))
  185. {
  186. $foreignColumns = explode(',', $foreignColumns);
  187. }
  188. $foreignColumns = implode(',', array_map(array($this->_db, 'quoteIdentifier'), array_map('trim', $foreignColumns)));
  189. $key = "FOREIGN KEY($columns) REFERENCES {{$foreignTable}}($foreignColumns)";
  190. if (!empty($actions))
  191. {
  192. foreach ($actions as $trigger => $action)
  193. {
  194. $key .= (' ON ' . self::$_triggers[$trigger] . ' ' . self::$_actions[$action]);
  195. }
  196. }
  197. $this->_foreignKeys[] = $key;
  198. }
  199. }
  200. // -----------------------------------------------------------------------------
  201. class MySQLFunction_alter_table extends MySQLFunction_create_table
  202. {
  203. public function __construct($db)
  204. {
  205. parent::__construct($db);
  206. }
  207. public function compile()
  208. {
  209. foreach ($this->_fields as $field)
  210. {
  211. $fields[] = "ADD COLUMN {$field}";
  212. }
  213. if (!empty($this->_primaryKey))
  214. {
  215. $fields[] = "ADD {$this->_primaryKey}";
  216. }
  217. foreach ($this->_foreignKeys as $field)
  218. {
  219. $fields[] = "ADD {$field}";
  220. }
  221. $fields = implode(', ', $fields);
  222. return "ALTER TABLE {{$this->_table}} {$fields}";
  223. }
  224. }
  225. // -----------------------------------------------------------------------------
  226. class MySQLFunction_create_index extends SparkDBQueryFunction implements iSparkDBQueryFunctionCreateIndex
  227. {
  228. protected $_db;
  229. protected $_table;
  230. protected $_index;
  231. public function __construct($db)
  232. {
  233. $this->_db = $db;
  234. $this->clear();
  235. }
  236. public function compile()
  237. {
  238. extract($this->_index);
  239. $sql = 'CREATE ';
  240. if ($type == self::kIndexTypeUnique)
  241. {
  242. $sql .= 'UNIQUE ';
  243. }
  244. if (!is_array($fields))
  245. {
  246. $fields = explode(',', $fields);
  247. }
  248. $fields = implode(',', array_map(array($this->_db, 'quoteIdentifier'), array_map('trim', $fields)));
  249. $multiField = strpos($fields, ',');
  250. if (empty($name))
  251. {
  252. $name = $fields;
  253. if ($multiField !== false)
  254. {
  255. $name = substr($name, 0, $multiField);
  256. }
  257. }
  258. $sql .= "INDEX {{$name}} ON {{$this->_table}} ({$fields})";
  259. return $sql;
  260. }
  261. public function clear()
  262. {
  263. $this->_table = '';
  264. $this->_index = NULL;
  265. return $this;
  266. }
  267. public function table($name)
  268. {
  269. $this->clear();
  270. $this->_table = $name;
  271. return $this;
  272. }
  273. public function index($type, $fields, $name = NULL)
  274. {
  275. $this->_index = array('type'=>$type, 'fields'=>$fields, 'name'=>$name);
  276. return $this;
  277. }
  278. }
  279. // -----------------------------------------------------------------------------
  280. class MySQLFunction_drop_index extends SparkDBQueryFunction implements iSparkDBQueryFunctionDropIndex
  281. {
  282. protected $_db;
  283. protected $_table;
  284. protected $_index;
  285. public function __construct($db)
  286. {
  287. $this->_db = $db;
  288. $this->clear();
  289. }
  290. public function compile()
  291. {
  292. return "DROP INDEX {{$this->_index}} ON {{$this->_table}}";
  293. }
  294. public function clear()
  295. {
  296. $this->_table = '';
  297. $this->_index = '';
  298. return $this;
  299. }
  300. public function table($name)
  301. {
  302. $this->clear();
  303. $this->_table = $name;
  304. return $this;
  305. }
  306. public function drop($name)
  307. {
  308. $this->_index = $name;
  309. return $this;
  310. }
  311. }
  312. // -----------------------------------------------------------------------------
  313. class MySQLFunction_reset_auto_increment extends SparkDBQueryFunction implements iSparkDBQueryFunctionResetAutoIncrement
  314. {
  315. protected $_db;
  316. protected $_table;
  317. protected $_value;
  318. public function __construct($db)
  319. {
  320. $this->_db = $db;
  321. $this->clear();
  322. }
  323. public function compile()
  324. {
  325. return "ALTER TABLE {{$this->_table}} AUTO_INCREMENT={$this->_value}";
  326. }
  327. public function clear()
  328. {
  329. $this->_table = '';
  330. $this->_value = 1;
  331. return $this;
  332. }
  333. public function table($name)
  334. {
  335. $this->clear();
  336. $this->_table = $name;
  337. return $this;
  338. }
  339. public function reset($value = 1)
  340. {
  341. $this->_value = $value;
  342. return $this;
  343. }
  344. }
  345. // -----------------------------------------------------------------------------
  346. class MySQLFunction_date extends SparkDBQueryFunction implements iSparkDBQueryFunctionDate
  347. {
  348. const kNow = 1;
  349. const kAdd = 2;
  350. const kSub = 3;
  351. const kInterval = 4;
  352. private $_opStack;
  353. public function __construct($db)
  354. {
  355. $this->_opStack = array();
  356. }
  357. public function compile()
  358. {
  359. $ops = array_reverse($this->_opStack);
  360. $work = array();
  361. while (!empty($ops))
  362. {
  363. $next = array_pop($ops);
  364. if (is_string($next))
  365. {
  366. $work[] = $next;
  367. continue;
  368. }
  369. switch ($next)
  370. {
  371. case self::kAdd:
  372. $work[] = $this->buildAdd(array_pop($work), array_pop($work));
  373. break;
  374. case self::kSub:
  375. $work[] = $this->buildSub(array_pop($work), array_pop($work));
  376. break;
  377. case self::kInterval:
  378. $work[] = $this->buildInterval(array_pop($work), array_pop($work));
  379. break;
  380. }
  381. }
  382. return implode(', ', $work);
  383. }
  384. public function clear()
  385. {
  386. $this->_opStack = array();
  387. return $this;
  388. }
  389. public function date($dateStr)
  390. {
  391. $this->_opStack[] = '\''.strval($dateStr).'\'';
  392. return $this;
  393. }
  394. public function now()
  395. {
  396. $this->_opStack[] = 'NOW()';
  397. return $this;
  398. }
  399. public function today()
  400. {
  401. $this->_opStack[] = 'CURDATE()';
  402. return $this;
  403. }
  404. public function add()
  405. {
  406. $this->_opStack[] = self::kAdd;
  407. return $this;
  408. }
  409. public function sub()
  410. {
  411. $this->_opStack[] = self::kSub;
  412. return $this;
  413. }
  414. public function interval($num, $type)
  415. {
  416. $this->_opStack[] = "{$num}";
  417. $this->_opStack[] = "{$type}";
  418. $this->_opStack[] = self::kInterval;
  419. return $this;
  420. }
  421. private function buildAdd($b, $a)
  422. {
  423. return "DATE_ADD({$a}, {$b})";
  424. }
  425. private function buildSub($b, $a)
  426. {
  427. return "DATE_SUB({$a}, {$b})";
  428. }
  429. private function buildInterval($type, $num)
  430. {
  431. switch ($type)
  432. {
  433. case self::kSeconds:
  434. return "INTERVAL {$num} SECOND";
  435. case self::kMinutes:
  436. return "INTERVAL {$num} MINUTE";
  437. case self::kHours:
  438. return "INTERVAL {$num} HOUR";
  439. case self::kDays:
  440. return "INTERVAL {$num} DAY";
  441. }
  442. }
  443. }
  444. // -----------------------------------------------------------------------------
  445. class MySQLFunction_replace extends SparkDBQueryFunction implements iSparkDBQueryFunctionReplace
  446. {
  447. private $_search;
  448. private $_replace;
  449. private $_column;
  450. public function __construct($db)
  451. {
  452. }
  453. public function compile()
  454. {
  455. return "REPLACE({$this->_column}, '{$this->_search}', '{$this->_replace}')";
  456. }
  457. public function clear()
  458. {
  459. $this->_search = '';
  460. $this->_replace = '';
  461. $this->_column = '';
  462. return $this;
  463. }
  464. public function replace($search, $replace, $column)
  465. {
  466. $this->_search = $search;
  467. $this->_replace = $replace;
  468. $this->_column = $column;
  469. return $this;
  470. }
  471. }
  472. // -----------------------------------------------------------------------------
  473. class MySQLFunction_condition extends SparkDBQueryFunction implements iSparkDBQueryFunctionCondition
  474. {
  475. private $_cond;
  476. private $_if;
  477. private $_else;
  478. public function __construct($db)
  479. {
  480. }
  481. public function compile()
  482. {
  483. return "IF({$this->_cond}, {$this->_if}, {$this->_else})";
  484. }
  485. public function clear()
  486. {
  487. $this->_cond = '';
  488. $this->_if = '';
  489. $this->_else = '';
  490. return $this;
  491. }
  492. public function condition($cond, $if, $else)
  493. {
  494. $this->_cond = $cond;
  495. $this->_if = $if;
  496. $this->_else = $else;
  497. return $this;
  498. }
  499. }
  500. // -----------------------------------------------------------------------------
  501. class _SparkDB_mysql extends SparkDatabase
  502. {
  503. public function __construct($params)
  504. {
  505. $charset = isset($params['charset']) ? $params['charset'] : 'utf8';
  506. $timezone = isset($params['timezone']) ? $params['timezone'] : '+00:00';
  507. if ($charset)
  508. {
  509. if (!defined('PDO_MYSQL_ATTR_INIT_COMMAND')) // work around bug in PHP 5.3.0-5.3.2 (fails to define class constant)
  510. {
  511. define('PDO_MYSQL_ATTR_INIT_COMMAND', defined('PDO::MYSQL_ATTR_INIT_COMMAND') ? PDO::MYSQL_ATTR_INIT_COMMAND : 1002);
  512. }
  513. $params['options'][PDO_MYSQL_ATTR_INIT_COMMAND] = "SET NAMES \"{$charset}\"";
  514. }
  515. parent::__construct($params);
  516. if ($timezone)
  517. {
  518. $this->query("SET time_zone = \"{$timezone}\"");
  519. }
  520. }
  521. public function quoteIdentifier($ident)
  522. {
  523. return '`' . $ident . '`';
  524. }
  525. public function buildSelectOrderBy($orderBy = NULL)
  526. {
  527. return preg_replace('/^\s*order\s+by\s+rand\s*$/i', 'ORDER BY RAND()', parent::buildSelectOrderBy($orderBy));
  528. }
  529. public function buildUpsert($table, $row, $primaryKey, &$bind)
  530. {
  531. $columns = array_keys($row);
  532. $bind = array_values($row);
  533. $bind = array_merge($bind, $bind);
  534. $sql = $this->buildInsert($table, array_combine($columns, array_fill(0, count($columns), '?')));
  535. foreach($row as $col => $val)
  536. {
  537. $set[] = $this->quoteIdentifier($col) . '=?';
  538. }
  539. $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $set);
  540. return $sql;
  541. }
  542. public function translateErrorCode($errorCode)
  543. {
  544. switch ($errorCode)
  545. {
  546. case 1007:
  547. return SparkDBException::kDatabaseExists;
  548. case 1054:
  549. return SparkDBException::kUnknownColumn;
  550. case 1062:
  551. return SparkDBException::kDuplicateRecord;
  552. case 1213:
  553. return SparkDBException::kDeadlock;
  554. default:
  555. return SparkDBException::kUnknown;
  556. }
  557. }
  558. public function getFunction($name)
  559. {
  560. switch ($name)
  561. {
  562. case 'metadata':
  563. return new MySQLFunction_metadata($this);
  564. case 'create_table':
  565. return new MySQLFunction_create_table($this);
  566. case 'create_index':
  567. return new MySQLFunction_create_index($this);
  568. case 'drop_index':
  569. return new MySQLFunction_drop_index($this);
  570. case 'alter_table':
  571. return new MySQLFunction_alter_table($this);
  572. case 'resetAutoIncrement':
  573. return new MySQLFunction_reset_auto_increment($this);
  574. case 'date':
  575. return new MySQLFunction_date($this);
  576. case 'replace':
  577. return new MySQLFunction_replace($this);
  578. case 'cond':
  579. return new MySQLFunction_condition($this);
  580. default:
  581. return parent::getFunction($name);
  582. }
  583. }
  584. }
  585. // -----------------------------------------------------------------------------