2012-11-09 12:18:50 +00:00
|
|
|
<?php defined('SYSPATH') OR die('No direct script access.');
|
2010-08-21 04:43:03 +00:00
|
|
|
/**
|
|
|
|
* MySQL database connection.
|
|
|
|
*
|
|
|
|
* @package Kohana/Database
|
|
|
|
* @category Drivers
|
|
|
|
* @author Kohana Team
|
|
|
|
* @copyright (c) 2008-2009 Kohana Team
|
|
|
|
* @license http://kohanaphp.com/license
|
|
|
|
*/
|
|
|
|
class Kohana_Database_MySQL extends Database {
|
|
|
|
|
|
|
|
// Database in use by each connection
|
|
|
|
protected static $_current_databases = array();
|
|
|
|
|
|
|
|
// Use SET NAMES to set the character set
|
|
|
|
protected static $_set_names;
|
|
|
|
|
|
|
|
// Identifier for this connection within the PHP driver
|
|
|
|
protected $_connection_id;
|
|
|
|
|
|
|
|
// MySQL uses a backtick for identifiers
|
|
|
|
protected $_identifier = '`';
|
|
|
|
|
|
|
|
public function connect()
|
|
|
|
{
|
|
|
|
if ($this->_connection)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (Database_MySQL::$_set_names === NULL)
|
|
|
|
{
|
|
|
|
// Determine if we can use mysql_set_charset(), which is only
|
|
|
|
// available on PHP 5.2.3+ when compiled against MySQL 5.0+
|
|
|
|
Database_MySQL::$_set_names = ! function_exists('mysql_set_charset');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the connection parameters, adding required variabels
|
|
|
|
extract($this->_config['connection'] + array(
|
|
|
|
'database' => '',
|
|
|
|
'hostname' => '',
|
|
|
|
'username' => '',
|
|
|
|
'password' => '',
|
|
|
|
'persistent' => FALSE,
|
|
|
|
));
|
|
|
|
|
|
|
|
// Prevent this information from showing up in traces
|
|
|
|
unset($this->_config['connection']['username'], $this->_config['connection']['password']);
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if ($persistent)
|
|
|
|
{
|
|
|
|
// Create a persistent connection
|
|
|
|
$this->_connection = mysql_pconnect($hostname, $username, $password);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Create a connection and force it to be a new link
|
|
|
|
$this->_connection = mysql_connect($hostname, $username, $password, TRUE);
|
|
|
|
}
|
|
|
|
}
|
2012-11-09 12:18:50 +00:00
|
|
|
catch (Exception $e)
|
2010-08-21 04:43:03 +00:00
|
|
|
{
|
|
|
|
// No connection exists
|
|
|
|
$this->_connection = NULL;
|
|
|
|
|
2011-05-13 06:00:25 +00:00
|
|
|
throw new Database_Exception(':error',
|
2012-11-09 12:18:50 +00:00
|
|
|
array(':error' => $e->getMessage()),
|
|
|
|
$e->getCode());
|
2010-08-21 04:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// \xFF is a better delimiter, but the PHP driver uses underscore
|
|
|
|
$this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
|
|
|
|
|
|
|
|
$this->_select_db($database);
|
|
|
|
|
|
|
|
if ( ! empty($this->_config['charset']))
|
|
|
|
{
|
|
|
|
// Set the character set
|
|
|
|
$this->set_charset($this->_config['charset']);
|
|
|
|
}
|
2012-11-09 12:18:50 +00:00
|
|
|
|
|
|
|
if ( ! empty($this->_config['connection']['variables']))
|
|
|
|
{
|
|
|
|
// Set session variables
|
|
|
|
$variables = array();
|
|
|
|
|
|
|
|
foreach ($this->_config['connection']['variables'] as $var => $val)
|
|
|
|
{
|
|
|
|
$variables[] = 'SESSION '.$var.' = '.$this->quote($val);
|
|
|
|
}
|
|
|
|
|
|
|
|
mysql_query('SET '.implode(', ', $variables), $this->_connection);
|
|
|
|
}
|
2010-08-21 04:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Select the database
|
|
|
|
*
|
2012-11-09 12:18:50 +00:00
|
|
|
* @param string $database Database
|
2010-08-21 04:43:03 +00:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected function _select_db($database)
|
|
|
|
{
|
|
|
|
if ( ! mysql_select_db($database, $this->_connection))
|
|
|
|
{
|
|
|
|
// Unable to select database
|
|
|
|
throw new Database_Exception(':error',
|
|
|
|
array(':error' => mysql_error($this->_connection)),
|
|
|
|
mysql_errno($this->_connection));
|
|
|
|
}
|
|
|
|
|
|
|
|
Database_MySQL::$_current_databases[$this->_connection_id] = $database;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function disconnect()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Database is assumed disconnected
|
|
|
|
$status = TRUE;
|
|
|
|
|
|
|
|
if (is_resource($this->_connection))
|
|
|
|
{
|
|
|
|
if ($status = mysql_close($this->_connection))
|
|
|
|
{
|
|
|
|
// Clear the connection
|
|
|
|
$this->_connection = NULL;
|
2011-05-13 06:00:25 +00:00
|
|
|
|
|
|
|
// Clear the instance
|
|
|
|
parent::disconnect();
|
2010-08-21 04:43:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception $e)
|
|
|
|
{
|
|
|
|
// Database is probably not disconnected
|
|
|
|
$status = ! is_resource($this->_connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $status;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function set_charset($charset)
|
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
|
|
|
if (Database_MySQL::$_set_names === TRUE)
|
|
|
|
{
|
|
|
|
// PHP is compiled against MySQL 4.x
|
|
|
|
$status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->_connection);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// PHP is compiled against MySQL 5.x
|
|
|
|
$status = mysql_set_charset($charset, $this->_connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($status === FALSE)
|
|
|
|
{
|
|
|
|
throw new Database_Exception(':error',
|
|
|
|
array(':error' => mysql_error($this->_connection)),
|
|
|
|
mysql_errno($this->_connection));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-13 06:00:25 +00:00
|
|
|
public function query($type, $sql, $as_object = FALSE, array $params = NULL)
|
2010-08-21 04:43:03 +00:00
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
2012-11-09 12:18:50 +00:00
|
|
|
if (Kohana::$profiling)
|
2010-08-21 04:43:03 +00:00
|
|
|
{
|
|
|
|
// Benchmark this query for the current instance
|
|
|
|
$benchmark = Profiler::start("Database ({$this->_instance})", $sql);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty($this->_config['connection']['persistent']) AND $this->_config['connection']['database'] !== Database_MySQL::$_current_databases[$this->_connection_id])
|
|
|
|
{
|
|
|
|
// Select database on persistent connections
|
|
|
|
$this->_select_db($this->_config['connection']['database']);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the query
|
|
|
|
if (($result = mysql_query($sql, $this->_connection)) === FALSE)
|
|
|
|
{
|
|
|
|
if (isset($benchmark))
|
|
|
|
{
|
|
|
|
// This benchmark is worthless
|
|
|
|
Profiler::delete($benchmark);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Database_Exception(':error [ :query ]',
|
|
|
|
array(':error' => mysql_error($this->_connection), ':query' => $sql),
|
|
|
|
mysql_errno($this->_connection));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($benchmark))
|
|
|
|
{
|
|
|
|
Profiler::stop($benchmark);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the last query
|
|
|
|
$this->last_query = $sql;
|
|
|
|
|
|
|
|
if ($type === Database::SELECT)
|
|
|
|
{
|
|
|
|
// Return an iterator of results
|
2011-05-13 06:00:25 +00:00
|
|
|
return new Database_MySQL_Result($result, $sql, $as_object, $params);
|
2010-08-21 04:43:03 +00:00
|
|
|
}
|
|
|
|
elseif ($type === Database::INSERT)
|
|
|
|
{
|
|
|
|
// Return a list of insert id and rows created
|
|
|
|
return array(
|
|
|
|
mysql_insert_id($this->_connection),
|
|
|
|
mysql_affected_rows($this->_connection),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Return the number of rows affected
|
|
|
|
return mysql_affected_rows($this->_connection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function datatype($type)
|
|
|
|
{
|
|
|
|
static $types = array
|
|
|
|
(
|
|
|
|
'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'),
|
|
|
|
'bool' => array('type' => 'bool'),
|
|
|
|
'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
|
|
|
|
'datetime' => array('type' => 'string'),
|
|
|
|
'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
|
|
|
'double' => array('type' => 'float'),
|
|
|
|
'double precision unsigned' => array('type' => 'float', 'min' => '0'),
|
|
|
|
'double unsigned' => array('type' => 'float', 'min' => '0'),
|
|
|
|
'enum' => array('type' => 'string'),
|
|
|
|
'fixed' => array('type' => 'float', 'exact' => TRUE),
|
|
|
|
'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
|
|
|
'float unsigned' => array('type' => 'float', 'min' => '0'),
|
|
|
|
'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
|
|
|
|
'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
|
|
|
|
'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'),
|
|
|
|
'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
|
|
|
|
'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'),
|
|
|
|
'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
|
|
|
|
'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
|
|
|
|
'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
|
|
|
|
'national varchar' => array('type' => 'string'),
|
|
|
|
'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
|
|
|
'nvarchar' => array('type' => 'string'),
|
|
|
|
'point' => array('type' => 'string', 'binary' => TRUE),
|
|
|
|
'real unsigned' => array('type' => 'float', 'min' => '0'),
|
|
|
|
'set' => array('type' => 'string'),
|
|
|
|
'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
|
|
|
|
'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
|
|
|
|
'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'),
|
|
|
|
'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
|
|
|
|
'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
|
|
|
|
'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
|
|
|
|
'year' => array('type' => 'string'),
|
|
|
|
);
|
|
|
|
|
|
|
|
$type = str_replace(' zerofill', '', $type);
|
|
|
|
|
|
|
|
if (isset($types[$type]))
|
|
|
|
return $types[$type];
|
|
|
|
|
|
|
|
return parent::datatype($type);
|
|
|
|
}
|
|
|
|
|
2011-05-13 06:00:25 +00:00
|
|
|
/**
|
|
|
|
* Start a SQL transaction
|
|
|
|
*
|
|
|
|
* @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
|
|
|
|
*
|
2012-11-09 12:18:50 +00:00
|
|
|
* @param string $mode Isolation level
|
2011-05-13 06:00:25 +00:00
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function begin($mode = NULL)
|
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
|
|
|
if ($mode AND ! mysql_query("SET TRANSACTION ISOLATION LEVEL $mode", $this->_connection))
|
|
|
|
{
|
|
|
|
throw new Database_Exception(':error',
|
|
|
|
array(':error' => mysql_error($this->_connection)),
|
|
|
|
mysql_errno($this->_connection));
|
|
|
|
}
|
|
|
|
|
|
|
|
return (bool) mysql_query('START TRANSACTION', $this->_connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Commit a SQL transaction
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function commit()
|
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
|
|
|
return (bool) mysql_query('COMMIT', $this->_connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rollback a SQL transaction
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function rollback()
|
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
|
|
|
return (bool) mysql_query('ROLLBACK', $this->_connection);
|
|
|
|
}
|
|
|
|
|
2010-08-21 04:43:03 +00:00
|
|
|
public function list_tables($like = NULL)
|
|
|
|
{
|
|
|
|
if (is_string($like))
|
|
|
|
{
|
|
|
|
// Search for table names
|
|
|
|
$result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Find all table names
|
|
|
|
$result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
$tables = array();
|
|
|
|
foreach ($result as $row)
|
|
|
|
{
|
|
|
|
$tables[] = reset($row);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tables;
|
|
|
|
}
|
|
|
|
|
2011-05-13 06:00:25 +00:00
|
|
|
public function list_columns($table, $like = NULL, $add_prefix = TRUE)
|
2010-08-21 04:43:03 +00:00
|
|
|
{
|
|
|
|
// Quote the table name
|
2011-05-13 06:00:25 +00:00
|
|
|
$table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table;
|
2010-08-21 04:43:03 +00:00
|
|
|
|
|
|
|
if (is_string($like))
|
|
|
|
{
|
|
|
|
// Search for column names
|
|
|
|
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Find all column names
|
|
|
|
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
$columns = array();
|
|
|
|
foreach ($result as $row)
|
|
|
|
{
|
|
|
|
list($type, $length) = $this->_parse_type($row['Type']);
|
|
|
|
|
|
|
|
$column = $this->datatype($type);
|
|
|
|
|
|
|
|
$column['column_name'] = $row['Field'];
|
|
|
|
$column['column_default'] = $row['Default'];
|
|
|
|
$column['data_type'] = $type;
|
|
|
|
$column['is_nullable'] = ($row['Null'] == 'YES');
|
|
|
|
$column['ordinal_position'] = ++$count;
|
|
|
|
|
|
|
|
switch ($column['type'])
|
|
|
|
{
|
|
|
|
case 'float':
|
|
|
|
if (isset($length))
|
|
|
|
{
|
|
|
|
list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'int':
|
|
|
|
if (isset($length))
|
|
|
|
{
|
|
|
|
// MySQL attribute
|
|
|
|
$column['display'] = $length;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'string':
|
|
|
|
switch ($column['data_type'])
|
|
|
|
{
|
|
|
|
case 'binary':
|
|
|
|
case 'varbinary':
|
|
|
|
$column['character_maximum_length'] = $length;
|
|
|
|
break;
|
|
|
|
case 'char':
|
|
|
|
case 'varchar':
|
|
|
|
$column['character_maximum_length'] = $length;
|
|
|
|
case 'text':
|
|
|
|
case 'tinytext':
|
|
|
|
case 'mediumtext':
|
|
|
|
case 'longtext':
|
|
|
|
$column['collation_name'] = $row['Collation'];
|
|
|
|
break;
|
|
|
|
case 'enum':
|
|
|
|
case 'set':
|
|
|
|
$column['collation_name'] = $row['Collation'];
|
|
|
|
$column['options'] = explode('\',\'', substr($length, 1, -1));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MySQL attributes
|
|
|
|
$column['comment'] = $row['Comment'];
|
|
|
|
$column['extra'] = $row['Extra'];
|
|
|
|
$column['key'] = $row['Key'];
|
|
|
|
$column['privileges'] = $row['Privileges'];
|
|
|
|
|
|
|
|
$columns[$row['Field']] = $column;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function escape($value)
|
|
|
|
{
|
|
|
|
// Make sure the database is connected
|
|
|
|
$this->_connection or $this->connect();
|
|
|
|
|
2011-05-13 06:00:25 +00:00
|
|
|
if (($value = mysql_real_escape_string( (string) $value, $this->_connection)) === FALSE)
|
2010-08-21 04:43:03 +00:00
|
|
|
{
|
|
|
|
throw new Database_Exception(':error',
|
2011-05-13 06:00:25 +00:00
|
|
|
array(':error' => mysql_error($this->_connection)),
|
|
|
|
mysql_errno($this->_connection));
|
2010-08-21 04:43:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SQL standard is to use single-quotes for all values
|
|
|
|
return "'$value'";
|
|
|
|
}
|
|
|
|
|
|
|
|
} // End Database_MySQL
|