From c1b5946d4048b41b6d5019c2a0841d7f37c148ae Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 22 Nov 2013 13:26:42 +0200 Subject: [PATCH] yii\sphinx\Command reworked to extend yii\db\Command. yii\sphinx\DataReader removed. --- extensions/sphinx/Command.php | 554 +++++++-------------------- extensions/sphinx/DataReader.php | 265 ------------- tests/unit/extensions/sphinx/CommandTest.php | 3 +- 3 files changed, 134 insertions(+), 688 deletions(-) delete mode 100644 extensions/sphinx/DataReader.php diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php index 2c0b5ed..93c02f8 100644 --- a/extensions/sphinx/Command.php +++ b/extensions/sphinx/Command.php @@ -8,9 +8,7 @@ namespace yii\sphinx; use Yii; -use yii\base\Component; -use yii\caching\Cache; -use yii\db\Exception; +use yii\base\NotSupportedException; /** * Command represents a SQL statement to be executed against a Sphinx. @@ -41,406 +39,14 @@ use yii\db\Exception; * * To build SELECT SQL statements, please use [[Query]] and [[QueryBuilder]] instead. * - * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in - * [[sql]]. This property is read-only. - * @property string $sql The SQL statement to be executed. + * @property \yii\sphinx\Connection $db the Sphinx connection that this command is associated with. * * @author Paul Klimov * @since 2.0 */ -class Command extends Component +class Command extends \yii\db\Command { /** - * @var Connection the Sphinx connection that this command is associated with - */ - public $db; - /** - * @var \PDOStatement the PDOStatement object that this command is associated with - */ - public $pdoStatement; - /** - * @var integer the default fetch mode for this command. - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - */ - 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. - */ - public $params = []; - /** - * @var string the SphinxQL statement that this command represents - */ - private $_sql; - - /** - * Returns the SQL statement for this command. - * @return string the SQL statement to be executed - */ - public function getSql() - { - return $this->_sql; - } - - /** - * Specifies the SQL statement to be executed. - * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. - * @param string $sql the SQL statement to be set. - * @return static this command instance - */ - public function setSql($sql) - { - if ($sql !== $this->_sql) { - $this->cancel(); - $this->_sql = $this->db->quoteSql($sql); - $this->params = []; - } - return $this; - } - - /** - * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]]. - * Note that the return value of this method should mainly be used for logging purpose. - * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders. - * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]]. - */ - public function getRawSql() - { - if (empty($this->params)) { - return $this->_sql; - } else { - $params = []; - foreach ($this->params as $name => $value) { - if (is_string($value)) { - $params[$name] = $this->db->quoteValue($value); - } elseif ($value === null) { - $params[$name] = 'NULL'; - } else { - $params[$name] = $value; - } - } - if (isset($params[1])) { - $sql = ''; - foreach (explode('?', $this->_sql) as $i => $part) { - $sql .= (isset($params[$i]) ? $params[$i] : '') . $part; - } - return $sql; - } else { - return strtr($this->_sql, $params); - } - } - } - - /** - * Prepares the SQL statement to be executed. - * For complex SQL statement that is to be executed multiple times, - * this may improve performance. - * For SQL statement with binding parameters, this method is invoked - * automatically. - * @throws Exception if there is any DB error - */ - public function prepare() - { - if ($this->pdoStatement == null) { - $sql = $this->getSql(); - try { - $this->pdoStatement = $this->db->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. - * This method mainly sets [[pdoStatement]] to be null. - */ - public function cancel() - { - $this->pdoStatement = null; - } - - /** - * Binds a parameter to the SQL statement to be executed. - * @param string|integer $name parameter identifier. For a prepared statement - * using named placeholders, this will be a parameter name of - * the form `:name`. For a prepared statement using question mark - * placeholders, this will be the 1-indexed position of the parameter. - * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter - * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @param integer $length length of the data type - * @param mixed $driverOptions the driver-specific options - * @return static the current command being executed - * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php - */ - public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) - { - $this->prepare(); - if ($dataType === null) { - $dataType = $this->db->getSchema()->getPdoType($value); - } - if ($length === null) { - $this->pdoStatement->bindParam($name, $value, $dataType); - } elseif ($driverOptions === null) { - $this->pdoStatement->bindParam($name, $value, $dataType, $length); - } else { - $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); - } - $this->params[$name] =& $value; - return $this; - } - - /** - * 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 - * the form `:name`. For a prepared statement using question mark - * placeholders, this will be the 1-indexed position of the parameter. - * @param mixed $value The value to bind to the parameter - * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @return static the current command being executed - * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php - */ - 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->params[$name] = $value; - return $this; - } - - /** - * Binds a list of values to the corresponding parameters. - * This is similar to [[bindValue()]] except that it binds multiple values at a time. - * Note that the SQL data type of each value is determined by its PHP type. - * @param array $values the values to be bound. This must be given in terms of an associative - * array with array keys being the parameter names, and array values the corresponding parameter values, - * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined - * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`, - * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`. - * @return static the current command being executed - */ - public function bindValues($values) - { - if (!empty($values)) { - $this->prepare(); - foreach ($values as $name => $value) { - if (is_array($value)) { - $type = $value[1]; - $value = $value[0]; - } else { - $type = $this->db->getSchema()->getPdoType($value); - } - $this->pdoStatement->bindValue($name, $value, $type); - $this->params[$name] = $value; - } - } - return $this; - } - - /** - * Executes the SQL statement. - * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. - * No result set will be returned. - * @return integer number of rows affected by the execution. - * @throws Exception execution failed - */ - public function execute() - { - $sql = $this->getSql(); - - $rawSql = $this->getRawSql(); - - Yii::trace($rawSql, __METHOD__); - - if ($sql == '') { - return 0; - } - - $token = $rawSql; - try { - Yii::beginProfile($token, __METHOD__); - - $this->prepare(); - $this->pdoStatement->execute(); - $n = $this->pdoStatement->rowCount(); - - Yii::endProfile($token, __METHOD__); - return $n; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); - } - } - - /** - * Executes the SQL statement and returns query result. - * This method is for executing a SQL query that returns result set, such as `SELECT`. - * @return DataReader the reader object for fetching the query result - * @throws Exception execution failed - */ - public function query() - { - return $this->queryInternal(''); - } - - /** - * Executes the SQL statement and returns ALL rows at once. - * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array all rows of the query result. Each array element is an array representing a row of data. - * An empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryAll($fetchMode = null) - { - return $this->queryInternal('fetchAll', $fetchMode); - } - - /** - * Executes the SQL statement and returns the first row of the result. - * This method is best used when only the first row of result is needed for a query. - * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query - * results in nothing. - * @throws Exception execution failed - */ - public function queryOne($fetchMode = null) - { - return $this->queryInternal('fetch', $fetchMode); - } - - /** - * Executes the SQL statement and returns the value of the first column in the first row of data. - * This method is best used when only a single value is needed for a query. - * @return string|boolean the value of the first column in the first row of the query result. - * False is returned if there is no value. - * @throws Exception execution failed - */ - public function queryScalar() - { - $result = $this->queryInternal('fetchColumn', 0); - if (is_resource($result) && get_resource_type($result) === 'stream') { - return stream_get_contents($result); - } else { - return $result; - } - } - - /** - * Executes the SQL statement and returns the first column of the result. - * This method is best used when only the first column of result (i.e. the first element in each row) - * is needed for a query. - * @return array the first column of the query result. Empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryColumn() - { - return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN); - } - - /** - * Performs the actual DB query of a SQL statement. - * @param string $method method of PDOStatement to be called - * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return mixed the method execution result - * @throws Exception if the query causes any problem - */ - private function queryInternal($method, $fetchMode = null) - { - $db = $this->db; - $rawSql = $this->getRawSql(); - - Yii::trace($rawSql, __METHOD__); - - /** @var $cache \yii\caching\Cache */ - if ($db->enableQueryCache && $method !== '') { - $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache; - } - - if (isset($cache) && $cache instanceof Cache) { - $cacheKey = [ - __CLASS__, - $db->dsn, - $db->username, - $rawSql, - ]; - if (($result = $cache->get($cacheKey)) !== false) { - Yii::trace('Query result served from cache', __METHOD__); - return $result; - } - } - - $token = $rawSql; - try { - Yii::beginProfile($token, __METHOD__); - - $this->prepare(); - $this->pdoStatement->execute(); - - if ($method === '') { - $result = new DataReader($this); - } else { - if ($fetchMode === null) { - $fetchMode = $this->fetchMode; - } - $result = call_user_func_array([$this->pdoStatement, $method], (array)$fetchMode); - $this->pdoStatement->closeCursor(); - } - - Yii::endProfile($token, __METHOD__); - - if (isset($cache, $cacheKey) && $cache instanceof Cache) { - $cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency); - Yii::trace('Saved query result in cache', __METHOD__); - } - - return $result; - } catch (\Exception $e) { - Yii::endProfile($token, __METHOD__); - $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($message, $errorInfo, (int)$e->getCode(), $e); - } - } - - /** - * Creates an INSERT command. - * For example, - * - * ~~~ - * $connection->createCommand()->insert('idx_user', [ - * 'name' => 'Sam', - * 'age' => 30, - * ])->execute(); - * ~~~ - * - * The method will properly escape the column names, and bind the values to be inserted. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $index the index that new rows will be inserted into. - * @param array $columns the column data (name => value) to be inserted into the index. - * @return static the command object itself - */ - public function insert($index, $columns) - { - $params = []; - $sql = $this->db->getQueryBuilder()->insert($index, $columns, $params); - return $this->setSql($sql)->bindValues($params); - } - - /** * Creates a batch INSERT command. * For example, * @@ -545,30 +151,6 @@ class Command extends Component } /** - * Creates a DELETE command. - * For example, - * - * ~~~ - * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute(); - * ~~~ - * - * The method will properly escape the index and column names. - * - * Note that the created command is not executed until [[execute()]] is called. - * - * @param string $index the index where the data will be deleted from. - * @param string|array $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the command - * @return static the command object itself - */ - public function delete($index, $condition = '', $params = []) - { - $sql = $this->db->getQueryBuilder()->delete($index, $condition, $params); - return $this->setSql($sql)->bindValues($params); - } - - /** * Creates a SQL command for truncating a runtime index. * @param string $index the index to be truncated. The name will be properly quoted by the method. * @return static the command object itself @@ -608,4 +190,134 @@ class Command extends Component $sql = $this->db->getQueryBuilder()->callKeywords($index, $text, $fetchStatistic, $params); return $this->setSql($sql)->bindValues($params); } + + // Not Supported : + + /** + * @inheritdoc + */ + public function createTable($table, $columns, $options = null) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function renameTable($table, $newName) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function dropTable($table) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function truncateTable($table) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function addColumn($table, $column, $type) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function dropColumn($table, $column) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function renameColumn($table, $oldName, $newName) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function alterColumn($table, $column, $type) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function addPrimaryKey($name, $table, $columns) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function dropPrimaryKey($name, $table) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function dropForeignKey($name, $table) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function createIndex($name, $table, $columns, $unique = false) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function dropIndex($name, $table) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function resetSequence($table, $value = null) + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } + + /** + * @inheritdoc + */ + public function checkIntegrity($check = true, $schema = '') + { + throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); + } } \ No newline at end of file diff --git a/extensions/sphinx/DataReader.php b/extensions/sphinx/DataReader.php deleted file mode 100644 index 4b8ffe5..0000000 --- a/extensions/sphinx/DataReader.php +++ /dev/null @@ -1,265 +0,0 @@ -query('SELECT * FROM idx_post'); - * - * while ($row = $reader->read()) { - * $rows[] = $row; - * } - * - * // equivalent to: - * foreach ($reader as $row) { - * $rows[] = $row; - * } - * - * // equivalent to: - * $rows = $reader->readAll(); - * ~~~ - * - * Note that since DataReader is a forward-only stream, you can only traverse it once. - * Doing it the second time will throw an exception. - * - * It is possible to use a specific mode of data fetching by setting - * [[fetchMode]]. See the [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for more details about possible fetch mode. - * - * @property integer $columnCount The number of columns in the result set. This property is read-only. - * @property integer $fetchMode Fetch mode. This property is write-only. - * @property boolean $isClosed Whether the reader is closed or not. This property is read-only. - * @property integer $rowCount Number of rows contained in the result. This property is read-only. - * - * @author Qiang Xue - * @since 2.0 - */ -class DataReader extends Object implements \Iterator, \Countable -{ - /** - * @var \PDOStatement the PDOStatement associated with the command - */ - private $_statement; - private $_closed = false; - private $_row; - private $_index = -1; - - /** - * Constructor. - * @param Command $command the command generating the query result - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct(Command $command, $config = []) - { - $this->_statement = $command->pdoStatement; - $this->_statement->setFetchMode(\PDO::FETCH_ASSOC); - parent::__construct($config); - } - - /** - * Binds a column to a PHP variable. - * When rows of data are being fetched, the corresponding column value - * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND. - * @param integer|string $column Number of the column (1-indexed) or name of the column - * in the result set. If using the column name, be aware that the name - * should match the case of the column, as returned by the driver. - * @param mixed $value Name of the PHP variable to which the column will be bound. - * @param integer $dataType Data type of the parameter - * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php - */ - public function bindColumn($column, &$value, $dataType = null) - { - if ($dataType === null) { - $this->_statement->bindColumn($column, $value); - } else { - $this->_statement->bindColumn($column, $value, $dataType); - } - } - - /** - * Set the default fetch mode for this statement - * @param integer $mode fetch mode - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - */ - public function setFetchMode($mode) - { - $params = func_get_args(); - call_user_func_array([$this->_statement, 'setFetchMode'], $params); - } - - /** - * Advances the reader to the next row in a result set. - * @return array the current row, false if no more row available - */ - public function read() - { - return $this->_statement->fetch(); - } - - /** - * Returns a single column from the next row of a result set. - * @param integer $columnIndex zero-based column index - * @return mixed the column of the current row, false if no more rows available - */ - public function readColumn($columnIndex) - { - return $this->_statement->fetchColumn($columnIndex); - } - - /** - * Returns an object populated with the next row of data. - * @param string $className class name of the object to be created and populated - * @param array $fields Elements of this array are passed to the constructor - * @return mixed the populated object, false if no more row of data available - */ - public function readObject($className, $fields) - { - return $this->_statement->fetchObject($className, $fields); - } - - /** - * Reads the whole result set into an array. - * @return array the result set (each array element represents a row of data). - * An empty array will be returned if the result contains no row. - */ - public function readAll() - { - return $this->_statement->fetchAll(); - } - - /** - * Advances the reader to the next result when reading the results of a batch of statements. - * This method is only useful when there are multiple result sets - * returned by the query. Not all DBMS support this feature. - * @return boolean Returns true on success or false on failure. - */ - public function nextResult() - { - if (($result = $this->_statement->nextRowset()) !== false) { - $this->_index = -1; - } - return $result; - } - - /** - * Closes the reader. - * This frees up the resources allocated for executing this SQL statement. - * Read attempts after this method call are unpredictable. - */ - public function close() - { - $this->_statement->closeCursor(); - $this->_closed = true; - } - - /** - * whether the reader is closed or not. - * @return boolean whether the reader is closed or not. - */ - public function getIsClosed() - { - return $this->_closed; - } - - /** - * Returns the number of rows in the result set. - * Note, most DBMS may not give a meaningful count. - * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. - * @return integer number of rows contained in the result. - */ - public function getRowCount() - { - return $this->_statement->rowCount(); - } - - /** - * Returns the number of rows in the result set. - * This method is required by the Countable interface. - * Note, most DBMS may not give a meaningful count. - * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. - * @return integer number of rows contained in the result. - */ - public function count() - { - return $this->getRowCount(); - } - - /** - * Returns the number of columns in the result set. - * Note, even there's no row in the reader, this still gives correct column number. - * @return integer the number of columns in the result set. - */ - public function getColumnCount() - { - return $this->_statement->columnCount(); - } - - /** - * Resets the iterator to the initial state. - * This method is required by the interface Iterator. - * @throws InvalidCallException if this method is invoked twice - */ - public function rewind() - { - if ($this->_index < 0) { - $this->_row = $this->_statement->fetch(); - $this->_index = 0; - } else { - throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.'); - } - } - - /** - * Returns the index of the current row. - * This method is required by the interface Iterator. - * @return integer the index of the current row. - */ - public function key() - { - return $this->_index; - } - - /** - * Returns the current row. - * This method is required by the interface Iterator. - * @return mixed the current row. - */ - public function current() - { - return $this->_row; - } - - /** - * Moves the internal pointer to the next row. - * This method is required by the interface Iterator. - */ - public function next() - { - $this->_row = $this->_statement->fetch(); - $this->_index++; - } - - /** - * Returns whether there is a row of data at current position. - * This method is required by the interface Iterator. - * @return boolean whether there is a row of data at current position. - */ - public function valid() - { - return $this->_row !== false; - } -} \ No newline at end of file diff --git a/tests/unit/extensions/sphinx/CommandTest.php b/tests/unit/extensions/sphinx/CommandTest.php index e83fad2..b2346ef 100644 --- a/tests/unit/extensions/sphinx/CommandTest.php +++ b/tests/unit/extensions/sphinx/CommandTest.php @@ -2,8 +2,7 @@ namespace yiiunit\extensions\sphinx; -use yii\sphinx\DataReader; -use yii\db\Expression; +use yii\db\DataReader; /** * @group sphinx