Commit 2018503c by Qiang Xue

WIP

parent 67a1e172
......@@ -68,11 +68,15 @@ class Command extends \yii\base\Component
public $fetchMode = \PDO::FETCH_ASSOC;
/**
* @var array the parameters (name => value) that are bound to the current PDO statement.
* This property is maintained by methods such as [[bindValue()]].
* Do not modify it directly.
* This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose
* and is used to generated [[rawSql]]. Do not modify it directly.
*/
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
*/
private $_sql;
......@@ -97,6 +101,7 @@ class Command extends \yii\base\Component
if ($sql !== $this->_sql) {
$this->cancel();
$this->_sql = $this->db->quoteSql($sql);
$this->_pendingParams = [];
$this->params = [];
}
......@@ -143,21 +148,32 @@ class Command extends \yii\base\Component
* this may improve performance.
* For SQL statement with binding parameters, this method is invoked
* 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
*/
public function prepare()
public function prepare($forRead = null)
{
if ($this->pdoStatement == null) {
if ($this->pdoStatement) {
return;
}
$sql = $this->getSql();
if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
$pdo = $this->db->getReadPdo();
} else {
$pdo = $this->db->getWritePdo();
}
try {
$this->pdoStatement = $this->db->pdo->prepare($sql);
$this->pdoStatement = $pdo->prepare($sql);
} catch (\Exception $e) {
$message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
}
}
}
/**
* Cancels the execution of the SQL statement.
......@@ -184,6 +200,9 @@ class Command extends \yii\base\Component
public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
{
$this->prepare();
$this->bindPendingParams();
if ($dataType === null) {
$dataType = $this->db->getSchema()->getPdoType($value);
}
......@@ -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.
* @param string|integer $name Parameter identifier. For a prepared statement
* using named placeholders, this will be a parameter name of
......@@ -212,11 +243,10 @@ class Command extends \yii\base\Component
*/
public function bindValue($name, $value, $dataType = null)
{
$this->prepare();
if ($dataType === null) {
$dataType = $this->db->getSchema()->getPdoType($value);
}
$this->pdoStatement->bindValue($name, $value, $dataType);
$this->_pendingParams[$name] = [$value, $dataType];
$this->params[$name] = $value;
return $this;
......@@ -235,19 +265,19 @@ class Command extends \yii\base\Component
*/
public function bindValues($values)
{
if (!empty($values)) {
$this->prepare();
if (empty($values)) {
return $this;
}
foreach ($values as $name => $value) {
if (is_array($value)) {
$type = $value[1];
$value = $value[0];
$this->_pendingParams[$name] = $value;
} else {
$type = $this->db->getSchema()->getPdoType($value);
$this->_pendingParams[$name] = [$value, $type];
}
$this->pdoStatement->bindValue($name, $value, $type);
$this->params[$name] = $value;
}
}
return $this;
}
......@@ -271,11 +301,13 @@ class Command extends \yii\base\Component
return 0;
}
$this->prepare(false);
$this->bindPendingParams();
$token = $rawSql;
try {
Yii::beginProfile($token, __METHOD__);
$this->prepare();
$this->pdoStatement->execute();
$n = $this->pdoStatement->rowCount();
......@@ -390,11 +422,13 @@ class Command extends \yii\base\Component
}
}
$this->prepare(true);
$this->bindPendingParams();
$token = $rawSql;
try {
Yii::beginProfile($token, 'yii\db\Command::query');
$this->prepare();
$this->pdoStatement->execute();
if ($method === '') {
......
......@@ -456,7 +456,6 @@ class Connection extends Component
*/
public function createCommand($sql = null, $params = [])
{
$this->open();
$command = new Command([
'db' => $this,
'sql' => $sql,
......@@ -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.
* 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.
......@@ -710,9 +732,11 @@ class Connection extends Component
}
/* @var $slave Connection */
$slave = Yii::createObject($config);
if (!isset($slave->attributes[PDO::ATTR_TIMEOUT])) {
$slave->attributes[PDO::ATTR_TIMEOUT] = $this->slaveTimeout;
}
try {
$slave->open();
return $slave;
......
......@@ -369,11 +369,12 @@ abstract class Schema extends Object
return $str;
}
$this->db->open();
if (($value = $this->db->pdo->quote($str)) !== false) {
return $value;
} else { // the driver doesn't support quote (e.g. oci)
$pdo = $this->db->getReadPdo();
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") . "'";
}
}
......@@ -520,4 +521,10 @@ abstract class Schema extends Object
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
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
$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', '<=')) {
return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
} 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