PageRenderTime 50ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/core/database_api.php

http://github.com/mantisbt/mantisbt
PHP | 1317 lines | 714 code | 138 blank | 465 comment | 111 complexity | 81e5ef6c8e5eaa990151d6c7856d9594 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. # MantisBT - A PHP based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Database API
  17. *
  18. * @package CoreAPI
  19. * @subpackage DatabaseAPI
  20. * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  21. * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  22. * @link http://www.mantisbt.org
  23. *
  24. * @uses config_api.php
  25. * @uses constant_inc.php
  26. * @uses error_api.php
  27. * @uses logging_api.php
  28. * @uses utility_api.php
  29. * @uses adodb/adodb.inc.php
  30. */
  31. require_api( 'config_api.php' );
  32. require_api( 'constant_inc.php' );
  33. require_api( 'error_api.php' );
  34. require_api( 'logging_api.php' );
  35. require_api( 'utility_api.php' );
  36. # An array in which all executed queries are stored. This is used for profiling
  37. # @global array $g_queries_array
  38. $g_queries_array = array();
  39. # Stores whether a database connection was successfully opened.
  40. # @global bool $g_db_connected
  41. $g_db_connected = false;
  42. # Store whether to log queries ( used for show_queries_count/query list)
  43. # @global bool $g_db_log_queries
  44. $g_db_log_queries = ( 0 != ( config_get_global( 'log_level' ) & LOG_DATABASE ) );
  45. # set adodb to associative fetch mode with lowercase column names
  46. # @global bool $ADODB_FETCH_MODE
  47. global $ADODB_FETCH_MODE;
  48. $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
  49. define( 'ADODB_ASSOC_CASE', ADODB_ASSOC_CASE_LOWER );
  50. # Stores the functional database type based on db driver
  51. $g_db_functional_type = db_get_type( config_get_global( 'db_type' ) );
  52. /**
  53. * Mantis Database Parameters Count class
  54. * Stores the current parameter count, provides method to generate parameters
  55. * and a simple stack mechanism to enable the caller to build multiple queries
  56. * concurrently on RDBMS using positional parameters (e.g. PostgreSQL)
  57. */
  58. class MantisDbParam {
  59. /**
  60. * Current parameter count
  61. */
  62. public $count = 0;
  63. /**
  64. * Parameter count stack
  65. */
  66. private $stack = array();
  67. /**
  68. * Generate a string to insert a parameter into a database query string
  69. * @return string 'wildcard' matching a parameter in correct ordered format for the current database.
  70. */
  71. public function assign() {
  72. global $g_db;
  73. return $g_db->Param( $this->count++ );
  74. }
  75. /**
  76. * Pushes current parameter count onto stack and resets its value to 0
  77. * @return void
  78. */
  79. public function push() {
  80. $this->stack[] = $this->count;
  81. $this->count = 0;
  82. }
  83. /**
  84. * Pops the previous value of param count from the stack
  85. * This function is called by {@see db_query()} and should not need
  86. * to be executed directly
  87. * @return void
  88. */
  89. public function pop() {
  90. global $g_db;
  91. $this->count = (int)array_pop( $this->stack );
  92. if( db_is_pgsql() ) {
  93. # Manually reset the ADOdb param number to the value we just popped
  94. $g_db->_pnum = $this->count;
  95. }
  96. }
  97. }
  98. # Tracks the query parameter count
  99. # @global object $g_db_param
  100. $g_db_param = new MantisDbParam();
  101. /**
  102. * Open a connection to the database.
  103. * @param string $p_dsn Database connection string ( specified instead of other params).
  104. * @param string $p_hostname Database server hostname.
  105. * @param string $p_username Database server username.
  106. * @param string $p_password Database server password.
  107. * @param string $p_database_name Database name.
  108. * @param boolean $p_pconnect Use a Persistent connection to database.
  109. * @return boolean indicating if the connection was successful
  110. */
  111. function db_connect( $p_dsn, $p_hostname = null, $p_username = null, $p_password = null, $p_database_name = null, $p_pconnect = false ) {
  112. global $g_db_connected, $g_db, $g_db_functional_type;
  113. $t_db_type = config_get_global( 'db_type' );
  114. $g_db_functional_type = db_get_type( $t_db_type );
  115. if( $g_db_functional_type == DB_TYPE_UNDEFINED ) {
  116. error_parameters( 0, 'Database type is not supported by MantisBT, check $g_db_type in config_inc.php' );
  117. trigger_error( ERROR_DB_CONNECT_FAILED, ERROR );
  118. }
  119. if( !db_check_database_support( $t_db_type ) ) {
  120. error_parameters( 0, 'PHP Support for database is not enabled' );
  121. trigger_error( ERROR_DB_CONNECT_FAILED, ERROR );
  122. }
  123. if( empty( $p_dsn ) ) {
  124. $g_db = ADONewConnection( $t_db_type );
  125. if( $p_pconnect ) {
  126. $t_result = $g_db->PConnect( $p_hostname, $p_username, $p_password, $p_database_name );
  127. } else {
  128. $t_result = $g_db->Connect( $p_hostname, $p_username, $p_password, $p_database_name );
  129. }
  130. } else {
  131. $g_db = ADONewConnection( $p_dsn );
  132. $t_result = $g_db->IsConnected();
  133. }
  134. if( $t_result ) {
  135. # For MySQL, the charset for the connection needs to be specified.
  136. if( db_is_mysql() ) {
  137. # @todo Is there a way to translate any charset name to MySQL format? e.g. remote the dashes?
  138. # @todo Is this needed for other databases?
  139. db_query( 'SET NAMES UTF8' );
  140. }
  141. } else {
  142. db_error();
  143. trigger_error( ERROR_DB_CONNECT_FAILED, ERROR );
  144. return false;
  145. }
  146. $g_db_connected = true;
  147. return true;
  148. }
  149. /**
  150. * Returns whether a connection to the database exists
  151. * @global stores database connection state
  152. * @return boolean indicating if the a database connection has been made
  153. */
  154. function db_is_connected() {
  155. global $g_db_connected;
  156. return $g_db_connected;
  157. }
  158. /**
  159. * Returns whether php support for a database is enabled
  160. * @param string $p_db_type Database type.
  161. * @return boolean indicating if php current supports the given database type
  162. */
  163. function db_check_database_support( $p_db_type ) {
  164. switch( $p_db_type ) {
  165. case 'mysqli':
  166. $t_support = function_exists( 'mysqli_connect' );
  167. break;
  168. case 'pgsql':
  169. $t_support = function_exists( 'pg_connect' );
  170. break;
  171. case 'mssqlnative':
  172. $t_support = function_exists( 'sqlsrv_connect' );
  173. break;
  174. case 'oci8':
  175. $t_support = function_exists( 'OCILogon' );
  176. break;
  177. case 'odbc_mssql':
  178. $t_support = function_exists( 'odbc_connect' );
  179. break;
  180. default:
  181. $t_support = false;
  182. }
  183. return $t_support;
  184. }
  185. /**
  186. * Maps a db driver type to the functional database type
  187. * @param string $p_driver_type Database driver name
  188. * @return int Database type
  189. */
  190. function db_get_type( $p_driver_type ) {
  191. switch( $p_driver_type ) {
  192. case 'mysqli':
  193. return DB_TYPE_MYSQL;
  194. case 'pgsql':
  195. return DB_TYPE_PGSQL;
  196. case 'mssqlnative':
  197. case 'odbc_mssql':
  198. return DB_TYPE_MSSQL;
  199. case 'oci8':
  200. return DB_TYPE_ORACLE;
  201. default:
  202. return DB_TYPE_UNDEFINED;
  203. }
  204. }
  205. /**
  206. * Checks if the database driver is MySQL
  207. * @return boolean true if mysql
  208. */
  209. function db_is_mysql() {
  210. global $g_db_functional_type;
  211. return( DB_TYPE_MYSQL == $g_db_functional_type );
  212. }
  213. /**
  214. * Checks if the database driver is PostgreSQL
  215. * @return boolean true if postgres
  216. */
  217. function db_is_pgsql() {
  218. global $g_db_functional_type;
  219. return( DB_TYPE_PGSQL == $g_db_functional_type );
  220. }
  221. /**
  222. * Checks if the database driver is MS SQL
  223. * @return boolean true if mssql
  224. */
  225. function db_is_mssql() {
  226. global $g_db_functional_type;
  227. return( DB_TYPE_MSSQL == $g_db_functional_type );
  228. }
  229. /**
  230. * Checks if the database driver is Oracle (oci8)
  231. * @return boolean true if oracle
  232. */
  233. function db_is_oracle() {
  234. global $g_db_functional_type;
  235. return( DB_TYPE_ORACLE == $g_db_functional_type );
  236. }
  237. /**
  238. * Validates that the given identifier's length is OK for the database platform
  239. * Triggers an error if the identifier is too long
  240. * @param string $p_identifier Identifier to check.
  241. * @return void
  242. */
  243. function db_check_identifier_size( $p_identifier ) {
  244. # Oracle does not support long object names (30 chars max)
  245. if( db_is_oracle() && 30 < strlen( $p_identifier ) ) {
  246. error_parameters( $p_identifier );
  247. trigger_error( ERROR_DB_IDENTIFIER_TOO_LONG, ERROR );
  248. }
  249. }
  250. /**
  251. * function alias for db_query() for legacy support of plugins
  252. * @deprecated db_query should be used in preference to this function. This function may be removed in 2.0
  253. */
  254. function db_query_bound() {
  255. error_parameters( __FUNCTION__ . '()', 'db_query()' );
  256. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  257. return call_user_func_array( 'db_query', func_get_args() );
  258. }
  259. /**
  260. * execute query, requires connection to be opened
  261. * An error will be triggered if there is a problem executing the query.
  262. * This will pop the database parameter stack {@see MantisDbParam} after a
  263. * successful execution, unless specified otherwise
  264. *
  265. * @global array of previous executed queries for profiling
  266. * @global adodb database connection object
  267. * @global boolean indicating whether queries array is populated
  268. * @param string $p_query Parameterlised Query string to execute.
  269. * @param array $p_arr_parms Array of parameters matching $p_query.
  270. * @param integer $p_limit Number of results to return.
  271. * @param integer $p_offset Offset query results for paging.
  272. * @param boolean $p_pop_param Set to false to leave the parameters on the stack
  273. * @return IteratorAggregate|boolean adodb result set or false if the query failed.
  274. */
  275. function db_query( $p_query, array $p_arr_parms = null, $p_limit = -1, $p_offset = -1, $p_pop_param = true ) {
  276. # Use DbQuery class to execute the query
  277. return DbQuery::compat_db_query( $p_query, $p_arr_parms, $p_limit, $p_offset, $p_pop_param );
  278. }
  279. /**
  280. * Generate a string to insert a parameter into a database query string
  281. * @return string 'wildcard' matching a parameter in correct ordered format for the current database.
  282. */
  283. function db_param() {
  284. global $g_db_param;
  285. return $g_db_param->assign();
  286. }
  287. /**
  288. * Pushes current parameter count onto stack and resets its value
  289. * Allows the caller to build multiple queries concurrently on RDBMS using
  290. * positional parameters (e.g. PostgreSQL)
  291. * @return void
  292. */
  293. function db_param_push() {
  294. global $g_db_param;
  295. $g_db_param->push();
  296. }
  297. /**
  298. * Pops the previous parameter count from the stack
  299. * It is generally not necessary to call this, because the param count is popped
  300. * automatically whenever a query is executed via db_query(). There are some
  301. * corner cases when doing it manually makes sense, e.g. when a query is built
  302. * but not executed.
  303. * @return void
  304. */
  305. function db_param_pop() {
  306. global $g_db_param;
  307. $g_db_param->pop();
  308. }
  309. /**
  310. * Retrieve number of rows returned for a specific database query
  311. * @param IteratorAggregate $p_result Database Query Record Set to retrieve record count for.
  312. * @return integer Record Count
  313. */
  314. function db_num_rows( IteratorAggregate $p_result ) {
  315. return $p_result->RecordCount();
  316. }
  317. /**
  318. * Retrieve number of rows affected by a specific database query
  319. * @return integer Affected Rows
  320. */
  321. function db_affected_rows() {
  322. global $g_db;
  323. return $g_db->Affected_Rows();
  324. }
  325. /**
  326. * Retrieve the next row returned from a specific database query
  327. * @param IteratorAggregate &$p_result Database Query Record Set to retrieve next result for.
  328. * @return array Database result
  329. */
  330. function db_fetch_array( IteratorAggregate &$p_result ) {
  331. global $g_db_functional_type;
  332. if( $p_result->EOF ) {
  333. return false;
  334. }
  335. # Retrieve the fields from the recordset
  336. $t_row = $p_result->fields;
  337. # Additional handling for specific RDBMS
  338. switch( $g_db_functional_type ) {
  339. case DB_TYPE_PGSQL:
  340. # pgsql's boolean fields are stored as 't' or 'f' and must be converted
  341. static $s_current_result = null, $s_convert_needed;
  342. if( $s_current_result != $p_result ) {
  343. # Processing a new query
  344. $s_current_result = $p_result;
  345. $s_convert_needed = false;
  346. } elseif( !$s_convert_needed ) {
  347. # No conversion needed, return the row as-is
  348. $p_result->MoveNext();
  349. return $t_row;
  350. }
  351. foreach( $p_result->FieldTypesArray() as $t_field ) {
  352. switch( $t_field->type ) {
  353. case 'bool':
  354. switch( $t_row[$t_field->name] ) {
  355. case 'f':
  356. $t_row[$t_field->name] = false;
  357. break;
  358. case 't':
  359. $t_row[$t_field->name] = true;
  360. break;
  361. }
  362. $s_convert_needed = true;
  363. break;
  364. }
  365. }
  366. break;
  367. case DB_TYPE_ORACLE:
  368. # oci8 returns null values for empty strings, convert them back
  369. foreach( $t_row as &$t_value ) {
  370. if( !isset( $t_value ) ) {
  371. $t_value = '';
  372. }
  373. }
  374. break;
  375. }
  376. $p_result->MoveNext();
  377. return $t_row;
  378. }
  379. /**
  380. * Retrieve a specific field from a database query result
  381. * @param boolean|IteratorAggregate $p_result Database Query Record Set to retrieve the field from.
  382. * @param integer $p_row_index Row to retrieve, zero-based (optional).
  383. * @param integer $p_col_index Column to retrieve, zero-based (optional).
  384. * @return mixed Database result
  385. */
  386. function db_result( $p_result, $p_row_index = 0, $p_col_index = 0 ) {
  387. if( $p_result && ( db_num_rows( $p_result ) > 0 ) ) {
  388. $p_result->Move( $p_row_index );
  389. $t_row = db_fetch_array( $p_result );
  390. # Make the array numerically indexed. This is required to retrieve the
  391. # column ($p_index2), since we use ADODB_FETCH_ASSOC fetch mode.
  392. $t_result = array_values( $t_row );
  393. return $t_result[$p_col_index];
  394. }
  395. return false;
  396. }
  397. /**
  398. * Return the last inserted ID after a insert statement.
  399. * Warning: this function must be used immediately after the insert statement
  400. *
  401. * This relies on ADOdb to get the entity id when this functionality is available
  402. * for the specific driver, and it makes sense in our model.
  403. * Natively supported:
  404. * - mysqli, using: mysqli_insert_id(connection).
  405. * - mssqlnative, using SCOPE_IDENTITY().
  406. * Not natively supported:
  407. * - pgsql, oracle, using the underlying sequence for the table.
  408. *
  409. * Since the table is needed for those drivers where a sequence is used, the
  410. * $p_table parameter is mandatory to ensure portability.
  411. * Warning: $p_table is not expected to be a different table than the one used
  412. * for the previous insert. Note that it's not even used by some drivers.
  413. *
  414. * @param string $p_table A valid database table name.
  415. * @param string $p_field A valid field name (default 'id').
  416. * @return integer last successful insert id
  417. */
  418. function db_insert_id( $p_table, $p_field = 'id' ) {
  419. global $g_db, $g_db_functional_type;
  420. switch( $g_db_functional_type ) {
  421. case DB_TYPE_ORACLE:
  422. $t_query = 'SELECT seq_' . $p_table . '.CURRVAL FROM DUAL';
  423. break;
  424. case DB_TYPE_PGSQL:
  425. $t_query = 'SELECT currval(\'' . $p_table . '_' . $p_field . '_seq\')';
  426. break;
  427. default:
  428. return $g_db->Insert_ID();
  429. }
  430. $t_result = db_query( $t_query );
  431. return (int)db_result( $t_result );
  432. }
  433. /**
  434. * Check if the specified table exists.
  435. * @param string $p_table_name A valid database table name.
  436. * @return boolean indicating whether the table exists
  437. */
  438. function db_table_exists( $p_table_name ) {
  439. if( is_blank( $p_table_name ) ) {
  440. return false;
  441. }
  442. $t_tables = db_get_table_list();
  443. if( !is_array( $t_tables ) ) {
  444. return false;
  445. }
  446. # Can't use in_array() since it is case sensitive
  447. $t_table_name = mb_strtolower( $p_table_name );
  448. foreach( $t_tables as $t_current_table ) {
  449. if( mb_strtolower( $t_current_table ) == $t_table_name ) {
  450. return true;
  451. }
  452. }
  453. return false;
  454. }
  455. /**
  456. * Check if the specified table index exists.
  457. * @param string $p_table_name A valid database table name.
  458. * @param string $p_index_name A valid database index name.
  459. * @return boolean indicating whether the index exists
  460. */
  461. function db_index_exists( $p_table_name, $p_index_name ) {
  462. global $g_db;
  463. if( is_blank( $p_index_name ) || is_blank( $p_table_name ) ) {
  464. return false;
  465. }
  466. $t_indexes = $g_db->MetaIndexes( $p_table_name );
  467. if( $t_indexes === false ) {
  468. # no index found
  469. return false;
  470. }
  471. if( !empty( $t_indexes ) ) {
  472. # Can't use in_array() since it is case sensitive
  473. $t_index_name = mb_strtolower( $p_index_name );
  474. foreach( $t_indexes as $t_current_index_name => $t_current_index_obj ) {
  475. if( mb_strtolower( $t_current_index_name ) == $t_index_name ) {
  476. return true;
  477. }
  478. }
  479. }
  480. return false;
  481. }
  482. /**
  483. * Check if the specified field exists in a given table
  484. * @param string $p_field_name A database field name.
  485. * @param string $p_table_name A valid database table name.
  486. * @return boolean indicating whether the field exists
  487. */
  488. function db_field_exists( $p_field_name, $p_table_name ) {
  489. $t_columns = db_field_names( $p_table_name );
  490. # ADOdb oci8 driver works with uppercase column names, and as of 5.19 does
  491. # not provide a way to force them to lowercase
  492. if( db_is_oracle() ) {
  493. $p_field_name = strtoupper( $p_field_name );
  494. }
  495. return in_array( $p_field_name, $t_columns );
  496. }
  497. /**
  498. * Retrieve list of fields for a given table
  499. * @param string $p_table_name A valid database table name.
  500. * @return array array of fields on table
  501. */
  502. function db_field_names( $p_table_name ) {
  503. global $g_db;
  504. $t_columns = $g_db->MetaColumnNames( $p_table_name );
  505. return is_array( $t_columns ) ? $t_columns : array();
  506. }
  507. /**
  508. * Returns the last error number. The error number is reset after every call to Execute(). If 0 is returned, no error occurred.
  509. * @return int last error number
  510. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  511. */
  512. function db_error_num() {
  513. global $g_db;
  514. return $g_db->ErrorNo();
  515. }
  516. /**
  517. * Returns the last status or error message. Returns the last status or error message. The error message is reset when Execute() is called.
  518. * This can return a string even if no error occurs. In general you do not need to call this function unless an ADOdb function returns false on an error.
  519. * @return string last error string
  520. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  521. */
  522. function db_error_msg() {
  523. global $g_db;
  524. return $g_db->ErrorMsg();
  525. }
  526. /**
  527. * send both the error number and error message and query (optional) as parameters for a triggered error
  528. * @param string $p_query Query that generated the error.
  529. * @return void
  530. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  531. */
  532. function db_error( $p_query = null ) {
  533. if( null !== $p_query ) {
  534. error_parameters( db_error_num(), db_error_msg(), $p_query );
  535. } else {
  536. error_parameters( db_error_num(), db_error_msg() );
  537. }
  538. }
  539. /**
  540. * close the connection.
  541. * Not really necessary most of the time since a connection is automatically closed when a page finishes loading.
  542. * @return void
  543. */
  544. function db_close() {
  545. global $g_db;
  546. $g_db->Close();
  547. }
  548. /**
  549. * prepare a string before DB insertion
  550. * @param string $p_string Unprepared string.
  551. * @return string prepared database query string
  552. * @deprecated db_query should be used in preference to this function. This function may be removed in 1.2.0 final
  553. */
  554. function db_prepare_string( $p_string ) {
  555. global $g_db;
  556. $t_db_type = config_get_global( 'db_type' );
  557. switch( $t_db_type ) {
  558. case 'mssqlnative':
  559. case 'odbc_mssql':
  560. return addslashes( $p_string );
  561. case 'mysqli':
  562. $t_escaped = $g_db->qstr( $p_string, false );
  563. return mb_substr( $t_escaped, 1, mb_strlen( $t_escaped ) - 2 );
  564. case 'pgsql':
  565. return pg_escape_string( $p_string );
  566. case 'oci8':
  567. return $p_string;
  568. default:
  569. error_parameters( 'db_type', $t_db_type );
  570. trigger_error( ERROR_CONFIG_OPT_INVALID, ERROR );
  571. }
  572. }
  573. /**
  574. * Prepare a binary string before DB insertion
  575. * Use of this function is required for some DB types, to properly encode
  576. * BLOB fields prior to calling db_query()
  577. * @param string $p_string Raw binary data.
  578. * @return string prepared database query string
  579. */
  580. function db_prepare_binary_string( $p_string ) {
  581. global $g_db;
  582. $t_db_type = config_get_global( 'db_type' );
  583. switch( $t_db_type ) {
  584. case 'odbc_mssql':
  585. $t_content = unpack( 'H*hex', $p_string );
  586. return '0x' . $t_content['hex'];
  587. break;
  588. case 'pgsql':
  589. return $g_db->BlobEncode( $p_string );
  590. break;
  591. case 'mssqlnative':
  592. case 'oci8':
  593. # Fall through, mssqlnative, oci8 store raw data in BLOB
  594. default:
  595. return $p_string;
  596. break;
  597. }
  598. }
  599. /**
  600. * prepare a int for database insertion.
  601. * @param integer $p_int Integer.
  602. * @return integer integer
  603. * @deprecated db_query should be used in preference to this function. This function may be removed in 1.2.0 final
  604. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  605. */
  606. function db_prepare_int( $p_int ) {
  607. return (int)$p_int;
  608. }
  609. /**
  610. * prepare a double for database insertion.
  611. * @param float $p_double Double.
  612. * @return double double
  613. * @deprecated db_query should be used in preference to this function. This function may be removed in 1.2.0 final
  614. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  615. */
  616. function db_prepare_double( $p_double ) {
  617. return (double)$p_double;
  618. }
  619. /**
  620. * prepare a boolean for database insertion.
  621. * @param boolean $p_bool Boolean value.
  622. * @return integer integer representing boolean
  623. * @deprecated db_query should be used in preference to this function. This function may be removed in 1.2.0 final
  624. * @todo Use/Behaviour of this function should be reviewed before 1.2.0 final
  625. */
  626. function db_prepare_bool( $p_bool ) {
  627. global $g_db;
  628. if( db_is_pgsql() ) {
  629. return $g_db->qstr( $p_bool );
  630. } else {
  631. return (int)(bool)$p_bool;
  632. }
  633. }
  634. /**
  635. * return current time as Unix timestamp
  636. * @return integer Unix timestamp of the current date and time
  637. */
  638. function db_now() {
  639. return time();
  640. }
  641. /**
  642. * convert minutes to a time format [h]h:mm
  643. * @param integer $p_min Integer representing number of minutes.
  644. * @return string representing formatted duration string in hh:mm format.
  645. */
  646. function db_minutes_to_hhmm( $p_min = 0 ) {
  647. return sprintf( '%02d:%02d', $p_min / 60, $p_min % 60 );
  648. }
  649. /**
  650. * A helper function that generates a case-sensitive or case-insensitive like phrase based on the current db type.
  651. * The field name and value are assumed to be safe to insert in a query (i.e. already cleaned).
  652. * @param string $p_field_name The name of the field to filter on.
  653. * @param boolean $p_case_sensitive True: case sensitive, false: case insensitive.
  654. * @return string returns (field LIKE 'value') OR (field ILIKE 'value')
  655. */
  656. function db_helper_like( $p_field_name, $p_case_sensitive = false ) {
  657. $t_like_keyword = ' LIKE ';
  658. if( $p_case_sensitive === false ) {
  659. if( db_is_pgsql() ) {
  660. $t_like_keyword = ' ILIKE ';
  661. }
  662. }
  663. return '(' . $p_field_name . $t_like_keyword . db_param() . ')';
  664. }
  665. /**
  666. * Compare two dates against a certain number of days
  667. * 'val_or_col' parameters will be used "as is" in the query component,
  668. * allowing use of a column name. To compare against a specific date,
  669. * it is recommended to pass db_param() instead of a date constant.
  670. * @param string $p_val_or_col_1 Value or Column to compare.
  671. * @param string $p_operator SQL comparison operator.
  672. * @param string $p_val_or_col_2 Value or Column to compare.
  673. * @param integer $p_num_secs Number of seconds to compare against
  674. * @return string Database query component to compare dates
  675. * @todo Check if there is a way to do that using ADODB rather than implementing it here.
  676. */
  677. function db_helper_compare_time( $p_val_or_col_1, $p_operator, $p_val_or_col_2, $p_num_secs ) {
  678. if( $p_num_secs == 0 ) {
  679. return "($p_val_or_col_1 $p_operator $p_val_or_col_2)";
  680. } elseif( $p_num_secs > 0 ) {
  681. return "($p_val_or_col_1 $p_operator $p_val_or_col_2 + $p_num_secs)";
  682. } else {
  683. # Invert comparison to avoid issues with unsigned integers on MySQL
  684. return "($p_val_or_col_1 - $p_num_secs $p_operator $p_val_or_col_2)";
  685. }
  686. }
  687. /**
  688. * count queries
  689. * @return integer
  690. */
  691. function db_count_queries() {
  692. global $g_queries_array;
  693. return count( $g_queries_array );
  694. }
  695. /**
  696. * count unique queries
  697. * @return integer
  698. */
  699. function db_count_unique_queries() {
  700. global $g_queries_array;
  701. $t_unique_queries = 0;
  702. $t_shown_queries = array();
  703. foreach( $g_queries_array as $t_val_array ) {
  704. if( !in_array( $t_val_array[0], $t_shown_queries ) ) {
  705. $t_unique_queries++;
  706. array_push( $t_shown_queries, $t_val_array[0] );
  707. }
  708. }
  709. return $t_unique_queries;
  710. }
  711. /**
  712. * get total time for queries
  713. * @return integer
  714. */
  715. function db_time_queries() {
  716. global $g_queries_array;
  717. $t_count = count( $g_queries_array );
  718. $t_total = 0;
  719. for( $i = 0;$i < $t_count;$i++ ) {
  720. $t_total += $g_queries_array[$i][1];
  721. }
  722. return $t_total;
  723. }
  724. /**
  725. * get database table name
  726. *
  727. * @param string $p_name Can either be specified as 'XXX' (e.g. 'bug'), or
  728. * using the legacy style 'mantis_XXX_table'; in the
  729. * latter case, a deprecation warning will be issued.
  730. * @return string containing full database table name (with prefix and suffix)
  731. */
  732. function db_get_table( $p_name ) {
  733. if( preg_match( '/^mantis_(.*)_table$/', $p_name, $t_matches ) ) {
  734. $t_table = $t_matches[1];
  735. error_parameters(
  736. __FUNCTION__ . "( '$p_name' )",
  737. __FUNCTION__ . "( '$t_table' )"
  738. );
  739. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  740. } else {
  741. $t_table = $p_name;
  742. }
  743. # Determine table prefix including trailing '_'
  744. $t_prefix = trim( config_get_global( 'db_table_prefix' ) );
  745. if( !empty( $t_prefix ) && '_' != substr( $t_prefix, -1 ) ) {
  746. $t_prefix .= '_';
  747. }
  748. # Determine table suffix including leading '_'
  749. $t_suffix = trim( config_get_global( 'db_table_suffix' ) );
  750. if( !empty( $t_suffix ) && '_' != substr( $t_suffix, 0, 1 ) ) {
  751. $t_suffix = '_' . $t_suffix;
  752. }
  753. # Physical table name
  754. $t_table = $t_prefix . $t_table . $t_suffix;
  755. db_check_identifier_size( $t_table );
  756. return $t_table;
  757. }
  758. /**
  759. * get list database tables
  760. * @return array containing table names
  761. */
  762. function db_get_table_list() {
  763. global $g_db;
  764. $t_tables = $g_db->MetaTables( 'TABLE' );
  765. return $t_tables;
  766. }
  767. /**
  768. * Updates a BLOB column
  769. *
  770. * This function is only needed for oci8; it will do nothing and return
  771. * false if used with another RDBMS.
  772. *
  773. * @param string $p_table Table name.
  774. * @param string $p_column The BLOB column to update.
  775. * @param string $p_val Data to store into the BLOB.
  776. * @param string $p_where Where clause to identify which record to update
  777. * if null, defaults to the last record inserted in $p_table.
  778. * @return boolean
  779. */
  780. function db_update_blob( $p_table, $p_column, $p_val, $p_where = null ) {
  781. global $g_db, $g_db_log_queries, $g_queries_array;
  782. if( !db_is_oracle() ) {
  783. return false;
  784. }
  785. if( null == $p_where ) {
  786. $p_where = 'id=' . db_insert_id( $p_table );
  787. }
  788. if( ON == $g_db_log_queries ) {
  789. $t_start = microtime( true );
  790. $t_backtrace = debug_backtrace();
  791. $t_caller = basename( $t_backtrace[0]['file'] );
  792. $t_caller .= ':' . $t_backtrace[0]['line'];
  793. # Is this called from another function?
  794. if( isset( $t_backtrace[1] ) ) {
  795. $t_caller .= ' ' . $t_backtrace[1]['function'] . '()';
  796. } else {
  797. # or from a script directly?
  798. $t_caller .= ' ' . $_SERVER['SCRIPT_NAME'];
  799. }
  800. }
  801. $t_result = $g_db->UpdateBlob( $p_table, $p_column, $p_val, $p_where );
  802. if( $g_db_log_queries ) {
  803. $t_elapsed = number_format( microtime( true ) - $t_start, 4 );
  804. $t_log_data = array(
  805. 'Update BLOB in ' . $p_table . '.' . $p_column . ' where ' . $p_where,
  806. $t_elapsed,
  807. $t_caller
  808. );
  809. log_event( LOG_DATABASE, var_export( $t_log_data, true ) );
  810. array_push( $g_queries_array, $t_log_data );
  811. }
  812. if( !$t_result ) {
  813. db_error();
  814. trigger_error( ERROR_DB_QUERY_FAILED, ERROR );
  815. return false;
  816. }
  817. return $t_result;
  818. }
  819. /**
  820. * Sorts bind variable numbers and puts them in sequential order
  821. * e.g. input: "... WHERE F1=:12 and F2=:97 ",
  822. * output: "... WHERE F1=:0 and F2=:1 ".
  823. * Used in db_oracle_adapt_query_syntax().
  824. * @param string $p_query Query string to sort.
  825. * @return string Query string with sorted bind variable numbers.
  826. */
  827. function db_oracle_order_binds_sequentially( $p_query ) {
  828. $t_new_query= '';
  829. $t_is_odd = true;
  830. $t_after_quote = false;
  831. $t_iter = 0;
  832. # Divide statement to skip processing string literals
  833. $t_p_query_arr = explode( '\'', $p_query );
  834. foreach( $t_p_query_arr as $t_p_query_part ) {
  835. if( $t_new_query != '' ) {
  836. $t_new_query .= '\'';
  837. }
  838. if( $t_is_odd ) {
  839. # Divide to process all bindvars
  840. $t_p_query_subpart_arr = explode( ':', $t_p_query_part );
  841. if( count( $t_p_query_subpart_arr ) > 1 ) {
  842. foreach( $t_p_query_subpart_arr as $t_p_query_subpart ) {
  843. if( ( !$t_after_quote ) && ( $t_new_query != '' ) ) {
  844. $t_new_query .= ':' . preg_replace( '/^(\d+?)/U', strval( $t_iter ), $t_p_query_subpart );
  845. $t_iter++;
  846. } else {
  847. $t_new_query .= $t_p_query_subpart;
  848. }
  849. $t_after_quote = false;
  850. }
  851. } else {
  852. $t_new_query .= $t_p_query_part;
  853. }
  854. $t_is_odd = false;
  855. } else {
  856. $t_after_quote = true;
  857. $t_new_query .= $t_p_query_part;
  858. $t_is_odd = true;
  859. }
  860. }
  861. return $t_new_query;
  862. }
  863. /**
  864. * Adapt input query string and bindvars array to Oracle DB syntax:
  865. * 1. Change bind vars id's to sequence beginning with 0
  866. * (calls db_oracle_order_binds_sequentially() )
  867. * 2. Remove "AS" keyword, because it is not supported with table aliasing
  868. * 3. Remove null bind variables in insert statements for default values support
  869. * 4. Replace "tab.column=:bind" to "tab.column IS NULL" when :bind is empty string
  870. * 5. Replace "SET tab.column=:bind" to "SET tab.column=DEFAULT" when :bind is empty string
  871. * @param string $p_query Query string to sort.
  872. * @param array &$p_arr_parms Array of parameters matching $p_query, function sorts array keys.
  873. * @return string Query string with sorted bind variable numbers.
  874. */
  875. function db_oracle_adapt_query_syntax( $p_query, array &$p_arr_parms = null ) {
  876. # Remove "AS" keyword, because not supported with table aliasing
  877. # - Do not remove text literal within "'" quotes
  878. # - Will remove all "AS", except when it's part of a "CAST(x AS y)" expression
  879. # To do so, we will assume that the "AS" following a "CAST", is safe to be kept.
  880. # Using a counter for "CAST" appearances to allow nesting: CAST(CAST(x AS y) AS z)
  881. # split the string by the relevant delimiters. The delimiters will be part of the split array
  882. $t_parts = preg_split("/(')|( AS )|(CAST\s*\()/mi", $p_query, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  883. $t_is_literal = false;
  884. $t_cast = 0;
  885. $t_query = '';
  886. foreach( $t_parts as $t_part ) {
  887. # if quotes, switch literal flag
  888. if( $t_part == '\'' ) {
  889. $t_is_literal = !$t_is_literal;
  890. $t_query .= $t_part;
  891. continue;
  892. }
  893. # if this part is litereal, do not change
  894. if( $t_is_literal ) {
  895. $t_query .= $t_part;
  896. continue;
  897. } else {
  898. # if there is "CAST" delimiter, flag the counter
  899. if( preg_match( '/^CAST\s*\($/i', $t_part ) ) {
  900. $t_cast++;
  901. $t_query .= $t_part;
  902. continue;
  903. }
  904. # if there is "AS"
  905. if( strcasecmp( $t_part, ' AS ' ) == 0 ) {
  906. # if there's a previous CAST, keep the AS
  907. if( $t_cast > 0 ) {
  908. $t_cast--;
  909. $t_query .= $t_part;
  910. } else {
  911. # otherwise, remove the " AS ", replace by a space
  912. $t_query .= ' ';
  913. }
  914. continue;
  915. }
  916. $t_query .= $t_part;
  917. continue;
  918. }
  919. }
  920. $p_query = $t_query;
  921. # Remove null bind variables in insert statements for default values support
  922. if( is_array( $p_arr_parms ) ) {
  923. preg_match( '/^[\s\n\r]*insert[\s\n\r]+(into){0,1}[\s\n\r]+(?P<table>[a-z0-9_]+)[\s\n\r]*\([\s\n\r]*[\s\n\r]*(?P<fields>[a-z0-9_,\s\n\r]+)[\s\n\r]*\)[\s\n\r]*values[\s\n\r]*\([\s\n\r]*(?P<values>[:a-z0-9_,\s\n\r]+)\)/i', $p_query, $t_matches );
  924. if( isset( $t_matches['values'] ) ) { #if statement is a INSERT INTO ... (...) VALUES(...)
  925. # iterates non-empty bind variables
  926. $i = 0;
  927. $t_fields_left = $t_matches['fields'];
  928. $t_values_left = $t_matches['values'];
  929. for( $t_arr_index = 0; $t_arr_index < count( $p_arr_parms ); $t_arr_index++ ) {
  930. # inserting fieldname search
  931. if( preg_match( '/^[\s\n\r]*([a-z0-9_]+)[\s\n\r]*,{0,1}([\d\D]*)\z/i', $t_fields_left, $t_fieldmatch ) ) {
  932. $t_fields_left = $t_fieldmatch[2];
  933. $t_fields_arr[$i] = $t_fieldmatch[1];
  934. }
  935. # inserting bindvar name search
  936. if( preg_match( '/^[\s\n\r]*(:[a-z0-9_]+)[\s\n\r]*,{0,1}([\d\D]*)\z/i', $t_values_left, $t_valuematch ) ) {
  937. $t_values_left = $t_valuematch[2];
  938. $t_values_arr[$i] = $t_valuematch[1];
  939. }
  940. # skip unsetting if bind array value not empty
  941. if( $p_arr_parms[$t_arr_index] !== '' ) {
  942. $i++;
  943. } else {
  944. $t_arr_index--;
  945. # Shift array and unset bind array element
  946. for( $n = $i + 1; $n < count( $p_arr_parms ); $n++ ) {
  947. $p_arr_parms[$n-1] = $p_arr_parms[$n];
  948. }
  949. unset( $t_fields_arr[$i] );
  950. unset( $t_values_arr[$i] );
  951. unset( $p_arr_parms[count( $p_arr_parms ) - 1] );
  952. }
  953. }
  954. # Combine statement from arrays
  955. $p_query = 'INSERT INTO ' . $t_matches['table'] . ' (' . $t_fields_arr[0];
  956. for( $i = 1; $i < count( $p_arr_parms ); $i++ ) {
  957. $p_query = $p_query . ', ' . $t_fields_arr[$i];
  958. }
  959. $p_query = $p_query . ') values (' . $t_values_arr[0];
  960. for( $i = 1; $i < count( $p_arr_parms ); $i++ ) {
  961. $p_query = $p_query . ', ' . $t_values_arr[$i];
  962. }
  963. $p_query = $p_query . ')';
  964. } else {
  965. # if input statement is NOT a INSERT INTO (...) VALUES(...)
  966. # "IS NULL" adoptation here
  967. $t_set_where_template_str = substr( md5( uniqid( rand(), true ) ), 0, 50 );
  968. $t_removed_set_where = '';
  969. # Need to order parameter array element correctly
  970. $p_query = db_oracle_order_binds_sequentially( $p_query );
  971. # Find and remove temporarily "SET var1=:bind1, var2=:bind2 WHERE" part
  972. preg_match( '/^(?P<before_set_where>.*)(?P<set_where>[\s\n\r]*set[\s\n\r]+[\s\n\ra-z0-9_\.=,:\']+)(?P<after_set_where>where[\d\D]*)$/i', $p_query, $t_matches );
  973. $t_set_where_stmt = isset( $t_matches['after_set_where'] );
  974. if( $t_set_where_stmt ) {
  975. $t_removed_set_where = $t_matches['set_where'];
  976. # Now work with statement without "SET ... WHERE" part
  977. $t_templated_query = $t_matches['before_set_where'] . $t_set_where_template_str . $t_matches['after_set_where'];
  978. } else {
  979. $t_templated_query = $p_query;
  980. }
  981. # Replace "var1=''" by "var1 IS NULL"
  982. while( preg_match( '/^(?P<before_empty_literal>[\d\D]*[\s\n\r(]+([a-z0-9_]*[\s\n\r]*\.){0,1}[\s\n\r]*[a-z0-9_]+)[\s\n\r]*=[\s\n\r]*\'\'(?P<after_empty_literal>[\s\n\r]*[\d\D]*\z)/i', $t_templated_query, $t_matches ) > 0 ) {
  983. $t_templated_query = $t_matches['before_empty_literal'] . ' IS NULL ' . $t_matches['after_empty_literal'];
  984. }
  985. # Replace "var1!=''" and "var1<>''" by "var1 IS NOT NULL"
  986. while( preg_match( '/^(?P<before_empty_literal>[\d\D]*[\s\n\r(]+([a-z0-9_]*[\s\n\r]*\.){0,1}[\s\n\r]*[a-z0-9_]+)[\s\n\r]*(![\s\n\r]*=|<[\s\n\r]*>)[\s\n\r]*\'\'(?P<after_empty_literal>[\s\n\r]*[\d\D]*\z)/i', $t_templated_query, $t_matches ) > 0 ) {
  987. $t_templated_query = $t_matches['before_empty_literal'] . ' IS NOT NULL ' . $t_matches['after_empty_literal'];
  988. }
  989. $p_query = $t_templated_query;
  990. # Process input bind variable array to replace "WHERE fld=:12"
  991. # by "WHERE fld IS NULL" if :12 is empty
  992. while( preg_match( '/^(?P<before_var>[\d\D]*[\s\n\r(]+)(?P<var_name>([a-z0-9_]*[\s\n\r]*\.){0,1}[\s\n\r]*[a-z0-9_]+)(?P<dividers>[\s\n\r]*=[\s\n\r]*:)(?P<bind_name>[0-9]+)(?P<after_var>[\s\n\r]*[\d\D]*\z)/i', $t_templated_query, $t_matches ) > 0 ) {
  993. $t_bind_num = $t_matches['bind_name'];
  994. $t_search_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] . $t_matches['after_var'];
  995. $t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . '=:' . $t_matches['bind_name']. $t_matches['after_var'];
  996. if( $p_arr_parms[$t_bind_num] === '' ) {
  997. for( $n = $t_bind_num + 1; $n < count( $p_arr_parms ); $n++ ) {
  998. $p_arr_parms[$n - 1] = $p_arr_parms[$n];
  999. }
  1000. unset( $p_arr_parms[count( $p_arr_parms ) - 1] );
  1001. $t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . ' IS NULL ' . $t_matches['after_var'];
  1002. }
  1003. $p_query = str_replace( $t_search_substr, $t_replace_substr, $p_query );
  1004. $t_templated_query = $t_matches['before_var'] . $t_matches['after_var'];
  1005. }
  1006. if( $t_set_where_stmt ) {
  1007. # Put temporarily removed "SET ... WHERE" part back
  1008. $p_query = str_replace( $t_set_where_template_str, $t_removed_set_where, $p_query );
  1009. # Need to order parameter array element correctly
  1010. $p_query = db_oracle_order_binds_sequentially( $p_query );
  1011. # Find and remove temporary "SET var1=:bind1, var2=:bind2 WHERE" part again
  1012. preg_match( '/^(?P<before_set_where>.*)(?P<set_where>[\s\n\r]*set[\s\n\r]+[\s\n\ra-z0-9_\.=,:\']+)(?P<after_set_where>where[\d\D]*)$/i', $p_query, $t_matches );
  1013. $t_removed_set_where = $t_matches['set_where'];
  1014. $p_query = $t_matches['before_set_where'] . $t_set_where_template_str . $t_matches['after_set_where'];
  1015. #Replace "SET fld1=:1" to "SET fld1=DEFAULT" if bind array value is empty
  1016. $t_removed_set_where_parsing = $t_removed_set_where;
  1017. while( preg_match( '/^(?P<before_var>[\d\D]*[\s\n\r,]+)(?P<var_name>([a-z0-9_]*[\s\n\r]*\.){0,1}[\s\n\r]*[a-z0-9_]+)(?P<dividers>[\s\n\r]*=[\s\n\r]*:)(?P<bind_name>[0-9]+)(?P<after_var>[,\s\n\r]*[\d\D]*\z)/i', $t_removed_set_where_parsing, $t_matches ) > 0 ) {
  1018. $t_bind_num = $t_matches['bind_name'];
  1019. $t_search_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] ;
  1020. $t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] ;
  1021. if( $p_arr_parms[$t_bind_num] === '' ) {
  1022. for( $n = $t_bind_num + 1; $n < count( $p_arr_parms ); $n++ ) {
  1023. $p_arr_parms[$n - 1] = $p_arr_parms[$n];
  1024. }
  1025. unset( $p_arr_parms[count( $p_arr_parms ) - 1] );
  1026. $t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . '=DEFAULT ';
  1027. }
  1028. $t_removed_set_where = str_replace( $t_search_substr, $t_replace_substr, $t_removed_set_where );
  1029. $t_removed_set_where_parsing = $t_matches['before_var'] . $t_matches['after_var'];
  1030. }
  1031. $p_query = str_replace( $t_set_where_template_str, $t_removed_set_where, $p_query );
  1032. }
  1033. }
  1034. }
  1035. $p_query = db_oracle_order_binds_sequentially( $p_query );
  1036. return $p_query;
  1037. }
  1038. /**
  1039. * Replace 4-byte UTF-8 chars
  1040. * This is a workaround to avoid data getting truncated on MySQL databases
  1041. * using native utf8 encoding, which only supports 3 bytes chars (see #20431)
  1042. * @param string $p_string
  1043. * @return string
  1044. */
  1045. function db_mysql_fix_utf8( $p_string ) {
  1046. if( !db_is_mysql() ) {
  1047. return $p_string;
  1048. }
  1049. return preg_replace(
  1050. # 4-byte UTF8 chars always start with bytes 0xF0-0xF7 (0b11110xxx)
  1051. '/[\xF0-\xF7].../s',
  1052. # replace with U+FFFD to avoid potential Unicode XSS attacks,
  1053. # see http://unicode.org/reports/tr36/#Deletion_of_Noncharacters
  1054. "\xEF\xBF\xBD",
  1055. $p_string
  1056. );
  1057. }
  1058. /**
  1059. * Creates an empty record set, compatible with db_query() result
  1060. * This object can be used when a query can't be performed, or is not needed,
  1061. * and still want to return an empty result as a transparent return value.
  1062. * @return \ADORecordSet_empty
  1063. */
  1064. function db_empty_result() {
  1065. return new ADORecordSet_empty();
  1066. }
  1067. /**
  1068. * Process a query string by replacing token parameters by their bound values
  1069. * @param string $p_query Query string
  1070. * @param array $p_arr_parms Parameter array
  1071. * @return string Processed query string
  1072. */
  1073. function db_format_query_log_msg( $p_query, array $p_arr_parms ) {
  1074. global $g_db, $g_db_functional_type;
  1075. $t_lastoffset = 0;
  1076. $i = 0;
  1077. if( !empty( $p_arr_parms ) ) {
  1078. # For mysql, tokens are '?', and parameters are bound sequentially
  1079. # For pgsql, tokens are '$number', and parameters are bound by the denoted
  1080. # index (1-based) in the parameter array
  1081. # For oracle, tokens are ':string', but mantis rewrites them as sequentially
  1082. # ordered, so they behave like mysql. See db_oracle_order_binds_sequentially()
  1083. $t_regex = '/(?<token>\?|\$|:)(?<index>[0-9]*)/';
  1084. while( preg_match( $t_regex , $p_query, $t_matches, PREG_OFFSET_CAPTURE, $t_lastoffset ) ) {
  1085. $t_match_param = $t_matches[0];
  1086. # Realign the offset returned by preg_match as it is byte-based,
  1087. # which causes issues with UTF-8 characters in the query string
  1088. # (e.g. from custom fields names)
  1089. $t_utf8_offset = mb_strlen( substr( $p_query, 0, $t_match_param[1] ), mb_internal_encoding() );
  1090. if( $i <= count( $p_arr_parms ) ) {
  1091. switch( $g_db_functional_type ) {
  1092. case DB_TYPE_PGSQL:
  1093. # For pgsql, the bound value is indexed by the parameter name (1-based)
  1094. $t_index = (int)$t_matches['index'][0];
  1095. $t_value = $p_arr_parms[$t_index-1];
  1096. break;
  1097. case DB_TYPE_ORACLE:
  1098. # For oracle, the value is indexed by the label
  1099. $t_index = $t_matches['index'][0];
  1100. $t_value = $p_arr_parms[$t_index];
  1101. break;
  1102. default:
  1103. # otherwise, the value is positional
  1104. $t_value = $p_arr_parms[$i];
  1105. }
  1106. if( is_null( $t_value ) ) {
  1107. $t_replace = 'NULL';
  1108. } else if( is_string( $t_value ) ) {
  1109. $t_replace = "'" . $t_value . "'";
  1110. } else if( is_integer( $t_value ) || is_float( $t_value ) ) {
  1111. $t_replace = (float)$t_value;
  1112. } else if( is_bool( $t_value ) ) {
  1113. # use the actual literal from db driver
  1114. $t_replace = $t_value ? $g_db->true : $g_db->false;
  1115. } else {
  1116. # Could not find a supported type for this parameter value.
  1117. # Skip this token, so replacing it with itself.
  1118. $t_replace = $t_match_param[0];
  1119. }
  1120. $p_query = mb_substr( $p_query, 0, $t_utf8_offset )
  1121. . $t_replace
  1122. . mb_substr( $p_query, $t_utf8_offset + mb_strlen( $t_match_param[0] ) );
  1123. $t_lastoffset = $t_match_param[1] + strlen( $t_replace ) + 1;
  1124. } else {
  1125. $t_lastoffset = $t_match_param[1] + 1;
  1126. }
  1127. $i++;
  1128. }
  1129. }
  1130. return $p_query;
  1131. }
  1132. /**
  1133. * Returns true if a specific capability is suported in the current database server,
  1134. * false otherwise.
  1135. *
  1136. * @param integer $p_capability See DB_CAPABILITY_* constants
  1137. * @return boolean True if the capability is supported, false otherwise.
  1138. */
  1139. function db_has_capability( $p_capability ) {
  1140. static $s_cache = array();
  1141. if( !isset( $s_cache[$p_capability] ) ) {
  1142. $s_cache[$p_capability] = db_test_capability( $p_capability );
  1143. }
  1144. return $s_cache[$p_capability];
  1145. }
  1146. /**
  1147. * Tests if a specific capability is suported in the current database server.
  1148. *
  1149. * @param integer $p_capability See DB_CAPABILITY_* constants
  1150. * @return boolean True if the capability is supported, false otherwise.
  1151. */
  1152. function db_test_capability( $p_capability ) {
  1153. global $g_db, $g_db_functional_type;
  1154. $t_server_info = $g_db->ServerInfo();
  1155. switch( $p_capability ) {
  1156. case DB_CAPABILITY_WINDOW_FUNCTIONS:
  1157. switch( $g_db_functional_type ) {
  1158. case DB_TYPE_ORACLE: # since 8i
  1159. case DB_TYPE_PGSQL: # since 8.4
  1160. case DB_TYPE_MSSQL: # since 2008
  1161. return true;
  1162. case DB_TYPE_MYSQL:
  1163. # mysql, since 8.0.2
  1164. if( version_compare( $t_server_info['version'], '8.0.2', '>=' )
  1165. && false !== stripos( $t_server_info['description'], 'mysql' ) ) {
  1166. return true;
  1167. }
  1168. # mariaDB, since 10.2
  1169. if( version_compare( $t_server_info['version'], '10.2', '>=' )
  1170. && false !== stripos( $t_server_info['description'], 'mariadb' ) ) {
  1171. return true;
  1172. }
  1173. # if server info cant provide enough information to identify the type,
  1174. # default to "not supported"
  1175. }
  1176. }
  1177. # if nothing was found, return false
  1178. return false;
  1179. }