PageRenderTime 141ms CodeModel.GetById 30ms RepoModel.GetById 9ms app.codeStats 0ms

/cake/libs/model/datasources/dbo/dbo_sqlite.php

https://github.com/msadouni/cakephp2x
PHP | 622 lines | 356 code | 51 blank | 215 comment | 67 complexity | 470ce4530d0c112bcea19aef71e476ba MD5 | raw file
  1. <?php
  2. /**
  3. * SQLite layer for DBO
  4. *
  5. * Long description for file
  6. *
  7. * PHP Version 5.x
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * Redistributions of files must retain the above copyright notice.
  14. *
  15. * @copyright Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package cake
  18. * @subpackage cake.cake.libs.model.datasources.dbo
  19. * @since CakePHP(tm) v 0.9.0
  20. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  21. */
  22. /**
  23. * DBO implementation for the SQLite DBMS.
  24. *
  25. * Long description for class
  26. *
  27. * @package cake
  28. * @subpackage cake.cake.libs.model.datasources.dbo
  29. */
  30. class DboSqlite extends DboSource {
  31. /**
  32. * Enter description here...
  33. *
  34. * @var unknown_type
  35. */
  36. public $description = "SQLite DBO Driver";
  37. /**
  38. * Opening quote for quoted identifiers
  39. *
  40. * @var string
  41. */
  42. public $startQuote = '"';
  43. /**
  44. * Closing quote for quoted identifiers
  45. *
  46. * @var string
  47. */
  48. public $endQuote = '"';
  49. /**
  50. * Keeps the transaction statistics of CREATE/UPDATE/DELETE queries
  51. *
  52. * @var array
  53. * @access protected
  54. */
  55. protected $_queryStats = array();
  56. /**
  57. * Base configuration settings for SQLite driver
  58. *
  59. * @var array
  60. */
  61. protected $_baseConfig = array(
  62. 'persistent' => true,
  63. 'database' => null,
  64. 'connect' => 'sqlite_popen'
  65. );
  66. /**
  67. * Index of basic SQL commands
  68. *
  69. * @var array
  70. * @access protected
  71. */
  72. protected $_commands = array(
  73. 'begin' => 'BEGIN TRANSACTION',
  74. 'commit' => 'COMMIT TRANSACTION',
  75. 'rollback' => 'ROLLBACK TRANSACTION'
  76. );
  77. /**
  78. * SQLite column definition
  79. *
  80. * @var array
  81. */
  82. public $columns = array(
  83. 'primary_key' => array('name' => 'integer primary key'),
  84. 'string' => array('name' => 'varchar', 'limit' => '255'),
  85. 'text' => array('name' => 'text'),
  86. 'integer' => array('name' => 'integer', 'limit' => 11, 'formatter' => 'intval'),
  87. 'float' => array('name' => 'float', 'formatter' => 'floatval'),
  88. 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  89. 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  90. 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
  91. 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
  92. 'binary' => array('name' => 'blob'),
  93. 'boolean' => array('name' => 'boolean')
  94. );
  95. /**
  96. * List of engine specific additional field parameters used on table creating
  97. *
  98. * @var array
  99. * @access public
  100. */
  101. var $fieldParameters = array(
  102. 'collate' => array(
  103. 'value' => 'COLLATE',
  104. 'quote' => false,
  105. 'join' => ' ',
  106. 'column' => 'Collate',
  107. 'position' => 'afterDefault',
  108. 'options' => array(
  109. 'BINARY', 'NOCASE', 'RTRIM'
  110. )
  111. ),
  112. );
  113. /**
  114. * Connects to the database using config['database'] as a filename.
  115. *
  116. * @param array $config Configuration array for connecting
  117. * @return mixed
  118. */
  119. public function connect() {
  120. $config = $this->config;
  121. $this->connection = $config['connect']($config['database']);
  122. $this->connected = is_resource($this->connection);
  123. if ($this->connected) {
  124. $this->_execute('PRAGMA count_changes = 1;');
  125. }
  126. return $this->connected;
  127. }
  128. /**
  129. * Check that SQLite is enabled/installed
  130. *
  131. * @return boolean
  132. */
  133. function enabled() {
  134. return extension_loaded('sqlite');
  135. }
  136. /**
  137. * Disconnects from database.
  138. *
  139. * @return boolean True if the database could be disconnected, else false
  140. */
  141. public function disconnect() {
  142. @sqlite_close($this->connection);
  143. $this->connected = false;
  144. return $this->connected;
  145. }
  146. /**
  147. * Executes given SQL statement.
  148. *
  149. * @param string $sql SQL statement
  150. * @return resource Result resource identifier
  151. */
  152. private function _execute($sql) {
  153. $result = sqlite_query($this->connection, $sql);
  154. if (preg_match('/^(INSERT|UPDATE|DELETE)/', $sql)) {
  155. $this->resultSet($result);
  156. list($this->_queryStats) = $this->fetchResult();
  157. }
  158. return $result;
  159. }
  160. /**
  161. * Overrides DboSource::execute() to correctly handle query statistics
  162. *
  163. * @param string $sql
  164. * @return unknown
  165. */
  166. public function execute($sql) {
  167. $result = parent::execute($sql);
  168. $this->_queryStats = array();
  169. return $result;
  170. }
  171. /**
  172. * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits.
  173. *
  174. * @return array Array of tablenames in the database
  175. */
  176. public function listSources() {
  177. $cache = parent::listSources();
  178. if ($cache != null) {
  179. return $cache;
  180. }
  181. $result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;", false);
  182. if (empty($result)) {
  183. return array();
  184. } else {
  185. $tables = array();
  186. foreach ($result as $table) {
  187. $tables[] = $table[0]['name'];
  188. }
  189. parent::listSources($tables);
  190. return $tables;
  191. }
  192. return array();
  193. }
  194. /**
  195. * Returns an array of the fields in given table name.
  196. *
  197. * @param string $tableName Name of database table to inspect
  198. * @return array Fields in table. Keys are name and type
  199. */
  200. public function describe(&$model) {
  201. $cache = parent::describe($model);
  202. if ($cache != null) {
  203. return $cache;
  204. }
  205. $fields = array();
  206. $result = $this->fetchAll('PRAGMA table_info(' . $this->fullTableName($model) . ')');
  207. foreach ($result as $column) {
  208. $fields[$column[0]['name']] = array(
  209. 'type' => $this->column($column[0]['type']),
  210. 'null' => !$column[0]['notnull'],
  211. 'default' => $column[0]['dflt_value'],
  212. 'length' => $this->length($column[0]['type'])
  213. );
  214. if ($column[0]['pk'] == 1) {
  215. $colLength = $this->length($column[0]['type']);
  216. $fields[$column[0]['name']] = array(
  217. 'type' => $fields[$column[0]['name']]['type'],
  218. 'null' => false,
  219. 'default' => $column[0]['dflt_value'],
  220. 'key' => $this->index['PRI'],
  221. 'length' => ($colLength != null) ? $colLength : 11
  222. );
  223. }
  224. }
  225. $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
  226. return $fields;
  227. }
  228. /**
  229. * Returns a quoted and escaped string of $data for use in an SQL statement.
  230. *
  231. * @param string $data String to be prepared for use in an SQL statement
  232. * @return string Quoted and escaped
  233. */
  234. public function value($data, $column = null, $safe = false) {
  235. $parent = parent::value($data, $column, $safe);
  236. if ($parent != null) {
  237. return $parent;
  238. }
  239. if ($data === null) {
  240. return 'NULL';
  241. }
  242. if ($data === '' && $column !== 'integer' && $column !== 'float' && $column !== 'boolean') {
  243. return "''";
  244. }
  245. switch ($column) {
  246. case 'boolean':
  247. $data = $this->boolean((bool)$data);
  248. break;
  249. case 'integer':
  250. case 'float':
  251. if ($data === '') {
  252. return 'NULL';
  253. }
  254. default:
  255. $data = sqlite_escape_string($data);
  256. break;
  257. }
  258. return "'" . $data . "'";
  259. }
  260. /**
  261. * Generates and executes an SQL UPDATE statement for given model, fields, and values.
  262. *
  263. * @param Model $model
  264. * @param array $fields
  265. * @param array $values
  266. * @param mixed $conditions
  267. * @return array
  268. */
  269. public function update(&$model, $fields = array(), $values = null, $conditions = null) {
  270. if (empty($values) && !empty($fields)) {
  271. foreach ($fields as $field => $value) {
  272. if (strpos($field, $model->alias . '.') !== false) {
  273. unset($fields[$field]);
  274. $field = str_replace($model->alias . '.', "", $field);
  275. $field = str_replace($model->alias . '.', "", $field);
  276. $fields[$field] = $value;
  277. }
  278. }
  279. }
  280. $result = parent::update($model, $fields, $values, $conditions);
  281. return $result;
  282. }
  283. /**
  284. * Deletes all the records in a table and resets the count of the auto-incrementing
  285. * primary key, where applicable.
  286. *
  287. * @param mixed $table A string or model class representing the table to be truncated
  288. * @return boolean SQL TRUNCATE TABLE statement, false if not applicable.
  289. * @access public
  290. */
  291. public function truncate($table) {
  292. return $this->execute('DELETE From ' . $this->fullTableName($table));
  293. }
  294. /**
  295. * Returns a formatted error message from previous database operation.
  296. *
  297. * @return string Error message
  298. */
  299. public function lastError() {
  300. $error = sqlite_last_error($this->connection);
  301. if ($error) {
  302. return $error.': '.sqlite_error_string($error);
  303. }
  304. return null;
  305. }
  306. /**
  307. * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
  308. *
  309. * @return integer Number of affected rows
  310. */
  311. public function lastAffected() {
  312. if (!empty($this->_queryStats)) {
  313. foreach (array('rows inserted', 'rows updated', 'rows deleted') as $key) {
  314. if (array_key_exists($key, $this->_queryStats)) {
  315. return $this->_queryStats[$key];
  316. }
  317. }
  318. }
  319. return false;
  320. }
  321. /**
  322. * Returns number of rows in previous resultset. If no previous resultset exists,
  323. * this returns false.
  324. *
  325. * @return integer Number of rows in resultset
  326. */
  327. public function lastNumRows() {
  328. if ($this->hasResult()) {
  329. sqlite_num_rows($this->_result);
  330. }
  331. return false;
  332. }
  333. /**
  334. * Returns the ID generated from the previous INSERT operation.
  335. *
  336. * @return int
  337. */
  338. public function lastInsertId() {
  339. return sqlite_last_insert_rowid($this->connection);
  340. }
  341. /**
  342. * Converts database-layer column types to basic types
  343. *
  344. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  345. * @return string Abstract column type (i.e. "string")
  346. */
  347. public function column($real) {
  348. if (is_array($real)) {
  349. $col = $real['name'];
  350. if (isset($real['limit'])) {
  351. $col .= '('.$real['limit'].')';
  352. }
  353. return $col;
  354. }
  355. $col = strtolower(str_replace(')', '', $real));
  356. $limit = null;
  357. if (strpos($col, '(') !== false) {
  358. list($col, $limit) = explode('(', $col);
  359. }
  360. if (in_array($col, array('text', 'integer', 'float', 'boolean', 'timestamp', 'date', 'datetime', 'time'))) {
  361. return $col;
  362. }
  363. if (strpos($col, 'varchar') !== false) {
  364. return 'string';
  365. }
  366. if (in_array($col, array('blob', 'clob'))) {
  367. return 'binary';
  368. }
  369. if (strpos($col, 'numeric') !== false) {
  370. return 'float';
  371. }
  372. return 'text';
  373. }
  374. /**
  375. * Enter description here...
  376. *
  377. * @param unknown_type $results
  378. */
  379. public function resultSet(&$results) {
  380. $this->results =& $results;
  381. $this->map = array();
  382. $fieldCount = sqlite_num_fields($results);
  383. $index = $j = 0;
  384. while ($j < $fieldCount) {
  385. $columnName = str_replace('"', '', sqlite_field_name($results, $j));
  386. if (strpos($columnName, '.')) {
  387. $parts = explode('.', $columnName);
  388. $this->map[$index++] = array($parts[0], $parts[1]);
  389. } else {
  390. $this->map[$index++] = array(0, $columnName);
  391. }
  392. $j++;
  393. }
  394. }
  395. /**
  396. * Fetches the next row from the current result set
  397. *
  398. * @return unknown
  399. */
  400. public function fetchResult() {
  401. if ($row = sqlite_fetch_array($this->results, SQLITE_ASSOC)) {
  402. $resultRow = array();
  403. $i = 0;
  404. foreach ($row as $index => $field) {
  405. if (strpos($index, '.')) {
  406. list($table, $column) = explode('.', str_replace('"', '', $index));
  407. $resultRow[$table][$column] = $row[$index];
  408. } else {
  409. $resultRow[0][str_replace('"', '', $index)] = $row[$index];
  410. }
  411. $i++;
  412. }
  413. return $resultRow;
  414. } else {
  415. return false;
  416. }
  417. }
  418. /**
  419. * Returns a limit statement in the correct format for the particular database.
  420. *
  421. * @param integer $limit Limit of results returned
  422. * @param integer $offset Offset from which to start results
  423. * @return string SQL limit/offset statement
  424. */
  425. public function limit($limit, $offset = null) {
  426. if ($limit) {
  427. $rt = '';
  428. if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
  429. $rt = ' LIMIT';
  430. }
  431. $rt .= ' ' . $limit;
  432. if ($offset) {
  433. $rt .= ' OFFSET ' . $offset;
  434. }
  435. return $rt;
  436. }
  437. return null;
  438. }
  439. /**
  440. * Generate a database-native column schema string
  441. *
  442. * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
  443. * where options can be 'default', 'length', or 'key'.
  444. * @return string
  445. */
  446. public function buildColumn($column) {
  447. $name = $type = null;
  448. $column = array_merge(array('null' => true), $column);
  449. extract($column);
  450. if (empty($name) || empty($type)) {
  451. trigger_error('Column name or type not defined in schema', E_USER_WARNING);
  452. return null;
  453. }
  454. if (!isset($this->columns[$type])) {
  455. trigger_error("Column type {$type} does not exist", E_USER_WARNING);
  456. return null;
  457. }
  458. $real = $this->columns[$type];
  459. $out = $this->name($name) . ' ' . $real['name'];
  460. if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') {
  461. return $this->name($name) . ' ' . $this->columns['primary_key']['name'];
  462. }
  463. return parent::buildColumn($column);
  464. }
  465. /**
  466. * Sets the database encoding
  467. *
  468. * @param string $enc Database encoding
  469. */
  470. public function setEncoding($enc) {
  471. if (!in_array($enc, array("UTF-8", "UTF-16", "UTF-16le", "UTF-16be"))) {
  472. return false;
  473. }
  474. return $this->_execute("PRAGMA encoding = \"{$enc}\"") !== false;
  475. }
  476. /**
  477. * Gets the database encoding
  478. *
  479. * @return string The database encoding
  480. */
  481. public function getEncoding() {
  482. return $this->fetchRow('PRAGMA encoding');
  483. }
  484. /**
  485. * Removes redundant primary key indexes, as they are handled in the column def of the key.
  486. *
  487. * @param array $indexes
  488. * @param string $table
  489. * @return string
  490. */
  491. public function buildIndex($indexes, $table = null) {
  492. $join = array();
  493. foreach ($indexes as $name => $value) {
  494. if ($name == 'PRIMARY') {
  495. continue;
  496. }
  497. $out = 'CREATE ';
  498. if (!empty($value['unique'])) {
  499. $out .= 'UNIQUE ';
  500. }
  501. if (is_array($value['column'])) {
  502. $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column']));
  503. } else {
  504. $value['column'] = $this->name($value['column']);
  505. }
  506. $out .= "INDEX {$name} ON {$table}({$value['column']});";
  507. $join[] = $out;
  508. }
  509. return $join;
  510. }
  511. /**
  512. * Overrides DboSource::index to handle SQLite indexe introspection
  513. * Returns an array of the indexes in given table name.
  514. *
  515. * @param string $model Name of model to inspect
  516. * @return array Fields in table. Keys are column and unique
  517. */
  518. public function index(&$model) {
  519. $index = array();
  520. $table = $this->fullTableName($model);
  521. if ($table) {
  522. $indexes = $this->query('PRAGMA index_list(' . $table . ')');
  523. $tableInfo = $this->query('PRAGMA table_info(' . $table . ')');
  524. foreach ($indexes as $i => $info) {
  525. $key = array_pop($info);
  526. $keyInfo = $this->query('PRAGMA index_info("' . $key['name'] . '")');
  527. foreach ($keyInfo as $keyCol) {
  528. if (!isset($index[$key['name']])) {
  529. $col = array();
  530. if (preg_match('/autoindex/', $key['name'])) {
  531. $key['name'] = 'PRIMARY';
  532. }
  533. $index[$key['name']]['column'] = $keyCol[0]['name'];
  534. $index[$key['name']]['unique'] = intval($key['unique'] == 1);
  535. } else {
  536. if (!is_array($index[$key['name']]['column'])) {
  537. $col[] = $index[$key['name']]['column'];
  538. }
  539. $col[] = $keyCol[0]['name'];
  540. $index[$key['name']]['column'] = $col;
  541. }
  542. }
  543. }
  544. }
  545. return $index;
  546. }
  547. /**
  548. * Overrides DboSource::renderStatement to handle schema generation with SQLite-style indexes
  549. *
  550. * @param string $type
  551. * @param array $data
  552. * @return string
  553. */
  554. public function renderStatement($type, $data) {
  555. switch (strtolower($type)) {
  556. case 'schema':
  557. extract($data);
  558. foreach (array('columns', 'indexes') as $var) {
  559. if (is_array(${$var})) {
  560. ${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
  561. }
  562. }
  563. return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
  564. break;
  565. default:
  566. return parent::renderStatement($type, $data);
  567. break;
  568. }
  569. }
  570. }
  571. ?>