PageRenderTime 27ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/db/firebird.php

http://github.com/phpbb/phpbb3
PHP | 583 lines | 458 code | 45 blank | 80 comment | 30 complexity | e855c71c09e5b8eca42c4d6fb4cae1cc MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package dbal
  5. * @copyright (c) 2005 phpBB Group
  6. * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
  7. *
  8. */
  9. /**
  10. * @ignore
  11. */
  12. if (!defined('IN_PHPBB'))
  13. {
  14. exit;
  15. }
  16. include_once($phpbb_root_path . 'includes/db/dbal.' . $phpEx);
  17. /**
  18. * Firebird/Interbase Database Abstraction Layer
  19. * Minimum Requirement is Firebird 2.1
  20. * @package dbal
  21. */
  22. class dbal_firebird extends dbal
  23. {
  24. var $last_query_text = '';
  25. var $service_handle = false;
  26. var $affected_rows = 0;
  27. var $connect_error = '';
  28. /**
  29. * Connect to server
  30. */
  31. function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
  32. {
  33. $this->persistency = $persistency;
  34. $this->user = $sqluser;
  35. $this->server = $sqlserver . (($port) ? ':' . $port : '');
  36. $this->dbname = str_replace('\\', '/', $database);
  37. // There are three possibilities to connect to an interbase db
  38. if (!$this->server)
  39. {
  40. $use_database = $this->dbname;
  41. }
  42. else if (strpos($this->server, '//') === 0)
  43. {
  44. $use_database = $this->server . $this->dbname;
  45. }
  46. else
  47. {
  48. $use_database = $this->server . ':' . $this->dbname;
  49. }
  50. if ($this->persistency)
  51. {
  52. if (!function_exists('ibase_pconnect'))
  53. {
  54. $this->connect_error = 'ibase_pconnect function does not exist, is interbase extension installed?';
  55. return $this->sql_error('');
  56. }
  57. $this->db_connect_id = @ibase_pconnect($use_database, $this->user, $sqlpassword, false, false, 3);
  58. }
  59. else
  60. {
  61. if (!function_exists('ibase_connect'))
  62. {
  63. $this->connect_error = 'ibase_connect function does not exist, is interbase extension installed?';
  64. return $this->sql_error('');
  65. }
  66. $this->db_connect_id = @ibase_connect($use_database, $this->user, $sqlpassword, false, false, 3);
  67. }
  68. // Do not call ibase_service_attach if connection failed,
  69. // otherwise error message from ibase_(p)connect call will be clobbered.
  70. if ($this->db_connect_id && function_exists('ibase_service_attach') && $this->server)
  71. {
  72. $this->service_handle = @ibase_service_attach($this->server, $this->user, $sqlpassword);
  73. }
  74. else
  75. {
  76. $this->service_handle = false;
  77. }
  78. return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
  79. }
  80. /**
  81. * Version information about used database
  82. * @param bool $raw if true, only return the fetched sql_server_version
  83. * @param bool $use_cache forced to false for Interbase
  84. * @return string sql server version
  85. */
  86. function sql_server_info($raw = false, $use_cache = true)
  87. {
  88. /**
  89. * force $use_cache false. I didn't research why the caching code there is no caching code
  90. * but I assume its because the IB extension provides a direct method to access it
  91. * without a query.
  92. */
  93. $use_cache = false;
  94. if ($this->service_handle !== false && function_exists('ibase_server_info'))
  95. {
  96. return @ibase_server_info($this->service_handle, IBASE_SVC_SERVER_VERSION);
  97. }
  98. return ($raw) ? '2.1' : 'Firebird/Interbase';
  99. }
  100. /**
  101. * SQL Transaction
  102. * @access private
  103. */
  104. function _sql_transaction($status = 'begin')
  105. {
  106. switch ($status)
  107. {
  108. case 'begin':
  109. return true;
  110. break;
  111. case 'commit':
  112. return @ibase_commit();
  113. break;
  114. case 'rollback':
  115. return @ibase_rollback();
  116. break;
  117. }
  118. return true;
  119. }
  120. /**
  121. * Base query method
  122. *
  123. * @param string $query Contains the SQL query which shall be executed
  124. * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache
  125. * @return mixed When casted to bool the returned value returns true on success and false on failure
  126. *
  127. * @access public
  128. */
  129. function sql_query($query = '', $cache_ttl = 0)
  130. {
  131. if ($query != '')
  132. {
  133. global $cache;
  134. // EXPLAIN only in extra debug mode
  135. if (defined('DEBUG_EXTRA'))
  136. {
  137. $this->sql_report('start', $query);
  138. }
  139. $this->last_query_text = $query;
  140. $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false;
  141. $this->sql_add_num_queries($this->query_result);
  142. if ($this->query_result === false)
  143. {
  144. $array = array();
  145. // We overcome Firebird's 32767 char limit by binding vars
  146. if (strlen($query) > 32767)
  147. {
  148. if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/s', $query, $regs))
  149. {
  150. if (strlen($regs[3]) > 32767)
  151. {
  152. preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER);
  153. $inserts = $vals[0];
  154. unset($vals);
  155. foreach ($inserts as $key => $value)
  156. {
  157. if (!empty($value) && $value[0] === "'" && strlen($value) > 32769) // check to see if this thing is greater than the max + 'x2
  158. {
  159. $inserts[$key] = '?';
  160. $array[] = str_replace("''", "'", substr($value, 1, -1));
  161. }
  162. }
  163. $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')';
  164. }
  165. }
  166. else if (preg_match('/^(UPDATE ([\\w_]++)\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|\\d+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data))
  167. {
  168. if (strlen($data[3]) > 32767)
  169. {
  170. $update = $data[1];
  171. $where = $data[4];
  172. preg_match_all('/(\\w++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[3], $temp, PREG_SET_ORDER);
  173. unset($data);
  174. $cols = array();
  175. foreach ($temp as $value)
  176. {
  177. if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 32769) // check to see if this thing is greater than the max + 'x2
  178. {
  179. $array[] = str_replace("''", "'", substr($value[2], 1, -1));
  180. $cols[] = $value[1] . '=?';
  181. }
  182. else
  183. {
  184. $cols[] = $value[1] . '=' . $value[2];
  185. }
  186. }
  187. $query = $update . implode(', ', $cols) . ' ' . $where;
  188. unset($cols);
  189. }
  190. }
  191. }
  192. if (!function_exists('ibase_affected_rows') && (preg_match('/^UPDATE ([\w_]++)\s+SET [\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\s*[\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+\s+(WHERE.*)?$/s', $query, $regs) || preg_match('/^DELETE FROM ([\w_]++)\s*(WHERE\s*.*)?$/s', $query, $regs)))
  193. {
  194. $affected_sql = 'SELECT COUNT(*) as num_rows_affected FROM ' . $regs[1];
  195. if (!empty($regs[2]))
  196. {
  197. $affected_sql .= ' ' . $regs[2];
  198. }
  199. if (!($temp_q_id = @ibase_query($this->db_connect_id, $affected_sql)))
  200. {
  201. return false;
  202. }
  203. $temp_result = @ibase_fetch_assoc($temp_q_id);
  204. @ibase_free_result($temp_q_id);
  205. $this->affected_rows = ($temp_result) ? $temp_result['NUM_ROWS_AFFECTED'] : false;
  206. }
  207. if (sizeof($array))
  208. {
  209. $p_query = @ibase_prepare($this->db_connect_id, $query);
  210. array_unshift($array, $p_query);
  211. $this->query_result = call_user_func_array('ibase_execute', $array);
  212. unset($array);
  213. if ($this->query_result === false)
  214. {
  215. $this->sql_error($query);
  216. }
  217. }
  218. else if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false)
  219. {
  220. $this->sql_error($query);
  221. }
  222. if (defined('DEBUG_EXTRA'))
  223. {
  224. $this->sql_report('stop', $query);
  225. }
  226. if (!$this->transaction)
  227. {
  228. if (function_exists('ibase_commit_ret'))
  229. {
  230. @ibase_commit_ret();
  231. }
  232. else
  233. {
  234. // way cooler than ibase_commit_ret :D
  235. @ibase_query('COMMIT RETAIN;');
  236. }
  237. }
  238. if ($cache_ttl && method_exists($cache, 'sql_save'))
  239. {
  240. $this->open_queries[(int) $this->query_result] = $this->query_result;
  241. $cache->sql_save($query, $this->query_result, $cache_ttl);
  242. }
  243. else if (strpos($query, 'SELECT') === 0 && $this->query_result)
  244. {
  245. $this->open_queries[(int) $this->query_result] = $this->query_result;
  246. }
  247. }
  248. else if (defined('DEBUG_EXTRA'))
  249. {
  250. $this->sql_report('fromcache', $query);
  251. }
  252. }
  253. else
  254. {
  255. return false;
  256. }
  257. return $this->query_result;
  258. }
  259. /**
  260. * Build LIMIT query
  261. */
  262. function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
  263. {
  264. $this->query_result = false;
  265. $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6);
  266. return $this->sql_query($query, $cache_ttl);
  267. }
  268. /**
  269. * Return number of affected rows
  270. */
  271. function sql_affectedrows()
  272. {
  273. // PHP 5+ function
  274. if (function_exists('ibase_affected_rows'))
  275. {
  276. return ($this->db_connect_id) ? @ibase_affected_rows($this->db_connect_id) : false;
  277. }
  278. else
  279. {
  280. return $this->affected_rows;
  281. }
  282. }
  283. /**
  284. * Fetch current row
  285. */
  286. function sql_fetchrow($query_id = false)
  287. {
  288. global $cache;
  289. if ($query_id === false)
  290. {
  291. $query_id = $this->query_result;
  292. }
  293. if (isset($cache->sql_rowset[$query_id]))
  294. {
  295. return $cache->sql_fetchrow($query_id);
  296. }
  297. if ($query_id === false)
  298. {
  299. return false;
  300. }
  301. $row = array();
  302. $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT);
  303. if (!$cur_row)
  304. {
  305. return false;
  306. }
  307. foreach (get_object_vars($cur_row) as $key => $value)
  308. {
  309. $row[strtolower($key)] = (is_string($value)) ? trim(str_replace(array("\\0", "\\n"), array("\0", "\n"), $value)) : $value;
  310. }
  311. return (sizeof($row)) ? $row : false;
  312. }
  313. /**
  314. * Seek to given row number
  315. * rownum is zero-based
  316. */
  317. function sql_rowseek($rownum, &$query_id)
  318. {
  319. global $cache;
  320. if ($query_id === false)
  321. {
  322. $query_id = $this->query_result;
  323. }
  324. if (isset($cache->sql_rowset[$query_id]))
  325. {
  326. return $cache->sql_rowseek($rownum, $query_id);
  327. }
  328. if ($query_id === false)
  329. {
  330. return;
  331. }
  332. $this->sql_freeresult($query_id);
  333. $query_id = $this->sql_query($this->last_query_text);
  334. if ($query_id === false)
  335. {
  336. return false;
  337. }
  338. // We do not fetch the row for rownum == 0 because then the next resultset would be the second row
  339. for ($i = 0; $i < $rownum; $i++)
  340. {
  341. if (!$this->sql_fetchrow($query_id))
  342. {
  343. return false;
  344. }
  345. }
  346. return true;
  347. }
  348. /**
  349. * Get last inserted id after insert statement
  350. */
  351. function sql_nextid()
  352. {
  353. $query_id = $this->query_result;
  354. if ($query_id !== false && $this->last_query_text != '')
  355. {
  356. if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#i', $this->last_query_text, $tablename))
  357. {
  358. $sql = 'SELECT GEN_ID(' . $tablename[1] . '_gen, 0) AS new_id FROM RDB$DATABASE';
  359. if (!($temp_q_id = @ibase_query($this->db_connect_id, $sql)))
  360. {
  361. return false;
  362. }
  363. $temp_result = @ibase_fetch_assoc($temp_q_id);
  364. @ibase_free_result($temp_q_id);
  365. return ($temp_result) ? $temp_result['NEW_ID'] : false;
  366. }
  367. }
  368. return false;
  369. }
  370. /**
  371. * Free sql result
  372. */
  373. function sql_freeresult($query_id = false)
  374. {
  375. global $cache;
  376. if ($query_id === false)
  377. {
  378. $query_id = $this->query_result;
  379. }
  380. if (isset($cache->sql_rowset[$query_id]))
  381. {
  382. return $cache->sql_freeresult($query_id);
  383. }
  384. if (isset($this->open_queries[(int) $query_id]))
  385. {
  386. unset($this->open_queries[(int) $query_id]);
  387. return @ibase_free_result($query_id);
  388. }
  389. return false;
  390. }
  391. /**
  392. * Escape string used in sql query
  393. */
  394. function sql_escape($msg)
  395. {
  396. return str_replace(array("'", "\0"), array("''", ''), $msg);
  397. }
  398. /**
  399. * Build LIKE expression
  400. * @access private
  401. */
  402. function _sql_like_expression($expression)
  403. {
  404. return $expression . " ESCAPE '\\'";
  405. }
  406. /**
  407. * Build db-specific query data
  408. * @access private
  409. */
  410. function _sql_custom_build($stage, $data)
  411. {
  412. return $data;
  413. }
  414. function _sql_bit_and($column_name, $bit, $compare = '')
  415. {
  416. return 'BIN_AND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
  417. }
  418. function _sql_bit_or($column_name, $bit, $compare = '')
  419. {
  420. return 'BIN_OR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
  421. }
  422. /**
  423. * @inheritdoc
  424. */
  425. function cast_expr_to_bigint($expression)
  426. {
  427. // Precision must be from 1 to 18
  428. return 'CAST(' . $expression . ' as DECIMAL(18, 0))';
  429. }
  430. /**
  431. * @inheritdoc
  432. */
  433. function cast_expr_to_string($expression)
  434. {
  435. return 'CAST(' . $expression . ' as VARCHAR(255))';
  436. }
  437. /**
  438. * return sql error array
  439. * @access private
  440. */
  441. function _sql_error()
  442. {
  443. // Need special handling here because ibase_errmsg returns
  444. // connection errors, however if the interbase extension
  445. // is not installed then ibase_errmsg does not exist and
  446. // we cannot call it.
  447. if (function_exists('ibase_errmsg'))
  448. {
  449. $msg = @ibase_errmsg();
  450. if (!$msg)
  451. {
  452. $msg = $this->connect_error;
  453. }
  454. }
  455. else
  456. {
  457. $msg = $this->connect_error;
  458. }
  459. return array(
  460. 'message' => $msg,
  461. 'code' => (@function_exists('ibase_errcode') ? @ibase_errcode() : '')
  462. );
  463. }
  464. /**
  465. * Close sql connection
  466. * @access private
  467. */
  468. function _sql_close()
  469. {
  470. if ($this->service_handle !== false)
  471. {
  472. @ibase_service_detach($this->service_handle);
  473. }
  474. return @ibase_close($this->db_connect_id);
  475. }
  476. /**
  477. * Build db-specific report
  478. * @access private
  479. */
  480. function _sql_report($mode, $query = '')
  481. {
  482. switch ($mode)
  483. {
  484. case 'start':
  485. break;
  486. case 'fromcache':
  487. $endtime = explode(' ', microtime());
  488. $endtime = $endtime[0] + $endtime[1];
  489. $result = @ibase_query($this->db_connect_id, $query);
  490. while ($void = @ibase_fetch_object($result, IBASE_TEXT))
  491. {
  492. // Take the time spent on parsing rows into account
  493. }
  494. @ibase_free_result($result);
  495. $splittime = explode(' ', microtime());
  496. $splittime = $splittime[0] + $splittime[1];
  497. $this->sql_report('record_fromcache', $query, $endtime, $splittime);
  498. break;
  499. }
  500. }
  501. }