Seditio Source
Root |
./othercms/ips_4.3.4/admin/convertutf8/system/Db/Db.php
<?php
/**
 * @brief        Database Class
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @since        18 Feb 2013
 */

namespace IPSUtf8;

/**
 * @brief    Database Class
 * @note    All functionality MUST be supported by MySQL 5.1.3 and higher. All references to the MySQL manual are therefore the 5.1 version.
 */
class Db extends \mysqli
{
   
/**
     * @brief    Datatypes
     */
   
public static $dataTypes = array(
       
'database_column_type_numeric'    => array(
           
'TINYINT'    => 'TINYINT [±127 ⊻ 255] [1B]',
           
'SMALLINT'    => 'SMALLINT [±3.3e4 ⊻ 6.6e4] [2B]',
           
'MEDIUMINT'    => 'MEDIUMINT [±8.4e6 ⊻ 1.7e7] [3B]',
           
'INT'        => 'INT [±2.1e9 ⊻ 4.3e9] [4B]',
           
'BIGINT'    => 'BIGINT [±9.2e18 ⊻ 1.8e19] [8B]',
           
'DECIMAL'    => 'DECIMAL',
           
'FLOAT'        => 'FLOAT',
           
'BIT'        => 'BIT',
           
        ),
       
'database_column_type_datetime'    => array(
           
'DATE'        => 'DATE',
           
'DATETIME'    => 'DATETIME',
           
'TIMESTAMP'    => 'TIMESTAMP',
           
'TIME'        => 'TIME',
           
'YEAR'        => 'YEAR',
        ),
       
'database_column_type_string'    => array(
           
'CHAR'        => 'CHAR [M≤6.6e4] [(M*w)B]',
           
'VARCHAR'    => 'VARCHAR [M≤6.6e4] [(L+(1∨2))B]',
           
'TINYTEXT'    => 'TINYTEXT [256B] [(L+1)B]',
           
'TEXT'        => 'TEXT [64kB] [(L+2)B]',
           
'MEDIUMTEXT'=> 'MEDIUMTEXT [16MB] [(L+3)B]',
           
'LONGTEXT'    => 'LONGTEXT [4GB] [(L+4)B]',
           
'BINARY'    => 'BINARY [M≤6.6e4] [(M)B]',
           
'VARBINARY'    => 'VARBINARY [M≤6.6e4] [(L+(1∨2))B]',
           
'TINYBLOB'    => 'TINYBLOB [256B] [(L+1)B]',
           
'BLOB'        => 'BLOB [64kB] [(L+2)B]',
           
'MEDIUMBLOB'=> 'MEDIUMBLOB [16MB] [(L+3)B]',
           
'BIGBLOB'    => 'BIGBLOB [4GB] [(L+4)B]',
           
'ENUM'        => 'ENUM [6.6e4] [(1∨2)B]',
           
'SET'        => 'SET [64] [(1∨2∨3∨4∨8)B]',
        )
    );

   
/**
     * @brief    Multiton Store
     */
   
protected static $multitons;
   
   
/**
     * @brief    Return Query
     */
   
public $returnQuery = FALSE;

   
/**
     * Get instance
     *
     * @param    mixed    $identifier            Identifier
     * @param    array    $connectionSettings    Connection settings (use when initiating a new connection)
     * @return    IPSUtf8\Db
     */
   
public static function i( $identifier=NULL, $connectionSettings=array() )
    {
       
/* Did we pass a null value? */
       
$identifier = ( $identifier === NULL ) ? '__MAIN' : $identifier;
   
       
/* Don't have an instance? */
       
if( !isset( self::$multitons[ $identifier ] ) )
        {
           
/* Load the default settings if necessary */
           
if ( $identifier === '__MAIN' OR $identifier == 'utf8' )
            {
                require(
ROOT_PATH . '/conf_global.php' );
               
$connectionSettings = $INFO;
            }
           
            if ( isset(
$connectionSettings['sql_tbl_prefix'] ) )
            {
               
$connectionSettings['sql_tbl_prefix'] = str_replace( 'x_utf_', '', $connectionSettings['sql_tbl_prefix'] );
            }
           
           
/* UTF8 Connection settings */
           
if ( mb_substr( $identifier, 0, 4 ) == 'utf8' )
            {
               
$connectionSettings['sql_tbl_prefix'] = 'x_utf_' . $connectionSettings['sql_tbl_prefix'];
            }
           
           
/* 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() )
            {
                throw new \
IPSUtf8\Db\Exception( $error, self::$multitons[ $identifier ]->connect_errno );
            }
           
           
/* If we succeeded, set the charset */
           
if ( mb_substr( $identifier, 0, 4 ) == 'utf8' )
            {
               
/* UTF8MB4? */
               
if ( isset( $connectionSettings['sql_utf8mb4'] ) and $connectionSettings['sql_utf8mb4'] )
                {
                   
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 ) )
                {
                   
/* Fallback 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( 'utf8' );
                }
            }
           
           
/* Set the prefix */
           
if ( isset( $connectionSettings['sql_tbl_prefix'] ) )
            {
               
self::$multitons[ $identifier ]->prefix = $connectionSettings['sql_tbl_prefix'];
            }
        }
       
       
/* Return */
       
return self::$multitons[ $identifier ];
    }
   
   
/**
     * @brief    Charset
     */
   
protected $charset = 'utf8';
   
   
/**
     * @brief    Collation
     */
   
protected $collation = 'utf8_unicode_ci';
   
   
/**
     * @brief    Binary Collation
     */
   
protected $binaryCollation = 'utf8_bin';
   
   
/**
     * @brief    Default MySQL Engine
     */
   
protected $defaultEngine = NULL;

   
/**
     * @brief    Table Prefix
     */
   
public $prefix = '';
   
   
/**
     * Run a query
     *
     * @param    string    $query    The query
     * @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 )
    {
       
/* Should we return the query instead of executing it? */
       
if( $this->returnQuery === TRUE )
        {
           
$this->returnQuery    = FALSE;
            return
$query;
        }

       
$return = parent::query( $query );
        if (
$return === FALSE )
        {
            throw new \
IPSUtf8\Db\Exception( $this->error, $this->errno );
        }
        return
$return;
    }
   
   
/**
     * 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();
        }
    }
   
   
/**
     * Overload this method so we can change the charset if required
     *
     * @param    string    Charset
     */
   
public function set_charset( $charset )
    {
        if (
mb_substr( $charset, 0, 4 ) == 'utf8' )
        {
            if (
$charset === 'utf8mb4' )
            {
               
$this->charset         = 'utf8mb4';
               
$this->collation       = 'utf8mb4_unicode_ci';
               
$this->binaryCollation = 'utf8mb4_bin';
            }
            else
            {
               
$this->charset            = 'utf8';
               
$this->collation        = 'utf8_unicode_ci';
               
$this->binaryCollation    = 'utf8_bin';
            }
        }
       
        return
parent::set_charset( $charset );
    }
   
   
/**
     * Run Prepared SQL Statement
     *
     * @param    string    $query    SQL Statement
     * @param    array    $_binds    Variables to bind
     * @return    \mysqli_stmt
     */
   
public function preparedQuery( $query, array $_binds )
    {
       
/* Init Bind object */
       
$bind = new Db\Bind();
       
       
/* Sort out subqueries */
       
$binds = array();
       
$i = 0;
        foreach (
$_binds as $bindVal )
        {
           
$i++;
            if (
$bindVal instanceof \IPSUtf8\Db\Select )
            {
               
$pos = 0;
                for (
$j=0; $j<$i; $j++ )
                {
                   
$pos = mb_strpos( $query, '?', $pos ) + 1;
                }                    
               
$query = mb_substr( $query, 0, $pos - 1 ) . $bindVal->query . mb_substr( $query, $pos );
               
$i--;
               
                foreach (
$bindVal->binds as $v )
                {
                   
$binds[] = $v;
                }
            }
            else
            {
               
$binds[] = $bindVal;
            }
        }
       
       
/* Loop values to bind */
       
$i = 0;
        foreach (
$binds as $bindVal )
        {
           
$i++;
            switch (
gettype( $bindVal ) )
            {
                case
'boolean':
                case
'integer':
                   
$bind->add( 'i', $bindVal );
                    break;
                   
                case
'double':
                   
$bind->add( 'd', $bindVal );
                    break;
                                               
                case
'string':
                   
$bind->add( 's', $bindVal );
                    break;
                   
                case
'object':
                    if(
method_exists( $bindVal, '__toString' ) )
                    {
                       
$bind->add( 's', (string) $bindVal );
                        break;
                    }
                   
// Deliberately no break
                   
               
case 'NULL':
                case
'array':
                case
'resource':
                case
'unknown type':
                default:
                   
/* For NULL values, you can't bind, so we adjust the query to actually pass a NULL value */
                   
$pos = 0;
                    for (
$j=0; $j<$i; $j++ )
                    {
                       
$pos = mb_strpos( $query, '?', $pos ) + 1;
                    }                    
                   
$query = mb_substr( $query, 0, $pos - 1 ) . 'NULL' . mb_substr( $query, $pos );
                   
$i--;                    
                    break;
            }
        }
       
       
/* Add a backtrace to the query so we know where it came from if it causes issues */
       
$comment = '??';
       
$line = '?';
        foreach(
debug_backtrace( FALSE ) as $b )
        {
            if ( isset(
$b['line'] ) )
            {
               
$line = $b['line'];
            }
           
            if( isset(
$b['class'] ) and $b['class'] !== 'IPSUtf8\_Db' )
            {
               
$comment = "{$b['class']}::{$b['function']}:{$line}";
                break;
            }
        }
       
$_query = $query;
       
$query = "/*{$comment}*/ {$query}";
       
       
/* Prepare */        
       
$stmt = parent::prepare( $query );
        if(
$stmt === FALSE )
        {
            throw new \
IPSUtf8\Db\Exception( $this->error, $this->errno, NULL, $_query, $binds );
        }
       
       
/* Bind values */
       
if( $bind->haveBinds() === TRUE )
        {
           
call_user_func_array( array( $stmt, 'bind_param' ), $bind->get() );
        }
       
       
/* Execute */
       
$stmt->execute();
        if (
$stmt->error )
        {
            throw new \
IPSUtf8\Db\Exception( $stmt->error, $stmt->errno, NULL, $_query, $binds );
        }
       
$stmt->store_result();
               
       
/* Return a Statement object */
       
return $stmt;
    }
   
