PageRenderTime 122ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/MDB2/Driver/sqlite3.php

https://github.com/jlgg/simple_trash
PHP | 1332 lines | 811 code | 79 blank | 442 comment | 140 complexity | 373837d345d052d03d47b95f8799fbf5 MD5 | raw file
Possible License(s): AGPL-3.0, AGPL-1.0, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Robin Appelman
  6. * @copyright 2011 Robin Appelman icewind1991@gmail.com
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. /**
  23. * MDB2 SQLite3 driver
  24. *
  25. * @package MDB2
  26. * @category Database
  27. * @author Lukas Smith <smith@pooteeweet.org>
  28. */
  29. class MDB2_Driver_sqlite3 extends MDB2_Driver_Common
  30. {
  31. // {{{ properties
  32. public $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false);
  33. public $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"');
  34. public $_lasterror = '';
  35. public $fix_assoc_fields_names = false;
  36. // }}}
  37. // {{{ constructor
  38. /**
  39. * Constructor
  40. */
  41. function __construct()
  42. {
  43. parent::__construct();
  44. $this->phptype = 'sqlite3';
  45. $this->dbsyntax = 'sqlite';
  46. $this->supported['sequences'] = 'emulated';
  47. $this->supported['indexes'] = true;
  48. $this->supported['affected_rows'] = true;
  49. $this->supported['summary_functions'] = true;
  50. $this->supported['order_by_text'] = true;
  51. $this->supported['current_id'] = 'emulated';
  52. $this->supported['limit_queries'] = true;
  53. $this->supported['LOBs'] = true;
  54. $this->supported['replace'] = true;
  55. $this->supported['transactions'] = false;
  56. $this->supported['savepoints'] = false;
  57. $this->supported['sub_selects'] = true;
  58. $this->supported['triggers'] = true;
  59. $this->supported['auto_increment'] = true;
  60. $this->supported['primary_key'] = false; // requires alter table implementation
  61. $this->supported['result_introspection'] = false; // not implemented
  62. $this->supported['prepared_statements'] = true;
  63. $this->supported['identifier_quoting'] = true;
  64. $this->supported['pattern_escaping'] = false;
  65. $this->supported['new_link'] = false;
  66. $this->options['DBA_username'] = false;
  67. $this->options['DBA_password'] = false;
  68. $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off';
  69. $this->options['fixed_float'] = 0;
  70. $this->options['database_path'] = '';
  71. $this->options['database_extension'] = '';
  72. $this->options['server_version'] = '';
  73. $this->options['max_identifiers_length'] = 128; //no real limit
  74. }
  75. // }}}
  76. // {{{ errorInfo()
  77. /**
  78. * This method is used to collect information about an error
  79. *
  80. * @param integer $error
  81. * @return array
  82. * @access public
  83. */
  84. function errorInfo($error = null)
  85. {
  86. $native_code = null;
  87. if ($this->connection) {
  88. $native_code = $this->connection->lastErrorCode();
  89. }
  90. $native_msg = html_entity_decode($this->_lasterror);
  91. // PHP 5.2+ prepends the function name to $php_errormsg, so we need
  92. // this hack to work around it, per bug #9599.
  93. $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg);
  94. if (is_null($error)) {
  95. static $error_regexps;
  96. if (empty($error_regexps)) {
  97. $error_regexps = array(
  98. '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE,
  99. '/^no such index:/' => MDB2_ERROR_NOT_FOUND,
  100. '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS,
  101. '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT,
  102. '/is not unique/' => MDB2_ERROR_CONSTRAINT,
  103. '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT,
  104. '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT,
  105. '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL,
  106. '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD,
  107. '/no column named/' => MDB2_ERROR_NOSUCHFIELD,
  108. '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD,
  109. '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX,
  110. '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW,
  111. );
  112. }
  113. foreach ($error_regexps as $regexp => $code) {
  114. if (preg_match($regexp, $native_msg)) {
  115. $error = $code;
  116. break;
  117. }
  118. }
  119. }
  120. return array($error, $native_code, $native_msg);
  121. }
  122. // }}}
  123. // {{{ escape()
  124. /**
  125. * Quotes a string so it can be safely used in a query. It will quote
  126. * the text so it can safely be used within a query.
  127. *
  128. * @param string the input string to quote
  129. * @param bool escape wildcards
  130. *
  131. * @return string quoted string
  132. *
  133. * @access public
  134. */
  135. public function escape($text, $escape_wildcards = false)
  136. {
  137. if($this->connection) {
  138. return $this->connection->escapeString($text);
  139. }else{
  140. return str_replace("'", "''", $text);//TODO; more
  141. }
  142. }
  143. // }}}
  144. // {{{ beginTransaction()
  145. /**
  146. * Start a transaction or set a savepoint.
  147. *
  148. * @param string name of a savepoint to set
  149. * @return mixed MDB2_OK on success, a MDB2 error on failure
  150. *
  151. * @access public
  152. */
  153. function beginTransaction($savepoint = null)
  154. {
  155. $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  156. if (!is_null($savepoint)) {
  157. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  158. 'savepoints are not supported', __FUNCTION__);
  159. } elseif ($this->in_transaction) {
  160. return MDB2_OK; //nothing to do
  161. }
  162. if (!$this->destructor_registered && $this->opened_persistent) {
  163. $this->destructor_registered = true;
  164. register_shutdown_function('MDB2_closeOpenTransactions');
  165. }
  166. $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name'];
  167. $result =$this->_doQuery($query, true);
  168. if (PEAR::isError($result)) {
  169. return $result;
  170. }
  171. $this->in_transaction = true;
  172. return MDB2_OK;
  173. }
  174. // }}}
  175. // {{{ commit()
  176. /**
  177. * Commit the database changes done during a transaction that is in
  178. * progress or release a savepoint. This function may only be called when
  179. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  180. * transaction is implicitly started after committing the pending changes.
  181. *
  182. * @param string name of a savepoint to release
  183. * @return mixed MDB2_OK on success, a MDB2 error on failure
  184. *
  185. * @access public
  186. */
  187. function commit($savepoint = null)
  188. {
  189. $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  190. if (!$this->in_transaction) {
  191. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  192. 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
  193. }
  194. if (!is_null($savepoint)) {
  195. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  196. 'savepoints are not supported', __FUNCTION__);
  197. }
  198. $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name'];
  199. $result =$this->_doQuery($query, true);
  200. if (PEAR::isError($result)) {
  201. return $result;
  202. }
  203. $this->in_transaction = false;
  204. return MDB2_OK;
  205. }
  206. // }}}
  207. // {{{
  208. /**
  209. * Cancel any database changes done during a transaction or since a specific
  210. * savepoint that is in progress. This function may only be called when
  211. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  212. * transaction is implicitly started after canceling the pending changes.
  213. *
  214. * @param string name of a savepoint to rollback to
  215. * @return mixed MDB2_OK on success, a MDB2 error on failure
  216. *
  217. * @access public
  218. */
  219. function rollback($savepoint = null)
  220. {
  221. $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  222. if (!$this->in_transaction) {
  223. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  224. 'rollback cannot be done changes are auto committed', __FUNCTION__);
  225. }
  226. if (!is_null($savepoint)) {
  227. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  228. 'savepoints are not supported', __FUNCTION__);
  229. }
  230. $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name'];
  231. $result =$this->_doQuery($query, true);
  232. if (PEAR::isError($result)) {
  233. return $result;
  234. }
  235. $this->in_transaction = false;
  236. return MDB2_OK;
  237. }
  238. // }}}
  239. // {{{ function setTransactionIsolation()
  240. /**
  241. * Set the transacton isolation level.
  242. *
  243. * @param string standard isolation level
  244. * READ UNCOMMITTED (allows dirty reads)
  245. * READ COMMITTED (prevents dirty reads)
  246. * REPEATABLE READ (prevents nonrepeatable reads)
  247. * SERIALIZABLE (prevents phantom reads)
  248. * @return mixed MDB2_OK on success, a MDB2 error on failure
  249. *
  250. * @access public
  251. * @since 2.1.1
  252. */
  253. function setTransactionIsolation($isolation, $options=array())
  254. {
  255. $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true));
  256. switch ($isolation) {
  257. case 'READ UNCOMMITTED':
  258. $isolation = 0;
  259. break;
  260. case 'READ COMMITTED':
  261. case 'REPEATABLE READ':
  262. case 'SERIALIZABLE':
  263. $isolation = 1;
  264. break;
  265. default:
  266. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  267. 'isolation level is not supported: '.$isolation, __FUNCTION__);
  268. }
  269. $query = "PRAGMA read_uncommitted=$isolation";
  270. return $this->_doQuery($query, true);
  271. }
  272. // }}}
  273. // {{{ getDatabaseFile()
  274. /**
  275. * Builds the string with path+dbname+extension
  276. *
  277. * @return string full database path+file
  278. * @access protected
  279. */
  280. function _getDatabaseFile($database_name)
  281. {
  282. if ($database_name === '' || $database_name === ':memory:') {
  283. return $database_name;
  284. }
  285. return $this->options['database_path'].$database_name.$this->options['database_extension'];
  286. }
  287. // }}}
  288. // {{{ connect()
  289. /**
  290. * Connect to the database
  291. *
  292. * @return true on success, MDB2 Error Object on failure
  293. **/
  294. function connect()
  295. {
  296. if($this->connection instanceof SQLite3) {
  297. return MDB2_OK;
  298. }
  299. $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" );
  300. $database_file = $this->_getDatabaseFile($this->database_name);
  301. if (is_resource($this->connection)) {
  302. //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0
  303. if (MDB2::areEquals($this->connected_dsn, $this->dsn)
  304. && $this->connected_database_name == $database_file
  305. && $this->opened_persistent == $this->options['persistent']
  306. ) {
  307. return MDB2_OK;
  308. }
  309. $this->disconnect(false);
  310. }
  311. if (!PEAR::loadExtension($this->phptype)) {
  312. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  313. 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__);
  314. }
  315. if (empty($this->database_name)) {
  316. return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
  317. 'unable to establish a connection', __FUNCTION__);
  318. }
  319. if ($database_file !== ':memory:') {
  320. if(!strpos($database_file, '.db')) {
  321. $database_file="$datadir/$database_file.db";
  322. }
  323. if (!file_exists($database_file)) {
  324. if (!touch($database_file)) {
  325. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  326. 'Could not create database file', __FUNCTION__);
  327. }
  328. if (!isset($this->dsn['mode'])
  329. || !is_numeric($this->dsn['mode'])
  330. ) {
  331. $mode = 0644;
  332. } else {
  333. $mode = octdec($this->dsn['mode']);
  334. }
  335. if (!chmod($database_file, $mode)) {
  336. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  337. 'Could not be chmodded database file', __FUNCTION__);
  338. }
  339. if (!file_exists($database_file)) {
  340. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  341. 'Could not be found database file', __FUNCTION__);
  342. }
  343. }
  344. if (!is_file($database_file)) {
  345. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  346. 'Database is a directory name', __FUNCTION__);
  347. }
  348. if (!is_readable($database_file)) {
  349. return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null,
  350. 'Could not read database file', __FUNCTION__);
  351. }
  352. }
  353. $php_errormsg = '';
  354. $this->connection = new SQLite3($database_file);
  355. if(is_callable(array($this->connection, 'busyTimeout'))) {//busy timout is only available in php>=5.3
  356. $this->connection->busyTimeout(100);
  357. }
  358. $this->_lasterror = $this->connection->lastErrorMsg();
  359. if (!$this->connection) {
  360. return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
  361. 'unable to establish a connection', __FUNCTION__);
  362. }
  363. if ($this->fix_assoc_fields_names ||
  364. $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) {
  365. $this->connection->exec("PRAGMA short_column_names = 1");
  366. $this->fix_assoc_fields_names = true;
  367. }
  368. $this->connected_dsn = $this->dsn;
  369. $this->connected_database_name = $database_file;
  370. $this->opened_persistent = $this->getoption('persistent');
  371. $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
  372. return MDB2_OK;
  373. }
  374. // }}}
  375. // {{{ databaseExists()
  376. /**
  377. * check if given database name is exists?
  378. *
  379. * @param string $name name of the database that should be checked
  380. *
  381. * @return mixed true/false on success, a MDB2 error on failure
  382. * @access public
  383. */
  384. function databaseExists($name)
  385. {
  386. $database_file = $this->_getDatabaseFile($name);
  387. $result = file_exists($database_file);
  388. return $result;
  389. }
  390. // }}}
  391. // {{{ disconnect()
  392. /**
  393. * Log out and disconnect from the database.
  394. *
  395. * @param boolean $force if the disconnect should be forced even if the
  396. * connection is opened persistently
  397. * @return mixed true on success, false if not connected and error
  398. * object on error
  399. * @access public
  400. */
  401. function disconnect($force = true)
  402. {
  403. if ($this->connection instanceof SQLite3) {
  404. if ($this->in_transaction) {
  405. $dsn = $this->dsn;
  406. $database_name = $this->database_name;
  407. $persistent = $this->options['persistent'];
  408. $this->dsn = $this->connected_dsn;
  409. $this->database_name = $this->connected_database_name;
  410. $this->options['persistent'] = $this->opened_persistent;
  411. $this->rollback();
  412. $this->dsn = $dsn;
  413. $this->database_name = $database_name;
  414. $this->options['persistent'] = $persistent;
  415. }
  416. if (!$this->opened_persistent || $force) {
  417. $this->connection->close();
  418. }
  419. } else {
  420. return false;
  421. }
  422. return parent::disconnect($force);
  423. }
  424. // }}}
  425. // {{{ _doQuery()
  426. /**
  427. * Execute a query
  428. * @param string $query query
  429. * @param boolean $is_manip if the query is a manipulation query
  430. * @param resource $connection
  431. * @param string $database_name
  432. * @return result or error object
  433. * @access protected
  434. */
  435. function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
  436. {
  437. $this->last_query = $query;
  438. $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
  439. if ($result) {
  440. if (PEAR::isError($result)) {
  441. return $result;
  442. }
  443. $query = $result;
  444. }
  445. if ($this->options['disable_query']) {
  446. $result = $is_manip ? 0 : null;
  447. return $result;
  448. }
  449. $result=$this->connection->query($query.';');
  450. $this->_lasterror = $this->connection->lastErrorMsg();
  451. if (!$result) {
  452. $err =$this->raiseError(null, null, null,
  453. 'Could not execute statement', __FUNCTION__);
  454. return $err;
  455. }
  456. $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
  457. return $result;
  458. }
  459. // }}}
  460. // {{{ _affectedRows()
  461. /**
  462. * Returns the number of rows affected
  463. *
  464. * @param resource $result
  465. * @param resource $connection
  466. * @return mixed MDB2 Error Object or the number of rows affected
  467. * @access private
  468. */
  469. function _affectedRows($connection, $result = null)
  470. {
  471. return $this->connection->changes();
  472. }
  473. // }}}
  474. // {{{ _modifyQuery()
  475. /**
  476. * Changes a query string for various DBMS specific reasons
  477. *
  478. * @param string $query query to modify
  479. * @param boolean $is_manip if it is a DML query
  480. * @param integer $limit limit the number of rows
  481. * @param integer $offset start reading from given offset
  482. * @return string modified query
  483. * @access protected
  484. */
  485. function _modifyQuery($query, $is_manip, $limit, $offset)
  486. {
  487. if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) {
  488. if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
  489. $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
  490. 'DELETE FROM \1 WHERE 1=1', $query);
  491. }
  492. }
  493. if ($limit > 0
  494. && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query)
  495. ) {
  496. $query = rtrim($query);
  497. if (substr($query, -1) == ';') {
  498. $query = substr($query, 0, -1);
  499. }
  500. if ($is_manip) {
  501. $query.= " LIMIT $limit";
  502. } else {
  503. $query.= " LIMIT $offset,$limit";
  504. }
  505. }
  506. return $query;
  507. }
  508. // }}}
  509. // {{{ getServerVersion()
  510. /**
  511. * return version information about the server
  512. *
  513. * @param bool $native determines if the raw version string should be returned
  514. * @return mixed array/string with version information or MDB2 error object
  515. * @access public
  516. */
  517. function getServerVersion($native = false)
  518. {
  519. $server_info = false;
  520. if ($this->connected_server_info) {
  521. $server_info = $this->connected_server_info;
  522. } elseif ($this->options['server_version']) {
  523. $server_info = $this->options['server_version'];
  524. } elseif (function_exists('sqlite_libversion')) {
  525. $server_info = @sqlite_libversion();
  526. }
  527. if (!$server_info) {
  528. return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  529. 'Requires either the "server_version" option or the sqlite_libversion() function', __FUNCTION__);
  530. }
  531. // cache server_info
  532. $this->connected_server_info = $server_info;
  533. if (!$native) {
  534. $tmp = explode('.', $server_info, 3);
  535. $server_info = array(
  536. 'major' => isset($tmp[0]) ? $tmp[0] : null,
  537. 'minor' => isset($tmp[1]) ? $tmp[1] : null,
  538. 'patch' => isset($tmp[2]) ? $tmp[2] : null,
  539. 'extra' => null,
  540. 'native' => $server_info,
  541. );
  542. }
  543. return $server_info;
  544. }
  545. // }}}
  546. // {{{ replace()
  547. /**
  548. * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
  549. * query, except that if there is already a row in the table with the same
  550. * key field values, the old row is deleted before the new row is inserted.
  551. *
  552. * The REPLACE type of query does not make part of the SQL standards. Since
  553. * practically only SQLite implements it natively, this type of query is
  554. * emulated through this method for other DBMS using standard types of
  555. * queries inside a transaction to assure the atomicity of the operation.
  556. *
  557. * @access public
  558. *
  559. * @param string $table name of the table on which the REPLACE query will
  560. * be executed.
  561. * @param array $fields associative array that describes the fields and the
  562. * values that will be inserted or updated in the specified table. The
  563. * indexes of the array are the names of all the fields of the table. The
  564. * values of the array are also associative arrays that describe the
  565. * values and other properties of the table fields.
  566. *
  567. * Here follows a list of field properties that need to be specified:
  568. *
  569. * value:
  570. * Value to be assigned to the specified field. This value may be
  571. * of specified in database independent type format as this
  572. * function can perform the necessary datatype conversions.
  573. *
  574. * Default:
  575. * this property is required unless the Null property
  576. * is set to 1.
  577. *
  578. * type
  579. * Name of the type of the field. Currently, all types Metabase
  580. * are supported except for clob and blob.
  581. *
  582. * Default: no type conversion
  583. *
  584. * null
  585. * Boolean property that indicates that the value for this field
  586. * should be set to null.
  587. *
  588. * The default value for fields missing in INSERT queries may be
  589. * specified the definition of a table. Often, the default value
  590. * is already null, but since the REPLACE may be emulated using
  591. * an UPDATE query, make sure that all fields of the table are
  592. * listed in this function argument array.
  593. *
  594. * Default: 0
  595. *
  596. * key
  597. * Boolean property that indicates that this field should be
  598. * handled as a primary key or at least as part of the compound
  599. * unique index of the table that will determine the row that will
  600. * updated if it exists or inserted a new row otherwise.
  601. *
  602. * This function will fail if no key field is specified or if the
  603. * value of a key field is set to null because fields that are
  604. * part of unique index they may not be null.
  605. *
  606. * Default: 0
  607. *
  608. * @return mixed MDB2_OK on success, a MDB2 error on failure
  609. */
  610. function replace($table, $fields)
  611. {
  612. $count = count($fields);
  613. $query = $values = '';
  614. $keys = $colnum = 0;
  615. for (reset($fields); $colnum < $count; next($fields), $colnum++) {
  616. $name = key($fields);
  617. if ($colnum > 0) {
  618. $query .= ',';
  619. $values.= ',';
  620. }
  621. $query.= $this->quoteIdentifier($name, true);
  622. if (isset($fields[$name]['null']) && $fields[$name]['null']) {
  623. $value = 'NULL';
  624. } else {
  625. $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null;
  626. $value = $this->quote($fields[$name]['value'], $type);
  627. if (PEAR::isError($value)) {
  628. return $value;
  629. }
  630. }
  631. $values.= $value;
  632. if (isset($fields[$name]['key']) && $fields[$name]['key']) {
  633. if ($value === 'NULL') {
  634. return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
  635. 'key value '.$name.' may not be NULL', __FUNCTION__);
  636. }
  637. $keys++;
  638. }
  639. }
  640. if ($keys == 0) {
  641. return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null,
  642. 'not specified which fields are keys', __FUNCTION__);
  643. }
  644. $connection = $this->getConnection();
  645. if (PEAR::isError($connection)) {
  646. return $connection;
  647. }
  648. $table = $this->quoteIdentifier($table, true);
  649. $query = "REPLACE INTO $table ($query) VALUES ($values)";
  650. $result =$this->_doQuery($query, true, $connection);
  651. if (PEAR::isError($result)) {
  652. return $result;
  653. }
  654. return $this->_affectedRows($connection, $result);
  655. }
  656. // }}}
  657. // {{{ nextID()
  658. /**
  659. * Returns the next free id of a sequence
  660. *
  661. * @param string $seq_name name of the sequence
  662. * @param boolean $ondemand when true the sequence is
  663. * automatic created, if it
  664. * not exists
  665. *
  666. * @return mixed MDB2 Error Object or id
  667. * @access public
  668. */
  669. function nextID($seq_name, $ondemand = true)
  670. {
  671. $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
  672. $seqcol_name = $this->options['seqcol_name'];
  673. $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)";
  674. $this->pushErrorHandling(PEAR_ERROR_RETURN);
  675. $this->expectError(MDB2_ERROR_NOSUCHTABLE);
  676. $result =$this->_doQuery($query, true);
  677. $this->popExpect();
  678. $this->popErrorHandling();
  679. if (PEAR::isError($result)) {
  680. if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) {
  681. $this->loadModule('Manager', null, true);
  682. $result = $this->manager->createSequence($seq_name);
  683. if (PEAR::isError($result)) {
  684. return $this->raiseError($result, null, null,
  685. 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
  686. } else {
  687. return $this->nextID($seq_name, false);
  688. }
  689. }
  690. return $result;
  691. }
  692. $value = $this->lastInsertID();
  693. if (is_numeric($value)) {
  694. $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
  695. $result =$this->_doQuery($query, true);
  696. if (PEAR::isError($result)) {
  697. $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
  698. }
  699. }
  700. return $value;
  701. }
  702. // }}}
  703. // {{{ lastInsertID()
  704. /**
  705. * Returns the autoincrement ID if supported or $id or fetches the current
  706. * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
  707. *
  708. * @param string $table name of the table into which a new row was inserted
  709. * @param string $field name of the field into which a new row was inserted
  710. * @return mixed MDB2 Error Object or id
  711. * @access public
  712. */
  713. function lastInsertID($table = null, $field = null)
  714. {
  715. return $this->connection->lastInsertRowID();
  716. }
  717. // }}}
  718. // {{{ currID()
  719. /**
  720. * Returns the current id of a sequence
  721. *
  722. * @param string $seq_name name of the sequence
  723. * @return mixed MDB2 Error Object or id
  724. * @access public
  725. */
  726. function currID($seq_name)
  727. {
  728. $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
  729. $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
  730. $query = "SELECT MAX($seqcol_name) FROM $sequence_name";
  731. return $this->queryOne($query, 'integer');
  732. }
  733. /**
  734. * Prepares a query for multiple execution with execute().
  735. * With some database backends, this is emulated.
  736. * prepare() requires a generic query as string like
  737. * 'INSERT INTO numbers VALUES(?,?)' or
  738. * 'INSERT INTO numbers VALUES(:foo,:bar)'.
  739. * The ? and :name and are placeholders which can be set using
  740. * bindParam() and the query can be sent off using the execute() method.
  741. * The allowed format for :name can be set with the 'bindname_format' option.
  742. *
  743. * @param string $query the query to prepare
  744. * @param mixed $types array that contains the types of the placeholders
  745. * @param mixed $result_types array that contains the types of the columns in
  746. * the result set or MDB2_PREPARE_RESULT, if set to
  747. * MDB2_PREPARE_MANIP the query is handled as a manipulation query
  748. * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders
  749. * @return mixed resource handle for the prepared query on success, a MDB2
  750. * error on failure
  751. * @access public
  752. * @see bindParam, execute
  753. */
  754. function prepare($query, $types = null, $result_types = null, $lobs = array())
  755. {
  756. if ($this->options['emulate_prepared']
  757. || $this->supported['prepared_statements'] !== true
  758. ) {
  759. $obj =& parent::prepare($query, $types, $result_types, $lobs);
  760. return $obj;
  761. }
  762. $this->last_query = $query;
  763. $is_manip = ($result_types === MDB2_PREPARE_MANIP);
  764. $offset = $this->offset;
  765. $limit = $this->limit;
  766. $this->offset = $this->limit = 0;
  767. $query = $this->_modifyQuery($query, $is_manip, $limit, $offset);
  768. $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre'));
  769. if ($result) {
  770. if (PEAR::isError($result)) {
  771. return $result;
  772. }
  773. $query = $result;
  774. }
  775. $placeholder_type_guess = $placeholder_type = null;
  776. $question = '?';
  777. $colon = ':';
  778. $positions = array();
  779. $position = 0;
  780. while ($position < strlen($query)) {
  781. $q_position = strpos($query, $question, $position);
  782. $c_position = strpos($query, $colon, $position);
  783. if ($q_position && $c_position) {
  784. $p_position = min($q_position, $c_position);
  785. } elseif ($q_position) {
  786. $p_position = $q_position;
  787. } elseif ($c_position) {
  788. $p_position = $c_position;
  789. } else {
  790. break;
  791. }
  792. if (is_null($placeholder_type)) {
  793. $placeholder_type_guess = $query[$p_position];
  794. }
  795. $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position);
  796. if (PEAR::isError($new_pos)) {
  797. return $new_pos;
  798. }
  799. if ($new_pos != $position) {
  800. $position = $new_pos;
  801. continue; //evaluate again starting from the new position
  802. }
  803. if ($query[$position] == $placeholder_type_guess) {
  804. if (is_null($placeholder_type)) {
  805. $placeholder_type = $query[$p_position];
  806. $question = $colon = $placeholder_type;
  807. }
  808. if ($placeholder_type == ':') {
  809. $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s';
  810. $parameter = preg_replace($regexp, '\\1', $query);
  811. if ($parameter === '') {
  812. $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null,
  813. 'named parameter name must match "bindname_format" option', __FUNCTION__);
  814. return $err;
  815. }
  816. $positions[$p_position] = $parameter;
  817. $query = substr_replace($query, '?', $position, strlen($parameter)+1);
  818. } else {
  819. $positions[$p_position] = count($positions);
  820. }
  821. $position = $p_position + 1;
  822. } else {
  823. $position = $p_position;
  824. }
  825. }
  826. $connection = $this->getConnection();
  827. if (PEAR::isError($connection)) {
  828. return $connection;
  829. }
  830. $statement =$this->connection->prepare($query);
  831. if (!$statement) {
  832. return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  833. 'unable to prepare statement: '.$query);
  834. }
  835. $class_name = 'MDB2_Statement_'.$this->phptype;
  836. $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset);
  837. $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj));
  838. return $obj;
  839. }
  840. }
  841. /**
  842. * MDB2 SQLite result driver
  843. *
  844. * @package MDB2
  845. * @category Database
  846. * @author Lukas Smith <smith@pooteeweet.org>
  847. */
  848. class MDB2_Result_sqlite3 extends MDB2_Result_Common
  849. {
  850. // }}}
  851. // {{{ fetchRow()
  852. /**
  853. * Fetch a row and insert the data into an existing array.
  854. *
  855. * @param int $fetchmode how the array data should be indexed
  856. * @param int $rownum number of the row where the data can be found
  857. * @return int data array on success, a MDB2 error on failure
  858. * @access public
  859. */
  860. function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
  861. {
  862. if (!is_null($rownum)) {
  863. $seek = $this->seek($rownum);
  864. if (PEAR::isError($seek)) {
  865. return $seek;
  866. }
  867. }
  868. if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
  869. $fetchmode = $this->db->fetchmode;
  870. }
  871. if ($fetchmode & MDB2_FETCHMODE_ASSOC) {
  872. //$row = @sqlite_fetch_array($this->result, SQLITE_ASSOC);
  873. $row=$this->result->fetchArray(SQLITE3_ASSOC);
  874. if (is_array($row)
  875. && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE
  876. ) {
  877. $row = array_change_key_case($row, $this->db->options['field_case']);
  878. }
  879. } else {
  880. $row=$this->result->fetchArray(SQLITE3_NUM);
  881. }
  882. if (!$row) {
  883. if ($this->result === false) {
  884. $err =$this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  885. 'resultset has already been freed', __FUNCTION__);
  886. return $err;
  887. }
  888. $null = null;
  889. return $null;
  890. }
  891. $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
  892. $rtrim = false;
  893. if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
  894. if (empty($this->types)) {
  895. $mode += MDB2_PORTABILITY_RTRIM;
  896. } else {
  897. $rtrim = true;
  898. }
  899. }
  900. if ($mode) {
  901. $this->db->_fixResultArrayValues($row, $mode);
  902. }
  903. if (!empty($this->types)) {
  904. $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
  905. }
  906. if (!empty($this->values)) {
  907. $this->_assignBindColumns($row);
  908. }
  909. if ($fetchmode === MDB2_FETCHMODE_OBJECT) {
  910. $object_class = $this->db->options['fetch_class'];
  911. if ($object_class == 'stdClass') {
  912. $row = (object) $row;
  913. } else {
  914. $row = new $object_class($row);
  915. }
  916. }
  917. ++$this->rownum;
  918. return $row;
  919. }
  920. // }}}
  921. // {{{ _getColumnNames()
  922. /**
  923. * Retrieve the names of columns returned by the DBMS in a query result.
  924. *
  925. * @return mixed Array variable that holds the names of columns as keys
  926. * or an MDB2 error on failure.
  927. * Some DBMS may not return any columns when the result set
  928. * does not contain any rows.
  929. * @access private
  930. */
  931. function _getColumnNames()
  932. {
  933. $columns = array();
  934. $numcols = $this->numCols();
  935. if (PEAR::isError($numcols)) {
  936. return $numcols;
  937. }
  938. for ($column = 0; $column < $numcols; $column++) {
  939. $column_name = $this->result->getColumnName($column);
  940. $columns[$column_name] = $column;
  941. }
  942. if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  943. $columns = array_change_key_case($columns, $this->db->options['field_case']);
  944. }
  945. return $columns;
  946. }
  947. // }}}
  948. // {{{ numCols()
  949. /**
  950. * Count the number of columns returned by the DBMS in a query result.
  951. *
  952. * @access public
  953. * @return mixed integer value with the number of columns, a MDB2 error
  954. * on failure
  955. */
  956. function numCols()
  957. {
  958. $this->result->numColumns();
  959. }
  960. }
  961. /**
  962. * MDB2 SQLite buffered result driver
  963. *
  964. * @package MDB2
  965. * @category Database
  966. * @author Lukas Smith <smith@pooteeweet.org>
  967. */
  968. class MDB2_BufferedResult_sqlite3 extends MDB2_Result_sqlite3
  969. {
  970. // {{{ seek()
  971. /**
  972. * Seek to a specific row in a result set
  973. *
  974. * @param int $rownum number of the row where the data can be found
  975. * @return mixed MDB2_OK on success, a MDB2 error on failure
  976. * @access public
  977. */
  978. function seek($rownum = 0)
  979. {
  980. $this->result->reset();
  981. for($i=0;$i<$rownum;$i++) {
  982. $this->result->fetchArray();
  983. }
  984. $this->rownum = $rownum - 1;
  985. return MDB2_OK;
  986. }
  987. // }}}
  988. // {{{ valid()
  989. /**
  990. * Check if the end of the result set has been reached
  991. *
  992. * @return mixed true or false on sucess, a MDB2 error on failure
  993. * @access public
  994. */
  995. function valid()
  996. {
  997. $numrows = $this->numRows();
  998. if (PEAR::isError($numrows)) {
  999. return $numrows;
  1000. }
  1001. return $this->rownum < ($numrows - 1);
  1002. }
  1003. // }}}
  1004. // {{{ numRows()
  1005. /**
  1006. * Returns the number of rows in a result object
  1007. *
  1008. * @return mixed MDB2 Error Object or the number of rows
  1009. * @access public
  1010. */
  1011. function numRows()
  1012. {
  1013. $rows = 0;
  1014. $this->result->reset();
  1015. while($this->result->fetchArray()) {
  1016. $rows++;
  1017. }
  1018. $this->result->reset();
  1019. return $rows;
  1020. }
  1021. }
  1022. /**
  1023. * MDB2 SQLite statement driver
  1024. *
  1025. * @package MDB2
  1026. * @category Database
  1027. * @author Lukas Smith <smith@pooteeweet.org>
  1028. */
  1029. class MDB2_Statement_sqlite3 extends MDB2_Statement_Common
  1030. {
  1031. // }}}
  1032. // {{{ function bindValue($parameter, &$value, $type = null)
  1033. private function getParamType($type) {
  1034. switch(strtolower($type)) {
  1035. case 'text':
  1036. return SQLITE3_TEXT;
  1037. case 'boolean':
  1038. case 'integer':
  1039. return SQLITE3_INTEGER;
  1040. case 'float':
  1041. return SQLITE3_FLOAT;
  1042. case 'blob':
  1043. return SQLITE3_BLOB;
  1044. }
  1045. }
  1046. /**
  1047. * Set the value of a parameter of a prepared query.
  1048. *
  1049. * @param int the order number of the parameter in the query
  1050. * statement. The order number of the first parameter is 1.
  1051. * @param mixed value that is meant to be assigned to specified
  1052. * parameter. The type of the value depends on the $type argument.
  1053. * @param string specifies the type of the field
  1054. *
  1055. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1056. *
  1057. * @access public
  1058. */
  1059. function bindValue($parameter, $value, $type = null) {
  1060. if($type) {
  1061. $type=$this->getParamType($type);
  1062. $this->statement->bindValue($parameter, $value, $type);
  1063. }else{
  1064. $this->statement->bindValue($parameter, $value);
  1065. }
  1066. return MDB2_OK;
  1067. }
  1068. /**
  1069. * Bind a variable to a parameter of a prepared query.
  1070. *
  1071. * @param int the order number of the parameter in the query
  1072. * statement. The order number of the first parameter is 1.
  1073. * @param mixed variable that is meant to be bound to specified
  1074. * parameter. The type of the value depends on the $type argument.
  1075. * @param string specifies the type of the field
  1076. *
  1077. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1078. *
  1079. * @access public
  1080. */
  1081. function bindParam($parameter, &$value, $type = null) {
  1082. if($type) {
  1083. $type=$this->getParamType($type);
  1084. $this->statement->bindParam($parameter, $value, $type);
  1085. }else{
  1086. $this->statement->bindParam($parameter, $value);
  1087. }
  1088. return MDB2_OK;
  1089. }
  1090. /**
  1091. * Release resources allocated for the specified prepared query.
  1092. *
  1093. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1094. * @access public
  1095. */
  1096. function free()
  1097. {
  1098. $this->statement->close();
  1099. }
  1100. /**
  1101. * Execute a prepared query statement helper method.
  1102. *
  1103. * @param mixed $result_class string which specifies which result class to use
  1104. * @param mixed $result_wrap_class string which specifies which class to wrap results in
  1105. *
  1106. * @return mixed MDB2_Result or integer (affected rows) on success,
  1107. * a MDB2 error on failure
  1108. * @access private
  1109. */
  1110. function _execute($result_class = true, $result_wrap_class = false) {
  1111. if (is_null($this->statement)) {
  1112. $result =& parent::_execute($result_class, $result_wrap_class);
  1113. return $result;
  1114. }
  1115. $this->db->last_query = $this->query;
  1116. $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values));
  1117. if ($this->db->getOption('disable_query')) {
  1118. $result = $this->is_manip ? 0 : null;
  1119. return $result;
  1120. }
  1121. $connection = $this->db->getConnection();
  1122. if (PEAR::isError($connection)) {
  1123. return $connection;
  1124. }
  1125. $result = $this->statement->execute();
  1126. if ($result==false) {
  1127. $err =$this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  1128. 'cant execute statement', __FUNCTION__);
  1129. }
  1130. if ($this->is_manip) {
  1131. $affected_rows = $this->db->_affectedRows($connection, $result);
  1132. return $affected_rows;
  1133. }
  1134. $result = $this->db->_wrapResult($result, $this->result_types,
  1135. $result_class, $result_wrap_class, $this->limit, $this->offset);
  1136. $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
  1137. return $result;
  1138. }
  1139. /**
  1140. * Set the values of multiple a parameter of a prepared query in bulk.
  1141. *
  1142. * @param array specifies all necessary information
  1143. * for bindValue() the array elements must use keys corresponding to
  1144. * the number of the position of the parameter.
  1145. * @param array specifies the types of the fields
  1146. *
  1147. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1148. *
  1149. * @access public
  1150. * @see bindParam()
  1151. */
  1152. function bindValueArray($values, $types = null)
  1153. {
  1154. $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
  1155. $parameters = array_keys($values);
  1156. foreach ($parameters as $key => $parameter) {
  1157. $this->db->pushErrorHandling(PEAR_ERROR_RETURN);
  1158. $this->db->expectError(MDB2_ERROR_NOT_FOUND);
  1159. $err = $this->bindValue($parameter+1, $values[$parameter], $types[$key]);
  1160. $this->db->popExpect();
  1161. $this->db->popErrorHandling();
  1162. if (PEAR::isError($err)) {
  1163. if ($err->getCode() == MDB2_ERROR_NOT_FOUND) {
  1164. //ignore (extra value for missing placeholder)
  1165. continue;
  1166. }
  1167. return $err;
  1168. }
  1169. }
  1170. return MDB2_OK;
  1171. }
  1172. // }}}
  1173. // {{{ function bindParamArray(&$values, $types = null)
  1174. /**
  1175. * Bind the variables of multiple a parameter of a prepared query in bulk.
  1176. *
  1177. * @param array specifies all necessary information
  1178. * for bindParam() the array elements must use keys corresponding to
  1179. * the number of the position of the parameter.
  1180. * @param array specifies the types of the fields
  1181. *
  1182. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1183. *
  1184. * @access public
  1185. * @see bindParam()
  1186. */
  1187. function bindParamArray(&$values, $types = null)
  1188. {
  1189. $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null);
  1190. $parameters = array_keys($values);
  1191. foreach ($parameters as $key => $parameter) {
  1192. $err = $this->bindParam($parameter+1, $values[$parameter], $types[$key]);
  1193. if (PEAR::isError($err)) {
  1194. return $err;
  1195. }
  1196. }
  1197. return MDB2_OK;
  1198. }
  1199. // }}}
  1200. // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false)
  1201. /**
  1202. * Execute a prepared query statement.
  1203. *
  1204. * @param array specifies all necessary information
  1205. * for bindParam() the array elements must use keys corresponding
  1206. * to the number of the position of the parameter.
  1207. * @param mixed specifies which result class to use
  1208. * @param mixed specifies which class to wrap results in
  1209. *
  1210. * @return mixed MDB2_Result or integer (affected rows) on success,
  1211. * a MDB2 error on failure
  1212. * @access public
  1213. */
  1214. function execute($values = null, $result_class = true, $result_wrap_class = false)
  1215. {
  1216. if (is_null($this->positions)) {
  1217. return $this->db->raiseError(MDB2_ERROR, null, null,
  1218. 'Prepared statement has already been freed', __FUNCTION__);
  1219. }
  1220. $values = (array)$values;
  1221. if (!empty($values)) {
  1222. if(count($this->types)) {
  1223. $types=$this->types;
  1224. }else{
  1225. $types=null;
  1226. }
  1227. $err = $this->bindValueArray($values, $types);
  1228. if (PEAR::isError($err)) {
  1229. return $this->db->raiseError(MDB2_ERROR, null, null,
  1230. 'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__);
  1231. }
  1232. }
  1233. $result =$this->_execute($result_class, $result_wrap_class);
  1234. return $result;
  1235. }
  1236. function __destruct() {
  1237. $this->free();
  1238. }
  1239. }