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

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

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