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

/baser/models/datasources/dbo/dbo_sqlite3.php

https://github.com/hashing/basercms
PHP | 736 lines | 373 code | 78 blank | 285 comment | 81 complexity | 0cd29a844655bc72b82da3e5c174aa90 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /* SVN FILE: $Id$ */
  3. /**
  4. * SQLite3 layer for DBO
  5. *
  6. * Long description for file
  7. *
  8. * PHP versions 5
  9. *
  10. * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
  11. * Copyright 2005-2008, Cake Software Foundation, Inc.
  12. * 1785 E. Sahara Avenue, Suite 490-204
  13. * Las Vegas, Nevada 89104
  14. *
  15. * Licensed under The MIT License
  16. * Redistributions of files must retain the above copyright notice.
  17. *
  18. * @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
  19. * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
  20. * @package cake
  21. * @subpackage cake.cake.libs.model.datasources.dbo
  22. * @since CakePHP(tm) v 0.9.0
  23. * @version $Revision$
  24. * @modifiedby $LastChangedBy$
  25. * @lastmodified $Date$
  26. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  27. */
  28. /**
  29. * DBO implementation for the SQLite3 DBMS.
  30. *
  31. * Long description for class
  32. *
  33. * @package cake
  34. * @subpackage cake.cake.libs.model.datasources.dbo
  35. */
  36. class DboSqlite3 extends DboSource {
  37. /**
  38. * Enter description here...
  39. *
  40. * @var string
  41. * @access public
  42. */
  43. var $description = "SQLite3 DBO Driver";
  44. /**
  45. * Enter description here...
  46. *
  47. * @var string
  48. * @access public
  49. */
  50. var $startQuote = '"';
  51. /**
  52. * Enter description here...
  53. *
  54. * @var string
  55. * @access public
  56. */
  57. var $endQuote = '"';
  58. /**
  59. * Base configuration settings for SQLite3 driver
  60. *
  61. * @var array
  62. * @access protected
  63. */
  64. var $_baseConfig = array(
  65. 'persistent' => false,
  66. 'database' => null,
  67. 'connect' => 'sqlite' //sqlite3 in pdo_sqlite is sqlite. sqlite2 is sqlite2
  68. );
  69. /**
  70. * SQLite3 column definition
  71. *
  72. * @var array
  73. * @access public
  74. */
  75. var $columns = array(
  76. 'primary_key' => array('name' => 'integer primary key autoincrement'),
  77. 'string' => array('name' => 'varchar', 'limit' => '255'),
  78. 'text' => array('name' => 'text'),
  79. 'integer' => array('name' => 'integer', 'limit' => null, 'formatter' => 'intval'),
  80. 'float' => array('name' => 'float', 'formatter' => 'floatval'),
  81. 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  82. 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  83. 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
  84. 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
  85. 'binary' => array('name' => 'blob'),
  86. 'boolean' => array('name' => 'boolean')
  87. );
  88. var $last_error = NULL;
  89. var $pdo_statement = NULL;
  90. var $rows = NULL;
  91. var $row_count = NULL;
  92. /**
  93. * Connects to the database using config['database'] as a filename.
  94. *
  95. * @param array $config Configuration array for connecting
  96. * @return mixed
  97. * @access public
  98. */
  99. function connect() {
  100. //echo "runs connect\n";
  101. $this->last_error = null;
  102. $config = $this->config;
  103. //$this->connection = $config['connect']($config['database']);
  104. try {
  105. $this->connection = new PDO($config['connect'].':'.$config['database']);
  106. $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  107. //$this->connected = is_resource($this->connection);
  108. $this->connected = is_object($this->connection);
  109. }
  110. catch(PDOException $e) {
  111. $this->last_error = array('Error connecting to database.',$e->getMessage());
  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. //echo "runs disconnect\n";
  122. //@sqlite3_close($this->connection);
  123. $this->connection = NULL;
  124. $this->connected = false;
  125. return $this->connected;
  126. }
  127. /**
  128. * Executes given SQL statement.
  129. *
  130. * @param string $sql SQL statement
  131. * @return resource Result resource identifier
  132. * @access protected
  133. */
  134. function _execute($sql) {
  135. //echo "runs execute\n";
  136. //return sqlite3_query($this->connection, $sql);
  137. for ($i = 0; $i < 2; $i++) {
  138. try {
  139. $this->last_error = NULL;
  140. $this->pdo_statement = $this->connection->query($sql);
  141. if (is_object($this->pdo_statement)) {
  142. $this->rows = $this->pdo_statement->fetchAll(PDO::FETCH_NUM);
  143. $this->row_count = count($this->rows);
  144. return $this->pdo_statement;
  145. }
  146. }
  147. catch(PDOException $e) {
  148. // Schema change; re-run query
  149. if ($e->errorInfo[1] === 17) continue;
  150. $this->last_error = $e->getMessage();
  151. }
  152. }
  153. return false;
  154. }
  155. /**
  156. * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits.
  157. *
  158. * @return array Array of tablenames in the database
  159. * @access public
  160. */
  161. function listSources() {
  162. //echo "runs listSources\n";
  163. $db = $this->config['database'];
  164. $this->config['database'] = basename($this->config['database']);
  165. $cache = parent::listSources();
  166. if ($cache != null) {
  167. // >>> ADD 2010/03/19 egashira
  168. // 接続をフルパスに戻す
  169. $this->config['database'] = $db;
  170. // <<<
  171. return $cache;
  172. }
  173. //echo "listsources:beforeresult ";
  174. // >>> CUSTOMIZE MODIFY 2010/12/26 ryuring
  175. //$result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;", false);
  176. // ---
  177. $result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' AND name<>'sqlite_sequence' ORDER BY name;", false);
  178. // <<<
  179. //echo "listsources:result ";
  180. //pr($result);
  181. if (!$result || empty($result)) {
  182. // >>> ADD 2010/03/19 egashira
  183. // 接続をフルパスに戻す
  184. $this->config['database'] = $db;
  185. // <<<
  186. return array();
  187. } else {
  188. $tables = array();
  189. foreach ($result as $table) {
  190. $tables[] = $table[0]['name'];
  191. }
  192. parent::listSources($tables);
  193. $this->config['database'] = $db;
  194. return $tables;
  195. }
  196. $this->config['database'] = $db;
  197. return array();
  198. }
  199. /**
  200. * Returns an array of the fields in given table name.
  201. *
  202. * @param string $tableName Name of database table to inspect
  203. * @return array Fields in table. Keys are name and type
  204. * @access public
  205. */
  206. function describe(&$model) {
  207. $cache = parent::describe($model);
  208. if ($cache != null) {
  209. return $cache;
  210. }
  211. $fields = array();
  212. $result = $this->fetchAll('PRAGMA table_info(' . $model->tablePrefix . $model->table . ')');
  213. foreach ($result as $column) {
  214. $fields[$column[0]['name']] = array(
  215. 'type' => $this->column($column[0]['type']),
  216. 'null' => !$column[0]['notnull'],
  217. 'default' => $column[0]['dflt_value'],
  218. 'length' => $this->length($column[0]['type'])
  219. );
  220. if($column[0]['pk'] == 1) {
  221. $fields[$column[0]['name']] = array(
  222. 'type' => $fields[$column[0]['name']]['type'],
  223. 'null' => false,
  224. 'default' => $column[0]['dflt_value'],
  225. 'key' => $this->index['PRI'],
  226. 'length' => 11
  227. );
  228. }
  229. }
  230. $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
  231. return $fields;
  232. }
  233. /**
  234. * Returns a quoted and escaped string of $data for use in an SQL statement.
  235. *
  236. * @param string $data String to be prepared for use in an SQL statement
  237. * @param string $column
  238. * @param int $safe
  239. * @return string Quoted and escaped
  240. * @access public
  241. */
  242. function value ($data, $column = null, $safe = false) {
  243. $parent = parent::value($data, $column, $safe);
  244. if ($parent != null) {
  245. return $parent;
  246. }
  247. if ($data === null) {
  248. return 'NULL';
  249. }
  250. switch ($column) {
  251. case 'boolean':
  252. if ($data === '') {
  253. return 0;
  254. }
  255. $data = $this->boolean((bool)$data);
  256. break;
  257. case 'integer';
  258. if ($data === '') {
  259. return 'NULL';
  260. }
  261. break;
  262. case 'datetime':
  263. if($data) {
  264. $data = trim(str_replace('/', '-', $data));
  265. }
  266. if ($data === '' || $data == '0000-00-00 00:00:00') {
  267. return "''";
  268. }
  269. break;
  270. default:
  271. if ($data === '') {
  272. return "''";
  273. }
  274. $data = $this->connection->quote($data);
  275. return $data;
  276. break;
  277. }
  278. return "'" . $data . "'";
  279. }
  280. /**
  281. * Generates and executes an SQL UPDATE statement for given model, fields, and values.
  282. *
  283. * @param Model $model
  284. * @param array $fields
  285. * @param array $values
  286. * @param mixed $conditions
  287. * @return array
  288. * @access public
  289. */
  290. function update(&$model, $fields = array(), $values = null, $conditions = null) {
  291. if (empty($values) && !empty($fields)) {
  292. foreach ($fields as $field => $value) {
  293. if (strpos($field, $model->alias . '.') !== false) {
  294. unset($fields[$field]);
  295. $field = str_replace($model->alias . '.', "", $field);
  296. $field = str_replace($model->alias . '.', "", $field);
  297. $fields[$field] = $value;
  298. }
  299. }
  300. }
  301. return parent::update($model, $fields, $values, $conditions);
  302. }
  303. /**
  304. * Begin a transaction
  305. * TODO データベースがロックされてしまい正常に処理が実行されないのでとりあえず未実装とする
  306. * ロックに関する原因については未解析
  307. *
  308. * @param string $model
  309. * @return boolean True on success, false on fail
  310. * (i.e. if the database/model does not support transactions).
  311. * @access public
  312. */
  313. function begin (&$model) {
  314. return null;
  315. /*if (parent::begin($model)) {
  316. if ($this->connection->beginTransaction()) {
  317. $this->_transactionStarted = true;
  318. return true;
  319. }
  320. }
  321. return false;*/
  322. }
  323. /**
  324. * Commit a transaction
  325. * TODO データベースがロックされてしまい正常に処理が実行されないのでとりあえず未実装とする
  326. * ロックに関する原因については未解析
  327. *
  328. * @param unknown_type $model
  329. * @return boolean True on success, false on fail
  330. * (i.e. if the database/model does not support transactions,
  331. * or a transaction has not started).
  332. * @access public
  333. */
  334. function commit (&$model) {
  335. return null;
  336. /*if (parent::commit($model)) {
  337. $this->_transactionStarted = false;
  338. return $this->connection->commit();
  339. }
  340. return false;*/
  341. }
  342. /**
  343. * Rollback a transaction
  344. * TODO データベースがロックされてしまい正常に処理が実行されないのでとりあえず未実装とする
  345. * ロックに関する原因については未解析
  346. *
  347. * @param unknown_type $model
  348. * @return boolean True on success, false on fail
  349. * (i.e. if the database/model does not support transactions,
  350. * or a transaction has not started).
  351. * @access public
  352. */
  353. function rollback (&$model) {
  354. return null;
  355. /*if (parent::rollback($model)) {
  356. return $this->connection->rollBack();
  357. }
  358. return false;*/
  359. }
  360. /**
  361. * Deletes all the records in a table and resets the count of the auto-incrementing
  362. * primary key, where applicable.
  363. *
  364. * @param mixed $table A string or model class representing the table to be truncated
  365. * @return boolean SQL TRUNCATE TABLE statement, false if not applicable.
  366. * @access public
  367. */
  368. function truncate($table) {
  369. return $this->execute('DELETE From ' . $this->fullTableName($table));
  370. }
  371. /**
  372. * Returns a formatted error message from previous database operation.
  373. *
  374. * @return string Error message
  375. * @access public
  376. */
  377. function lastError() {
  378. return $this->last_error;
  379. }
  380. /**
  381. * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
  382. *
  383. * @return integer Number of affected rows
  384. * @access public
  385. */
  386. function lastAffected() {
  387. if ($this->_result) {
  388. return $this->pdo_statement->rowCount();
  389. }
  390. return false;
  391. }
  392. /**
  393. * Returns number of rows in previous resultset. If no previous resultset exists,
  394. * this returns false.
  395. *
  396. * @return integer Number of rows in resultset
  397. * @access public
  398. */
  399. function lastNumRows() {
  400. if ($this->pdo_statement) {
  401. // pdo_statement->rowCount() doesn't work for this case
  402. return $this->row_count;
  403. }
  404. return false;
  405. }
  406. /**
  407. * Returns the ID generated from the previous INSERT operation.
  408. *
  409. * @return int
  410. * @access public
  411. */
  412. function lastInsertId() {
  413. //return sqlite3_last_insert_rowid($this->connection);
  414. return $this->connection->lastInsertId();
  415. }
  416. /**
  417. * Converts database-layer column types to basic types
  418. *
  419. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  420. * @return string Abstract column type (i.e. "string")
  421. * @access public
  422. */
  423. function column($real) {
  424. if (is_array($real)) {
  425. $col = $real['name'];
  426. if (isset($real['limit'])) {
  427. $col .= '('.$real['limit'].')';
  428. }
  429. return $col;
  430. }
  431. $col = strtolower(str_replace(')', '', $real));
  432. $limit = null;
  433. @list($col, $limit) = explode('(', $col);
  434. if (in_array($col, array('text', 'integer', 'float', 'boolean', 'timestamp', 'date', 'datetime', 'time'))) {
  435. return $col;
  436. }
  437. if (strpos($col, 'varchar') !== false || strpos($col, 'char') !== false) {
  438. return 'string';
  439. }
  440. if (in_array($col, array('blob', 'clob'))) {
  441. return 'binary';
  442. }
  443. if (strpos($col, 'numeric') !== false) {
  444. return 'float';
  445. }
  446. return 'text';
  447. }
  448. /**
  449. * Enter description here...
  450. *
  451. * @param unknown_type $results
  452. * @return string
  453. * @access public
  454. */
  455. function resultSet(&$results) {
  456. $this->results =& $results;
  457. //echo "resultSet:results ";
  458. //pr($results);
  459. $this->map = array();
  460. $num_fields = $results->columnCount();
  461. $index = 0;
  462. $j = 0;
  463. //PDO::getColumnMeta is experimental and does not work with sqlite3,
  464. //so try to figure it out based on the querystring
  465. $querystring = $results->queryString;
  466. if (strpos($querystring,"SELECT") === 0)
  467. {
  468. $last = strpos($querystring,"FROM");
  469. if ($last !== false)
  470. {
  471. $selectpart = substr($querystring,7,$last-8);
  472. $selects = explode(",",$selectpart);
  473. }
  474. }
  475. elseif (strpos($querystring,"PRAGMA table_info") === 0)
  476. {
  477. $selects = array("cid","name","type","notnull","dflt_value","pk");
  478. }
  479. while ($j < $num_fields) {
  480. //echo "resultSet:columnmeta ";
  481. //$columnName = str_replace('"', '', sqlite3_field_name($results, $j));
  482. if(preg_match('/.*AS "(.*)".*/i', $selects[$j], $matches)){
  483. $columnName = $matches[1];
  484. }else{
  485. $columnName = trim(str_replace('"', '', $selects[$j]));
  486. }
  487. if (strpos($columnName, '.')) {
  488. $parts = explode('.', $columnName);
  489. $this->map[$index++] = array(trim($parts[0]), trim($parts[1]));
  490. } else {
  491. $this->map[$index++] = array(0, $columnName);
  492. }
  493. $j++;
  494. }
  495. }
  496. /**
  497. * Fetches the next row from the current result set
  498. *
  499. * @return unknown
  500. * @access public
  501. */
  502. function fetchResult() {
  503. //if ($row = sqlite3_fetch_array($this->results, SQLITE3_ASSOC)) {
  504. if (count($this->rows)) {
  505. $row = array_shift($this->rows);
  506. //echo "fetchResult:nextrow ";
  507. //pr($row);
  508. $resultRow = array();
  509. $i = 0;
  510. foreach ($row as $index => $field) {
  511. //pr($index);
  512. if (isset($this->map[$index]) and $this->map[$index] != "") {
  513. //echo "asdf: ".$this->map[$index];
  514. list($table, $column) = $this->map[$index];
  515. $resultRow[$table][$column] = $row[$index];
  516. } else {
  517. $resultRow[0][str_replace('"', '', $index)] = $row[$index];
  518. }
  519. $i++;
  520. }
  521. //pr($resultRow);
  522. return $resultRow;
  523. } else {
  524. return false;
  525. }
  526. }
  527. /**
  528. * Returns a limit statement in the correct format for the particular database.
  529. *
  530. * @param integer $limit Limit of results returned
  531. * @param integer $offset Offset from which to start results
  532. * @return string SQL limit/offset statement
  533. * @access public
  534. */
  535. function limit ($limit, $offset = null) {
  536. if ($limit) {
  537. $rt = '';
  538. if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
  539. $rt = ' LIMIT';
  540. }
  541. $rt .= ' ' . $limit;
  542. if ($offset) {
  543. $rt .= ' OFFSET ' . $offset;
  544. }
  545. return $rt;
  546. }
  547. return null;
  548. }
  549. /**
  550. * Generate a database-native column schema string
  551. *
  552. * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
  553. * where options can be 'default', 'length', or 'key'.
  554. * @return string
  555. * @access public
  556. */
  557. function buildColumn($column) {
  558. $name = $type = null;
  559. $column = array_merge(array('null' => true), $column);
  560. extract($column);
  561. if (empty($name) || empty($type)) {
  562. trigger_error('Column name or type not defined in schema', E_USER_WARNING);
  563. return null;
  564. }
  565. if (!isset($this->columns[$type])) {
  566. trigger_error("Column type {$type} does not exist", E_USER_WARNING);
  567. return null;
  568. }
  569. $real = $this->columns[$type];
  570. if (isset($column['key']) && $column['key'] == 'primary') {
  571. $out = $this->name($name) . ' ' . $this->columns['primary_key']['name'];
  572. } else {
  573. $out = $this->name($name) . ' ' . $real['name'];
  574. if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
  575. if (isset($column['length'])) {
  576. $length = $column['length'];
  577. } elseif (isset($column['limit'])) {
  578. $length = $column['limit'];
  579. } elseif (isset($real['length'])) {
  580. $length = $real['length'];
  581. } else {
  582. $length = $real['limit'];
  583. }
  584. $out .= '(' . $length . ')';
  585. }
  586. if (isset($column['key']) && $column['key'] == 'primary') {
  587. $out .= ' NOT NULL';
  588. } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
  589. $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
  590. } elseif (isset($column['default'])) {
  591. $out .= ' DEFAULT ' . $this->value($column['default'], $type);
  592. } elseif (isset($column['null']) && $column['null'] == true) {
  593. $out .= ' DEFAULT NULL';
  594. } elseif (isset($column['null']) && $column['null'] == false) {
  595. $out .= ' NOT NULL';
  596. }
  597. }
  598. return $out;
  599. }
  600. /**
  601. * Removes redundant primary key indexes, as they are handled in the column def of the key.
  602. *
  603. * @param array $indexes
  604. * @param string $table
  605. * @return string
  606. */
  607. function buildIndex($indexes, $table = null) {
  608. $join = array();
  609. foreach ($indexes as $name => $value) {
  610. if ($name == 'PRIMARY') {
  611. continue;
  612. } else {
  613. $out = 'CREATE ';
  614. if (!empty($value['unique'])) {
  615. $out .= 'UNIQUE ';
  616. }
  617. if (is_array($value['column'])) {
  618. $value['column'] = join(', ', array_map(array(&$this, 'name'), $value['column']));
  619. } else {
  620. $value['column'] = $this->name($value['column']);
  621. }
  622. $out .= "INDEX {$name} ON {$table}({$value['column']});";
  623. }
  624. $join[] = $out;
  625. }
  626. return $join;
  627. }
  628. /**
  629. * Overrides DboSource::renderStatement to handle schema generation with SQLite3-style indexes
  630. *
  631. * @param string $type
  632. * @param array $data
  633. * @return string
  634. */
  635. function renderStatement($type, $data) {
  636. switch (strtolower($type)) {
  637. case 'schema':
  638. extract($data);
  639. foreach (array('columns', 'indexes') as $var) {
  640. if (is_array(${$var})) {
  641. ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
  642. }
  643. }
  644. return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
  645. break;
  646. default:
  647. return parent::renderStatement($type, $data);
  648. break;
  649. }
  650. }
  651. /**
  652. * PDO deals in objects, not resources, so overload accordingly.
  653. */
  654. function hasResult() {
  655. return is_object($this->_result);
  656. }
  657. }
  658. ?>