Version 4.1.19
This commit is contained in:
1 parent
28bd025b35
commit
2bd5025018
2674 files changed
+233518
-77766
No files matched your search
+423
-144
@@ -2,9 +2,9 @@
|
||||
/**
|
||||
* @brief Database Class
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @package IPS Community Suite
|
||||
* @since 18 Feb 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
@@ -30,6 +30,7 @@ class _Db extends \mysqli
|
||||
const SELECT_DISTINCT = 1;
|
||||
const SELECT_SQL_CALC_FOUND_ROWS = 2;
|
||||
const SELECT_MULTIDIMENSIONAL_JOINS = 4;
|
||||
const SELECT_FROM_WRITE_SERVER = 8;
|
||||
|
||||
/**
|
||||
* INSERT/UPDATE flags
|
||||
@@ -95,7 +96,7 @@ class _Db extends \mysqli
|
||||
$identifier = ( $identifier === NULL ) ? '__MAIN' : $identifier;
|
||||
|
||||
/* Don't have an instance? */
|
||||
if( !isset( self::$multitons[ $identifier ] ) )
|
||||
if( !isset( static::$multitons[ $identifier ] ) )
|
||||
{
|
||||
/* Load the default settings if necessary */
|
||||
if( $identifier === '__MAIN' )
|
||||
@@ -103,57 +104,130 @@ class _Db extends \mysqli
|
||||
require( \IPS\ROOT_PATH . '/conf_global.php' );
|
||||
$connectionSettings = isset( $INFO ) ? $INFO : array();
|
||||
}
|
||||
|
||||
|
||||
/* Connect */
|
||||
$classname = get_called_class();
|
||||
self::$multitons[ $identifier ] = @new $classname(
|
||||
$connectionSettings['sql_host'],
|
||||
$connectionSettings['sql_user'],
|
||||
$connectionSettings['sql_pass'],
|
||||
$connectionSettings['sql_database'],
|
||||
( isset( $connectionSettings['sql_port'] ) and $connectionSettings['sql_port']) ? $connectionSettings['sql_port'] : NULL,
|
||||
( isset( $connectionSettings['sql_socket'] ) and $connectionSettings['sql_socket'] ) ? $connectionSettings['sql_socket'] : NULL
|
||||
);
|
||||
|
||||
/* If the connection failed, throw an exception */
|
||||
if( $error = mysqli_connect_error() )
|
||||
static::$multitons[ $identifier ] = static::_establishConnection(
|
||||
get_called_class(),
|
||||
array(
|
||||
'host' => $connectionSettings['sql_host'],
|
||||
'username' => $connectionSettings['sql_user'],
|
||||
'password' => $connectionSettings['sql_pass'],
|
||||
'database' => $connectionSettings['sql_database'],
|
||||
'port' => ( isset( $connectionSettings['sql_port'] ) and $connectionSettings['sql_port']) ? $connectionSettings['sql_port'] : NULL,
|
||||
'socket' => ( isset( $connectionSettings['sql_socket'] ) and $connectionSettings['sql_socket'] ) ? $connectionSettings['sql_socket'] : NULL,
|
||||
),
|
||||
isset( $connectionSettings['sql_utf8mb4'] ) and $connectionSettings['sql_utf8mb4']
|
||||
);
|
||||
|
||||
/* Set charset / collation properties */
|
||||
if ( static::$multitons[ $identifier ]->getCharset() === 'utf8mb4' )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $error, self::$multitons[ $identifier ]->connect_errno );
|
||||
static::$multitons[ $identifier ]->charset = 'utf8mb4';
|
||||
static::$multitons[ $identifier ]->collation = 'utf8mb4_unicode_ci';
|
||||
static::$multitons[ $identifier ]->binaryCollation = 'utf8mb4_bin';
|
||||
}
|
||||
|
||||
/* UTF8MB4? */
|
||||
if ( isset( $connectionSettings['sql_utf8mb4'] ) and $connectionSettings['sql_utf8mb4'] )
|
||||
else
|
||||
{
|
||||
self::$multitons[ $identifier ]->charset = 'utf8mb4';
|
||||
self::$multitons[ $identifier ]->collation = 'utf8mb4_unicode_ci';
|
||||
self::$multitons[ $identifier ]->binaryCollation = 'utf8mb4_bin';
|
||||
}
|
||||
|
||||
/* If we succeeded, set the charset */
|
||||
if( !self::$multitons[ $identifier ]->set_charset( self::$multitons[ $identifier ]->charset ) )
|
||||
{
|
||||
/* But if setting that failed, fall back to UTF8 */
|
||||
self::$multitons[ $identifier ]->charset = 'utf8';
|
||||
self::$multitons[ $identifier ]->collation = 'utf8_unicode_ci';
|
||||
self::$multitons[ $identifier ]->binaryCollation = 'utf8_bin';
|
||||
|
||||
self::$multitons[ $identifier ]->set_charset( self::$multitons[ $identifier ]->charset );
|
||||
static::$multitons[ $identifier ]->charset = 'utf8';
|
||||
static::$multitons[ $identifier ]->collation = 'utf8_unicode_ci';
|
||||
static::$multitons[ $identifier ]->binaryCollation = 'utf8_bin';
|
||||
}
|
||||
|
||||
if ( \IPS\IN_DEV )
|
||||
{
|
||||
self::$multitons[ $identifier ]->query( "SET sql_mode=STRICT_ALL_TABLES" );
|
||||
}
|
||||
|
||||
/* Set the prefix */
|
||||
if ( isset( $connectionSettings['sql_tbl_prefix'] ) )
|
||||
{
|
||||
self::$multitons[ $identifier ]->prefix = $connectionSettings['sql_tbl_prefix'];
|
||||
static::$multitons[ $identifier ]->prefix = $connectionSettings['sql_tbl_prefix'];
|
||||
}
|
||||
|
||||
/* Read/Write Separation? */
|
||||
if ( isset( $connectionSettings['sql_read_host'] ) and \IPS\READ_WRITE_SEPARATION )
|
||||
{
|
||||
static::$multitons[ $identifier ]->reader = static::_establishConnection(
|
||||
'mysqli',
|
||||
array(
|
||||
'host' => $connectionSettings['sql_read_host'],
|
||||
'username' => $connectionSettings['sql_read_user'],
|
||||
'password' => $connectionSettings['sql_read_pass'],
|
||||
'database' => $connectionSettings['sql_read_database'],
|
||||
'port' => ( isset( $connectionSettings['sql_read_port'] ) and $connectionSettings['sql_read_port']) ? $connectionSettings['sql_read_port'] : NULL,
|
||||
'socket' => ( isset( $connectionSettings['sql_read_socket'] ) and $connectionSettings['sql_read_socket'] ) ? $connectionSettings['sql_read_socket'] : NULL,
|
||||
),
|
||||
isset( $connectionSettings['sql_utf8mb4'] ) and $connectionSettings['sql_utf8mb4']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return self::$multitons[ $identifier ];
|
||||
return static::$multitons[ $identifier ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apparently, get_charset can be unavailable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCharset()
|
||||
{
|
||||
if ( method_exists( $this, 'get_charset' ) )
|
||||
{
|
||||
return static::get_charset()->charset;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static::character_set_name();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish database connection
|
||||
*
|
||||
* @note Credentials are passed as an array so that if there's an error the PHP error will say "Array" rather than reveal the credentials in any log or if display_errors is on
|
||||
* @param string $classname Class to use
|
||||
* @param string $sqlCredentials array( 'host' => MySQL Host, 'username' => MySQL Username, 'password' => MySQL Password, 'database' => MySQL Database Name, 'port' => MySQL Port or NULL, 'socket' => MySQL Socket or NULL )
|
||||
* @param bool $utf8Mb4 Use UTF8MB4?
|
||||
* @return mysqli
|
||||
*/
|
||||
protected static function _establishConnection( $classname, $sqlCredentials, $utf8Mb4 = TRUE )
|
||||
{
|
||||
/* Connect */
|
||||
$object = @new $classname(
|
||||
$sqlCredentials['host'],
|
||||
$sqlCredentials['username'],
|
||||
$sqlCredentials['password'],
|
||||
$sqlCredentials['database'],
|
||||
$sqlCredentials['port'],
|
||||
$sqlCredentials['socket']
|
||||
);
|
||||
|
||||
/* If the connection failed, throw an exception */
|
||||
if( $error = mysqli_connect_error() )
|
||||
{
|
||||
$errorNumber = $object->connect_errno;
|
||||
throw new \IPS\Db\Exception( $error, $errorNumber );
|
||||
}
|
||||
|
||||
/* Charset */
|
||||
if ( $utf8Mb4 )
|
||||
{
|
||||
if ( $object->set_charset( 'utf8mb4' ) === FALSE )
|
||||
{
|
||||
/* If setting utf8mb4 fails, then gracefully fallback to normal utf8 */
|
||||
$object->set_charset( 'utf8' );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$object->set_charset( 'utf8' );
|
||||
}
|
||||
|
||||
/* Strict mode */
|
||||
if ( \IPS\IN_DEV )
|
||||
{
|
||||
$object->query( "SET sql_mode='STRICT_ALL_TABLES,ONLY_FULL_GROUP_BY,ANSI_QUOTES'" );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +243,7 @@ class _Db extends \mysqli
|
||||
/**
|
||||
* @brief Binary Collation
|
||||
*/
|
||||
protected $binaryCollation = 'utf8_bin';
|
||||
public $binaryCollation = 'utf8_bin';
|
||||
|
||||
/**
|
||||
* @brief Table Prefix
|
||||
@@ -186,17 +260,29 @@ class _Db extends \mysqli
|
||||
* @note Only designed to work with methods that call query() vs prepared statements
|
||||
*/
|
||||
public $returnQuery = FALSE;
|
||||
|
||||
/**
|
||||
* @brief MySQLi object for reading, if using read/write separation
|
||||
*/
|
||||
protected $reader = NULL;
|
||||
|
||||
/**
|
||||
* @brief Read/Write Separation Enabled
|
||||
* @todo This is hacky. Do it properly later
|
||||
*/
|
||||
public $readWriteSeparation = TRUE;
|
||||
|
||||
/**
|
||||
* Run a query
|
||||
*
|
||||
* @param string $query The query
|
||||
* @param bool $log Should be logged?
|
||||
* @param bool $read If TRUE and read/write separation is in use, will use the "read" connection
|
||||
* @return mixed
|
||||
* @see <a href="http://uk1.php.net/manual/en/mysqli.query.php">mysqli::query</a>
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function query( $query, $log = TRUE )
|
||||
public function query( $query, $log = TRUE, $read=FALSE )
|
||||
{
|
||||
/* Should we return the query instead of executing it? */
|
||||
if( $this->returnQuery === TRUE )
|
||||
@@ -208,15 +294,28 @@ class _Db extends \mysqli
|
||||
/* Log */
|
||||
if ( \IPS\QUERY_LOG and $log )
|
||||
{
|
||||
$this->log( $query );
|
||||
$this->log( $query, ( $read and $this->readWriteSeparation ) ? 'read' : 'write' );
|
||||
}
|
||||
|
||||
/* Run */
|
||||
$return = parent::query( $query );
|
||||
if ( $return === FALSE )
|
||||
if ( $read and $this->reader and $this->readWriteSeparation )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->error, $this->errno );
|
||||
$return = $this->reader->query( $query );
|
||||
if ( $return === FALSE )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->reader->error, $this->reader->errno );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = parent::query( $query );
|
||||
if ( $return === FALSE )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->error, $this->errno );
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -225,16 +324,17 @@ class _Db extends \mysqli
|
||||
*
|
||||
* @param string $query The query
|
||||
* @param bool $log Should be logged?
|
||||
* @param bool $read If TRUE and read/write separation is in use, will use the "read" connection
|
||||
* @return mixed
|
||||
* @see <a href="http://uk1.php.net/manual/en/mysqli.query.php">mysqli::query</a>
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function forceQuery( $query, $log = TRUE )
|
||||
public function forceQuery( $query, $log = TRUE, $read=FALSE )
|
||||
{
|
||||
$return = $this->returnQuery;
|
||||
$this->returnQuery = false;
|
||||
|
||||
$result = $this->query( $query, $log );
|
||||
$result = $this->query( $query, $log, $read );
|
||||
|
||||
$this->returnQuery = $return;
|
||||
|
||||
@@ -246,9 +346,10 @@ class _Db extends \mysqli
|
||||
*
|
||||
* @param string $query SQL Statement
|
||||
* @param array $_binds Variables to bind
|
||||
* @param bool $read If TRUE and read/write separation is in use, will use the "read" connection
|
||||
* @return \mysqli_stmt
|
||||
*/
|
||||
public function preparedQuery( $query, array $_binds )
|
||||
public function preparedQuery( $query, array $_binds, $read=FALSE )
|
||||
{
|
||||
/* Init Bind object */
|
||||
$bind = new Db\Bind();
|
||||
@@ -256,33 +357,46 @@ class _Db extends \mysqli
|
||||
/* Sort out subqueries */
|
||||
$binds = array();
|
||||
$i = 0;
|
||||
for ( $j = 0; $j < mb_strlen( $query ); $j++ )
|
||||
for ( $j = 0; $j < \strlen( $query ); $j++ )
|
||||
{
|
||||
if ( $query[ $j ] == '?' )
|
||||
{
|
||||
if ( $_binds[ $i ] instanceof \IPS\Db\Select )
|
||||
if ( array_key_exists( $i, $_binds ) )
|
||||
{
|
||||
$query = mb_substr( $query, 0, $j ) . $_binds[ $i ]->query . mb_substr( $query, $j + 1);
|
||||
$j += mb_strlen( $_binds[ $i ]->query );
|
||||
|
||||
foreach ( $_binds[ $i ]->binds as $_bind )
|
||||
if ( $_binds[ $i ] instanceof \IPS\Db\Select )
|
||||
{
|
||||
$binds[] = $_bind;
|
||||
$query = \substr( $query, 0, $j ) . $_binds[ $i ]->query . \substr( $query, $j + 1);
|
||||
$j += \strlen( $_binds[ $i ]->query );
|
||||
|
||||
foreach ( $_binds[ $i ]->binds as $_bind )
|
||||
{
|
||||
$binds[] = $_bind;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$binds[] = $_binds[ $i ];
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$binds[] = $_binds[ $i ];
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Store the original query before the bind checks are done as NULL replaces ? which throws out the order in the query log */
|
||||
$queryForLog = $query;
|
||||
|
||||
/* Loop values to bind */
|
||||
$i = 0;
|
||||
$longThreshold = 1048576;
|
||||
$sendAsLong = array();
|
||||
foreach ( $binds as $bindVal )
|
||||
{
|
||||
if( ( is_object( $bindVal ) OR is_string( $bindVal ) ) AND \strlen( (string) $bindVal ) > $longThreshold )
|
||||
{
|
||||
$sendAsLong[ $i ] = (string) $bindVal;
|
||||
}
|
||||
|
||||
$i++;
|
||||
switch ( gettype( $bindVal ) )
|
||||
{
|
||||
@@ -296,13 +410,27 @@ class _Db extends \mysqli
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
$bind->add( 's', $bindVal );
|
||||
if( \strlen( $bindVal ) > $longThreshold )
|
||||
{
|
||||
$bind->add( 'b', NULL );
|
||||
}
|
||||
else
|
||||
{
|
||||
$bind->add( 's', $bindVal );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if( method_exists( $bindVal, '__toString' ) )
|
||||
{
|
||||
$bind->add( 's', (string) $bindVal );
|
||||
if( \strlen( $bindVal ) > $longThreshold )
|
||||
{
|
||||
$bind->add( 'b', NULL );
|
||||
}
|
||||
else
|
||||
{
|
||||
$bind->add( 's', (string) $bindVal );
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Deliberately no break
|
||||
@@ -328,14 +456,14 @@ class _Db extends \mysqli
|
||||
if ( \IPS\QUERY_LOG )
|
||||
{
|
||||
/* Log */
|
||||
$this->log( $this->_replaceBinds( $query, $binds ) );
|
||||
$this->log( static::_replaceBinds( $queryForLog, $binds ), ( $read and $this->readWriteSeparation ) ? 'read' : 'write' );
|
||||
}
|
||||
|
||||
/* Return full query */
|
||||
if( $this->returnQuery === TRUE )
|
||||
{
|
||||
$this->returnQuery = FALSE;
|
||||
return $this->_replaceBinds( $query, $binds );
|
||||
return static::_replaceBinds( $queryForLog, $binds );
|
||||
}
|
||||
|
||||
/* Add a backtrace to the query so we know where it came from if it causes issues */
|
||||
@@ -356,26 +484,66 @@ class _Db extends \mysqli
|
||||
}
|
||||
$_query = $query;
|
||||
$query = "/*{$comment}*/ {$query}";
|
||||
|
||||
|
||||
/* Prepare */
|
||||
$stmt = parent::prepare( $query );
|
||||
if( $stmt === FALSE )
|
||||
if ( $read and $this->reader and $this->readWriteSeparation )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->error, $this->errno, NULL, $_query, $binds );
|
||||
$stmt = $this->reader->prepare( $query );
|
||||
if( $stmt === FALSE )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->reader->error, $this->reader->errno, NULL, $queryForLog, $binds );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$stmt = parent::prepare( $query );
|
||||
if( $stmt === FALSE )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $this->error, $this->errno, NULL, $queryForLog, $binds );
|
||||
}
|
||||
}
|
||||
|
||||
/* Bind values */
|
||||
if( $bind->haveBinds() === TRUE )
|
||||
{
|
||||
call_user_func_array( array( $stmt, 'bind_param' ), $bind->get() );
|
||||
|
||||
if( count( $sendAsLong ) )
|
||||
{
|
||||
foreach( $sendAsLong as $index => $data )
|
||||
{
|
||||
$chunks = str_split( $data, $longThreshold - 1 );
|
||||
|
||||
foreach( $chunks as $chunk )
|
||||
{
|
||||
$stmt->send_long_data( $index, $chunk );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute */
|
||||
$stmt->execute();
|
||||
if ( $stmt->error )
|
||||
$stmt->execute();
|
||||
|
||||
/* Handle errors */
|
||||
$count = 1;
|
||||
while ( $stmt->error )
|
||||
{
|
||||
throw new \IPS\Db\Exception( $stmt->error, $stmt->errno, NULL, $_query, $binds );
|
||||
/* If we hit a deadlock, try again once */
|
||||
if ( $stmt->errno === 1213 and $count < 2 )
|
||||
{
|
||||
$stmt->execute();
|
||||
$count++;
|
||||
}
|
||||
|
||||
/* Throw error */
|
||||
else
|
||||
{
|
||||
throw new \IPS\Db\Exception( $stmt->error, $stmt->errno, NULL, $queryForLog, $binds );
|
||||
}
|
||||
}
|
||||
|
||||
/* Store result */
|
||||
$stmt->store_result();
|
||||
|
||||
/* Return a Statement object */
|
||||
@@ -386,14 +554,16 @@ class _Db extends \mysqli
|
||||
* Log
|
||||
*
|
||||
* @param string $logQuery Query to log
|
||||
* @param string $server Will be "read" or "write" to indicate which server was (or would be) used in read/write separation
|
||||
* @return void
|
||||
*/
|
||||
protected function log( $logQuery )
|
||||
{
|
||||
protected function log( $logQuery, $server=NULL )
|
||||
{
|
||||
$this->log[] = array(
|
||||
'query' => $logQuery,
|
||||
'server' => $server,
|
||||
'backtrace' => var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ),
|
||||
'extra' => NULL
|
||||
'extra' => NULL,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -402,7 +572,7 @@ class _Db extends \mysqli
|
||||
*
|
||||
* @param array|string $columns The columns (as an array) to select or an expression
|
||||
* @param array|string $table The table to select from. Either (string) table_name or (array) ( name, alias ) or \IPS\Db\Select object
|
||||
* @param array|string|NULL $where WHERE clause (see example)
|
||||
* @param array|string|NULL $where WHERE clause - see \IPS\Db::compileWhereClause() for details
|
||||
* @param string|NULL $order ORDER BY clause
|
||||
* @param array|int $limit Rows to fetch or array( offset, limit )
|
||||
* @param string|NULL|array $group Column(s) to GROUP BY
|
||||
@@ -411,6 +581,7 @@ class _Db extends \mysqli
|
||||
* @li \IPS\Db::SELECT_DISTINCT Will use SELECT DISTINCT
|
||||
* @li \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS Will add SQL_CALC_FOUND_ROWS
|
||||
* @li \IPS\Db::SELECT_MULTIDIMENSIONAL_JOINS Will return the result as a multidimensional array, with each joined table separately
|
||||
* @li \IPS\Db::SELECT_FROM_WRITE_SERVER Will send the query to the write server (if read/write separation is enabled)
|
||||
* @return \IPS\Db\Select
|
||||
*
|
||||
*/
|
||||
@@ -464,7 +635,8 @@ class _Db extends \mysqli
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= " FROM `{$this->prefix}{$table[0]}` AS `{$table[1]}`";
|
||||
$tableName = ( $table[0] instanceof \IPS\Db\Select ) ? '(' . $table[0] . ')' : '`' . $this->prefix . $table[0] . '`';
|
||||
$query .= " FROM {$tableName} AS `{$table[1]}`";
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -477,7 +649,7 @@ class _Db extends \mysqli
|
||||
{
|
||||
$where = $this->compileWhereClause( $where );
|
||||
$query .= ' WHERE ' . $where['clause'];
|
||||
$binds = $where['binds'];
|
||||
$binds = array_merge( $binds, $where['binds'] );
|
||||
}
|
||||
|
||||
/* Group? */
|
||||
@@ -487,12 +659,40 @@ class _Db extends \mysqli
|
||||
{
|
||||
$query .= " GROUP BY " . implode( ',', array_map( function( $val )
|
||||
{
|
||||
if( mb_strpos( $val, '.' ) !== FALSE )
|
||||
{
|
||||
$pieces = explode( '.', $val );
|
||||
|
||||
foreach( $pieces as $k => $piece )
|
||||
{
|
||||
$pieces[ $k ] = '`' . $piece . '`';
|
||||
}
|
||||
|
||||
return implode( '.', $pieces );
|
||||
}
|
||||
|
||||
return "`{$val}`";
|
||||
}, $group ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= " GROUP BY `{$group}`";
|
||||
if( mb_strpos( $group, '.' ) !== FALSE )
|
||||
{
|
||||
$pieces = explode( '.', $group );
|
||||
|
||||
foreach( $pieces as $k => $piece )
|
||||
{
|
||||
$pieces[ $k ] = '`' . $piece . '`';
|
||||
}
|
||||
|
||||
$group = implode( '.', $pieces );
|
||||
}
|
||||
else
|
||||
{
|
||||
$group = "`{$group}`";
|
||||
}
|
||||
|
||||
$query .= " GROUP BY {$group}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,23 +717,24 @@ class _Db extends \mysqli
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Db\Select( $query, $binds, $this, $flags & static::SELECT_MULTIDIMENSIONAL_JOINS );
|
||||
return new \IPS\Db\Select( $query, $binds, $this, $flags & static::SELECT_MULTIDIMENSIONAL_JOINS, $flags & static::SELECT_FROM_WRITE_SERVER );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build UNION statement
|
||||
*
|
||||
* @param array $selects Array of \IPS\Db\Select objects
|
||||
* @param string|NULL $order ORDER BY clause
|
||||
* @param array|int $limit Rows to fetch or array( offset, limit )
|
||||
* @param string|null $group Group by clause
|
||||
* @param bool $unionAll TRUE to perform a UNION ALL, FALSE (default) to perform a regular UNION
|
||||
* @param int $flags Bitwise flags
|
||||
* @param array|string|NULL $where WHERE clause (see example)
|
||||
* @li \IPS\DB::SELECT_SQL_CALC_FOUND_ROWS Will add SQL_CALC_FOUND_ROWS
|
||||
* @param array $selects Array of \IPS\Db\Select objects
|
||||
* @param string|NULL $order ORDER BY clause
|
||||
* @param array|int $limit Rows to fetch or array( offset, limit )
|
||||
* @param string|null $group Group by clause
|
||||
* @param bool $unionAll TRUE to perform a UNION ALL, FALSE (default) to perform a regular UNION
|
||||
* @param int $flags Bitwise flags
|
||||
* @param array|string|NULL $where WHERE clause (see example)
|
||||
* @param string $querySelect Custom select for the outer query
|
||||
* @li \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS Will add SQL_CALC_FOUND_ROWS
|
||||
* @return \IPS\Db|Select
|
||||
*/
|
||||
public function union( $selects, $order, $limit, $group=NULL, $unionAll=FALSE, $flags=0, $where=NULL )
|
||||
public function union( $selects, $order, $limit, $group=NULL, $unionAll=FALSE, $flags=0, $where=NULL, $querySelect='*' )
|
||||
{
|
||||
/* Combine selects */
|
||||
$query = array();
|
||||
@@ -546,7 +747,7 @@ class _Db extends \mysqli
|
||||
|
||||
$union = $unionAll ? "UNION ALL" : "UNION";
|
||||
|
||||
$query = "SELECT " . ( $flags & static::SELECT_SQL_CALC_FOUND_ROWS ? "SQL_CALC_FOUND_ROWS " : "" ) . "* FROM( " . implode( ' ' . $union . ' ', $query ) . ") derivedTable ";
|
||||
$query = "SELECT " . ( $flags & static::SELECT_SQL_CALC_FOUND_ROWS ? "SQL_CALC_FOUND_ROWS " : "" ) . $querySelect . " FROM( " . implode( ' ' . $union . ' ', $query ) . ") derivedTable ";
|
||||
|
||||
/* WHERE */
|
||||
if ( $where )
|
||||
@@ -610,8 +811,12 @@ class _Db extends \mysqli
|
||||
{
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
$insertId = $stmt->insert_id;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return $stmt->insert_id;
|
||||
return $insertId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -638,7 +843,11 @@ class _Db extends \mysqli
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
return $getInsertId ? $stmt->insert_id : $stmt->affected_rows;
|
||||
$return = $getInsertId ? $stmt->insert_id : $stmt->affected_rows;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -704,13 +913,12 @@ class _Db extends \mysqli
|
||||
/**
|
||||
* Run UPDATE statement and return number of affected rows
|
||||
*
|
||||
* @see \IPS\Db::build
|
||||
* @see <a href='http://dev.mysql.com/doc/refman/5.1/en/update.html'>UPDATE Syntax</a>
|
||||
* @param string|array $table Table Name, or array( Table Name => Identifier )
|
||||
* @param string|array $set Values to set (keys should be the table columns) or pre-formatted SET clause or \IPS\Db\Select object
|
||||
* @param mixed $where WHERE clause (see \IPS\Db::build for details)
|
||||
* @param array $joins Tables to join (see \IPS\Db::build for details)
|
||||
* @param int|array|null $limit LIMIT clause (see \IPS\Db::build for details)
|
||||
* @param mixed $where WHERE clause (see \IPS\Db::compileWhereClause for details)
|
||||
* @param array $joins Tables to join
|
||||
* @param int|array|null $limit LIMIT clause (see \IPS\Db::select for details)
|
||||
* @param int $flags Bitwise flags
|
||||
* @li \IPS\Db::LOW_PRIORITY Will use LOW_PRIORITY
|
||||
* @li \IPS\Db::IGNORE Will use IGNORE
|
||||
@@ -787,19 +995,22 @@ class _Db extends \mysqli
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
return $stmt->affected_rows;
|
||||
$return = $stmt->affected_rows;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run DELETE statement and return number of affected rows
|
||||
*
|
||||
* @see \IPS\Db::build
|
||||
* @see <a href='http://dev.mysql.com/doc/refman/5.1/en/delete.html'>DELETE Syntax</a>
|
||||
* @param string $table Table Name
|
||||
* @param string|array|\IPS\Db\Statement|null $where WHERE clause (see \IPS\Db::build for details)
|
||||
* @param string|null $order ORDER BY clause (see \IPS\Db::build for details)
|
||||
* @param int|array|null $limit LIMIT clause (see \IPS\Db::build for details)
|
||||
* @param string|null $statementColumn If \IPS\Db\Statement is passed, this is the name of the column that results are being loaded from
|
||||
* @param string $table Table Name
|
||||
* @param string|array|\IPS\Db\Statement|null $where WHERE clause (see \IPS\Db::compileWhereClause for details)
|
||||
* @param string|null $order ORDER BY clause
|
||||
* @param int|array|null $limit LIMIT clause (see \IPS\Db::select for details)
|
||||
* @param string|null $statementColumn If \IPS\Db\Statement is passed, this is the name of the column that results are being loaded from
|
||||
* @return \IPS\Db\Statement
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
@@ -816,8 +1027,12 @@ class _Db extends \mysqli
|
||||
{
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
$return = $stmt->affected_rows;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return $stmt->affected_rows;
|
||||
return $return;
|
||||
}
|
||||
|
||||
/* Basic query */
|
||||
@@ -864,13 +1079,26 @@ class _Db extends \mysqli
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
return $stmt->affected_rows;
|
||||
$return = $stmt->affected_rows;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile WHERE clause
|
||||
*
|
||||
* @param string|array $data See \IPS\Db::build for details
|
||||
* @code
|
||||
// Single clause
|
||||
"foo IS NOT NULL"
|
||||
// Single clause with bound values (always bind values to ensure they are properly escaped)
|
||||
array( 'foo=?', 'fooValue' )
|
||||
array( 'foo=? OR bar=?', 'fooValue', 'barValue' )
|
||||
// Multiple clauses (will be joined with AND) with bound values
|
||||
array( array( 'foo=?, 'fooValue' ), array( 'bar=?', 'barValue' ) )
|
||||
* @endcode
|
||||
* @param string|array $data See examples
|
||||
* @return array Array containing the WHERE clause and the values to be bound - array( 'clause' => '1=1', 'binds' => array() )
|
||||
*/
|
||||
public function compileWhereClause( $data )
|
||||
@@ -941,10 +1169,10 @@ class _Db extends \mysqli
|
||||
/**
|
||||
* Compile LIMIT clause
|
||||
*
|
||||
* @param int|array $data See \IPS\Db::build for details
|
||||
* @param int|array $data Rows to fetch or array( offset, limit )
|
||||
* @return string
|
||||
*/
|
||||
protected function compileLimitClause( $data )
|
||||
public function compileLimitClause( $data )
|
||||
{
|
||||
$limit = NULL;
|
||||
if( is_array( $data ) )
|
||||
@@ -996,7 +1224,7 @@ class _Db extends \mysqli
|
||||
public function compileColumnDefinition( $data )
|
||||
{
|
||||
/* Specify name and type */
|
||||
$definition = "`{$data['name']}` {$data['type']} ";
|
||||
$definition = "`{$data['name']}` " . \strtoupper( $data['type'] ) . ' ';
|
||||
|
||||
/* Some types specify length */
|
||||
if(
|
||||
@@ -1082,7 +1310,7 @@ class _Db extends \mysqli
|
||||
}
|
||||
else
|
||||
{
|
||||
$defaultValue = in_array( $data['type'], array( 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC' ) ) ? floatval( $data['default'] ) : ( ! in_array( $data['default'], array( 'CURRENT_TIMESTAMP', 'BIT' ) ) ? '\'' . $this->escape_string( $data['default'] ) . '\'' : $data['default'] );
|
||||
$defaultValue = in_array( \strtoupper( $data['type'] ), array( 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC' ) ) ? floatval( $data['default'] ) : ( ! in_array( $data['default'], array( 'CURRENT_TIMESTAMP', 'BIT' ) ) ? '\'' . $this->escape_string( $data['default'] ) . '\'' : $data['default'] );
|
||||
$definition .= "DEFAULT {$defaultValue} ";
|
||||
}
|
||||
}
|
||||
@@ -1103,11 +1331,11 @@ class _Db extends \mysqli
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
if( isset( $data['comment'] ) )
|
||||
if( isset( $data['comment'] ) and ! empty( $data['comment'] ) )
|
||||
{
|
||||
$definition .= "COMMENT '{$this->escape_string( $data['comment'] )}'";
|
||||
}
|
||||
|
||||
|
||||
/* Return */
|
||||
return $definition;
|
||||
}
|
||||
@@ -1245,6 +1473,7 @@ class _Db extends \mysqli
|
||||
public function _createTableQuery( $data )
|
||||
{
|
||||
$data = $this->updateDefinitionIndexLengths( $data );
|
||||
$mysqlVersion = \IPS\Db::i()->server_info;
|
||||
|
||||
/* Start with a basic CREATE TABLE */
|
||||
$query = 'CREATE ';
|
||||
@@ -1273,7 +1502,7 @@ class _Db extends \mysqli
|
||||
{
|
||||
/* If this is a fulltext index, set engine to myisam but only if engine is something besides innodb or myisam OR the mysql version is less than 5.6 - in
|
||||
this case we assume the default engine is most likely either myisam or innodb */
|
||||
if( \IPS\Db::i()->server_version < 50600 OR ( isset( $data['engine'] ) AND !in_array( \strtoupper( $data['engine'] ), array( 'INNODB', 'MYISAM' ) ) ) )
|
||||
if( !$this->_innoDbSupportsFulltextIndexes() OR ( isset( $data['engine'] ) AND !in_array( \strtoupper( $data['engine'] ), array( 'INNODB', 'MYISAM' ) ) ) )
|
||||
{
|
||||
$data['engine'] = 'MYISAM';
|
||||
}
|
||||
@@ -1383,14 +1612,28 @@ class _Db extends \mysqli
|
||||
return $engine['Engine'];
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Db::i()->server_version < 50600 )
|
||||
|
||||
return $this->_innoDbSupportsFulltextIndexes() ? 'InnoDB' : 'MyISAM';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is InnoDB supported for fulltext indexes?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _innoDbSupportsFulltextIndexes()
|
||||
{
|
||||
/* MariaDB supports fulltext for InnoDB on versions higher than 10.0.5 */
|
||||
if ( preg_match( '/^(\d*\.\d*(\.\d*)-)?(\d*\.\d*(\.\d*))-MariaDB/', $this->server_info, $matches ) )
|
||||
{
|
||||
return 'MyISAM';
|
||||
$mariaVersion = $matches[3];
|
||||
return version_compare( $mariaVersion, '10.0.5', '>=' );
|
||||
}
|
||||
|
||||
/* Normal MySQL supports fulltext for InnoDB on versions higher than 5.6 */
|
||||
else
|
||||
{
|
||||
return 'InnoDB';
|
||||
return $this->server_version >= 50600;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1401,7 +1644,7 @@ class _Db extends \mysqli
|
||||
* @param string|array $table Table Name(s)
|
||||
* @param bool $ifExists Adds an "IF EXISTS" clause to the query
|
||||
* @param bool $temporary Table is temporary?
|
||||
* @return void
|
||||
* @return mixed
|
||||
*/
|
||||
public function dropTable( $table, $ifExists=FALSE, $temporary=FALSE )
|
||||
{
|
||||
@@ -1482,7 +1725,7 @@ class _Db extends \mysqli
|
||||
|
||||
if( $columnDefinition['type'] === 'ENUM' or $columnDefinition['type'] === 'SET' )
|
||||
{
|
||||
preg_match_all( "/'(.+?)'/", $matches[2], $enum );
|
||||
preg_match_all( "/'(.*?)'/", $matches[2], $enum );
|
||||
$columnDefinition['values'] = $enum[1];
|
||||
}
|
||||
else
|
||||
@@ -1727,12 +1970,18 @@ class _Db extends \mysqli
|
||||
* @see \IPS\Db::compileIndexDefinition
|
||||
* @param string $table Table name
|
||||
* @param array $definition New index definition (see \IPS\Db::compileIndexDefinition for details)
|
||||
* @param array|NULL $data Table definition, or null to pull from database
|
||||
* @return void
|
||||
*/
|
||||
public function buildIndex( $table, $definition )
|
||||
public function buildIndex( $table, $definition, $data=NULL )
|
||||
{
|
||||
$indexName = $definition['name'];
|
||||
$data = $this->getTableDefinition( $table, FALSE, TRUE );
|
||||
|
||||
if ( $data === NULL )
|
||||
{
|
||||
$data = $this->getTableDefinition( $table, FALSE, TRUE );
|
||||
}
|
||||
|
||||
$engine = mb_strtolower( $data['engine'] );
|
||||
|
||||
/* Add the index to the table definition */
|
||||
@@ -1767,7 +2016,7 @@ class _Db extends \mysqli
|
||||
|
||||
$indexes = array();
|
||||
|
||||
if( \IPS\DB::i()->returnQuery )
|
||||
if( \IPS\Db::i()->returnQuery )
|
||||
{
|
||||
foreach( $index as $key => $col )
|
||||
{
|
||||
@@ -1788,7 +2037,19 @@ class _Db extends \mysqli
|
||||
|
||||
try
|
||||
{
|
||||
return $_index ? $this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` {$_index};" ) : '';
|
||||
$return = '';
|
||||
|
||||
if ( $_index )
|
||||
{
|
||||
$return = $this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` {$_index};" );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Even if we do not run a query here, we need to reset this */
|
||||
\IPS\Db::i()->returnQuery = FALSE;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
@@ -1827,17 +2088,12 @@ class _Db extends \mysqli
|
||||
$where[] = ( $reverse ? 'NOT ' : '' ) . "FIND_IN_SET('" . $this->real_escape_string( $i ) . "'," . $column . ")";
|
||||
}
|
||||
}
|
||||
|
||||
$return = array();
|
||||
|
||||
$statement = $reverse ? 'AND' : 'OR';
|
||||
|
||||
if ( ! empty( $where ) )
|
||||
{
|
||||
$return[] = '( ' . implode( " OR ", $where ) . ' )';
|
||||
}
|
||||
|
||||
if ( count( $return ) )
|
||||
{
|
||||
return '( ' . implode( ' OR ', $return ) . ' )';
|
||||
return '( ' . implode( " {$statement} ", $where ) . ' )';
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1866,7 +2122,8 @@ class _Db extends \mysqli
|
||||
|
||||
foreach( $values as $i )
|
||||
{
|
||||
if ( $i != NULL and is_numeric( $i ) and ( is_int( $i ) or is_float( $i ) ) )
|
||||
/* We must use the !== comparison so that 0 is not treated the same as NULL */
|
||||
if ( $i !== NULL and is_numeric( $i ) and ( is_int( $i ) or is_float( $i ) ) )
|
||||
{
|
||||
$in[] = $i;
|
||||
}
|
||||
@@ -1875,7 +2132,7 @@ class _Db extends \mysqli
|
||||
$in[] = "'" . $this->real_escape_string( $i ) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$return = array();
|
||||
|
||||
if ( ! empty( $in ) )
|
||||
@@ -1929,8 +2186,11 @@ class _Db extends \mysqli
|
||||
|
||||
if ( isset( $definition['indexes'] ) )
|
||||
{
|
||||
foreach( $definition['indexes'] as $key => $index )
|
||||
foreach( $definition['indexes'] as $key => &$index )
|
||||
{
|
||||
/* Make sure the keys are in the correct order otherwise normal variances trigger differences just because 'columns' can come before 'length', etc */
|
||||
ksort( $index );
|
||||
|
||||
if( isset( $index['length'] ) )
|
||||
{
|
||||
foreach( $index['length'] as $_key => $length )
|
||||
@@ -1950,7 +2210,16 @@ class _Db extends \mysqli
|
||||
{
|
||||
unset( $definition['engine'] );
|
||||
}
|
||||
|
||||
|
||||
/* Prevent conflicts when schema says DEFAULT '0' but it is DEFAULT 0 and an INT type column as this is always set as a 0 anyway */
|
||||
foreach( $definition['columns'] as $name => $data )
|
||||
{
|
||||
if ( in_array( \strtoupper( $data['type'] ), array_keys( static::$dataTypes['database_column_type_numeric'] ) ) and ( ! in_array( \strtoupper( $data['type'] ), array( 'DECIMAL', 'FLOAT', 'BIT' ) ) ) and is_numeric( $data['default'] ) )
|
||||
{
|
||||
$definition['columns'][ $name ]['default'] = intval( $data['default'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
@@ -1981,7 +2250,7 @@ class _Db extends \mysqli
|
||||
{
|
||||
/* If this is a fulltext index, set engine to myisam but only if engine is something besides innodb or myisam OR the mysql version is less than 5.6 - in
|
||||
this case we assume the default engine is most likely either myisam or innodb */
|
||||
if( \IPS\Db::i()->server_version < 50600 OR ( isset( $definition['engine'] ) AND !in_array( \strtoupper( $definition['engine'] ), array( 'INNODB', 'MYISAM' ) ) ) )
|
||||
if( !$this->_innoDbSupportsFulltextIndexes() OR ( isset( $definition['engine'] ) AND !in_array( \strtoupper( $definition['engine'] ), array( 'INNODB', 'MYISAM' ) ) ) )
|
||||
{
|
||||
$definition['engine'] = 'myisam';
|
||||
}
|
||||
@@ -2074,8 +2343,11 @@ class _Db extends \mysqli
|
||||
}
|
||||
|
||||
/* Recalculate value to multiply index sub lengths with (subtracting debt) */
|
||||
$reduceEachBy = ( ( 100 / ($totalLength - $debt) ) * ( $maxChars - $debt ) ) / 100;
|
||||
|
||||
if ( $debt < $totalLength )
|
||||
{
|
||||
$reduceEachBy = ( ( 100 / ($totalLength - $debt) ) * ( $maxChars - $debt ) ) / 100;
|
||||
}
|
||||
|
||||
foreach( $i as $x => $vals )
|
||||
{
|
||||
/* No length? */
|
||||
@@ -2086,7 +2358,14 @@ class _Db extends \mysqli
|
||||
|
||||
if ( in_array( mb_strtoupper( $vals[2] ), array_keys( static::$dataTypes['database_column_type_numeric'] ) ) )
|
||||
{
|
||||
/* Preserve col len */
|
||||
/* Preserve col len where possible but if the column length is greater than subpart allowed, NULL the length
|
||||
otherwise MySQL will complain as you cannot use subpart on non-string column. */
|
||||
if ( $vals[1] > floor( $maxLen / $multiplier ) )
|
||||
{
|
||||
$vals[1] = NULL;
|
||||
$i[ $x ] = $vals;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2099,7 +2378,7 @@ class _Db extends \mysqli
|
||||
{
|
||||
if ( $definition['columns'][ $definition['indexes'][ $key ]['columns'][ $x ] ]['length'] != $vals[1] )
|
||||
{
|
||||
$definition['indexes'][ $key ]['length'][ $x ] = $vals[1];
|
||||
$definition['indexes'][ $key ]['length'][ $x ] = intval( $vals[1] );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2146,7 +2425,7 @@ class _Db extends \mysqli
|
||||
* @param array $binds Any binds in the query
|
||||
* @return string
|
||||
*/
|
||||
protected function _replaceBinds( $query, $binds )
|
||||
public static function _replaceBinds( $query, $binds )
|
||||
{
|
||||
/* Replace ?s with the actual values */
|
||||
if( is_array( $binds ) AND count( $binds ) )
|
||||
|
||||
Reference in new issue
Block a user