PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/program/lib/Roundcube/rcube_db.php

https://github.com/trimbakgopalghare/roundcubemail
PHP | 1185 lines | 625 code | 151 blank | 409 comment | 137 complexity | 926857d87257141e0a915d3a85a1f5ed MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2005-2012, The Roundcube Dev Team |
  6. | |
  7. | Licensed under the GNU General Public License version 3 or |
  8. | any later version with exceptions for skins & plugins. |
  9. | See the README file for a full license statement. |
  10. | |
  11. | PURPOSE: |
  12. | Database wrapper class that implements PHP PDO functions |
  13. +-----------------------------------------------------------------------+
  14. | Author: Aleksander Machniak <alec@alec.pl> |
  15. +-----------------------------------------------------------------------+
  16. */
  17. /**
  18. * Database independent query interface.
  19. * This is a wrapper for the PHP PDO.
  20. *
  21. * @package Framework
  22. * @subpackage Database
  23. */
  24. class rcube_db
  25. {
  26. public $db_provider;
  27. protected $db_dsnw; // DSN for write operations
  28. protected $db_dsnr; // DSN for read operations
  29. protected $db_connected = false; // Already connected ?
  30. protected $db_mode; // Connection mode
  31. protected $dbh; // Connection handle
  32. protected $dbhs = array();
  33. protected $table_connections = array();
  34. protected $db_error = false;
  35. protected $db_error_msg = '';
  36. protected $conn_failure = false;
  37. protected $db_index = 0;
  38. protected $last_result;
  39. protected $tables;
  40. protected $variables;
  41. protected $options = array(
  42. // column/table quotes
  43. 'identifier_start' => '"',
  44. 'identifier_end' => '"',
  45. );
  46. const DEBUG_LINE_LENGTH = 4096;
  47. /**
  48. * Factory, returns driver-specific instance of the class
  49. *
  50. * @param string $db_dsnw DSN for read/write operations
  51. * @param string $db_dsnr Optional DSN for read only operations
  52. * @param bool $pconn Enables persistent connections
  53. *
  54. * @return rcube_db Object instance
  55. */
  56. public static function factory($db_dsnw, $db_dsnr = '', $pconn = false)
  57. {
  58. $driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
  59. $driver_map = array(
  60. 'sqlite2' => 'sqlite',
  61. 'sybase' => 'mssql',
  62. 'dblib' => 'mssql',
  63. 'mysqli' => 'mysql',
  64. );
  65. $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
  66. $class = "rcube_db_$driver";
  67. if (!$driver || !class_exists($class)) {
  68. rcube::raise_error(array('code' => 600, 'type' => 'db',
  69. 'line' => __LINE__, 'file' => __FILE__,
  70. 'message' => "Configuration error. Unsupported database driver: $driver"),
  71. true, true);
  72. }
  73. return new $class($db_dsnw, $db_dsnr, $pconn);
  74. }
  75. /**
  76. * Object constructor
  77. *
  78. * @param string $db_dsnw DSN for read/write operations
  79. * @param string $db_dsnr Optional DSN for read only operations
  80. * @param bool $pconn Enables persistent connections
  81. */
  82. public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
  83. {
  84. if (empty($db_dsnr)) {
  85. $db_dsnr = $db_dsnw;
  86. }
  87. $this->db_dsnw = $db_dsnw;
  88. $this->db_dsnr = $db_dsnr;
  89. $this->db_pconn = $pconn;
  90. $this->db_dsnw_array = self::parse_dsn($db_dsnw);
  91. $this->db_dsnr_array = self::parse_dsn($db_dsnr);
  92. $config = rcube::get_instance()->config;
  93. $this->options['table_prefix'] = $config->get('db_prefix');
  94. $this->options['dsnw_noread'] = $config->get('db_dsnw_noread', false);
  95. $this->options['table_dsn_map'] = array_map(array($this, 'table_name'), $config->get('db_table_dsn', array()));
  96. }
  97. /**
  98. * Connect to specific database
  99. *
  100. * @param array $dsn DSN for DB connections
  101. * @param string $mode Connection mode (r|w)
  102. */
  103. protected function dsn_connect($dsn, $mode)
  104. {
  105. $this->db_error = false;
  106. $this->db_error_msg = null;
  107. // return existing handle
  108. if ($this->dbhs[$mode]) {
  109. $this->dbh = $this->dbhs[$mode];
  110. $this->db_mode = $mode;
  111. return $this->dbh;
  112. }
  113. // Get database specific connection options
  114. $dsn_string = $this->dsn_string($dsn);
  115. $dsn_options = $this->dsn_options($dsn);
  116. if ($this->db_pconn) {
  117. $dsn_options[PDO::ATTR_PERSISTENT] = true;
  118. }
  119. // Connect
  120. try {
  121. // with this check we skip fatal error on PDO object creation
  122. if (!class_exists('PDO', false)) {
  123. throw new Exception('PDO extension not loaded. See http://php.net/manual/en/intro.pdo.php');
  124. }
  125. $this->conn_prepare($dsn);
  126. $dbh = new PDO($dsn_string, $dsn['username'], $dsn['password'], $dsn_options);
  127. // don't throw exceptions or warnings
  128. $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  129. }
  130. catch (Exception $e) {
  131. $this->db_error = true;
  132. $this->db_error_msg = $e->getMessage();
  133. rcube::raise_error(array('code' => 500, 'type' => 'db',
  134. 'line' => __LINE__, 'file' => __FILE__,
  135. 'message' => $this->db_error_msg), true, false);
  136. return null;
  137. }
  138. $this->dbh = $dbh;
  139. $this->dbhs[$mode] = $dbh;
  140. $this->db_mode = $mode;
  141. $this->db_connected = true;
  142. $this->conn_configure($dsn, $dbh);
  143. }
  144. /**
  145. * Driver-specific preparation of database connection
  146. *
  147. * @param array $dsn DSN for DB connections
  148. */
  149. protected function conn_prepare($dsn)
  150. {
  151. }
  152. /**
  153. * Driver-specific configuration of database connection
  154. *
  155. * @param array $dsn DSN for DB connections
  156. * @param PDO $dbh Connection handler
  157. */
  158. protected function conn_configure($dsn, $dbh)
  159. {
  160. }
  161. /**
  162. * Connect to appropriate database depending on the operation
  163. *
  164. * @param string $mode Connection mode (r|w)
  165. * @param boolean $force Enforce using the given mode
  166. */
  167. public function db_connect($mode, $force = false)
  168. {
  169. // previous connection failed, don't attempt to connect again
  170. if ($this->conn_failure) {
  171. return;
  172. }
  173. // no replication
  174. if ($this->db_dsnw == $this->db_dsnr) {
  175. $mode = 'w';
  176. }
  177. // Already connected
  178. if ($this->db_connected) {
  179. // connected to db with the same or "higher" mode (if allowed)
  180. if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->options['dsnw_noread']) {
  181. return;
  182. }
  183. }
  184. $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
  185. $this->dsn_connect($dsn, $mode);
  186. // use write-master when read-only fails
  187. if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) {
  188. $this->dsn_connect($this->db_dsnw_array, 'w');
  189. }
  190. $this->conn_failure = !$this->db_connected;
  191. }
  192. /**
  193. * Analyze the given SQL statement and select the appropriate connection to use
  194. */
  195. protected function dsn_select($query)
  196. {
  197. // no replication
  198. if ($this->db_dsnw == $this->db_dsnr) {
  199. return 'w';
  200. }
  201. // Read or write ?
  202. $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
  203. // find tables involved in this query
  204. if (preg_match_all('/(?:^|\s)(from|update|into|join)\s+'.$this->options['identifier_start'].'?([a-z0-9._]+)'.$this->options['identifier_end'].'?\s+/i', $query, $matches, PREG_SET_ORDER)) {
  205. foreach ($matches as $m) {
  206. $table = $m[2];
  207. // always use direct mapping
  208. if ($this->options['table_dsn_map'][$table]) {
  209. $mode = $this->options['table_dsn_map'][$table];
  210. break; // primary table rules
  211. }
  212. else if ($mode == 'r') {
  213. // connected to db with the same or "higher" mode for this table
  214. $db_mode = $this->table_connections[$table];
  215. if ($db_mode == 'w' && !$this->options['dsnw_noread']) {
  216. $mode = $db_mode;
  217. }
  218. }
  219. }
  220. // remember mode chosen (for primary table)
  221. $table = $matches[0][2];
  222. $this->table_connections[$table] = $mode;
  223. }
  224. return $mode;
  225. }
  226. /**
  227. * Activate/deactivate debug mode
  228. *
  229. * @param boolean $dbg True if SQL queries should be logged
  230. */
  231. public function set_debug($dbg = true)
  232. {
  233. $this->options['debug_mode'] = $dbg;
  234. }
  235. /**
  236. * Writes debug information/query to 'sql' log file
  237. *
  238. * @param string $query SQL query
  239. */
  240. protected function debug($query)
  241. {
  242. if ($this->options['debug_mode']) {
  243. if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) {
  244. $diff = $len - self::DEBUG_LINE_LENGTH;
  245. $query = substr($query, 0, self::DEBUG_LINE_LENGTH)
  246. . "... [truncated $diff bytes]";
  247. }
  248. rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
  249. }
  250. }
  251. /**
  252. * Getter for error state
  253. *
  254. * @param mixed $result Optional query result
  255. *
  256. * @return string Error message
  257. */
  258. public function is_error($result = null)
  259. {
  260. if ($result !== null) {
  261. return $result === false ? $this->db_error_msg : null;
  262. }
  263. return $this->db_error ? $this->db_error_msg : null;
  264. }
  265. /**
  266. * Connection state checker
  267. *
  268. * @return boolean True if in connected state
  269. */
  270. public function is_connected()
  271. {
  272. return !is_object($this->dbh) ? false : $this->db_connected;
  273. }
  274. /**
  275. * Is database replication configured?
  276. *
  277. * @return bool Returns true if dsnw != dsnr
  278. */
  279. public function is_replicated()
  280. {
  281. return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
  282. }
  283. /**
  284. * Get database runtime variables
  285. *
  286. * @param string $varname Variable name
  287. * @param mixed $default Default value if variable is not set
  288. *
  289. * @return mixed Variable value or default
  290. */
  291. public function get_variable($varname, $default = null)
  292. {
  293. // to be implemented by driver class
  294. return $default;
  295. }
  296. /**
  297. * Execute a SQL query
  298. *
  299. * @param string SQL query to execute
  300. * @param mixed Values to be inserted in query
  301. *
  302. * @return number Query handle identifier
  303. */
  304. public function query()
  305. {
  306. $params = func_get_args();
  307. $query = array_shift($params);
  308. // Support one argument of type array, instead of n arguments
  309. if (count($params) == 1 && is_array($params[0])) {
  310. $params = $params[0];
  311. }
  312. return $this->_query($query, 0, 0, $params);
  313. }
  314. /**
  315. * Execute a SQL query with limits
  316. *
  317. * @param string SQL query to execute
  318. * @param int Offset for LIMIT statement
  319. * @param int Number of rows for LIMIT statement
  320. * @param mixed Values to be inserted in query
  321. *
  322. * @return PDOStatement|bool Query handle or False on error
  323. */
  324. public function limitquery()
  325. {
  326. $params = func_get_args();
  327. $query = array_shift($params);
  328. $offset = array_shift($params);
  329. $numrows = array_shift($params);
  330. return $this->_query($query, $offset, $numrows, $params);
  331. }
  332. /**
  333. * Execute a SQL query with limits
  334. *
  335. * @param string $query SQL query to execute
  336. * @param int $offset Offset for LIMIT statement
  337. * @param int $numrows Number of rows for LIMIT statement
  338. * @param array $params Values to be inserted in query
  339. *
  340. * @return PDOStatement|bool Query handle or False on error
  341. */
  342. protected function _query($query, $offset, $numrows, $params)
  343. {
  344. $query = ltrim($query);
  345. $this->db_connect($this->dsn_select($query), true);
  346. // check connection before proceeding
  347. if (!$this->is_connected()) {
  348. return $this->last_result = false;
  349. }
  350. if ($numrows || $offset) {
  351. $query = $this->set_limit($query, $numrows, $offset);
  352. }
  353. // Because in Roundcube we mostly use queries that are
  354. // executed only once, we will not use prepared queries
  355. $pos = 0;
  356. $idx = 0;
  357. if (count($params)) {
  358. while ($pos = strpos($query, '?', $pos)) {
  359. if ($query[$pos+1] == '?') { // skip escaped '?'
  360. $pos += 2;
  361. }
  362. else {
  363. $val = $this->quote($params[$idx++]);
  364. unset($params[$idx-1]);
  365. $query = substr_replace($query, $val, $pos, 1);
  366. $pos += strlen($val);
  367. }
  368. }
  369. }
  370. // replace escaped '?' back to normal, see self::quote()
  371. $query = str_replace('??', '?', $query);
  372. $query = rtrim($query, " \t\n\r\0\x0B;");
  373. $this->debug($query);
  374. // destroy reference to previous result, required for SQLite driver (#1488874)
  375. $this->last_result = null;
  376. $this->db_error_msg = null;
  377. // send query
  378. $result = $this->dbh->query($query);
  379. if ($result === false) {
  380. $result = $this->handle_error($query);
  381. }
  382. $this->last_result = $result;
  383. return $result;
  384. }
  385. /**
  386. * Helper method to handle DB errors.
  387. * This by default logs the error but could be overriden by a driver implementation
  388. *
  389. * @param string Query that triggered the error
  390. * @return mixed Result to be stored and returned
  391. */
  392. protected function handle_error($query)
  393. {
  394. $error = $this->dbh->errorInfo();
  395. if (empty($this->options['ignore_key_errors']) || !in_array($error[0], array('23000', '23505'))) {
  396. $this->db_error = true;
  397. $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
  398. rcube::raise_error(array('code' => 500, 'type' => 'db',
  399. 'line' => __LINE__, 'file' => __FILE__,
  400. 'message' => $this->db_error_msg . " (SQL Query: $query)"
  401. ), true, false);
  402. }
  403. return false;
  404. }
  405. /**
  406. * Get number of affected rows for the last query
  407. *
  408. * @param mixed $result Optional query handle
  409. *
  410. * @return int Number of (matching) rows
  411. */
  412. public function affected_rows($result = null)
  413. {
  414. if ($result || ($result === null && ($result = $this->last_result))) {
  415. return $result->rowCount();
  416. }
  417. return 0;
  418. }
  419. /**
  420. * Get number of rows for a SQL query
  421. * If no query handle is specified, the last query will be taken as reference
  422. *
  423. * @param mixed $result Optional query handle
  424. * @return mixed Number of rows or false on failure
  425. * @deprecated This method shows very poor performance and should be avoided.
  426. */
  427. public function num_rows($result = null)
  428. {
  429. if ($result || ($result === null && ($result = $this->last_result))) {
  430. // repeat query with SELECT COUNT(*) ...
  431. if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) {
  432. $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM);
  433. return $query ? intval($query->fetchColumn(0)) : false;
  434. }
  435. else {
  436. $num = count($result->fetchAll());
  437. $result->execute(); // re-execute query because there's no seek(0)
  438. return $num;
  439. }
  440. }
  441. return false;
  442. }
  443. /**
  444. * Get last inserted record ID
  445. *
  446. * @param string $table Table name (to find the incremented sequence)
  447. *
  448. * @return mixed ID or false on failure
  449. */
  450. public function insert_id($table = '')
  451. {
  452. if (!$this->db_connected || $this->db_mode == 'r') {
  453. return false;
  454. }
  455. if ($table) {
  456. // resolve table name
  457. $table = $this->table_name($table);
  458. }
  459. $id = $this->dbh->lastInsertId($table);
  460. return $id;
  461. }
  462. /**
  463. * Get an associative array for one row
  464. * If no query handle is specified, the last query will be taken as reference
  465. *
  466. * @param mixed $result Optional query handle
  467. *
  468. * @return mixed Array with col values or false on failure
  469. */
  470. public function fetch_assoc($result = null)
  471. {
  472. return $this->_fetch_row($result, PDO::FETCH_ASSOC);
  473. }
  474. /**
  475. * Get an index array for one row
  476. * If no query handle is specified, the last query will be taken as reference
  477. *
  478. * @param mixed $result Optional query handle
  479. *
  480. * @return mixed Array with col values or false on failure
  481. */
  482. public function fetch_array($result = null)
  483. {
  484. return $this->_fetch_row($result, PDO::FETCH_NUM);
  485. }
  486. /**
  487. * Get col values for a result row
  488. *
  489. * @param mixed $result Optional query handle
  490. * @param int $mode Fetch mode identifier
  491. *
  492. * @return mixed Array with col values or false on failure
  493. */
  494. protected function _fetch_row($result, $mode)
  495. {
  496. if ($result || ($result === null && ($result = $this->last_result))) {
  497. return $result->fetch($mode);
  498. }
  499. return false;
  500. }
  501. /**
  502. * Adds LIMIT,OFFSET clauses to the query
  503. *
  504. * @param string $query SQL query
  505. * @param int $limit Number of rows
  506. * @param int $offset Offset
  507. *
  508. * @return string SQL query
  509. */
  510. protected function set_limit($query, $limit = 0, $offset = 0)
  511. {
  512. if ($limit) {
  513. $query .= ' LIMIT ' . intval($limit);
  514. }
  515. if ($offset) {
  516. $query .= ' OFFSET ' . intval($offset);
  517. }
  518. return $query;
  519. }
  520. /**
  521. * Returns list of tables in a database
  522. *
  523. * @return array List of all tables of the current database
  524. */
  525. public function list_tables()
  526. {
  527. // get tables if not cached
  528. if ($this->tables === null) {
  529. $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
  530. if ($q) {
  531. $this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0);
  532. }
  533. else {
  534. $this->tables = array();
  535. }
  536. }
  537. return $this->tables;
  538. }
  539. /**
  540. * Returns list of columns in database table
  541. *
  542. * @param string $table Table name
  543. *
  544. * @return array List of table cols
  545. */
  546. public function list_cols($table)
  547. {
  548. $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
  549. array($table));
  550. if ($q) {
  551. return $q->fetchAll(PDO::FETCH_COLUMN, 0);
  552. }
  553. return array();
  554. }
  555. /**
  556. * Formats input so it can be safely used in a query
  557. *
  558. * @param mixed $input Value to quote
  559. * @param string $type Type of data (integer, bool, ident)
  560. *
  561. * @return string Quoted/converted string for use in query
  562. */
  563. public function quote($input, $type = null)
  564. {
  565. // handle int directly for better performance
  566. if ($type == 'integer' || $type == 'int') {
  567. return intval($input);
  568. }
  569. if (is_null($input)) {
  570. return 'NULL';
  571. }
  572. if ($type == 'ident') {
  573. return $this->quote_identifier($input);
  574. }
  575. // create DB handle if not available
  576. if (!$this->dbh) {
  577. $this->db_connect('r');
  578. }
  579. if ($this->dbh) {
  580. $map = array(
  581. 'bool' => PDO::PARAM_BOOL,
  582. 'integer' => PDO::PARAM_INT,
  583. );
  584. $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
  585. return strtr($this->dbh->quote($input, $type), array('?' => '??')); // escape ?
  586. }
  587. return 'NULL';
  588. }
  589. /**
  590. * Escapes a string so it can be safely used in a query
  591. *
  592. * @param string $str A string to escape
  593. *
  594. * @return string Escaped string for use in a query
  595. */
  596. public function escape($str)
  597. {
  598. if (is_null($str)) {
  599. return 'NULL';
  600. }
  601. return substr($this->quote($str), 1, -1);
  602. }
  603. /**
  604. * Quotes a string so it can be safely used as a table or column name
  605. *
  606. * @param string $str Value to quote
  607. *
  608. * @return string Quoted string for use in query
  609. * @deprecated Replaced by rcube_db::quote_identifier
  610. * @see rcube_db::quote_identifier
  611. */
  612. public function quoteIdentifier($str)
  613. {
  614. return $this->quote_identifier($str);
  615. }
  616. /**
  617. * Escapes a string so it can be safely used in a query
  618. *
  619. * @param string $str A string to escape
  620. *
  621. * @return string Escaped string for use in a query
  622. * @deprecated Replaced by rcube_db::escape
  623. * @see rcube_db::escape
  624. */
  625. public function escapeSimple($str)
  626. {
  627. return $this->escape($str);
  628. }
  629. /**
  630. * Quotes a string so it can be safely used as a table or column name
  631. *
  632. * @param string $str Value to quote
  633. *
  634. * @return string Quoted string for use in query
  635. */
  636. public function quote_identifier($str)
  637. {
  638. $start = $this->options['identifier_start'];
  639. $end = $this->options['identifier_end'];
  640. $name = array();
  641. foreach (explode('.', $str) as $elem) {
  642. $elem = str_replace(array($start, $end), '', $elem);
  643. $name[] = $start . $elem . $end;
  644. }
  645. return implode($name, '.');
  646. }
  647. /**
  648. * Return SQL function for current time and date
  649. *
  650. * @param int $interval Optional interval (in seconds) to add/subtract
  651. *
  652. * @return string SQL function to use in query
  653. */
  654. public function now($interval = 0)
  655. {
  656. if ($interval) {
  657. $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL ';
  658. $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
  659. $add .= ' SECOND';
  660. }
  661. return "now()" . $add;
  662. }
  663. /**
  664. * Return list of elements for use with SQL's IN clause
  665. *
  666. * @param array $arr Input array
  667. * @param string $type Type of data (integer, bool, ident)
  668. *
  669. * @return string Comma-separated list of quoted values for use in query
  670. */
  671. public function array2list($arr, $type = null)
  672. {
  673. if (!is_array($arr)) {
  674. return $this->quote($arr, $type);
  675. }
  676. foreach ($arr as $idx => $item) {
  677. $arr[$idx] = $this->quote($item, $type);
  678. }
  679. return implode(',', $arr);
  680. }
  681. /**
  682. * Return SQL statement to convert a field value into a unix timestamp
  683. *
  684. * This method is deprecated and should not be used anymore due to limitations
  685. * of timestamp functions in Mysql (year 2038 problem)
  686. *
  687. * @param string $field Field name
  688. *
  689. * @return string SQL statement to use in query
  690. * @deprecated
  691. */
  692. public function unixtimestamp($field)
  693. {
  694. return "UNIX_TIMESTAMP($field)";
  695. }
  696. /**
  697. * Return SQL statement to convert from a unix timestamp
  698. *
  699. * @param int $timestamp Unix timestamp
  700. *
  701. * @return string Date string in db-specific format
  702. */
  703. public function fromunixtime($timestamp)
  704. {
  705. return date("'Y-m-d H:i:s'", $timestamp);
  706. }
  707. /**
  708. * Return SQL statement for case insensitive LIKE
  709. *
  710. * @param string $column Field name
  711. * @param string $value Search value
  712. *
  713. * @return string SQL statement to use in query
  714. */
  715. public function ilike($column, $value)
  716. {
  717. return $this->quote_identifier($column).' LIKE '.$this->quote($value);
  718. }
  719. /**
  720. * Abstract SQL statement for value concatenation
  721. *
  722. * @return string SQL statement to be used in query
  723. */
  724. public function concat(/* col1, col2, ... */)
  725. {
  726. $args = func_get_args();
  727. if (is_array($args[0])) {
  728. $args = $args[0];
  729. }
  730. return '(' . join(' || ', $args) . ')';
  731. }
  732. /**
  733. * Encodes non-UTF-8 characters in string/array/object (recursive)
  734. *
  735. * @param mixed $input Data to fix
  736. * @param bool $serialized Enable serialization
  737. *
  738. * @return mixed Properly UTF-8 encoded data
  739. */
  740. public static function encode($input, $serialized = false)
  741. {
  742. // use Base64 encoding to workaround issues with invalid
  743. // or null characters in serialized string (#1489142)
  744. if ($serialized) {
  745. return base64_encode(serialize($input));
  746. }
  747. if (is_object($input)) {
  748. foreach (get_object_vars($input) as $idx => $value) {
  749. $input->$idx = self::encode($value);
  750. }
  751. return $input;
  752. }
  753. else if (is_array($input)) {
  754. foreach ($input as $idx => $value) {
  755. $input[$idx] = self::encode($value);
  756. }
  757. return $input;
  758. }
  759. return utf8_encode($input);
  760. }
  761. /**
  762. * Decodes encoded UTF-8 string/object/array (recursive)
  763. *
  764. * @param mixed $input Input data
  765. * @param bool $serialized Enable serialization
  766. *
  767. * @return mixed Decoded data
  768. */
  769. public static function decode($input, $serialized = false)
  770. {
  771. // use Base64 encoding to workaround issues with invalid
  772. // or null characters in serialized string (#1489142)
  773. if ($serialized) {
  774. // Keep backward compatybility where base64 wasn't used
  775. if (strpos(substr($input, 0, 16), ':') !== false) {
  776. return self::decode(@unserialize($input));
  777. }
  778. return @unserialize(base64_decode($input));
  779. }
  780. if (is_object($input)) {
  781. foreach (get_object_vars($input) as $idx => $value) {
  782. $input->$idx = self::decode($value);
  783. }
  784. return $input;
  785. }
  786. else if (is_array($input)) {
  787. foreach ($input as $idx => $value) {
  788. $input[$idx] = self::decode($value);
  789. }
  790. return $input;
  791. }
  792. return utf8_decode($input);
  793. }
  794. /**
  795. * Return correct name for a specific database table
  796. *
  797. * @param string $table Table name
  798. *
  799. * @return string Translated table name
  800. */
  801. public function table_name($table)
  802. {
  803. // add prefix to the table name if configured
  804. if (($prefix = $this->options['table_prefix']) && strpos($table, $prefix) !== 0) {
  805. return $prefix . $table;
  806. }
  807. return $table;
  808. }
  809. /**
  810. * Set class option value
  811. *
  812. * @param string $name Option name
  813. * @param mixed $value Option value
  814. */
  815. public function set_option($name, $value)
  816. {
  817. $this->options[$name] = $value;
  818. }
  819. /**
  820. * Set DSN connection to be used for the given table
  821. *
  822. * @param string Table name
  823. * @param string DSN connection ('r' or 'w') to be used
  824. */
  825. public function set_table_dsn($table, $mode)
  826. {
  827. $this->options['table_dsn_map'][$this->table_name($table)] = $mode;
  828. }
  829. /**
  830. * MDB2 DSN string parser
  831. *
  832. * @param string $sequence Secuence name
  833. *
  834. * @return array DSN parameters
  835. */
  836. public static function parse_dsn($dsn)
  837. {
  838. if (empty($dsn)) {
  839. return null;
  840. }
  841. // Find phptype and dbsyntax
  842. if (($pos = strpos($dsn, '://')) !== false) {
  843. $str = substr($dsn, 0, $pos);
  844. $dsn = substr($dsn, $pos + 3);
  845. }
  846. else {
  847. $str = $dsn;
  848. $dsn = null;
  849. }
  850. // Get phptype and dbsyntax
  851. // $str => phptype(dbsyntax)
  852. if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
  853. $parsed['phptype'] = $arr[1];
  854. $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
  855. }
  856. else {
  857. $parsed['phptype'] = $str;
  858. $parsed['dbsyntax'] = $str;
  859. }
  860. if (empty($dsn)) {
  861. return $parsed;
  862. }
  863. // Get (if found): username and password
  864. // $dsn => username:password@protocol+hostspec/database
  865. if (($at = strrpos($dsn,'@')) !== false) {
  866. $str = substr($dsn, 0, $at);
  867. $dsn = substr($dsn, $at + 1);
  868. if (($pos = strpos($str, ':')) !== false) {
  869. $parsed['username'] = rawurldecode(substr($str, 0, $pos));
  870. $parsed['password'] = rawurldecode(substr($str, $pos + 1));
  871. }
  872. else {
  873. $parsed['username'] = rawurldecode($str);
  874. }
  875. }
  876. // Find protocol and hostspec
  877. // $dsn => proto(proto_opts)/database
  878. if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
  879. $proto = $match[1];
  880. $proto_opts = $match[2] ? $match[2] : false;
  881. $dsn = $match[3];
  882. }
  883. // $dsn => protocol+hostspec/database (old format)
  884. else {
  885. if (strpos($dsn, '+') !== false) {
  886. list($proto, $dsn) = explode('+', $dsn, 2);
  887. }
  888. if ( strpos($dsn, '//') === 0
  889. && strpos($dsn, '/', 2) !== false
  890. && $parsed['phptype'] == 'oci8'
  891. ) {
  892. //oracle's "Easy Connect" syntax:
  893. //"username/password@[//]host[:port][/service_name]"
  894. //e.g. "scott/tiger@//mymachine:1521/oracle"
  895. $proto_opts = $dsn;
  896. $pos = strrpos($proto_opts, '/');
  897. $dsn = substr($proto_opts, $pos + 1);
  898. $proto_opts = substr($proto_opts, 0, $pos);
  899. }
  900. else if (strpos($dsn, '/') !== false) {
  901. list($proto_opts, $dsn) = explode('/', $dsn, 2);
  902. }
  903. else {
  904. $proto_opts = $dsn;
  905. $dsn = null;
  906. }
  907. }
  908. // process the different protocol options
  909. $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
  910. $proto_opts = rawurldecode($proto_opts);
  911. if (strpos($proto_opts, ':') !== false) {
  912. list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
  913. }
  914. if ($parsed['protocol'] == 'tcp') {
  915. $parsed['hostspec'] = $proto_opts;
  916. }
  917. else if ($parsed['protocol'] == 'unix') {
  918. $parsed['socket'] = $proto_opts;
  919. }
  920. // Get dabase if any
  921. // $dsn => database
  922. if ($dsn) {
  923. // /database
  924. if (($pos = strpos($dsn, '?')) === false) {
  925. $parsed['database'] = rawurldecode($dsn);
  926. // /database?param1=value1&param2=value2
  927. }
  928. else {
  929. $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
  930. $dsn = substr($dsn, $pos + 1);
  931. if (strpos($dsn, '&') !== false) {
  932. $opts = explode('&', $dsn);
  933. }
  934. else { // database?param1=value1
  935. $opts = array($dsn);
  936. }
  937. foreach ($opts as $opt) {
  938. list($key, $value) = explode('=', $opt);
  939. if (!array_key_exists($key, $parsed) || false === $parsed[$key]) {
  940. // don't allow params overwrite
  941. $parsed[$key] = rawurldecode($value);
  942. }
  943. }
  944. }
  945. }
  946. return $parsed;
  947. }
  948. /**
  949. * Returns PDO DSN string from DSN array
  950. *
  951. * @param array $dsn DSN parameters
  952. *
  953. * @return string DSN string
  954. */
  955. protected function dsn_string($dsn)
  956. {
  957. $params = array();
  958. $result = $dsn['phptype'] . ':';
  959. if ($dsn['hostspec']) {
  960. $params[] = 'host=' . $dsn['hostspec'];
  961. }
  962. if ($dsn['port']) {
  963. $params[] = 'port=' . $dsn['port'];
  964. }
  965. if ($dsn['database']) {
  966. $params[] = 'dbname=' . $dsn['database'];
  967. }
  968. if (!empty($params)) {
  969. $result .= implode(';', $params);
  970. }
  971. return $result;
  972. }
  973. /**
  974. * Returns driver-specific connection options
  975. *
  976. * @param array $dsn DSN parameters
  977. *
  978. * @return array Connection options
  979. */
  980. protected function dsn_options($dsn)
  981. {
  982. $result = array();
  983. return $result;
  984. }
  985. /**
  986. * Execute the given SQL script
  987. *
  988. * @param string SQL queries to execute
  989. *
  990. * @return boolen True on success, False on error
  991. */
  992. public function exec_script($sql)
  993. {
  994. $sql = $this->fix_table_names($sql);
  995. $buff = '';
  996. foreach (explode("\n", $sql) as $line) {
  997. if (preg_match('/^--/', $line) || trim($line) == '')
  998. continue;
  999. $buff .= $line . "\n";
  1000. if (preg_match('/(;|^GO)$/', trim($line))) {
  1001. $this->query($buff);
  1002. $buff = '';
  1003. if ($this->db_error) {
  1004. break;
  1005. }
  1006. }
  1007. }
  1008. return !$this->db_error;
  1009. }
  1010. /**
  1011. * Parse SQL file and fix table names according to table prefix
  1012. */
  1013. protected function fix_table_names($sql)
  1014. {
  1015. if (!$this->options['table_prefix']) {
  1016. return $sql;
  1017. }
  1018. $sql = preg_replace_callback(
  1019. '/((TABLE|TRUNCATE|(?<!ON )UPDATE|INSERT INTO|FROM'
  1020. . '| ON(?! (DELETE|UPDATE))|REFERENCES|CONSTRAINT|FOREIGN KEY|INDEX)'
  1021. . '\s+(IF (NOT )?EXISTS )?[`"]*)([^`"\( \r\n]+)/',
  1022. array($this, 'fix_table_names_callback'),
  1023. $sql
  1024. );
  1025. return $sql;
  1026. }
  1027. /**
  1028. * Preg_replace callback for fix_table_names()
  1029. */
  1030. protected function fix_table_names_callback($matches)
  1031. {
  1032. return $matches[1] . $this->options['table_prefix'] . $matches[count($matches)-1];
  1033. }
  1034. }