Commit 2018503c by Qiang Xue

WIP

parent 67a1e172
...@@ -68,11 +68,15 @@ class Command extends \yii\base\Component ...@@ -68,11 +68,15 @@ class Command extends \yii\base\Component
public $fetchMode = \PDO::FETCH_ASSOC; public $fetchMode = \PDO::FETCH_ASSOC;
/** /**
* @var array the parameters (name => value) that are bound to the current PDO statement. * @var array the parameters (name => value) that are bound to the current PDO statement.
* This property is maintained by methods such as [[bindValue()]]. * This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose
* Do not modify it directly. * and is used to generated [[rawSql]]. Do not modify it directly.
*/ */
public $params = []; public $params = [];
/** /**
* @var array pending parameters to be bound to the current PDO statement.
*/
private $_pendingParams = [];
/**
* @var string the SQL statement that this command represents * @var string the SQL statement that this command represents
*/ */
private $_sql; private $_sql;
...@@ -97,6 +101,7 @@ class Command extends \yii\base\Component ...@@ -97,6 +101,7 @@ class Command extends \yii\base\Component
if ($sql !== $this->_sql) { if ($sql !== $this->_sql) {
$this->cancel(); $this->cancel();
$this->_sql = $this->db->quoteSql($sql); $this->_sql = $this->db->quoteSql($sql);
$this->_pendingParams = [];
$this->params = []; $this->params = [];
} }
...@@ -143,21 +148,32 @@ class Command extends \yii\base\Component ...@@ -143,21 +148,32 @@ class Command extends \yii\base\Component
* this may improve performance. * this may improve performance.
* For SQL statement with binding parameters, this method is invoked * For SQL statement with binding parameters, this method is invoked
* automatically. * automatically.
* @param boolean $forRead whether this method is called for a read query. If null, it means
* the SQL statement should be used to determine whether it is for read or write.
* @throws Exception if there is any DB error * @throws Exception if there is any DB error
*/ */
public function prepare() public function prepare($forRead = null)
{ {
if ($this->pdoStatement == null) { if ($this->pdoStatement) {
return;
}
$sql = $this->getSql(); $sql = $this->getSql();
if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
$pdo = $this->db->getReadPdo();
} else {
$pdo = $this->db->getWritePdo();
}
try { try {
$this->pdoStatement = $this->db->pdo->prepare($sql); $this->pdoStatement = $pdo->prepare($sql);
} catch (\Exception $e) { } catch (\Exception $e) {
$message = $e->getMessage() . "\nFailed to prepare SQL: $sql"; $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int) $e->getCode(), $e); throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
} }
} }
}
/** /**
* Cancels the execution of the SQL statement. * Cancels the execution of the SQL statement.
...@@ -184,6 +200,9 @@ class Command extends \yii\base\Component ...@@ -184,6 +200,9 @@ class Command extends \yii\base\Component
public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
{ {
$this->prepare(); $this->prepare();
$this->bindPendingParams();
if ($dataType === null) { if ($dataType === null) {
$dataType = $this->db->getSchema()->getPdoType($value); $dataType = $this->db->getSchema()->getPdoType($value);
} }
...@@ -200,6 +219,18 @@ class Command extends \yii\base\Component ...@@ -200,6 +219,18 @@ class Command extends \yii\base\Component
} }
/** /**
* Binds pending parameters that were registered via [[bindValue()]] and [[bindValues()]].
* Note that this method requires an active [[pdoStatement]].
*/
protected function bindPendingParams()
{
foreach ($this->_pendingParams as $name => $value) {
$this->pdoStatement->bindValue($name, $value[0], $value[1]);
}
$this->_pendingParams = [];
}
/**
* Binds a value to a parameter. * Binds a value to a parameter.
* @param string|integer $name Parameter identifier. For a prepared statement * @param string|integer $name Parameter identifier. For a prepared statement
* using named placeholders, this will be a parameter name of * using named placeholders, this will be a parameter name of
...@@ -212,11 +243,10 @@ class Command extends \yii\base\Component ...@@ -212,11 +243,10 @@ class Command extends \yii\base\Component
*/ */
public function bindValue($name, $value, $dataType = null) public function bindValue($name, $value, $dataType = null)
{ {
$this->prepare();
if ($dataType === null) { if ($dataType === null) {
$dataType = $this->db->getSchema()->getPdoType($value); $dataType = $this->db->getSchema()->getPdoType($value);
} }
$this->pdoStatement->bindValue($name, $value, $dataType); $this->_pendingParams[$name] = [$value, $dataType];
$this->params[$name] = $value; $this->params[$name] = $value;
return $this; return $this;
...@@ -235,19 +265,19 @@ class Command extends \yii\base\Component ...@@ -235,19 +265,19 @@ class Command extends \yii\base\Component
*/ */
public function bindValues($values) public function bindValues($values)
{ {
if (!empty($values)) { if (empty($values)) {
$this->prepare(); return $this;
}
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
if (is_array($value)) { if (is_array($value)) {
$type = $value[1]; $this->_pendingParams[$name] = $value;
$value = $value[0];
} else { } else {
$type = $this->db->getSchema()->getPdoType($value); $type = $this->db->getSchema()->getPdoType($value);
$this->_pendingParams[$name] = [$value, $type];
} }
$this->pdoStatement->bindValue($name, $value, $type);
$this->params[$name] = $value; $this->params[$name] = $value;
} }
}
return $this; return $this;
} }
...@@ -271,11 +301,13 @@ class Command extends \yii\base\Component ...@@ -271,11 +301,13 @@ class Command extends \yii\base\Component
return 0; return 0;
} }
$this->prepare(false);
$this->bindPendingParams();
$token = $rawSql; $token = $rawSql;
try { try {
Yii::beginProfile($token, __METHOD__); Yii::beginProfile($token, __METHOD__);
$this->prepare();
$this->pdoStatement->execute(); $this->pdoStatement->execute();
$n = $this->pdoStatement->rowCount(); $n = $this->pdoStatement->rowCount();
...@@ -390,11 +422,13 @@ class Command extends \yii\base\Component ...@@ -390,11 +422,13 @@ class Command extends \yii\base\Component
} }
} }
$this->prepare(true);
$this->bindPendingParams();
$token = $rawSql; $token = $rawSql;
try { try {
Yii::beginProfile($token, 'yii\db\Command::query'); Yii::beginProfile($token, 'yii\db\Command::query');
$this->prepare();
$this->pdoStatement->execute(); $this->pdoStatement->execute();
if ($method === '') { if ($method === '') {
......
...@@ -456,7 +456,6 @@ class Connection extends Component ...@@ -456,7 +456,6 @@ class Connection extends Component
*/ */
public function createCommand($sql = null, $params = []) public function createCommand($sql = null, $params = [])
{ {
$this->open();
$command = new Command([ $command = new Command([
'db' => $this, 'db' => $this,
'sql' => $sql, 'sql' => $sql,
...@@ -661,6 +660,29 @@ class Connection extends Component ...@@ -661,6 +660,29 @@ class Connection extends Component
} }
/** /**
* Returns the PDO instance for read queries.
* When [[enableSlave]] is true, one of the slaves will be used for read queries, and its PDO instance
* will be returned by this method. If no slave is available, the [[writePdo]] will be returned.
* @return PDO the PDO instance for read queries.
*/
public function getReadPdo()
{
$db = $this->getSlave();
return $db ? $db->pdo : $this->getWritePdo();
}
/**
* Returns the PDO instance for write queries.
* This method will open the master DB connection and then return [[pdo]].
* @return PDO the PDO instance for write queries.
*/
public function getWritePdo()
{
$this->open();
return $this->pdo;
}
/**
* Returns the currently active slave. * Returns the currently active slave.
* If this method is called the first time, it will try to open a slave connection when [[enableSlave]] is true. * If this method is called the first time, it will try to open a slave connection when [[enableSlave]] is true.
* @return Connection the currently active slave. Null is returned if there is slave available. * @return Connection the currently active slave. Null is returned if there is slave available.
...@@ -710,9 +732,11 @@ class Connection extends Component ...@@ -710,9 +732,11 @@ class Connection extends Component
} }
/* @var $slave Connection */ /* @var $slave Connection */
$slave = Yii::createObject($config); $slave = Yii::createObject($config);
if (!isset($slave->attributes[PDO::ATTR_TIMEOUT])) { if (!isset($slave->attributes[PDO::ATTR_TIMEOUT])) {
$slave->attributes[PDO::ATTR_TIMEOUT] = $this->slaveTimeout; $slave->attributes[PDO::ATTR_TIMEOUT] = $this->slaveTimeout;
} }
try { try {
$slave->open(); $slave->open();
return $slave; return $slave;
......
...@@ -369,11 +369,12 @@ abstract class Schema extends Object ...@@ -369,11 +369,12 @@ abstract class Schema extends Object
return $str; return $str;
} }
$this->db->open(); $pdo = $this->db->getReadPdo();
if (($value = $this->db->pdo->quote($str)) !== false) {
return $value;
} else { // the driver doesn't support quote (e.g. oci)
if (($value = $pdo->quote($str)) !== false) {
return $value;
} else {
// the driver doesn't support quote (e.g. oci)
return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
} }
} }
...@@ -520,4 +521,10 @@ abstract class Schema extends Object ...@@ -520,4 +521,10 @@ abstract class Schema extends Object
throw new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e); throw new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e);
} }
} }
public function isReadQuery($sql)
{
$pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
return preg_match($pattern, $sql);
}
} }
...@@ -116,13 +116,14 @@ class Schema extends \yii\db\Schema ...@@ -116,13 +116,14 @@ class Schema extends \yii\db\Schema
return $str; return $str;
} }
$this->db->open(); $pdo = $this->db->getReadPdo();
// workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658
$version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION); $version = $pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION);
if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) { if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) {
return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
} else { } else {
return $this->db->pdo->quote($str); return $pdo->quote($str);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment