PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

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