    const
SELECT_DISTINCT = 1;
    const
SELECT_SQL_CALC_FOUND_ROWS = 2;
   
   
/**
     * Build SELECT statement
     *
     * @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 )
     * @param    array|string|NULL    $where        WHERE clause (see example)
     * @param    string|NULL            $order        ORDER BY clause
     * @param    array|int            $limit        Rows to fetch or array( offset, limit )
     * @param    string|NULL            $group        Column to GROUP BY
     * @param    array|string|NULL    $having        HAVING clause (same format as WHERE clause)
     * @param    int                    $flags        Bitwise flags
     *    @li    \IPSUtf8\Db::SELECT_DISTINCT            Will use SELECT DISTINCT
     *    @li    \IPSUtf8\DB::SELECT_SQL_CALC_FOUND_ROWS    Will add SQL_CALC_FOUND_ROWS
     * @return    \IPSUtf8\Db\Select
     *
     */
   
public function select( $columns=NULL, $table, $where=NULL, $order=NULL, $limit=NULL, $group=NULL, $having=NULL, $flags=0 )
    {
       
$binds = array();
       
$query = 'SELECT ';
       
       
/* Flags */
       
if ( $flags & static::SELECT_DISTINCT )
        {
           
$query .= 'DISTINCT ';
        }
        if (
$flags & static::SELECT_SQL_CALC_FOUND_ROWS )
        {
           
$query .= 'SQL_CALC_FOUND_ROWS ';
        }
       
       
/* Columns */
       
if ( is_string( $columns ) )
        {
           
$query .= $columns;
        }
        else
        {
           
$query .= implode( ', ', array_map( function( $col )
            {
                return
'`' . $col . '`';
            },
$columns ) );
        }
       
       
/* Tables */
       
if ( is_array( $table ) )
        {
           
$query .= " FROM `{$this->prefix}{$table[0]}` AS `{$table[1]}`";
        }
        else
        {
           
$query .= " FROM `{$this->prefix}{$table}` AS `{$table}`";
        }
       
       
/* WHERE */
       
if ( $where )
        {
           
$where = $this->compileWhereClause( $where );
           
$query .= ' WHERE ' . $where['clause'];
           
$binds = $where['binds'];
        }
       
       
/* Group? */
       
if( $group )
        {
           
$query .= " GROUP BY `{$group}`";
        }
               
       
/* Having? */
       
if( $having )
        {
           
$having = $this->compileWhereClause( $having );
           
$query .= ' HAVING ' . $having['clause'];
           
$binds = array_merge( $binds, $having['binds'] );
        }
       
       
/* Order? */
       
if( $order )
        {
           
$query .= ' ORDER BY ' . $order;
        }
       
       
/* Limit */
       
if( $limit )
        {
           
$query .= $this->compileLimitClause( $limit );
        }
       
       
/* Return */
       
return new \IPSUtf8\Db\Select( $query, $binds, $this );
    }
   
   
/**
     * Build UNION statement
     *
     * @param    array            $selects    Array of \IPSUtf8\Db\Select objects
     * @param    string|NULL        $order        ORDER BY clause
     * @param    array|int        $limit        Rows to fetch or array( offset, limit )
     * @return    \IPSUtf8\Db|Select
     */
   
public function union( $selects, $order, $limit )
    {
       
/* Combine selects */
       
$query = array();
       
$binds = array();
        foreach (
$selects as $s )
        {
           
$query[] = '( ' . $s->query . ' )';
           
$binds = array_merge( $binds, $s->binds );
        }
       
$query = implode( ' UNION ', $query );
       
       
/* Order? */
       
if( $order )
        {
           
$query .= ' ORDER BY ' . $order;
        }
       
       
/* Limit */
       
if( $limit )
        {
           
$query .= $this->compileLimitClause( $limit );
        }
       
       
/* Return */
       
return new \IPSUtf8\Db\Select( $query, $binds, $this );
    }
   
   
/**
     * Run INSERT statement and return insert ID
     *
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/insert.html'>INSERT Syntax</a>
     * @param    string                    $table                Table name
     * @param    array|\IPSUtf8\Db\Select    $set            Values to insert or array of values to set for multiple rows (NB, if providing multiple rows, they MUST all contain the same columns) or a statement to do INSERT INTO SELECT FROM
     * @param    bool                    $odkUpdate            Append an ON DUPLICATE KEY UPDATE clause to the query.  Similar to the replace() method but updates if a record is found, instead of delete and reinsert.
     * @see        \IPSUtf8\Db::replace()
     * @param    bool                    $bulkInsertNoPrep    Turns off prepared statements for bulk inserts
     * @return    int
     * @throws    \IPSUtf8\Db\Exception
     */
   
public function insert( $table, $set, $odkUpdate=FALSE, $bulkInsertNoPrep=FALSE, $tableDefinition=NULL )
    {
       
/* Is a statement? */
       
if ( $set instanceof \IPSUtf8\Db\Select )
        {
           
$query = "INSERT " . ( $odkUpdate ? " IGNORE " : '' ) . " INTO `{$this->prefix}{$table}` " . $set->query;
           
$binds = $set->binds;
           
$odkUpdate = false;
        }
       
       
/* Nope, normal array */
       
else
        {
           
/* Is this just one row? */
           
foreach ( $set as $k => $v )
            {
                if ( !
is_array( $v ) )
                {
                   
$set = array( $set );
                }
                break;
            }
           
           
/* Compile */
           
$columns = NULL;
           
$values = array();
           
$binds = array();
            foreach (
$set as $row )
            {
                if (
$columns === NULL )
                {
                   
$columns = array_map( function( $val ){ return "`{$val}`"; }, array_keys( $row ) );
                }
               
                if (
count( $set ) > 1 AND $bulkInsertNoPrep )
                {
                    if (
$tableDefinition === NULL )
                    {
                       
$tableDefinition = $this->getTableDefinition( $table );
                    }
                   
                    foreach(
$row as $k => $v )
                    {
                        if ( isset(
$tableDefinition['definition']['columns'][ $k ] ) )
                        {
                           
$isInt = in_array( \mb_strtolower( $tableDefinition['definition']['columns'][ $k ]['type'] ), array( 'integer', 'int', 'smallint', 'tinyint', 'mediumint', 'bigint', 'decimal', 'numeric', 'float', 'double' ) );
                           
                            if (
$isInt )
                            {
                                if ( empty(
$v ) )
                                {
                                   
                                    if ( ! empty(
$tableDefinition['definition']['columns'][ $k ]['allow_null'] ) AND $v !== 0 )
                                    {
                                       
$row[ $k ] = 'null';
                                    }
                                    else
                                    {
                                       
$row[ $k ] = 0;
                                    }
                                }
                            }
                            else
                            {
                                if ( empty(
$v ) )
                                {
                                    if ( ! empty(
$tableDefinition['definition']['columns'][ $k ]['allow_null'] ) AND $v !== '' )
                                    {
                                       
$row[ $k ] = 'null';
                                    }
                                    else
                                    {
                                       
$row[ $k ] = "''";
                                    }
                                }
                                else
                                {
                                   
$row[ $k ] = "'" . $this->real_escape_string( $v ) . "'";
                                }
                            }
                        }
                        else
                        {
                            if ( !
ctype_digit( (string) $v ) )
                            {
                                if ( empty(
$v ) )
                                {
                                   
$row[ $k ] = 'null';
                                }
                                else
                                {
                                   
$row[ $k ] = "'" . $this->real_escape_string( $v ) . "'";
                                }
                            }
                        }
                    }
                   
                   
$values[] = '(' . implode( ', ', $row ) . ')';
                }
                else
                {
                   
$binds    = array_merge( $binds, array_values( $row ) );
                   
$values[] = '( ' . implode( ', ', array_fill( 0, count( $columns ), '?' ) ) . ' )';
                }
            }
           
           
/* Construct query */
           
$query = "INSERT INTO `{$this->prefix}{$table}` ( " . implode( ', ', $columns ) . ' ) VALUES ' . implode( ', ', $values );
        }
       
       
/* Add "ON DUPLICATE KEY UPDATE" */
       
if( $odkUpdate )
        {
           
$query    .= " ON DUPLICATE KEY UPDATE " . implode( ', ', array_map( function( $val ){ return "{$val}=VALUES({$val})"; }, $columns ) );
        }
       
       
/* Run */
       
if ( count( $set ) > 1 AND $bulkInsertNoPrep )
        {
           
$stmt = $this->query( $query );
            return
$this->insert_id;
        }
        else
        {
           
$stmt = $this->preparedQuery( $query, $binds );
            return
$stmt->insert_id;
        }
    }
   
   
/**
     * Run REPLACE statament and return number of affected rows
     *
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/replace.html'>REPLACE Syntax</a>
     * @param    string    $table    Table name
     * @param    array    $set    Values to insert
     * @return    \IPSUtf8\Db\Statement
     * @throws    \IPSUtf8\Db\Exception
     */
   
public function replace( $table, $set )
    {
       
$columns = implode( ', ', array_map( function( $val ){ return "`{$val}`"; }, array_keys( $set ) ) );
       
$query = "REPLACE INTO `{$this->prefix}{$table}` ( " . $columns . " ) VALUES ( " . implode( ', ', array_fill( 0, count( $set ), '?' ) ) . " )";

       
$stmt = $this->preparedQuery( $query, array_values( $set ) );
        return
$stmt->affected_rows;
    }
   
   
/**
     * Run UPDATE statement and return number of affected rows
     *
     * @see        \IPSUtf8\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
     * @param    mixed            $where        WHERE clause (see \IPSUtf8\Db::build for details)
     * @param    array            $joins        Tables to join (see \IPSUtf8\Db::build for details)
     * @return    int
     * @throws    \IPSUtf8\Db\Exception
     */
   
public function update( $table, $set, $where='', $joins=array() )
    {
       
$binds = array();
       
       
/* Work out table */
       
$table = is_array( $table ) ? "`{$this->prefix}{$table[0]}` {$this->prefix}{$table[1]}" : "`{$this->prefix}{$table}`";
       
       
/* Work out joins */
       
foreach ( $joins as $join )
        {
           
$type = ( isset( $join['type'] ) and in_array( strtoupper( $join['type'] ), array( 'LEFT', 'INNER', 'RIGHT' ) ) ) ? strtoupper( $join['type'] ) : 'LEFT';
           
$_table = is_array( $join['from'] ) ? "`{$this->prefix}{$join['from'][0]}` {$this->prefix}{$join['from'][1]}" : $join['from'];
           
           
$on = $this->compileWhereClause( $join['where'] );
           
$binds = array_merge( $binds, $on['binds'] );

           
$joins[] = "{$type} JOIN {$_table} ON {$on['clause']}";
        }
       
$joins = empty( $joins ) ? '' : ( ' ' . implode( "\n", $joins ) );
       
       
/* Work out SET clause */
       
if ( is_array( $set ) )
        {
           
$_set = array();
            foreach (
$set as $k => $v )
            {
               
$_set[] = "`{$k}`=?";
               
$binds[] = $v;
            }
           
$set = implode( ',', $_set );
        }
       
       
/* Compile where clause */
       
if ( $where !== '' )
        {
           
$_where = $this->compileWhereClause( $where );
           
$where = 'WHERE ' . $_where['clause'];
           
$binds = array_merge( $binds, $_where['binds'] );
        }
               
       
/* Run it */
       
$stmt = $this->preparedQuery( "UPDATE {$table} {$joins} SET {$set} {$where}", $binds );
        return
$stmt->affected_rows;
    }
   
   
/**
     * Run DELETE statement and return number of affected rows
     *
     * @see        \IPSUtf8\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|\IPSUtf8\Db\Statement|null    $where        WHERE clause (see \IPSUtf8\Db::build for details)
     * @param    string|null            $order        ORDER BY clause (see \IPSUtf8\Db::build for details)
     * @param    int|array|null        $limit        LIMIT clause (see \IPSUtf8\Db::build for details)
     * @param    string|null            $statementColumn    If \IPSUtf8\Db\Statement is passed, this is the name of the column that results are being loaded from
     * @return    \IPSUtf8\Db\Statement
     * @throws    \IPSUtf8\Db\Exception
     */
   
public function delete( $table, $where=NULL, $order=NULL, $limit=NULL, $statementColumn=NULL )
    {
       
/* Basic query */
       
$query = "DELETE FROM `{$this->prefix}{$table}`";

       
/* Is a statement? */
       
if ( $where instanceof \IPSUtf8\Db\Statement )
        {
           
$query .= ' WHERE ' . $statementColumn . ' IN(' . $where->query . ')';
           
$binds = $where->binds;
        }

       
/* Add where clause */
       
else
        {
           
$binds = array();
            if (
$where !== NULL )
            {
               
$_where = $this->compileWhereClause( $where );
               
$query .= ' WHERE ' . $_where['clause'];
               
$binds = $_where['binds'];
            }
        }
       
       
/* Order? */
       
if( $order !== NULL )
        {
           
$query .= ' ORDER BY ' . $order;
        }
       
       
/* Limit */
       
if( $limit !== NULL )
        {
           
$query .= $this->compileLimitClause( $limit );
        }
       
       
/* Run it */
       
$stmt = $this->preparedQuery( $query, $binds );        
        return
$stmt->affected_rows;
    }
           
   
/**
     * Compile WHERE clause
     *
     * @param    string|array    $data    See \IPSUtf8\Db::build for details
     * @return    array    Array containing the WHERE clause and the values to be bound - array( 'clause' => '1=1', 'binds' => array() )
     */
   
public function compileWhereClause( $data )
    {
       
$return = array( 'clause' => '1=1', 'binds' => array() );
       
        if(
is_string( $data ) )
        {
           
$return['clause'] = $data;
        }
        elseif (
is_array( $data ) and ! empty( $data ) )
        {
            if (
is_string( $data[0] ) )
            {
               
$data = array( $data );
            }
       
           
$clauses = array();
            foreach (
$data as $bit )
            {
                if( !
is_array( $bit ) )
                {
                   
$clauses[] = $bit;
                }
                else
                {
                   
$clauses[] = array_shift( $bit );
                   
$return['binds'] = array_merge( $return['binds'], $bit );
                }
            }
           
           
$return['clause'] = implode( ' AND ', $clauses );
        }
       
        return
$return;
    }
   
   
/**
     * Compile LIMIT clause
     *
     * @param    int|array    $data    See \IPSUtf8\Db::build for details
     * @return    string
     */
   
protected function compileLimitClause( $data )
    {
       
$limit = NULL;
        if(
is_array( $data ) )
        {
           
$offset = intval( $data[0] );
           
$limit  = intval( $data[1] );
        }
        else
        {
           
$offset = intval( $data );
        }

        if(
$limit !== NULL )
        {
            return
" LIMIT {$offset},{$limit}";
        }
        else
        {
            return
" LIMIT {$offset}";
        }
    }
   
   
/**
     * Compile column definition
     *
     * @code
         \IPSUtf8\Db::i()->compileColumnDefinition( array(
             'name'            => 'column_name',        // Column name
             'type'            => 'VARCHAR',            // Data type (do not specify length, etc. here)
             'length'        => 255,                    // Length. May be required or optional depending on data type.
             'decimals'        => 2,                    // Decimals. May be required or optional depending on data type.
             'values'        => array( 0, 1 ),        // Acceptable values. Required for ENUM and SET data types.
             'null'            => FALSE,                // (Optional) Specifies whether or not NULL vavlues are allowed. Defaults to TRUE.
             'default'        => 'Default Value',        // (Optional) Default value
             'comment'        => 'Column Comment',    // (Optional) Column comment
             'unsigned'        => TRUE,                // (Optional) Will specify UNSIGNED for numeric types. Defaults to FALSE.
             'zerofill'        => TRUE,                // (Optional) Will specify ZEROFILL for numeric types. Defaults to FALSE.
             'auto_increment'=> TRUE,                // (Optional) Will specify auto_increment. Defaults to FALSE.
             'binary'        => TRUE,                // (Optional) Will specify BINARY for TEXT types. Defaults to FALSE.
             'primary'        => TRUE,                // (Optional) Will specify PRIMARY KEY. Defaults to FALSE.
             'unqiue'        => TRUE,                // (Optional) Will specify UNIQUE. Defaults to FALSE.
             'key'            => TRUE,                // (Optional) Will specify KEY. Defaults to FALSE.
         ) );
     * @endcode
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/create-table.html'>MySQL CREATE TABLE syntax</a>
     * @param    array    $data    Column Data (see \IPSUtf8\Db::createTable for details)
     * @return    string
     */    
   
public function compileColumnDefinition( $data )
    {
       
/* Specify name and type */
       
$definition = "`{$data['name']}` {$data['type']} ";
       
       
/* Some types specify length */
       
if(
           
in_array( $data['type'], array( 'VARCHAR', 'VARBINARY' ) )
            or
            (
                isset(
$data['length'] ) and $data['length']
                and
               
in_array( $data['type'], array( 'BIT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC', 'CHAR', 'BINARY' ) )
            )
        ) {
           
$definition .= "({$data['length']}";
           
           
/* And some of those specify decimals (which may or may not be optional) */                    
           
if( in_array( $data['type'], array( 'REAL', 'DOUBLE', 'FLOAT' ) ) or ( in_array( $data['type'], array( 'DECIMAL', 'NUMERIC' ) ) and isset( $data['decimals'] ) ) )
            {
               
$definition .= ',' . $data['decimals'];
            }
           
           
$definition .= ') ';
        }
       
       
/* Numeric types can be UNSIGNED and ZEROFILL */
       
if( in_array( $data['type'], array( 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC' ) ) )
        {
            if( isset(
$data['unsigned'] ) and $data['unsigned'] === TRUE )
            {
               
$definition .= 'UNSIGNED ';
            }
            if( isset(
$data['zerofill'] ) and $data['zerofill'] === TRUE )
            {
               
$definition .= 'ZEROFILL ';
            }
        }
       
       
/* ENUM and SETs have values */
       
if( in_array( $data['type'], array( 'ENUM', 'SET' ) ) )
        {
           
$values = array();
            foreach (
$data['values'] as $v )
            {
               
$values[] = "'{$this->escape_string( $v )}'";
            }
           
           
$definition .= '(' . implode( ',', $values ) . ') ';
        }
               
       
/* Some types can be binary or not */
       
if( isset( $data['binary'] ) and $data['binary'] === TRUE and in_array( $data['type'], array( 'CHAR', 'VARCHAR', 'TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT' ) ) )
        {
           
$definition .= 'BINARY ';
        }
       
       
/* Text types specify a character set and collation */
       
if( in_array( $data['type'], array( 'CHAR', 'VARCHAR', 'TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT', 'ENUM', 'SET' ) ) )
        {
           
$definition .= "CHARACTER SET {$this->charset} COLLATE {$this->collation} ";
        }
       
       
/* NULL? */
       
if( isset( $data['allow_null'] ) and $data['allow_null'] === FALSE )
        {
           
$definition .= 'NOT NULL ';
        }
        else
        {
           
$definition .= 'NULL ';
        }
               
       
/* Default value */
       
if( isset( $data['default'] ) and !in_array( $data['type'], array( 'TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT', 'BLOB', 'MEDIUMBLOB', 'BIGBLOB' ) ) )
        {
           
$defaultValue = in_array( $data['type'], array( 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'FLOAT', 'DECIMAL', 'NUMERIC', 'BIT' ) ) ? floatval( $data['default'] ) : ( ! in_array( $data['default'], array( 'CURRENT_TIMESTAMP' ) ) ? '\'' . $this->escape_string( $data['default'] ) . '\'' : $data['default'] );
           
           
/* Strict Mode isn't nice */
           
$toTest = trim( $defaultValue, "'" );
            if (
in_array( $data['type'], array( 'DATETIME' ) ) AND empty( $toTest ) ) # array here in case we need to expand later
           
{
               
$defaultValue = '\'0000-00-00 00:00:00\'';
            }
           
           
$definition .= "DEFAULT {$defaultValue} ";
        }
       
       
/* auto_increment? */
       
if( isset( $data['auto_increment'] ) and $data['auto_increment'] === TRUE )
        {
           
$definition .= 'AUTO_INCREMENT ';
        }
       
       
/* Index? */
       
if( isset( $data['primary'] ) )
        {
           
$definition .= 'PRIMARY KEY ';
        }
        elseif( isset(
$data['unique'] ) )
        {
           
$definition .= 'UNIQUE ';
        }
        if( isset(
$data['key'] ) )
        {
           
$definition .= 'KEY ';
        }
       
       
/* Comment */
       
if( isset( $data['comment'] ) )
        {
           
$definition .= "COMMENT '{$this->escape_string( $data['comment'] )}'";
        }
                           
       
/* Return */
       
return $definition;
    }
   
   
/**
     * Compile index definition
     *
     * @code
         \IPSUtf8\Db::i()->compileIndexDefinition( array(
             'type'        => 'key',                // "primary", "unique", "fulltext" or "key"
             'name'        => 'index_name',        // Index name. Not required if type is "primary"
             'length'    => 200,                    // Index length (used when taking part of a text field, for example)
             'columns    => array( 'column' )    // Columns to be in the index
         ) );
     * @endcode
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/create-index.html'>MySQL CREATE INDEX syntax</a>
     * @see        \IPSUtf8\Db::createTable
     * @param    array    $data    Index Data (see \IPSUtf8\Db::createTable for details)
     * @return    string
     */
   
public function compileIndexDefinition( $data )
    {
       
$definition = '';
       
       
/* Specify type */
       
switch ( $data['type'] )
        {
            case
'primary':
               
$definition .= 'PRIMARY KEY ';
                break;
               
            case
'unique':
               
$definition .= "UNIQUE KEY `{$data['name']}` ";
                break;
               
            case
'fulltext':
               
$definition .= "FULLTEXT KEY `{$data['name']}` ";
                break;
               
            default:
               
$definition .= "KEY `{$data['name']}` ";
                break;
        }
       
       
/* Specify columns */
       
$definition .= '(' . implode( ',', array_map( function ( $val, $len )
        {
            return ( ! empty(
$len ) ) ? "`{$val}`({$len})" : "`{$val}`";
        },
$data['columns'], ( ( isset( $data['length'] ) AND is_array( $data['length'] ) ) ? $data['length'] : array_fill( 0, count( $data['columns'] ), null ) ) ) ) . ')';
       
       
/* Return */
       
return $definition;
    }
   
   
/**
     * Does table exist?
     *
     * @param    string    $name    Table Name
     * @return    bool
     */
   
public function checkForTable( $name )
    {
        return (
$this->query( "SHOW TABLES LIKE '". $this->escape_string( "{$this->prefix}{$name}" ) . "'" )->num_rows > 0 );
    }
   
   
/**
     * Does index exist?
     *
     * @param    string    $name    Table Name
     * @param    string    $index    Index Name
     * @return    bool
     */
   
public function checkForIndex( $name, $index )
    {
        return (
$this->query( "SHOW INDEXES FROM ". $this->escape_string( "{$this->prefix}{$name}" ) . " WHERE Key_name LIKE '". $this->escape_string( $index ) . "'" )->num_rows > 0 );
    }
   
   
/**
     * Create Table
     *
     * @code
         \IPSUtf8\Db::createTable( array(
             'name'            => 'table_name',    // Table name
             'columns'        => array( ... ),    // Column data - see \IPSUtf8\Db::compileColumnDefinition for details
             'indexes'        => array( ... ),    // (Optional) Index data - see \IPSUtf8\Db::compileIndexDefinition for details
             'comment'        => '...',            // (Optional) Table comment
             'engine'        => 'MEMORY',        // (Optional) Engine to use - will default to not specifying one, unless a FULLTEXT index is specified, in which case MyISAM is forced
             'temporary'        => TRUE,            // (Optional) Will sepcify CREATE TEMPORARY TABLE - defaults to FALSE
             'if_not_exists'    => TRUE,            // (Optional) Will sepcify CREATE TABLE name IF NOT EXISTS - defaults to FALSE
         ) );
     * @endcode
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/create-table.html'>MySQL CREATE TABLE syntax</a>
     * @see        \IPSUtf8\Db::compileColumnDefinition
     * @see        \IPSUtf8\Db::compileIndexDefinition
     * @param    array    $data    Table Definition (see code sample for details)
     * @throws    \IPSUtf8\Db\Exception
     * @return    void
     */
   
public function createTable( $data )
    {
       
/* Start with a basic CREATE TABLE */
       
$query = 'CREATE ';
        if( isset(
$data['temporary'] ) and $data['temporary'] === TRUE )
        {
           
$query.= 'TEMPORARY ';
        }
       
$query .= 'TABLE ';
        if( isset(
$data['if_not_exists'] ) and $data['if_not_exists'] === TRUE )
        {
           
$query.= 'IF NOT EXISTS ';
        }
               
       
/* Add in our create definition */
       
$query .= "`{$this->prefix}{$data['name']}` (\n\t";
       
$createDefinitons = array();
        foreach (
$data['columns'] as $field )
        {
           
$createDefinitons[] = $this->compileColumnDefinition( $field );
        }
        if( isset(
$data['indexes'] ) )
        {
            foreach (
$data['indexes'] as $index )
            {
                if(
$index['type'] === 'fulltext' )
                {
                   
$data['engine'] = 'MYISAM';
                }
               
$createDefinitons[] = $this->compileIndexDefinition( $index );
            }
        }
       
$query .= implode( ",\n\t", $createDefinitons );
       
$query .= "\n)\n";
       
       
/* Specifying a particular engine? */
       
if( isset( $data['engine'] ) and $data['engine'] )
        {
           
$query .= "ENGINE {$data['engine']} ";
        }
       
       
/* Specify UTF8 */
       
$query .= "CHARACTER SET {$this->charset} COLLATE {$this->collation} ";
       
       
/* Add comment */
       
if( isset( $data['comment'] ) )
        {
           
$query .= "COMMENT '{$this->escape_string( $data['comment'] )}'";
        }

       
/* Do it */
       
return $this->query( $query );
    }
   
   
/**
     * Rename table
     *
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/rename-table.html'>
     * @param    string    $oldName    The current table name
     * @param    string    $newName    The new name
     * @return    void
     */
   
public function renameTable( $oldName, $newName )
    {
        return
$this->query( "RENAME TABLE `{$this->prefix}{$this->escape_string( $oldName )}` TO `{$this->prefix}{$this->escape_string( $newName )}`" );
    }
   
   
/**
     * Alter Table
     * Can only update the comment and engine
     *
     * @param    string            $table        Table name
     * @param    string|null        $comment    Table comment. NULL to not change
     * @param    string|null        $engine        Engine to use. NULL to not change
     * @return    void
     */
   
public function alterTable( $table, $comment=NULL, $engine=NULL )
    {
        if (
$comment === NULL and $engine === NULL )
        {
            return;
        }
       
       
$query = "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` ";
        if (
$comment !== NULL )
        {
           
$query .= "COMMENT='{$this->escape_string( $comment )}' ";
        }
        if (
$engine !== NULL )
        {
           
$query .= "ENGINE={$engine}";
        }
               
        return
$this->query( $query );
    }
   
   
/**
     * Drop table
     *
     * @see        <a href='http://dev.mysql.com/doc/refman/5.1/en/drop-table.html'>DROP TABLE Syntax</a>
     * @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
     */
   
public function dropTable( $table, $ifExists=FALSE, $temporary=FALSE )
    {
       
$prefix = $this->prefix;
       
        return
$this->query(
             
'DROP '
           
. ( $temporary ? 'TEMPORARY ' : '' )
            .
'TABLE '
           
. ( $ifExists ? 'IF EXISTS ' :'' )
            .
implode( ', ', array_map(
                function(
$val ) use ( $prefix )
                {
                    return
'`' . $prefix . $val . '`';
                },
                (
is_array( $table ) ? $table : array( $table ) )
            ) )
        );
    }
   
   
/**
     * Get the table definition for an existing table
     *
     * @see        \IPSUtf8\Db::createTable
     * @param    string    $table    Table Name
     * @return    array    Table definition - see IPSUtf8\Db::createTable for details
     * @throws    \OutOfRangeException
     * @throws    \IPSUtf8\Db\Exception
     */
   
public function getTableDefinition( $table )
    {
       
/* Set name */
       
$definition = array(
           
'name'        => $table,
        );
   
       
/* Fetch columns */
       
$query = $this->query( "SHOW FULL COLUMNS FROM `{$this->prefix}" . $this->escape_string( $table ) . '`' );
       
        if (
$query->num_rows === 0 )
        {
            throw new \
OutOfRangeException;
        }
        while (
$row = $query->fetch_assoc() )
        {
           
/* Set basic information */
           
$columnDefinition = array(
               
'name' => $row['Field'],
               
'type'        => '',
               
'length'    => 0,
               
'decimals'    => NULL,
               
'values'    => array()
            );
               
           
/* Parse the type */
           
if( mb_strpos( $row['Type'], '(' ) !== FALSE )
            {
               
/* First, we need to protect the enum options as they may have spaces before splitting */
               
preg_match( '/(.+?)\((.+?)\)/', $row['Type'], $matches );
               
$options = $matches[2];
               
$type = preg_replace( '/(.+?)\((.+?)\)/', "$1(___TEMP___)", $row['Type'] );
               
$typeInfo = explode( ' ', $type );
               
$typeInfo[0] = str_replace( "___TEMP___", $options, $typeInfo[0] );

               
/* Now we match out the options */
               
preg_match( '/(.+?)\((.+?)\)/', $typeInfo[0], $matches );
               
$columnDefinition['type'] = mb_strtoupper( $matches[1] );
               
                if(
$columnDefinition['type'] === 'ENUM' or $columnDefinition['type'] === 'SET' )
                {
                   
preg_match_all( "/'(.+?)'/", $matches[2], $enum );
                   
$columnDefinition['values'] = $enum[1];
                }
                else
                {                        
                   
$lengthInfo = explode( ',', $matches[2] );
                   
$columnDefinition['length'] = intval( $lengthInfo[0] );
                    if( isset(
$lengthInfo[1] ) )
                    {
                       
$columnDefinition['decimals'] = intval( $lengthInfo[1] );
                    }
                }
            }
            else
            {
               
$typeInfo = explode( ' ', $row['Type'] );

               
$columnDefinition['type'] = mb_strtoupper( $typeInfo[0] );
               
$columnDefinition['length'] = 0;
            }
           
           
/* unsigned? */
           
$columnDefinition['unsigned'] = in_array( 'unsigned', $typeInfo );
           
           
/* zerofill? */
           
$columnDefinition['zerofill'] = in_array( 'zerofill', $typeInfo );
           
           
/* binary? */
           
$columnDefinition['binary'] = ( $row['Collation'] === $this->binaryCollation );
           
           
/* Allow NULL? */
           
$columnDefinition['allow_null'] = ( $row['Null'] === 'YES' );
                       
           
/* Default value */
           
$columnDefinition['default'] = $row['Default'];
           
//if ( $columnDefinition['default'] === NULL and $columnDefinition['type'] != 'DATETIME' and !$columnDefinition['allow_null'] and mb_strpos( $row['Extra'], 'auto_increment' ) === FALSE )
            //{
            //    $columnDefinition['default'] = '';
            //}
           
            /* auto_increment */
           
$columnDefinition['auto_increment'] = mb_strpos( $row['Extra'], 'auto_increment' ) !== FALSE;
           
           
/* Comment */
           
$columnDefinition['comment'] = $row['Comment'] ?: '';
           
           
/* Collation */
           
$columnDefinition['collation'] = $row['Collation'] ?: NULL;
           
           
/* Add it in the definition */
           
ksort( $columnDefinition );
           
$definition['columns'][ $columnDefinition['name'] ] = $columnDefinition;
        }
       
       
/* Fetch indexes */
       
$indexes = array();
       
$query = $this->query( "SHOW INDEXES FROM `{$this->prefix}{$table}`" );
        while (
$row = $query->fetch_assoc() )
        {
           
$length = ( isset( $row['Sub_part'] ) AND ! empty( $row['Sub_part'] ) ) ? intval( $row['Sub_part'] ) : null;
           
            if( isset(
$indexes[ $row['Key_name'] ] ) )
            {
               
$indexes[ $row['Key_name'] ]['length'][]  = $length;
               
$indexes[ $row['Key_name'] ]['columns'][] = $row['Column_name'];
            }
            else
            {
               
$type = 'key';
                if(
$row['Key_name'] === 'PRIMARY' )
                {
                   
$type = 'primary';
                }
                elseif(
$row['Index_type'] === 'FULLTEXT' )
                {
                   
$definition['engine'] = 'MYISAM';
                   
$type = 'fulltext';
                }
                elseif( !
$row['Non_unique'] )
                {
                   
$type = 'unique';
                }
               
               
$indexes[ $row['Key_name'] ] = array(
                   
'type'        => $type,
                   
'name'        => $row['Key_name'],
                   
'length'    => array( $length ),
                   
'columns'    => array( $row['Column_name'] )
                    );
            }
        }
       
$definition['indexes'] = $indexes;
       
       
/* Finally, get the table comment */
       
$row = $this->query( "SHOW TABLE STATUS LIKE '{$table}'" )->fetch_assoc();
       
       
$definition['comment']   = $row['Comment'];
       
$definition['collation'] = $row['Collation'];
       
$definition['engine']     = $row['Engine'];
       
        if ( ! isset(
$definition['engine'] ) )
        {
           
$definition['engine'] = $this->defaultEngine();
        }
       
       
/* Return */
       
return $definition;
    }
   
   
/**
     * Fetches the default engine
     *
     * @return    string
     */
   
public function defaultEngine()
    {
        if (
$this->defaultEngine === NULL )
        {
           
/* If this is an IPB, we should use what is defined in conf_global.php */
           
if ( file_exists( ROOT_PATH . '/conf_global.php' ) )
            {
                require(
ROOT_PATH . '/conf_global.php' );
                if ( isset(
$INFO['mysql_tbl_type'] ) )
                {
                    if (
mb_strtolower( $INFO['mysql_tbl_type'] ) == 'myisam' )
                    {
                       
$this->defaultEngine = 'MyISAM';
                    }
                    else
                    {
                       
$this->defaultEngine = 'InnoDB';
                    }
                   
                    return
$this->defaultEngine;
                }
            }
           
           
$query = $this->query( "SHOW ENGINES" );
           
            while (
$row = $query->fetch_assoc() )
            {
                if ( \
mb_strtolower( $row['Support'] ) === 'default' )
                {
                   
$this->defaultEngine = \mb_strtolower( $row['Engine'] );
                    break;
                }
            }
        }
       
        return
$this->defaultEngine;
    }
   
   
/**
     * Add column to table in database
     *
     * @see        \IPSUtf8\Db::compileColumnDefinition
     * @param    string    $table            Table name
     * @param    array    $definition        Column Definition (see \IPSUtf8\Db::compileColumnDefinition for details)
     * @return    void
     */
   
public function addColumn( $table, $definition )
    {
        return
$this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` ADD COLUMN {$this->compileColumnDefinition( $definition )}" );
    }
   
   
/**
     * Modify an existing column
     *
     * @see        \IPSUtf8\Db::compileColumnDefinition
     * @param    string    $table            Table name
     * @param    string    $column            Column name
     * @param    array    $definition        New column definition (see \IPSUtf8\Db::compileColumnDefinition for details)
     * @return    void
     */
   
public function changeColumn( $table, $column, $definition )
    {
        return
$this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` CHANGE COLUMN `{$this->escape_string( $column )}` {$this->compileColumnDefinition( $definition )}" );
    }
   
   
/**
     * Drop a column
     *
     * @param    string    $table            Table name
     * @param    string    $column            Column name
     * @return    void
     */
   
public function dropColumn( $table, $column )
    {
        return
$this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` DROP COLUMN `{$this->escape_string( $column )}`;" );
    }
   
   
/**
     * Add index to table in database
     *
     * @see        \IPSUtf8\Db::compileIndexDefinition
     * @param    string    $table            Table name
     * @param    array    $definition        Index Definition (see \IPSUtf8\Db::compileIndexDefinition for details)
     * @return    void
     */
   
public function addIndex( $table, $definition )
    {    
        return
$this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` ADD {$this->compileIndexDefinition( $definition )}" );
    }
   
   
/**
     * Modify an existing index
     *
     * @see        \IPSUtf8\Db::compileIndexDefinition
     * @param    string    $table            Table name
     * @param    string    $index            Index name
     * @param    array    $definition        New index definition (see \IPSUtf8\Db::compileIndexDefinition for details)
     * @return    void
     */
   
public function changeIndex( $table, $index, $definition )
    {
       
$this->dropIndex( $table, $index );
       
$this->addIndex( $table, $definition );
    }
   
   
/**
     * Drop an index
     *
     * @param    string    $table            Table name
     * @param    string    $index            Column name
     * @return    void
     */
   
public function dropIndex( $table, $index )
    {
        return
$this->query( "ALTER TABLE `{$this->prefix}{$this->escape_string( $table )}` DROP INDEX `{$this->escape_string( $index )}`;" );
    }
   
   
/**
     * Find In Set
     *
     * @param    string    $column    Column name
     * @param    array    $values    Acceptable values
     * @return     string    Where clause
     */
   
public function in( $column, $values )
    {
       
$where = array();
       
$in    = array();
       
        foreach(
$values as $i )
        {
            if (
$i and is_numeric( $i ) )
            {
               
$where[] = "FIND_IN_SET(" . $i . "," . $column . ")";
            }
            else if (
$i and is_string( $i ) )
            {
               
$in[] = "'" . $this->real_escape_string( $i ) . "'";
            }
        }
       
       
$return = array();
       
        if ( ! empty(
$where ) )
        {
           
$return[] = '( ' . implode( " OR ", $where ) . ' )';
        }
       
        if ( ! empty(
$in ) )
        {
           
$return[] = $column . ' IN(' . implode( ',', $in ) . ')';
        }
       
        if (
count( $return ) )
        {
            return
'(' . implode( ' OR ', $return ) . ')';
        }
        else
        {
            return
'1=1';
        }
    }

}