sqlite3.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  1. <?php
  2. // vim: set et ts=4 sw=4 fdm=marker:
  3. // +----------------------------------------------------------------------+
  4. // | PHP versions 4 - 7 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox, |
  7. // | Stig. S. Bakken, Lukas Smith |
  8. // | All rights reserved. |
  9. // +----------------------------------------------------------------------+
  10. // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB |
  11. // | API as well as database abstraction for PHP applications. |
  12. // | This LICENSE is in the BSD license style. |
  13. // | |
  14. // | Redistribution and use in source and binary forms, with or without |
  15. // | modification, are permitted provided that the following conditions |
  16. // | are met: |
  17. // | |
  18. // | Redistributions of source code must retain the above copyright |
  19. // | notice, this list of conditions and the following disclaimer. |
  20. // | |
  21. // | Redistributions in binary form must reproduce the above copyright |
  22. // | notice, this list of conditions and the following disclaimer in the |
  23. // | documentation and/or other materials provided with the distribution. |
  24. // | |
  25. // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
  26. // | Lukas Smith nor the names of his contributors may be used to endorse |
  27. // | or promote products derived from this software without specific prior|
  28. // | written permission. |
  29. // | |
  30. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  31. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  32. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
  33. // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
  34. // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
  35. // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  36. // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
  37. // | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
  38. // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  39. // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
  40. // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
  41. // | POSSIBILITY OF SUCH DAMAGE. |
  42. // +----------------------------------------------------------------------+
  43. // | Author: Lorenzo Alberton <l.alberton@quipo.it> |
  44. // +----------------------------------------------------------------------+
  45. //
  46. // $Id$
  47. //
  48. /**
  49. * MDB2 SQLite3 driver
  50. *
  51. * @package MDB2
  52. * @category Database
  53. * @author Lorenzo Alberton <l.alberton@quipo.it>
  54. */
  55. class MDB2_Driver_sqlite3 extends MDB2_Driver_Common
  56. {
  57. // {{{ properties
  58. var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false);
  59. var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"');
  60. var $_lasterror = '';
  61. var $fix_assoc_fields_names = false;
  62. // }}}
  63. // {{{ constructor
  64. /**
  65. * Constructor
  66. */
  67. function __construct()
  68. {
  69. parent::__construct();
  70. $this->phptype = 'sqlite3';
  71. $this->dbsyntax = 'sqlite3';
  72. $this->supported['sequences'] = 'emulated';
  73. $this->supported['indexes'] = true;
  74. $this->supported['affected_rows'] = true;
  75. $this->supported['summary_functions'] = true;
  76. $this->supported['order_by_text'] = true;
  77. $this->supported['current_id'] = 'emulated';
  78. $this->supported['limit_queries'] = true;
  79. $this->supported['LOBs'] = true;
  80. $this->supported['replace'] = true;
  81. $this->supported['transactions'] = true;
  82. $this->supported['savepoints'] = false;
  83. $this->supported['sub_selects'] = true;
  84. $this->supported['triggers'] = true;
  85. $this->supported['auto_increment'] = true;
  86. $this->supported['primary_key'] = false; // requires alter table implementation
  87. $this->supported['result_introspection'] = false; // not implemented
  88. $this->supported['prepared_statements'] = 'emulated';
  89. $this->supported['identifier_quoting'] = true;
  90. $this->supported['pattern_escaping'] = false;
  91. $this->supported['new_link'] = false;
  92. $this->options['DBA_username'] = false;
  93. $this->options['DBA_password'] = false;
  94. $this->options['base_transaction_name'] = '___php_MDB2_sqlite3_auto_commit_off';
  95. $this->options['fixed_float'] = 0;
  96. $this->options['database_path'] = '';
  97. $this->options['database_extension'] = '';
  98. $this->options['server_version'] = '';
  99. $this->options['max_identifiers_length'] = 128; //no real limit
  100. }
  101. // }}}
  102. // {{{ errorInfo()
  103. /**
  104. * This method is used to collect information about an error
  105. *
  106. * @param integer $error
  107. * @return array
  108. * @access public
  109. */
  110. function errorInfo($error = null)
  111. {
  112. $native_code = null;
  113. if ($this->connection) {
  114. $native_code = @$this->connection->lastErrorCode();
  115. }
  116. $native_msg = $this->_lasterror
  117. ? html_entity_decode($this->_lasterror) : '';//@$this->getConnection()->lastErrorMsg();
  118. // PHP 5.2+ prepends the function name to $php_errormsg, so we need
  119. // this hack to work around it, per bug #9599.
  120. $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg);
  121. if (null === $error) {
  122. static $error_regexps;
  123. if (empty($error_regexps)) {
  124. $error_regexps = array(
  125. '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE,
  126. '/^no such index:/' => MDB2_ERROR_NOT_FOUND,
  127. '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS,
  128. '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT,
  129. '/is not unique/' => MDB2_ERROR_CONSTRAINT,
  130. '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT,
  131. '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT,
  132. '/violates .*constraint/' => MDB2_ERROR_CONSTRAINT,
  133. '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL,
  134. '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD,
  135. '/no column named/' => MDB2_ERROR_NOSUCHFIELD,
  136. '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD,
  137. '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX,
  138. '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW,
  139. );
  140. }
  141. foreach ($error_regexps as $regexp => $code) {
  142. if (preg_match($regexp, $native_msg)) {
  143. $error = $code;
  144. break;
  145. }
  146. }
  147. }
  148. return array($error, $native_code, $native_msg);
  149. }
  150. // }}}
  151. // {{{ escape()
  152. /**
  153. * Quotes a string so it can be safely used in a query. It will quote
  154. * the text so it can safely be used within a query.
  155. *
  156. * @param string the input string to quote
  157. * @param bool escape wildcards
  158. *
  159. * @return string quoted string
  160. *
  161. * @access public
  162. */
  163. function escape($text, $escape_wildcards = false)
  164. {
  165. $connection = $this->getConnection();
  166. if (MDB2::isError($connection)) {
  167. return $connection;
  168. }
  169. $text = @$connection->escapeString($text);
  170. return $text;
  171. }
  172. // }}}
  173. // {{{ beginTransaction()
  174. /**
  175. * Start a transaction or set a savepoint.
  176. *
  177. * @param string name of a savepoint to set
  178. * @return mixed MDB2_OK on success, a MDB2 error on failure
  179. *
  180. * @access public
  181. */
  182. function beginTransaction($savepoint = null)
  183. {
  184. $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  185. if (null !== $savepoint) {
  186. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  187. 'savepoints are not supported', __FUNCTION__);
  188. }
  189. if ($this->in_transaction) {
  190. return MDB2_OK; //nothing to do
  191. }
  192. if (!$this->destructor_registered && $this->opened_persistent) {
  193. $this->destructor_registered = true;
  194. register_shutdown_function('MDB2_closeOpenTransactions');
  195. }
  196. $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name'];
  197. $result = $this->_doQuery($query, true);
  198. if (MDB2::isError($result)) {
  199. return $result;
  200. }
  201. $this->in_transaction = true;
  202. return MDB2_OK;
  203. }
  204. // }}}
  205. // {{{ commit()
  206. /**
  207. * Commit the database changes done during a transaction that is in
  208. * progress or release a savepoint. This function may only be called when
  209. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  210. * transaction is implicitly started after committing the pending changes.
  211. *
  212. * @param string name of a savepoint to release
  213. * @return mixed MDB2_OK on success, a MDB2 error on failure
  214. *
  215. * @access public
  216. */
  217. function commit($savepoint = null)
  218. {
  219. $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  220. if (!$this->in_transaction) {
  221. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  222. 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
  223. }
  224. if (null !== $savepoint) {
  225. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  226. 'savepoints are not supported', __FUNCTION__);
  227. }
  228. $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name'];
  229. $result = $this->_doQuery($query, true);
  230. if (MDB2::isError($result)) {
  231. return $result;
  232. }
  233. $this->in_transaction = false;
  234. return MDB2_OK;
  235. }
  236. // }}}
  237. // {{{
  238. /**
  239. * Cancel any database changes done during a transaction or since a specific
  240. * savepoint that is in progress. This function may only be called when
  241. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  242. * transaction is implicitly started after canceling the pending changes.
  243. *
  244. * @param string name of a savepoint to rollback to
  245. * @return mixed MDB2_OK on success, a MDB2 error on failure
  246. *
  247. * @access public
  248. */
  249. function rollback($savepoint = null)
  250. {
  251. $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  252. if (!$this->in_transaction) {
  253. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  254. 'rollback cannot be done changes are auto committed', __FUNCTION__);
  255. }
  256. if (null !== $savepoint) {
  257. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  258. 'savepoints are not supported', __FUNCTION__);
  259. }
  260. $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name'];
  261. $result = $this->_doQuery($query, true);
  262. if (MDB2::isError($result)) {
  263. return $result;
  264. }
  265. $this->in_transaction = false;
  266. return MDB2_OK;
  267. }
  268. // }}}
  269. // {{{ function setTransactionIsolation()
  270. /**
  271. * Set the transacton isolation level.
  272. *
  273. * @param string standard isolation level
  274. * READ UNCOMMITTED (allows dirty reads)
  275. * READ COMMITTED (prevents dirty reads)
  276. * REPEATABLE READ (prevents nonrepeatable reads)
  277. * SERIALIZABLE (prevents phantom reads)
  278. * @param array some transaction options:
  279. * 'wait' => 'WAIT' | 'NO WAIT'
  280. * 'rw' => 'READ WRITE' | 'READ ONLY'
  281. *
  282. * @return mixed MDB2_OK on success, a MDB2 error on failure
  283. *
  284. * @access public
  285. * @since 2.1.1
  286. */
  287. function setTransactionIsolation($isolation, $options = array())
  288. {
  289. $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
  290. switch ($isolation) {
  291. case 'READ UNCOMMITTED':
  292. $isolation = 0;
  293. break;
  294. case 'READ COMMITTED':
  295. case 'REPEATABLE READ':
  296. case 'SERIALIZABLE':
  297. $isolation = 1;
  298. break;
  299. default:
  300. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  301. 'isolation level is not supported: '.$isolation, __FUNCTION__);
  302. }
  303. $query = "PRAGMA read_uncommitted=$isolation";
  304. return $this->_doQuery($query, true);
  305. }
  306. // }}}
  307. // {{{ getDatabaseFile()
  308. /**
  309. * Builds the string with path+dbname+extension
  310. *
  311. * @return string full database path+file
  312. * @access protected
  313. */
  314. function _getDatabaseFile($database_name)
  315. {
  316. if ($database_name === '' || $database_name === ':memory:') {
  317. return $database_name;
  318. }
  319. return $this->options['database_path'].$database_name.$this->options['database_extension'];
  320. }
  321. // }}}
  322. // {{{ connect()
  323. /**
  324. * Connect to the database
  325. *
  326. * @return true on success, MDB2 Error Object on failure
  327. **/
  328. function connect()
  329. {
  330. $database_file = $this->_getDatabaseFile($this->database_name);
  331. if (is_object($this->connection) && ($this->connection instanceof SQLite3)) {
  332. //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
  333. if (MDB2::areEquals($this->connected_dsn, $this->dsn)
  334. && $this->connected_database_name == $database_file
  335. && $this->opened_persistent == $this->options['persistent']
  336. ) {
  337. return MDB2_OK;
  338. }
  339. $this->disconnect(false);
  340. }
  341. if (!extension_loaded($this->phptype)) {
  342. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  343. 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
  344. }
  345. if (empty($this->database_name)) {
  346. return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
  347. 'unable to establish a connection', __FUNCTION__);
  348. }
  349. if ($database_file !== ':memory:') {
  350. if (!file_exists($database_file)) {
  351. if (!touch($database_file)) {
  352. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  353. 'Could not create database file', __FUNCTION__);
  354. }
  355. if (!isset($this->dsn['mode'])
  356. || !is_numeric($this->dsn['mode'])
  357. ) {
  358. $mode = 0644;
  359. } else {
  360. $mode = octdec($this->dsn['mode']);
  361. }
  362. if (!chmod($database_file, $mode)) {
  363. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  364. 'Could not be chmodded database file', __FUNCTION__);
  365. }
  366. if (!file_exists($database_file)) {
  367. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  368. 'Could not be found database file', __FUNCTION__);
  369. }
  370. }
  371. if (!is_file($database_file)) {
  372. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  373. 'Database is a directory name', __FUNCTION__);
  374. }
  375. if (!is_readable($database_file)) {
  376. return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null,
  377. 'Could not read database file', __FUNCTION__);
  378. }
  379. }
  380. $php_errormsg = '';
  381. @ini_set('track_errors', true);
  382. $connection = new SQLite3($database_file); // persistent connection not supported?
  383. @ini_restore('track_errors');
  384. $this->_lasterror = $php_errormsg;
  385. if (!$connection) {
  386. return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
  387. 'unable to establish a connection', __FUNCTION__);
  388. }
  389. if ($this->fix_assoc_fields_names ||
  390. $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES)
  391. {
  392. @$connection->query("PRAGMA short_column_names = 1");
  393. $this->fix_assoc_fields_names = true;
  394. }
  395. $this->connection = $connection;
  396. $this->connected_dsn = $this->dsn;
  397. $this->connected_database_name = $database_file;
  398. $this->opened_persistent = $this->getoption('persistent');
  399. $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
  400. return MDB2_OK;
  401. }
  402. // }}}
  403. // {{{ databaseExists()
  404. /**
  405. * check if given database name is exists?
  406. *
  407. * @param string $name name of the database that should be checked
  408. *
  409. * @return mixed true/false on success, a MDB2 error on failure
  410. * @access public
  411. */
  412. function databaseExists($name)
  413. {
  414. $database_file = $this->_getDatabaseFile($name);
  415. $result = file_exists($database_file);
  416. return $result;
  417. }
  418. // }}}
  419. // {{{ disconnect()
  420. /**
  421. * Log out and disconnect from the database.
  422. *
  423. * @param boolean $force if the disconnect should be forced even if the
  424. * connection is opened persistently
  425. * @return mixed true on success, false if not connected and error
  426. * object on error
  427. * @access public
  428. */
  429. function disconnect($force = true)
  430. {
  431. if (is_object($this->connection) && ($this->connection instanceof SQLite3)) {
  432. if ($this->in_transaction) {
  433. $dsn = $this->dsn;
  434. $database_name = $this->database_name;
  435. $persistent = $this->options['persistent'];
  436. $this->dsn = $this->connected_dsn;
  437. $this->database_name = $this->connected_database_name;
  438. $this->options['persistent'] = $this->opened_persistent;
  439. $this->rollback();
  440. $this->dsn = $dsn;
  441. $this->database_name = $database_name;
  442. $this->options['persistent'] = $persistent;
  443. }
  444. if (!$this->opened_persistent || $force) {
  445. @$this->connection->close();
  446. }
  447. } else {
  448. return false;
  449. }
  450. return parent::disconnect($force);
  451. }
  452. // }}}
  453. // {{{ _doQuery()
  454. /**
  455. * Execute a query
  456. * @param string $query query
  457. * @param boolean $is_manip if the query is a manipulation query
  458. * @param resource $connection
  459. * @param string $database_name
  460. * @return result or error object
  461. * @access protected
  462. */
  463. function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
  464. {
  465. $this->last_query = $query;
  466. $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
  467. if ($result) {
  468. if (MDB2::isError($result)) {
  469. return $result;
  470. }
  471. $query = $result;
  472. }
  473. if ($this->options['disable_query']) {
  474. $result = $is_manip ? 0 : null;
  475. return $result;
  476. }
  477. if (null === $connection) {
  478. $connection = $this->getConnection();
  479. if (MDB2::isError($connection)) {
  480. return $connection;
  481. }
  482. }
  483. $php_errormsg = '';
  484. @ini_set('track_errors', true);
  485. do {
  486. $result = @$connection->query($query.';'); // result_buffering unsupported?
  487. } while ($connection->lastErrorCode() == 17); // SQLITE_SCHEMA
  488. @ini_restore('track_errors');
  489. $this->_lasterror = $php_errormsg;
  490. if (!$result) {
  491. $code = null;
  492. if (false !== strpos($this->_lasterror, 'no such table')) {
  493. $code = MDB2_ERROR_NOSUCHTABLE;
  494. }
  495. $err = $this->raiseError($code, null, null,
  496. 'Could not execute statement', __FUNCTION__);
  497. return $err;
  498. }
  499. $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
  500. return $result;
  501. }
  502. // }}}
  503. // {{{ _affectedRows()
  504. /**
  505. * Returns the number of rows affected
  506. *
  507. * @param resource $result
  508. * @param resource $connection
  509. * @return mixed MDB2 Error Object or the number of rows affected
  510. * @access private
  511. */
  512. function _affectedRows($connection, $result = null)
  513. {
  514. if (null === $connection) {
  515. $connection = $this->getConnection();
  516. if (MDB2::isError($connection)) {
  517. return $connection;
  518. }
  519. }
  520. return @$connection->changes();
  521. }
  522. // }}}
  523. // {{{ _modifyQuery()
  524. /**
  525. * Changes a query string for various DBMS specific reasons
  526. *
  527. * @param string $query query to modify
  528. * @param boolean $is_manip if it is a DML query
  529. * @param integer $limit limit the number of rows
  530. * @param integer $offset start reading from given offset
  531. * @return string modified query
  532. * @access protected
  533. */
  534. function _modifyQuery($query, $is_manip, $limit, $offset)
  535. {
  536. if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
  537. if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
  538. $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
  539. 'DELETE FROM \1 WHERE 1=1', $query);
  540. }
  541. }
  542. if ($limit > 0
  543. && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
  544. ) {
  545. $query = rtrim($query);
  546. if (substr($query, -1) == ';') {
  547. $query = substr($query, 0, -1);
  548. }
  549. if ($is_manip) {
  550. $query.= " LIMIT $limit";
  551. } else {
  552. $query.= " LIMIT $offset,$limit";
  553. }
  554. }
  555. return $query;
  556. }
  557. // }}}
  558. // {{{ getServerVersion()
  559. /**
  560. * return version information about the server
  561. *
  562. * @param bool $native determines if the raw version string should be returned
  563. * @return mixed array/string with version information or MDB2 error object
  564. * @access public
  565. */
  566. function getServerVersion($native = false)
  567. {
  568. $server_info = false;
  569. if ($this->connected_server_info) {
  570. $server_info = $this->connected_server_info;
  571. } elseif ($this->options['server_version']) {
  572. $server_info = $this->options['server_version'];
  573. } elseif (method_exists('SQLite3', 'version')) {
  574. $tmp = @SQLite3::version();
  575. $server_info = $tmp['versionString'];
  576. }
  577. if (!$server_info) {
  578. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  579. 'Requires either the "server_version" option or the SQLite3::version() function', __FUNCTION__);
  580. }
  581. // cache server_info
  582. $this->connected_server_info = $server_info;
  583. if (!$native) {
  584. $tmp = explode('.', $server_info, 3);
  585. $server_info = array(
  586. 'major' => isset($tmp[0]) ? $tmp[0] : null,
  587. 'minor' => isset($tmp[1]) ? $tmp[1] : null,
  588. 'patch' => isset($tmp[2]) ? $tmp[2] : null,
  589. 'extra' => null,
  590. 'native' => $server_info,
  591. );
  592. }
  593. return $server_info;
  594. }
  595. // }}}
  596. // {{{ replace()
  597. /**
  598. * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
  599. * query, except that if there is already a row in the table with the same
  600. * key field values, the old row is deleted before the new row is inserted.
  601. *
  602. * The REPLACE type of query does not make part of the SQL standards. Since
  603. * practically only SQLite implements it natively, this type of query is
  604. * emulated through this method for other DBMS using standard types of
  605. * queries inside a transaction to assure the atomicity of the operation.
  606. *
  607. * @access public
  608. *
  609. * @param string $table name of the table on which the REPLACE query will
  610. * be executed.
  611. * @param array $fields associative array that describes the fields and the
  612. * values that will be inserted or updated in the specified table. The
  613. * indexes of the array are the names of all the fields of the table. The
  614. * values of the array are also associative arrays that describe the
  615. * values and other properties of the table fields.
  616. *
  617. * Here follows a list of field properties that need to be specified:
  618. *
  619. * value:
  620. * Value to be assigned to the specified field. This value may be
  621. * of specified in database independent type format as this
  622. * function can perform the necessary datatype conversions.
  623. *
  624. * Default:
  625. * this property is required unless the Null property
  626. * is set to 1.
  627. *
  628. * type
  629. * Name of the type of the field. Currently, all types Metabase
  630. * are supported except for clob and blob.
  631. *
  632. * Default: no type conversion
  633. *
  634. * null
  635. * Boolean property that indicates that the value for this field
  636. * should be set to null.
  637. *
  638. * The default value for fields missing in INSERT queries may be
  639. * specified the definition of a table. Often, the default value
  640. * is already null, but since the REPLACE may be emulated using
  641. * an UPDATE query, make sure that all fields of the table are
  642. * listed in this function argument array.
  643. *
  644. * Default: 0
  645. *
  646. * key
  647. * Boolean property that indicates that this field should be
  648. * handled as a primary key or at least as part of the compound
  649. * unique index of the table that will determine the row that will
  650. * updated if it exists or inserted a new row otherwise.
  651. *
  652. * This function will fail if no key field is specified or if the
  653. * value of a key field is set to null because fields that are
  654. * part of unique index they may not be null.
  655. *
  656. * Default: 0
  657. *
  658. * @return mixed MDB2_OK on success, a MDB2 error on failure
  659. */
  660. function replace($table, $fields)
  661. {
  662. $count = count($fields);
  663. $query = $values = '';
  664. $keys = $colnum = 0;
  665. for (reset($fields); $colnum < $count; next($fields), $colnum++) {
  666. $name = key($fields);
  667. if ($colnum > 0) {
  668. $query .= ',';
  669. $values.= ',';
  670. }
  671. $query.= $this->quoteIdentifier($name, true);
  672. if (isset($fields[$name]['null']) && $fields[$name]['null']) {
  673. $value = 'NULL';
  674. } else {
  675. $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
  676. $value = $this->quote($fields[$name]['value'], $type);
  677. if (MDB2::isError($value)) {
  678. return $value;
  679. }
  680. }
  681. $values.= $value;
  682. if (isset($fields[$name]['key']) && $fields[$name]['key']) {
  683. if ($value === 'NULL') {
  684. return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
  685. 'key value '.$name.' may not be NULL', __FUNCTION__);
  686. }
  687. $keys++;
  688. }
  689. }
  690. if ($keys == 0) {
  691. return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
  692. 'not specified which fields are keys', __FUNCTION__);
  693. }
  694. $connection = $this->getConnection();
  695. if (MDB2::isError($connection)) {
  696. return $connection;
  697. }
  698. $table = $this->quoteIdentifier($table, true);
  699. $query = "REPLACE INTO $table ($query) VALUES ($values)";
  700. $result = $this->_doQuery($query, true, $connection);
  701. if (MDB2::isError($result)) {
  702. return $result;
  703. }
  704. return $this->_affectedRows($connection, $result);
  705. }
  706. // }}}
  707. // {{{ nextID()
  708. /**
  709. * Returns the next free id of a sequence
  710. *
  711. * @param string $seq_name name of the sequence
  712. * @param boolean $ondemand when true the sequence is
  713. * automatic created, if it
  714. * not exists
  715. *
  716. * @return mixed MDB2 Error Object or id
  717. * @access public
  718. */
  719. function nextID($seq_name, $ondemand = true)
  720. {
  721. $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
  722. $seqcol_name = $this->options['seqcol_name'];
  723. $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
  724. $this->pushErrorHandling(PEAR_ERROR_RETURN);
  725. $this->expectError(MDB2_ERROR_NOSUCHTABLE);
  726. $result = $this->_doQuery($query, true);
  727. $this->popExpect();
  728. $this->popErrorHandling();
  729. if (MDB2::isError($result)) {
  730. $conn = $this->getConnection();
  731. if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
  732. $this->loadModule('Manager', null, true);
  733. $result = $this->manager->createSequence($seq_name);
  734. if (MDB2::isError($result)) {
  735. return $this->raiseError($result, null, null,
  736. 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
  737. }
  738. return $this->nextID($seq_name, false);
  739. }
  740. return $result;
  741. }
  742. $value = $this->lastInsertID();
  743. if (is_numeric($value)) {
  744. $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
  745. $result = $this->_doQuery($query, true);
  746. if (MDB2::isError($result)) {
  747. $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
  748. }
  749. }
  750. return $value;
  751. }
  752. // }}}
  753. // {{{ lastInsertID()
  754. /**
  755. * Returns the autoincrement ID if supported or $id or fetches the current
  756. * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
  757. *
  758. * @param string $table name of the table into which a new row was inserted
  759. * @param string $field name of the field into which a new row was inserted
  760. * @return mixed MDB2 Error Object or id
  761. * @access public
  762. */
  763. function lastInsertID($table = null, $field = null)
  764. {
  765. $connection = $this->getConnection();
  766. if (MDB2::isError($connection)) {
  767. return $connection;
  768. }
  769. $value = @$connection->lastInsertRowID();
  770. if (!$value) {
  771. return $this->raiseError(null, null, null,
  772. 'Could not get last insert ID', __FUNCTION__);
  773. }
  774. return $value;
  775. }
  776. // }}}
  777. // {{{ currID()
  778. /**
  779. * Returns the current id of a sequence
  780. *
  781. * @param string $seq_name name of the sequence
  782. * @return mixed MDB2 Error Object or id
  783. * @access public
  784. */
  785. function currID($seq_name)
  786. {
  787. $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
  788. $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
  789. $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
  790. return $this->queryOne($query, 'integer');
  791. }
  792. }
  793. /**
  794. * MDB2 SQLite result driver
  795. *
  796. * @package MDB2
  797. * @category Database
  798. * @author Lukas Smith <smith@pooteeweet.org>
  799. */
  800. class MDB2_Result_sqlite3 extends MDB2_Result_Common
  801. {
  802. // }}}
  803. // {{{ fetchRow()
  804. /**
  805. * Fetch a row and insert the data into an existing array.
  806. *
  807. * @param int $fetchmode how the array data should be indexed
  808. * @param int $rownum number of the row where the data can be found
  809. * @return int data array on success, a MDB2 error on failure
  810. * @access public
  811. */
  812. function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
  813. {
  814. if (null !== $rownum) {
  815. $seek = $this->seek($rownum);
  816. if (MDB2::isError($seek)) {
  817. return $seek;
  818. }
  819. }
  820. if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
  821. $fetchmode = $this->db->fetchmode;
  822. }
  823. if ( $fetchmode == MDB2_FETCHMODE_ASSOC
  824. || $fetchmode == MDB2_FETCHMODE_OBJECT
  825. ) {
  826. $row = @$this->result->fetchArray(SQLITE3_ASSOC);
  827. if (is_array($row)
  828. && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
  829. ) {
  830. $row = array_change_key_case($row, $this->db->options['field_case']);
  831. }
  832. } else {
  833. $row = @$this->result->fetchArray(SQLITE3_NUM);
  834. }
  835. if (!$row) {
  836. if (false === $this->result) {
  837. $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  838. 'resultset has already been freed', __FUNCTION__);
  839. return $err;
  840. }
  841. return null;
  842. }
  843. $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
  844. $rtrim = false;
  845. if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
  846. if (empty($this->types)) {
  847. $mode += MDB2_PORTABILITY_RTRIM;
  848. } else {
  849. $rtrim = true;
  850. }
  851. }
  852. if ($mode) {
  853. $this->db->_fixResultArrayValues($row, $mode);
  854. }
  855. if ( ( $fetchmode != MDB2_FETCHMODE_ASSOC
  856. && $fetchmode != MDB2_FETCHMODE_OBJECT)
  857. && !empty($this->types)
  858. ) {
  859. $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
  860. } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC
  861. || $fetchmode == MDB2_FETCHMODE_OBJECT)
  862. && !empty($this->types_assoc)
  863. ) {
  864. $row = $this->db->datatype->convertResultRow($this->types_assoc, $row, $rtrim);
  865. }
  866. if (!empty($this->values)) {
  867. $this->_assignBindColumns($row);
  868. }
  869. if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
  870. $object_class = $this->db->options['fetch_class'];
  871. if ($object_class == 'stdClass') {
  872. $row = (object) $row;
  873. } else {
  874. $rowObj = new $object_class($row);
  875. $row = $rowObj;
  876. }
  877. }
  878. ++$this->rownum;
  879. return $row;
  880. }
  881. // }}}
  882. // {{{ _getColumnNames()
  883. /**
  884. * Retrieve the names of columns returned by the DBMS in a query result.
  885. *
  886. * @return mixed Array variable that holds the names of columns as keys
  887. * or an MDB2 error on failure.
  888. * Some DBMS may not return any columns when the result set
  889. * does not contain any rows.
  890. * @access private
  891. */
  892. function _getColumnNames()
  893. {
  894. $columns = array();
  895. $numcols = $this->numCols();
  896. if (MDB2::isError($numcols)) {
  897. return $numcols;
  898. }
  899. for ($column = 0; $column < $numcols; $column++) {
  900. $column_name = @$this->result->columnName($column);
  901. $columns[$column_name] = $column;
  902. }
  903. if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  904. $columns = array_change_key_case($columns, $this->db->options['field_case']);
  905. }
  906. return $columns;
  907. }
  908. // }}}
  909. // {{{ numCols()
  910. /**
  911. * Count the number of columns returned by the DBMS in a query result.
  912. *
  913. * @access public
  914. * @return mixed integer value with the number of columns, a MDB2 error
  915. * on failure
  916. */
  917. function numCols()
  918. {
  919. $cols = @$this->result->numColumns();
  920. if (null === $cols) {
  921. if (false === $this->result) {
  922. return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  923. 'resultset has already been freed', __FUNCTION__);
  924. }
  925. if (null === $this->result) {
  926. return count($this->types);
  927. }
  928. return $this->db->raiseError(null, null, null,
  929. 'Could not get column count', __FUNCTION__);
  930. }
  931. return $cols;
  932. }
  933. }
  934. /**
  935. * MDB2 SQLite buffered result driver
  936. *
  937. * @package MDB2
  938. * @category Database
  939. * @author Lukas Smith <smith@pooteeweet.org>
  940. */
  941. class MDB2_BufferedResult_sqlite3 extends MDB2_Result_sqlite3
  942. {
  943. // {{{ seek()
  944. /**
  945. * Seek to a specific row in a result set
  946. *
  947. * @param int $rownum number of the row where the data can be found
  948. * @return mixed MDB2_OK on success, a MDB2 error on failure
  949. * @access public
  950. */
  951. function seek($rownum = 0)
  952. {
  953. if (false === $this->result) {
  954. return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  955. 'resultset has already been freed', __FUNCTION__);
  956. }
  957. if (null === $this->result) {
  958. return MDB2_OK;
  959. }
  960. if ($rownum == 0) {
  961. $this->result->reset();
  962. $this->rownum = -1;
  963. return MDB2_OK;
  964. }
  965. $this->result->reset();
  966. $this->rownum = -1;
  967. $res = true;
  968. while ($rownum > -1 && $res !== false) {
  969. $res = $this->result->fetchArray();
  970. $this->rownum++;
  971. $rownum--;
  972. }
  973. if ($res === false) {
  974. return $this->db->raiseError(MDB2_ERROR_INVALID, null, null,
  975. 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__);
  976. }
  977. return MDB2_OK;
  978. }
  979. // }}}
  980. // {{{ valid()
  981. /**
  982. * Check if the end of the result set has been reached
  983. *
  984. * @return mixed true or false on sucess, a MDB2 error on failure
  985. * @access public
  986. */
  987. function valid()
  988. {
  989. $numrows = $this->numRows();
  990. if (MDB2::isError($numrows)) {
  991. return $numrows;
  992. }
  993. return $this->rownum < ($numrows - 1);
  994. }
  995. // }}}
  996. // {{{ numRows()
  997. /**
  998. * Returns the number of rows in a result object
  999. *
  1000. * @return mixed MDB2 Error Object or the number of rows
  1001. * @access public
  1002. */
  1003. function numRows()
  1004. {
  1005. if (false === $this->result) {
  1006. return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  1007. 'resultset has already been freed', __FUNCTION__);
  1008. }
  1009. if (null === $this->result) {
  1010. return 0;
  1011. }
  1012. $previous = $this->rownum; // save cursor position
  1013. $this->result->reset();
  1014. $nrows = 0;
  1015. while ($this->result->fetchArray()) {
  1016. $nrows++;
  1017. }
  1018. $this->seek($previous); // restore cursor position
  1019. $this->rownum = $previous;
  1020. return $nrows;
  1021. }
  1022. }
  1023. /**
  1024. * MDB2 SQLite statement driver
  1025. *
  1026. * @package MDB2
  1027. * @category Database
  1028. * @author Lukas Smith <smith@pooteeweet.org>
  1029. */
  1030. class MDB2_Statement_sqlite3 extends MDB2_Statement_Common
  1031. {
  1032. }
  1033. ?>