%PDF- %PDF-
| Direktori : /www/loslex_o/tracker/core/classes/ |
| Current File : /www/loslex_o/tracker/core/classes/DbQuery.class.php |
<?php
# MantisBT - A PHP based bugtracking system
# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
/**
* DbQuery class.
* @copyright Copyright 2017 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
* @package MantisBT
* @subpackage classes
*
* @uses config_api.php
* @uses constant_inc.php
* @uses database_api.php
* @uses logging_api.php
*/
require_api( 'config_api.php' );
require_api( 'constant_inc.php' );
require_api( 'database_api.php' );
require_api( 'logging_api.php' );
/**
* Database Query abstraction class.
*
* Allows building a query and fetching data, providing compatible functionality
* for all supported databases. Hides the underlaying details from the ADOdb layer.
*
* Supports inline parameters with ":label" syntax, and anonymous parameters
* through the param() method.
* Supports binding arrays which will be later expanded to individual parameters.
*
* The internal processing steps for query string are:
* 1) $query_string: stores the user input string, containing parameter tokens for all
* supported formats:
* - ":{string}" For labeled parameters. Binded values are stored in $query_bind_array
* - "${int}" For anonymous parameters. Binded values are stored in $query_autobind_array
* - "${string}{int}" For special constructs, eg: $in0 for late binding IN clauses
* 2) $expanded_query_string: stores the query string after expansion of special constructs
* into standard "${int}" parameters
* 3) $db_query_string: stores the query string after all parameters have been renamed,
* reordered and converted to the specific database parameter syntax expected by ADOdb.
* Binded values are stored in $db_param_array
*
* The steps for query string processing are performed by:
* - process_expand_params(): converts query string (1) into (2)
* - process_bind_params(): converts query string(2) into (3)
* - process_sql_syntax(): modifies query string (3) performing some general and database
* specific modifications.
*
* Execution of the query is supported by:
* - db_execute(): performs the low level, actual execution from ADOdb
* - execute(): performs all query processing steps and then calls db_execute().
*/
class DbQuery {
/**
* Stores the user input for query string.
* @var string
*/
protected $query_string;
/**
* Stores the query string with "late binding" placeholders expanded into final
* sql syntax and parameters.
* @var string
*/
protected $expanded_query_string = null;
/**
* Array of values binded to labeled parameters, indexed by label names.
* @var array
*/
protected $query_bind_array;
/**
* Array of values binded to anonymous parameters, indexed by numerical index.
* @var array
*/
protected $query_autobind_array = array();
/**
* Counter for assigning numerical indexes to anonymous parameters.
* @var integer
*/
protected $query_autobind_count = 0;
/**
* Stores the final query string to be passed to ADOdb.
* @var string
*/
protected $db_query_string;
/**
* Array of values for parameters included in the final query string for ADOdb,
* indexed as needed for the ADOdb driver
* @var array
*/
protected $db_param_array;
/**
* Stores the adodb result set for the query after it has been executed, or false if the query failed.
* @var IteratorAggregate|boolean
*/
protected $db_result = null;
/**
* Stores execution time of the query (microseconds).
* @var integer
*/
protected $db_query_time = null;
/**
* Stores the latest fetched array fromthe result set.
* @var array
*/
protected $current_row = null;
/**
* Stores row limit value for query execution.
* Number of rows to be returned from the query result
* Use -1 to disable this option.
* @var integer
*/
protected $select_limit = -1;
/**
* Stores the offset value for query execution.
* The resultset starts at this position from the query result
* Use -1 to disable this option.
* @var integer
*/
protected $select_offset = -1;
/**
* Array to manage late binding for IN constructs
* @var array
*/
protected $late_binding_in_clause = array();
protected static $oracle_in_limit = 1000; # this could be a constant
/**
* Construct a new query object.
* Optional parameters are the query string, and an array of values to be
* binded to labeled parameters
* @param string $p_query_string Query string
* @param array $p_bind_array Bind values
* @return void
*/
public function __construct( $p_query_string = null, array $p_bind_array = null ) {
# Itialization
if( null === $p_query_string ) {
$this->query_string = '';
} else {
$this->query_string = $p_query_string;
}
if( null === $p_bind_array ) {
$this->query_bind_array = array();
} else {
$this->query_bind_array = $p_bind_array;
}
}
/**
* Set or replaces current query string
* @param string $p_query_string Query string
* @return void
*/
public function sql( $p_query_string ) {
$this->query_string = $p_query_string;
}
/**
* Append to current query string
* @param string $p_query_string Query string
* @return void
*/
public function append_sql( $p_query_string ) {
$this->query_string .= $p_query_string;
}
/**
* Creates a string containing a parameter that can be appended to the query string
* The provided value is binded to the parameter and stored for use at execution time
* The parameters created by this method are anonymous parameters, so they can't be
* accessed later to have values modified or rebinded.
* The format of the token created is: "$n", where "n" is an incremental integer
* @param mixed $p_value Value to bind for this parameter
* @return string Token string
*/
public function param( $p_value ) {
$t_new_id = $this->query_autobind_count++;
$this->query_autobind_array[$t_new_id] = $p_value;
$t_par = '$' . $t_new_id;
return $t_par;
}
/**
* Sets the "limit" value. This value is used for all subsequent query executions
* Use -1 to disable this option
* @param integer $p_limit Number of rows to limit
* @return void
*/
public function set_limit( $p_limit = -1 ) {
$this->select_limit = $p_limit;
}
/**
* Sets the "offset" value. This value is used for all subsequent query executions
* Use -1 to disable this option
* @param integer $p_offset Number of rows to offset
* @return void
*/
public function set_offset( $p_offset = -1 ) {
$this->select_offset = $p_offset;
}
/**
* Executes the query, performing all preprocess and binding steps.
* A bind array can provided, which will be added to current bindings.
* Limit and offset options can be provided, which will affect only to this execution.
* @param array $p_bind_array Array for binding values
* @param integer $p_limit Limit value
* @param integer $p_offset Offset value
* @return IteratorAggregate|boolean ADOdb result set or false if the query failed.
*/
public function execute( array $p_bind_array = null, $p_limit = null, $p_offset = null ) {
# For backwards compatibility with legacy code still relying on DB API,
# we need to save the parameters count before binding otherwise it will
# be reset after query execution, which will cause issues on RDBMS with
# numbered params (e.g. PostgreSQL).
db_param_push();
# bind values if provided
if( null !== $p_bind_array ) {
$this->bind_values( $p_bind_array );
}
# preprocess parameters
$this->process_expand_params();
$this->process_bind_params();
$this->process_sql_syntax();
$t_result = $this->db_execute($p_limit, $p_offset);
db_param_pop();
return $t_result;
}
/**
* Call ADOdb execution of sql string.
* At this point all preprocessing and value binding has been performed.
* @param integer $p_limit Limit value
* @param integer $p_offset Offset value
* @return IteratorAggregate|boolean ADOdb result set or false if the query failed.
*/
protected function db_execute( $p_limit = null, $p_offset = null ) {
global $g_db;
# get limit and offset
if( null !== $p_limit ) {
$t_limit = $p_limit;
} else {
$t_limit = $this->select_limit;
}
if( null !== $p_offset ) {
$t_offset = $p_offset;
} else {
$t_offset = $this->select_offset;
}
$t_start = microtime( true );
if( ( $t_limit != -1 ) || ( $t_offset != -1 ) ) {
$this->db_result = $g_db->SelectLimit( $this->db_query_string, $t_limit, $t_offset, $this->db_param_array );
} else {
$this->db_result = $g_db->Execute( $this->db_query_string, $this->db_param_array );
}
$this->db_query_time = number_format( microtime( true ) - $t_start, 4 );
$this->log_query();
if( !$this->db_result ) {
db_error( $this->db_query_string );
trigger_error( ERROR_DB_QUERY_FAILED, ERROR );
$this->db_result = false;
}
$this->current_row = null;
return $this->db_result;
}
/**
* Logs data from latest execution
* @return void
*/
protected function log_query() {
global $g_db_log_queries, $g_queries_array;
if( ON == $g_db_log_queries ) {
$t_query_text = db_format_query_log_msg( $this->db_query_string, $this->db_param_array );
log_event( LOG_DATABASE, array( $t_query_text, $this->db_query_time ) );
} else {
# If not logging the queries the actual text is not needed
$t_query_text = '';
}
array_push( $g_queries_array, array( $t_query_text, $this->db_query_time ) );
}
/**
* Bind values for labeled parameters.
* An array indexed by label names must be provided.
* @param array $p_values_array Array of values
* @return void
*/
public function bind_values( array $p_values_array ) {
$this->query_bind_array = $p_values_array + $this->query_bind_array;
}
/**
* Binds values to a labeled parameters
* When using:
* bind(string, value) binds one value to one parameter
* bind(array) is an alias for method "bind_values(array)"
* @param mixed $p_label_or_values A labeled parameter name, or array for value bindings
* @param mixed $p_value A value to bind
* @return void
*/
public function bind( $p_label_or_values, $p_value = null ) {
if( is_array( $p_label_or_values ) ) {
# is a values array
$this->bind_values( $p_label_or_values );
} else {
# is a label string
$this->query_bind_array[$p_label_or_values] = $p_value;
}
}
/**
* Performs some specific preprocessing on sql string
* - replace table names from abbreviated syntax
* - replaces boolean values to fix some db driver inconsistencies
* - process oracle syntax fixes for compatibility
* @return void
*/
protected function process_sql_syntax() {
global $g_db;
static $s_prefix;
static $s_suffix;
if( $s_prefix === null ) {
# Determine table prefix and suffixes including trailing and leading '_'
$s_prefix = trim( config_get_global( 'db_table_prefix' ) );
$s_suffix = trim( config_get_global( 'db_table_suffix' ) );
if( !empty( $s_prefix ) && '_' != substr( $s_prefix, -1 ) ) {
$s_prefix .= '_';
}
if( !empty( $s_suffix ) && '_' != substr( $s_suffix, 0, 1 ) ) {
$s_suffix = '_' . $s_suffix;
}
}
$this->db_query_string = strtr( $this->db_query_string,
array( '{' => $s_prefix, '}' => $s_suffix )
);
# check parameters for special treatment of boolean types
# use the native values provided by the ADOdb driver
foreach( $this->db_param_array as $t_key => $t_value ) {
if( $t_value === false ) {
$this->db_param_array[$t_key] = $g_db->false;
} elseif( $t_value === true ) {
$this->db_param_array[$t_key] = $g_db->true;
}
}
if( db_is_oracle() ) {
$this->process_sql_syntax_oracle();
}
}
/**
* Process current query string converting all internal parameter placeholders
* to final ADOdb parameter syntax.
* Will convert all labeled ":xxx", and anonymous "$n" parameters, and build
* a values array suitable for ADOdb.
* @param integer $p_counter_start
* @return integer Number of parameters created
*/
protected function process_bind_params( $p_counter_start = 0) {
global $g_db;
# shortcut, if no values are binded, skip parameter replacement
if( empty( $this->query_autobind_array ) && empty( $this->query_bind_array ) ) {
$this->db_query_string = $this->query_string;
$this->db_param_array = array();
}
$t_query_string = $this->expanded_query_string;
$t_new_query = '';
$t_new_binds = array();
$t_par_index = $p_counter_start;
$t_par_count = 0;
$t_parts = preg_split( '/(:[a-z0-9_]+)|(\$[0-9]+)/mi', $t_query_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
foreach( $t_parts as $t_part ) {
$t_first = substr( $t_part, 0, 1 );
if( $t_first === '$' || $t_first === ':' ) {
$t_label = substr( $t_part, 1 );
switch( $t_first ) {
case '$':
$t_value = $this->query_autobind_array[(int)$t_label];
break;
case ':':
$t_value = $this->query_bind_array[$t_label];
break;
}
if( is_array( $t_value ) ) {
$t_params_for_array = array();
foreach( $t_value as $t_array_item ) {
$t_params_for_array[] = $g_db->Param( $t_par_index );
$t_new_binds[$t_par_index] = $t_array_item;
$t_par_count++;
$t_par_index++;
}
$t_new_query .= '(' . implode( ',', $t_params_for_array ) . ')';
} elseif( $t_value instanceof DbQuery ) {
# preprocess subquery object
$t_value->process_expand_params();
$t_sub_params = $t_value->process_bind_params( $t_par_index );
$t_par_index += $t_sub_params;
$t_par_count += $t_sub_params;
# append subquery
$t_new_binds = $t_new_binds + $t_value->db_param_array;
$t_new_query .= '(' . $t_value->db_query_string . ')';
} else {
$t_new_query .= $g_db->Param( $t_par_index );
$t_new_binds[$t_par_index] = $t_value;
$t_par_count++;
$t_par_index++;
}
continue;
}
# default
$t_new_query .= $t_part;
}
$this->db_query_string = $t_new_query;
$this->db_param_array = $t_new_binds;
return $t_par_count;
}
/**
* Builds an alternative IN expression to work around Oracle limits.
* (X, field) IN ( (X,V1), (X,V2), ... ) where X is a constant value
* is equivalent to: field IN (V1,V2,..)
* @param string $p_alias
* @param array $p_values
* @return string
*/
protected function helper_in_oracle_fix( $p_alias, array $p_values ) {
$t_in_tuples = array();
foreach( $p_values as $t_value ) {
$t_in_tuples[] = '(1,' . $this->param( $t_value ) . ')';
}
$t_sql = '(1,' . $p_alias . ') IN (' . implode( ',', $t_in_tuples ) . ')';
return $t_sql;
}
/**
* Process query string to expand late binding constructs
* @return void
*/
protected function process_expand_params() {
# original query_string should not be modified to allow for rebinding
$this->expanded_query_string = $this->query_string;
if( !empty( $this->late_binding_in_clause ) ) {
$this->process_expand_params_in();
}
}
/**
* Process query string to expand late binding constructs for IN clauses
* @return void
*/
protected function process_expand_params_in() {
$t_new_query = '';
$t_parts = preg_split( '/(\$in[0-9]+)/m', $this->expanded_query_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
foreach( $t_parts as $t_part ) {
$t_is_token = substr( $t_part, 0, 3 ) === '$in';
if( $t_is_token ) {
$t_index = (int)substr( $t_part, 3 );
$t_label = $this->late_binding_in_clause[$t_index]['label'];
$t_alias = $this->late_binding_in_clause[$t_index]['alias'];
$t_values = $this->query_bind_array[$t_label];
if( count( $t_values ) > self::$oracle_in_limit ) {
$t_new_query .= $this->helper_in_oracle_fix( $t_alias, $t_values );
} elseif( count( $t_values ) == 1 ) {
$t_new_query .= $t_alias . ' = ' . $this->param( reset( $t_values ) );
} else {
$t_new_query .= $t_alias . ' IN ' . $this->param( $t_values );
}
continue;
}
$t_new_query .= $t_part;
}
$this->expanded_query_string = $t_new_query;
}
/**
* Creates a string construction for an IN expression, providing:
* - alias: is the name of the column as a valid identifier in the final sql query
* - a label, or an array of values
* If a label is provided, the values must be binded as separate calls to binding methods.
*
* The returned string would replace the sql part for: "alias IN (x,x,x)"
*
* To work around Oracle limit of 1000 elements in IN clauses, using a label
* is recommended in situations that this number may be reached.
* This construct will then automatically deal with a compatible syntax fix.
* Using a label in this scenario is needed because the fix must be implemented
* with a late binding of the array at execution time (this also allows correctly
* rebinding the elements for the IN clause)
*
* @param string $p_alias A valid sql column identifier
* @param mixed $p_label_or_values Label or values array
* @return string Constructed string to be added to query
*/
public function sql_in( $p_alias, $p_label_or_values ) {
if( is_array( $p_label_or_values ) ) {
if( count( $p_label_or_values ) > self::$oracle_in_limit ) {
$t_sql = $this->helper_in_oracle_fix( $p_alias, $p_label_or_values );
} elseif( count( $p_label_or_values ) == 1 ) {
$t_sql = $p_alias . ' = ' . $this->param( reset( $p_label_or_values ) );
} else {
$t_sql = $p_alias . ' IN ' . $this->param( $p_label_or_values );
}
} else {
# is a label
# create placeholder for late binding
$t_new_index = count( $this->late_binding_in_clause );
$this->late_binding_in_clause[$t_new_index] = array();
$this->late_binding_in_clause[$t_new_index]['alias'] = $p_alias;
$this->late_binding_in_clause[$t_new_index]['label'] = $p_label_or_values;
$t_sql = '$in' . $t_new_index;
}
return $t_sql;
}
protected function process_sql_syntax_oracle() {
# Remove "AS" keyword, because not supported with table aliasing
# - Do not remove text literal within "'" quotes
# - Will remove all "AS", except when it's part of a "CAST(x AS y)" expression
# To do so, we will assume that the "AS" following a "CAST", is safe to be kept.
# Using a counter for "CAST" appearances to allow nesting: CAST(CAST(x AS y) AS z)
$t_query = $this->db_query_string;
# split the string by the relevant delimiters. The delimiters will be part of the split array
$t_parts = preg_split("/(')|( AS )|(CAST\s*\()/mi", $t_query, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$t_is_literal = false;
$t_cast = 0;
$t_new_query = '';
foreach( $t_parts as $t_part ) {
# if quotes, switch literal flag
if( $t_part == '\'' ) {
$t_is_literal = !$t_is_literal;
$t_new_query .= $t_part;
continue;
}
# if this part is litereal, do not change
if( $t_is_literal ) {
$t_new_query .= $t_part;
continue;
} else {
# if there is "CAST" delimiter, flag the counter
if( preg_match( '/^CAST\s*\($/i', $t_part ) ) {
$t_cast++;
$t_new_query .= $t_part;
continue;
}
# if there is "AS"
if( strcasecmp( $t_part, ' AS ' ) == 0 ) {
# if there's a previous CAST, keep the AS
if( $t_cast > 0 ) {
$t_cast--;
$t_new_query .= $t_part;
} else {
# otherwise, remove the " AS ", replace by a space
$t_new_query .= ' ';
}
continue;
}
$t_new_query .= $t_part;
continue;
}
}
$t_query = $t_new_query;
# Remove null bind variables in insert statements for default values support
if( is_array( $this->db_param_array ) ) {
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', $t_query, $t_matches );
if( isset( $t_matches['values'] ) ) { #if statement is a INSERT INTO ... (...) VALUES(...)
# iterates non-empty bind variables
$i = 0;
$t_fields_left = $t_matches['fields'];
$t_values_left = $t_matches['values'];
//for( $t_arr_index = 0; $t_arr_index < count( $this->db_param_array ); $t_arr_index++ ) {
foreach( $this->db_param_array as $t_arr_index => $t_arr_value ) {
# inserting fieldname search
if( preg_match( '/^[\s\n\r]*([a-z0-9_]+)[\s\n\r]*,{0,1}([\d\D]*)\z/i', $t_fields_left, $t_fieldmatch ) ) {
$t_fields_left = $t_fieldmatch[2];
$t_fields_arr[$i] = $t_fieldmatch[1];
}
# inserting bindvar name search
if( preg_match( '/^[\s\n\r]*(:[a-z0-9_]+)[\s\n\r]*,{0,1}([\d\D]*)\z/i', $t_values_left, $t_valuematch ) ) {
$t_values_left = $t_valuematch[2];
$t_values_arr[$i] = $t_valuematch[1];
}
# skip unsetting if bind array value not empty
//if( $this->db_param_array[$t_arr_index] !== '' ) {
if( $t_arr_value !== '' ) {
$i++;
} else {
unset( $t_fields_arr[$i] );
unset( $t_values_arr[$i] );
unset( $this->db_param_array[$t_arr_index] );
/*
$t_arr_index--;
# Shift array and unset bind array element
for( $n = $i + 1; $n < count( $this->db_param_array ); $n++ ) {
$this->db_param_array[$n-1] = $this->db_param_array[$n];
}
unset( $t_fields_arr[$i] );
unset( $t_values_arr[$i] );
unset( $this->db_param_array[count( $this->db_param_array ) - 1] );
*/
}
}
# Combine statement from arrays
$t_query = 'INSERT INTO ' . $t_matches['table'] . ' (' . implode( ',', $t_fields_arr ) . ')'
. ' VALUES (' . implode( ',', $t_values_arr ) . ')';
/*
$t_query = 'INSERT INTO ' . $t_matches['table'] . ' (' . $t_fields_arr[0];
for( $i = 1; $i < count( $this->db_param_array ); $i++ ) {
$t_query = $t_query . ', ' . $t_fields_arr[$i];
}
$t_query = $t_query . ') values (' . $t_values_arr[0];
for( $i = 1; $i < count( $this->db_param_array ); $i++ ) {
$t_query = $t_query . ', ' . $t_values_arr[$i];
}
$t_query = $t_query . ')';
*/
} else {
# if input statement is NOT a INSERT INTO (...) VALUES(...)
# "IS NULL" adoptation here
$t_set_where_template_str = substr( md5( uniqid( rand(), true ) ), 0, 50 );
$t_removed_set_where = '';
# Find and remove temporarily "SET var1=:bind1, var2=:bind2 WHERE" part
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', $t_query, $t_matches );
$t_set_where_stmt = isset( $t_matches['after_set_where'] );
if( $t_set_where_stmt ) {
$t_removed_set_where = $t_matches['set_where'];
# Now work with statement without "SET ... WHERE" part
$t_templated_query = $t_matches['before_set_where'] . $t_set_where_template_str . $t_matches['after_set_where'];
} else {
$t_templated_query = $t_query;
}
# Replace "var1=''" by "var1 IS NULL"
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 ) {
$t_templated_query = $t_matches['before_empty_literal'] . ' IS NULL ' . $t_matches['after_empty_literal'];
}
# Replace "var1!=''" and "var1<>''" by "var1 IS NOT NULL"
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 ) {
$t_templated_query = $t_matches['before_empty_literal'] . ' IS NOT NULL ' . $t_matches['after_empty_literal'];
}
$t_query = $t_templated_query;
# Process input bind variable array to replace "WHERE fld=:12"
# by "WHERE fld IS NULL" if :12 is empty
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 ) {
$t_bind_num = $t_matches['bind_name'];
$t_search_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] . $t_matches['after_var'];
$t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . '=:' . $t_matches['bind_name']. $t_matches['after_var'];
if( $this->db_param_array[$t_bind_num] === '' ) {
unset( $this->db_param_array[$t_bind_num] );
/*
for( $n = $t_bind_num + 1; $n < count( $this->db_param_array ); $n++ ) {
$this->db_param_array[$n - 1] = $this->db_param_array[$n];
}
unset( $this->db_param_array[count( $this->db_param_array ) - 1] );
*/
$t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . ' IS NULL ' . $t_matches['after_var'];
}
$t_query = str_replace( $t_search_substr, $t_replace_substr, $t_query );
$t_templated_query = $t_matches['before_var'] . $t_matches['after_var'];
}
if( $t_set_where_stmt ) {
# Put temporarily removed "SET ... WHERE" part back
$t_query = str_replace( $t_set_where_template_str, $t_removed_set_where, $t_query );
# Find and remove temporary "SET var1=:bind1, var2=:bind2 WHERE" part again
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', $t_query, $t_matches );
$t_removed_set_where = $t_matches['set_where'];
$t_query = $t_matches['before_set_where'] . $t_set_where_template_str . $t_matches['after_set_where'];
#Replace "SET fld1=:1" to "SET fld1=DEFAULT" if bind array value is empty
$t_removed_set_where_parsing = $t_removed_set_where;
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 ) {
$t_bind_num = $t_matches['bind_name'];
$t_search_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] ;
$t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . $t_matches['dividers'] . $t_matches['bind_name'] ;
if( $this->db_param_array[$t_bind_num] === '' ) {
unset( $this->db_param_array[$t_bind_num] );
/*
for( $n = $t_bind_num + 1; $n < count( $this->db_param_array ); $n++ ) {
$this->db_param_array[$n - 1] = $this->db_param_array[$n];
}
unset( $this->db_param_array[count( $this->db_param_array ) - 1] );
*/
$t_replace_substr = $t_matches['before_var'] . $t_matches['var_name'] . '=DEFAULT ';
}
$t_removed_set_where = str_replace( $t_search_substr, $t_replace_substr, $t_removed_set_where );
$t_removed_set_where_parsing = $t_matches['before_var'] . $t_matches['after_var'];
}
$t_query = str_replace( $t_set_where_template_str, $t_removed_set_where, $t_query );
}
}
}
$this->db_query_string = $t_query;
}
/**
* Compatibility method to support execution of legacy query syntax through db_query(...)
* @param string $p_query Query string
* @param array $p_arr_parms Values array for parameters
* @param integer $p_limit Query limit
* @param integer $p_offset Query offset
* @param boolean $p_pop_param Set to false to leave the parameters on the stack
* @return IteratorAggregate|boolean ADOdb result set or false if the query failed
*/
public static function compat_db_query( $p_query, array $p_arr_parms = null, $p_limit = -1, $p_offset = -1, $p_pop_param = true ) {
global $g_db_param;
if( !is_array( $p_arr_parms ) ) {
$p_arr_parms = array();
}
$t_query = new DbQuery();
$t_query->db_query_string = $p_query;
$t_query->db_param_array = $p_arr_parms;
$t_query->process_sql_syntax();
# Pushing params to safeguard the ADOdb parameter count (required for pgsql)
$g_db_param->push();
$t_query->db_execute( $p_limit, $p_offset );
# Restore ADOdb parameter count
$g_db_param->pop();
if( $p_pop_param && !empty( $p_arr_parms ) ) {
$g_db_param->pop();
}
return $t_query->db_result;
}
/**
* Returns next row of values from current resultset, or false if empty or
* the pointer has reached the end.
* This method will execute current query if it hasn't been executed yet.
* @return array|boolean Next row from result
*/
public function fetch() {
if( null === $this->db_result ) {
$this->execute();
}
if( !$this->db_result ) {
return false;
}
$this->current_row = db_fetch_array( $this->db_result );
return $this->current_row;
}
/**
* Returns all rows as an array
* @return array|boolean Array with all rows from the result, false if result is empty.
*/
public function fetch_all() {
if( null === $this->db_result ) {
$this->execute();
}
if( !$this->db_result ) {
return false;
}
$t_all_rows = array();
while( $t_row = db_fetch_array( $this->db_result ) ) {
$t_all_rows[] = $t_row;
}
return $t_all_rows;
}
/**
* Returns one value from current row from resultset
* Provided parameter can be a name of the column referenced in the query,
* or a numerical index (zero-based)
* Without column parameters,first column value will be returned.
*
* Current row is the latest one fetched, if none was fetched previously, an
* automatic fetch() is performed for first row
*
* @param integer|string $p_index_or_name Column name or numeric index
* @return string|boolean Value, or false if end of result or index is not valid
*/
public function value( $p_index_or_name = 0) {
if( !$this->current_row ) {
$this->fetch();
}
if( is_numeric( $p_index_or_name ) ) {
if( $this->current_row && count( $this->current_row ) > $p_index_or_name ) {
# get the element at that numerical position
$t_keys = array_keys( $this->current_row );
$t_value = $this->current_row[$t_keys[$p_index_or_name]];
} else {
$t_value = false;
}
} else {
if( isset( $this->current_row[$p_index_or_name] ) ) {
# get the value by column name
$t_value = $this->current_row[$p_index_or_name];
} else {
$t_value = false;
}
}
return $t_value;
}
/**
* Alias for value()
* @param integer|string $p_index_or_name Column name or numeric index
*/
public function field( $p_index_or_name = 0) {
return $this->value( $p_index_or_name );
}
/**
* Creates a string construction for a case-insensitive LIKE expression
* This is an alias for sql_like() with the force_ci parameter set to true.
* @param string $p_alias A valid sql column identifier
* @param string $p_pattern Pattern string
* @param string $p_escape Escape character
* @return string Constructed string to be added to query
*/
public function sql_ilike( $p_alias, $p_pattern, $p_escape = null ) {
return $this->sql_like( $p_alias, $p_pattern, $p_escape, true );
}
/**
* Creates a string construction for a LIKE expression, providing:
* - alias: is the name of the column as a valid identifier in the final sql query
* - value: is the string used as pattern for the like expression.
* - escape: optionally, a character used as escape character in the pattern string
* Optionally, the expression can be forced to be case insensitive, otherwise the default
* behaviour from the database is used.
*
* The returned string would replace the sql part for: "alias LIKE 'xxx'"
*
* For portability reasons, the supported wildcards are '%' and '_'. Other special tokens
* are automatically escaped:
* - [] syntax in mssql are treated as literal characters.
* - \ as default escape char in mysql is treated as a literal character.
* Note that the pattern string uses c-style escaping, so a "\" character must be written as "\\"
*
* The pattern string must be prepared by the caller, with proper wildcards and character escaping.
*
* @param string $p_alias A valid sql column identifier
* @param string $p_pattern Pattern string
* @param string $p_escape Escape character
* @param boolean $p_force_ci If true, force a case-insensitive expression
* @return string Constructed string to be added to query
*/
public function sql_like( $p_alias, $p_pattern, $p_escape = null, $p_force_ci = false ) {
# for mssql replace "[" as this is a special non portable token
if( db_is_mssql() && strpos( $p_pattern, '[' ) !== false ) {
if( null === $p_escape = null ) {
$p_escape = '\\';
}
$p_pattern = str_replace( '[', $p_escape . '[', $p_pattern );
}
# for mysql replace "\\" if this char is nor already a explicit escape char
# because mysql uses \ as default escape char if ESCAPE caluse is not used
if( db_is_mysql() && $p_escape != '\\' && strpos( $p_pattern, '\\' ) !== false ) {
if( null === $p_escape = null ) {
$p_escape = '\\';
}
$p_pattern = str_replace( '\\', $p_escape . '\\', $p_pattern );
}
$t_expr = $p_alias;
$t_operator = 'LIKE';
$t_pattern = $p_pattern;
# Here we assume that by default:
# mysql, mssql: have case-insensitive collations
# pgsql, oracle: have case-sensitive collations
# Otherwise, a more complicated discovery should be implemented.
if( $p_force_ci ) {
global $g_db_functional_type;
switch( $g_db_functional_type ) {
case DB_TYPE_PGSQL:
$t_operator = 'ILIKE';
break;
case DB_TYPE_ORACLE:
$t_expr = 'upper(' . $t_expr . ')';
$t_pattern = strtoupper( $t_pattern );
break;
}
}
$t_sql = $t_expr . ' ' . $t_operator . ' ' . $this->param( $t_pattern );
if( null !== $p_escape ) {
$t_sql .= ' ESCAPE ' . $this->param( $p_escape );
}
return $t_sql;
}
}