Qiang Xue
11 years ago
34 changed files with 6161 additions and 1 deletions
@ -0,0 +1,204 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\base\InvalidCallException; |
||||||
|
use yii\db\ActiveQueryInterface; |
||||||
|
use yii\db\ActiveQueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveQuery represents a Sphinx query associated with an Active Record class. |
||||||
|
* |
||||||
|
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]]. |
||||||
|
* |
||||||
|
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], |
||||||
|
* [[orderBy()]] to customize the query options. |
||||||
|
* |
||||||
|
* ActiveQuery also provides the following additional query options: |
||||||
|
* |
||||||
|
* - [[with()]]: list of relations that this query should be performed with. |
||||||
|
* - [[indexBy()]]: the name of the column by which the query result should be indexed. |
||||||
|
* - [[asArray()]]: whether to return each record as an array. |
||||||
|
* |
||||||
|
* These options can be configured using methods of the same name. For example: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $articles = Article::find()->with('source')->asArray()->all(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* ActiveQuery allows to build the snippets using sources provided by ActiveRecord. |
||||||
|
* You can use [[snippetByModel()]] method to enable this. |
||||||
|
* For example: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* class Article extends ActiveRecord |
||||||
|
* { |
||||||
|
* public function getSource() |
||||||
|
* { |
||||||
|
* return $this->hasOne('db', ArticleDb::className(), ['id' => 'id']); |
||||||
|
* } |
||||||
|
* |
||||||
|
* public function getSnippetSource() |
||||||
|
* { |
||||||
|
* return $this->source->content; |
||||||
|
* } |
||||||
|
* |
||||||
|
* ... |
||||||
|
* } |
||||||
|
* |
||||||
|
* $articles = Article::find()->with('source')->snippetByModel()->all(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveQuery extends Query implements ActiveQueryInterface |
||||||
|
{ |
||||||
|
use ActiveQueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var string the SQL statement to be executed for retrieving AR records. |
||||||
|
* This is set by [[ActiveRecord::findBySql()]]. |
||||||
|
*/ |
||||||
|
public $sql; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the [[snippetCallback]] to [[fetchSnippetSourceFromModels()]], which allows to |
||||||
|
* fetch the snippet source strings from the Active Record models, using method |
||||||
|
* [[ActiveRecord::getSnippetSource()]]. |
||||||
|
* For example: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* class Article extends ActiveRecord |
||||||
|
* { |
||||||
|
* public function getSnippetSource() |
||||||
|
* { |
||||||
|
* return file_get_contents('/path/to/source/files/' . $this->id . '.txt');; |
||||||
|
* } |
||||||
|
* } |
||||||
|
* |
||||||
|
* $articles = Article::find()->snippetByModel()->all(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Warning: this option should NOT be used with [[asArray]] at the same time! |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function snippetByModel() |
||||||
|
{ |
||||||
|
$this->snippetCallback([$this, 'fetchSnippetSourceFromModels']); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes query and returns all results as an array. |
||||||
|
* @param Connection $db the DB connection used to create the DB command. |
||||||
|
* If null, the DB connection returned by [[modelClass]] will be used. |
||||||
|
* @return array the query results. If the query results in nothing, an empty array will be returned. |
||||||
|
*/ |
||||||
|
public function all($db = null) |
||||||
|
{ |
||||||
|
$command = $this->createCommand($db); |
||||||
|
$rows = $command->queryAll(); |
||||||
|
if (!empty($rows)) { |
||||||
|
$models = $this->createModels($rows); |
||||||
|
if (!empty($this->with)) { |
||||||
|
$this->findWith($this->with, $models); |
||||||
|
} |
||||||
|
$models = $this->fillUpSnippets($models); |
||||||
|
return $models; |
||||||
|
} else { |
||||||
|
return []; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes query and returns a single row of result. |
||||||
|
* @param Connection $db the DB connection used to create the DB command. |
||||||
|
* If null, the DB connection returned by [[modelClass]] will be used. |
||||||
|
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], |
||||||
|
* the query result may be either an array or an ActiveRecord object. Null will be returned |
||||||
|
* if the query results in nothing. |
||||||
|
*/ |
||||||
|
public function one($db = null) |
||||||
|
{ |
||||||
|
$command = $this->createCommand($db); |
||||||
|
$row = $command->queryOne(); |
||||||
|
if ($row !== false) { |
||||||
|
if ($this->asArray) { |
||||||
|
$model = $row; |
||||||
|
} else { |
||||||
|
/** @var $class ActiveRecord */ |
||||||
|
$class = $this->modelClass; |
||||||
|
$model = $class::create($row); |
||||||
|
} |
||||||
|
if (!empty($this->with)) { |
||||||
|
$models = [$model]; |
||||||
|
$this->findWith($this->with, $models); |
||||||
|
$model = $models[0]; |
||||||
|
} |
||||||
|
list ($model) = $this->fillUpSnippets([$model]); |
||||||
|
return $model; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a DB command that can be used to execute this query. |
||||||
|
* @param Connection $db the DB connection used to create the DB command. |
||||||
|
* If null, the DB connection returned by [[modelClass]] will be used. |
||||||
|
* @return Command the created DB command instance. |
||||||
|
*/ |
||||||
|
public function createCommand($db = null) |
||||||
|
{ |
||||||
|
/** @var $modelClass ActiveRecord */ |
||||||
|
$modelClass = $this->modelClass; |
||||||
|
$this->setConnection($db); |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$params = $this->params; |
||||||
|
if ($this->sql === null) { |
||||||
|
if ($this->from === null) { |
||||||
|
$tableName = $modelClass::indexName(); |
||||||
|
if ($this->select === null && !empty($this->join)) { |
||||||
|
$this->select = ["$tableName.*"]; |
||||||
|
} |
||||||
|
$this->from = [$tableName]; |
||||||
|
} |
||||||
|
list ($this->sql, $params) = $db->getQueryBuilder()->build($this); |
||||||
|
} |
||||||
|
return $db->createCommand($this->sql, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
protected function defaultConnection() |
||||||
|
{ |
||||||
|
$modelClass = $this->modelClass; |
||||||
|
return $modelClass::getDb(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches the source for the snippets using [[ActiveRecord::getSnippetSource()]] method. |
||||||
|
* @param ActiveRecord[] $models raw query result rows. |
||||||
|
* @throws \yii\base\InvalidCallException if [[asArray]] enabled. |
||||||
|
* @return array snippet source strings |
||||||
|
*/ |
||||||
|
protected function fetchSnippetSourceFromModels($models) |
||||||
|
{ |
||||||
|
if ($this->asArray) { |
||||||
|
throw new InvalidCallException('"' . __METHOD__ . '" unable to determine snippet source from plain array. Either disable "asArray" option or use regular "snippetCallback"'); |
||||||
|
} |
||||||
|
$result = []; |
||||||
|
foreach ($models as $model) { |
||||||
|
$result[] = $model->getSnippetSource(); |
||||||
|
} |
||||||
|
return $result; |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\db\ActiveRelationInterface; |
||||||
|
use yii\db\ActiveRelationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* ActiveRelation represents a relation to Sphinx Active Record class. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface |
||||||
|
{ |
||||||
|
use ActiveRelationTrait; |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\base\Object; |
||||||
|
use yii\db\Expression; |
||||||
|
|
||||||
|
/** |
||||||
|
* ColumnSchema class describes the metadata of a column in a Sphinx index. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ColumnSchema extends Object |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var string name of this column (without quotes). |
||||||
|
*/ |
||||||
|
public $name; |
||||||
|
/** |
||||||
|
* @var string abstract type of this column. Possible abstract types include: |
||||||
|
* string, text, boolean, smallint, integer, bigint, float, decimal, datetime, |
||||||
|
* timestamp, time, date, binary, and money. |
||||||
|
*/ |
||||||
|
public $type; |
||||||
|
/** |
||||||
|
* @var string the PHP type of this column. Possible PHP types include: |
||||||
|
* string, boolean, integer, double. |
||||||
|
*/ |
||||||
|
public $phpType; |
||||||
|
/** |
||||||
|
* @var string the DB type of this column. Possible DB types vary according to the type of DBMS. |
||||||
|
*/ |
||||||
|
public $dbType; |
||||||
|
/** |
||||||
|
* @var boolean whether this column is a primary key |
||||||
|
*/ |
||||||
|
public $isPrimaryKey; |
||||||
|
/** |
||||||
|
* @var boolean whether this column is an attribute |
||||||
|
*/ |
||||||
|
public $isAttribute; |
||||||
|
/** |
||||||
|
* @var boolean whether this column is a indexed field |
||||||
|
*/ |
||||||
|
public $isField; |
||||||
|
/** |
||||||
|
* @var boolean whether this column is a multi value attribute (MVA) |
||||||
|
*/ |
||||||
|
public $isMva; |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts the input value according to [[phpType]]. |
||||||
|
* If the value is null or an [[Expression]], it will not be converted. |
||||||
|
* @param mixed $value input value |
||||||
|
* @return mixed converted value |
||||||
|
*/ |
||||||
|
public function typecast($value) |
||||||
|
{ |
||||||
|
if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { |
||||||
|
return $value; |
||||||
|
} |
||||||
|
if ($value === '' && $this->type !== Schema::TYPE_STRING) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
switch ($this->phpType) { |
||||||
|
case 'string': |
||||||
|
return (string)$value; |
||||||
|
case 'integer': |
||||||
|
return (integer)$value; |
||||||
|
case 'boolean': |
||||||
|
return (boolean)$value; |
||||||
|
} |
||||||
|
return $value; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,323 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use Yii; |
||||||
|
use yii\base\NotSupportedException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command represents a SQL statement to be executed against a Sphinx. |
||||||
|
* |
||||||
|
* A command object is usually created by calling [[Connection::createCommand()]]. |
||||||
|
* The SQL statement it represents can be set via the [[sql]] property. |
||||||
|
* |
||||||
|
* To execute a non-query SQL (such as INSERT, REPLACE, DELETE, UPDATE), call [[execute()]]. |
||||||
|
* To execute a SQL statement that returns result data set (such as SELECT, CALL SNIPPETS, CALL KEYWORDS), |
||||||
|
* use [[queryAll()]], [[queryOne()]], [[queryColumn()]], [[queryScalar()]], or [[query()]]. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $articles = $connection->createCommand("SELECT * FROM `idx_article` WHERE MATCH('programming')")->queryAll(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Command supports SQL statement preparation and parameter binding just as [[\yii\db\Command]] does. |
||||||
|
* |
||||||
|
* Command also supports building SQL statements by providing methods such as [[insert()]], |
||||||
|
* [[update()]], etc. For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->update('idx_article', [ |
||||||
|
* 'genre_id' => 15, |
||||||
|
* 'author_id' => 157, |
||||||
|
* ])->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* To build SELECT SQL statements, please use [[Query]] and [[QueryBuilder]] instead. |
||||||
|
* |
||||||
|
* @property \yii\sphinx\Connection $db the Sphinx connection that this command is associated with. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class Command extends \yii\db\Command |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Creates a batch INSERT command. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [ |
||||||
|
* ['Tom', 30], |
||||||
|
* ['Jane', 20], |
||||||
|
* ['Linda', 25], |
||||||
|
* ])->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Note that the values in each row must match the corresponding column names. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be inserted into. |
||||||
|
* @param array $columns the column names |
||||||
|
* @param array $rows the rows to be batch inserted into the index |
||||||
|
* @return static the command object itself |
||||||
|
*/ |
||||||
|
public function batchInsert($index, $columns, $rows) |
||||||
|
{ |
||||||
|
$params = []; |
||||||
|
$sql = $this->db->getQueryBuilder()->batchInsert($index, $columns, $rows, $params); |
||||||
|
return $this->setSql($sql)->bindValues($params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an REPLACE 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 replaced. |
||||||
|
* |
||||||
|
* Note that the created command is not executed until [[execute()]] is called. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be replaced into. |
||||||
|
* @param array $columns the column data (name => value) to be replaced into the index. |
||||||
|
* @return static the command object itself |
||||||
|
*/ |
||||||
|
public function replace($index, $columns) |
||||||
|
{ |
||||||
|
$params = []; |
||||||
|
$sql = $this->db->getQueryBuilder()->replace($index, $columns, $params); |
||||||
|
return $this->setSql($sql)->bindValues($params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a batch REPLACE command. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->batchInsert('idx_user', ['name', 'age'], [ |
||||||
|
* ['Tom', 30], |
||||||
|
* ['Jane', 20], |
||||||
|
* ['Linda', 25], |
||||||
|
* ])->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Note that the values in each row must match the corresponding column names. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be replaced. |
||||||
|
* @param array $columns the column names |
||||||
|
* @param array $rows the rows to be batch replaced in the index |
||||||
|
* @return static the command object itself |
||||||
|
*/ |
||||||
|
public function batchReplace($index, $columns, $rows) |
||||||
|
{ |
||||||
|
$params = []; |
||||||
|
$sql = $this->db->getQueryBuilder()->batchReplace($index, $columns, $rows, $params); |
||||||
|
return $this->setSql($sql)->bindValues($params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an UPDATE command. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->update('tbl_user', ['status' => 1], 'age > 30')->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The method will properly escape the column names and bind the values to be updated. |
||||||
|
* |
||||||
|
* Note that the created command is not executed until [[execute()]] is called. |
||||||
|
* |
||||||
|
* @param string $index the index to be updated. |
||||||
|
* @param array $columns the column data (name => value) to be updated. |
||||||
|
* @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 |
||||||
|
* @param array $options list of options in format: optionName => optionValue |
||||||
|
* @return static the command object itself |
||||||
|
*/ |
||||||
|
public function update($index, $columns, $condition = '', $params = [], $options = []) |
||||||
|
{ |
||||||
|
$sql = $this->db->getQueryBuilder()->update($index, $columns, $condition, $params, $options); |
||||||
|
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 |
||||||
|
*/ |
||||||
|
public function truncateIndex($index) |
||||||
|
{ |
||||||
|
$sql = $this->db->getQueryBuilder()->truncateIndex($index); |
||||||
|
return $this->setSql($sql); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a snippet from provided data and query, using specified index settings. |
||||||
|
* @param string $index name of the index, from which to take the text processing settings. |
||||||
|
* @param string|array $source is the source data to extract a snippet from. |
||||||
|
* It could be either a single string or array of strings. |
||||||
|
* @param string $match the full-text query to build snippets for. |
||||||
|
* @param array $options list of options in format: optionName => optionValue |
||||||
|
* @return static the command object itself |
||||||
|
*/ |
||||||
|
public function callSnippets($index, $source, $match, $options = []) |
||||||
|
{ |
||||||
|
$params = []; |
||||||
|
$sql = $this->db->getQueryBuilder()->callSnippets($index, $source, $match, $options, $params); |
||||||
|
return $this->setSql($sql)->bindValues($params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics. |
||||||
|
* @param string $index the name of the index from which to take the text processing settings |
||||||
|
* @param string $text the text to break down to keywords. |
||||||
|
* @param boolean $fetchStatistic whether to return document and hit occurrence statistics |
||||||
|
* @return string the SQL statement for call keywords. |
||||||
|
*/ |
||||||
|
public function callKeywords($index, $text, $fetchStatistic = false) |
||||||
|
{ |
||||||
|
$params = []; |
||||||
|
$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.'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
use yii\base\NotSupportedException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Connection represents the Sphinx connection via MySQL protocol. |
||||||
|
* This class uses [PDO](http://www.php.net/manual/en/ref.pdo.php) to maintain such connection. |
||||||
|
* Note: although PDO supports numerous database drivers, this class supports only MySQL. |
||||||
|
* |
||||||
|
* In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added: |
||||||
|
* ~~~ |
||||||
|
* searchd |
||||||
|
* { |
||||||
|
* listen = localhost:9306:mysql41 |
||||||
|
* ... |
||||||
|
* } |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The following example shows how to create a Connection instance and establish |
||||||
|
* the Sphinx connection: |
||||||
|
* ~~~ |
||||||
|
* $connection = new \yii\db\Connection([ |
||||||
|
* 'dsn' => 'mysql:host=127.0.0.1;port=9306;', |
||||||
|
* 'username' => $username, |
||||||
|
* 'password' => $password, |
||||||
|
* ]); |
||||||
|
* $connection->open(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* After the Sphinx connection is established, one can execute SQL statements like the following: |
||||||
|
* ~~~ |
||||||
|
* $command = $connection->createCommand("SELECT * FROM idx_article WHERE MATCH('programming')"); |
||||||
|
* $articles = $command->queryAll(); |
||||||
|
* $command = $connection->createCommand('UPDATE idx_article SET status=2 WHERE id=1'); |
||||||
|
* $command->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* For more information about how to perform various DB queries, please refer to [[Command]]. |
||||||
|
* |
||||||
|
* This class supports transactions exactly as "yii\db\Connection". |
||||||
|
* |
||||||
|
* Note: while this class extends "yii\db\Connection" some of its methods are not supported. |
||||||
|
* |
||||||
|
* @property Schema $schema The schema information for this Sphinx connection. This property is read-only. |
||||||
|
* @property \yii\sphinx\QueryBuilder $queryBuilder The query builder for this Sphinx connection. This property is |
||||||
|
* read-only. |
||||||
|
* @method \yii\sphinx\Schema getSchema() The schema information for this Sphinx connection |
||||||
|
* @method \yii\sphinx\QueryBuilder getQueryBuilder() the query builder for this Sphinx connection |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class Connection extends \yii\db\Connection |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @inheritdoc |
||||||
|
*/ |
||||||
|
public $schemaMap = [ |
||||||
|
'mysqli' => 'yii\sphinx\Schema', // MySQL |
||||||
|
'mysql' => 'yii\sphinx\Schema', // MySQL |
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtains the schema information for the named index. |
||||||
|
* @param string $name index name. |
||||||
|
* @param boolean $refresh whether to reload the table schema even if it is found in the cache. |
||||||
|
* @return IndexSchema index schema information. Null if the named index does not exist. |
||||||
|
*/ |
||||||
|
public function getIndexSchema($name, $refresh = false) |
||||||
|
{ |
||||||
|
return $this->getSchema()->getIndexSchema($name, $refresh); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a index name for use in a query. |
||||||
|
* If the index name contains schema prefix, the prefix will also be properly quoted. |
||||||
|
* If the index name is already quoted or contains special characters including '(', '[[' and '{{', |
||||||
|
* then this method will do nothing. |
||||||
|
* @param string $name index name |
||||||
|
* @return string the properly quoted index name |
||||||
|
*/ |
||||||
|
public function quoteIndexName($name) |
||||||
|
{ |
||||||
|
return $this->getSchema()->quoteIndexName($name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alias of [[quoteIndexName()]]. |
||||||
|
* @param string $name table name |
||||||
|
* @return string the properly quoted table name |
||||||
|
*/ |
||||||
|
public function quoteTableName($name) |
||||||
|
{ |
||||||
|
return $this->quoteIndexName($name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a command for execution. |
||||||
|
* @param string $sql the SQL statement to be executed |
||||||
|
* @param array $params the parameters to be bound to the SQL statement |
||||||
|
* @return Command the Sphinx command |
||||||
|
*/ |
||||||
|
public function createCommand($sql = null, $params = []) |
||||||
|
{ |
||||||
|
$this->open(); |
||||||
|
$command = new Command([ |
||||||
|
'db' => $this, |
||||||
|
'sql' => $sql, |
||||||
|
]); |
||||||
|
return $command->bindValues($params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method is not supported by Sphinx. |
||||||
|
* @param string $sequenceName name of the sequence object |
||||||
|
* @return string the row ID of the last row inserted, or the last value retrieved from the sequence object |
||||||
|
* @throws \yii\base\NotSupportedException always. |
||||||
|
*/ |
||||||
|
public function getLastInsertID($sequenceName = '') |
||||||
|
{ |
||||||
|
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright © 2008-2011 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\base\Object; |
||||||
|
use yii\base\InvalidParamException; |
||||||
|
|
||||||
|
/** |
||||||
|
* IndexSchema represents the metadata of a Sphinx index. |
||||||
|
* |
||||||
|
* @property array $columnNames List of column names. This property is read-only. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class IndexSchema extends Object |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var string name of this index. |
||||||
|
*/ |
||||||
|
public $name; |
||||||
|
/** |
||||||
|
* @var string type of the index. |
||||||
|
*/ |
||||||
|
public $type; |
||||||
|
/** |
||||||
|
* @var boolean whether this index is a runtime index. |
||||||
|
*/ |
||||||
|
public $isRuntime; |
||||||
|
/** |
||||||
|
* @var string primary key of this index. |
||||||
|
*/ |
||||||
|
public $primaryKey; |
||||||
|
/** |
||||||
|
* @var ColumnSchema[] column metadata of this index. Each array element is a [[ColumnSchema]] object, indexed by column names. |
||||||
|
*/ |
||||||
|
public $columns = []; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the named column metadata. |
||||||
|
* This is a convenient method for retrieving a named column even if it does not exist. |
||||||
|
* @param string $name column name |
||||||
|
* @return ColumnSchema metadata of the named column. Null if the named column does not exist. |
||||||
|
*/ |
||||||
|
public function getColumn($name) |
||||||
|
{ |
||||||
|
return isset($this->columns[$name]) ? $this->columns[$name] : null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the names of all columns in this table. |
||||||
|
* @return array list of column names |
||||||
|
*/ |
||||||
|
public function getColumnNames() |
||||||
|
{ |
||||||
|
return array_keys($this->columns); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
The Yii framework is free software. It is released under the terms of |
||||||
|
the following BSD License. |
||||||
|
|
||||||
|
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) |
||||||
|
All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions |
||||||
|
are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer. |
||||||
|
* Redistributions in binary form must reproduce the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer in |
||||||
|
the documentation and/or other materials provided with the |
||||||
|
distribution. |
||||||
|
* Neither the name of Yii Software LLC nor the names of its |
||||||
|
contributors may be used to endorse or promote products derived |
||||||
|
from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||||
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||||
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||||
|
POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,701 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use Yii; |
||||||
|
use yii\base\Component; |
||||||
|
use yii\base\InvalidCallException; |
||||||
|
use yii\db\Expression; |
||||||
|
use yii\db\QueryInterface; |
||||||
|
use yii\db\QueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* Query represents a SELECT SQL statement. |
||||||
|
* |
||||||
|
* Query provides a set of methods to facilitate the specification of different clauses |
||||||
|
* in a SELECT statement. These methods can be chained together. |
||||||
|
* |
||||||
|
* By calling [[createCommand()]], we can get a [[Command]] instance which can be further |
||||||
|
* used to perform/execute the Sphinx query. |
||||||
|
* |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $query = new Query; |
||||||
|
* $query->select('id, groupd_id') |
||||||
|
* ->from('idx_item') |
||||||
|
* ->limit(10); |
||||||
|
* // build and execute the query |
||||||
|
* $command = $query->createCommand(); |
||||||
|
* // $command->sql returns the actual SQL |
||||||
|
* $rows = $command->queryAll(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Since Sphinx does not store the original indexed text, the snippets for the rows in query result |
||||||
|
* should be build separately via another query. You can simplify this workflow using [[snippetCallback]]. |
||||||
|
* |
||||||
|
* Warning: even if you do not set any query limit, implicit LIMIT 0,20 is present by default! |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class Query extends Component implements QueryInterface |
||||||
|
{ |
||||||
|
use QueryTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var array the columns being selected. For example, `['id', 'group_id']`. |
||||||
|
* This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns. |
||||||
|
* @see select() |
||||||
|
*/ |
||||||
|
public $select; |
||||||
|
/** |
||||||
|
* @var string additional option that should be appended to the 'SELECT' keyword. |
||||||
|
*/ |
||||||
|
public $selectOption; |
||||||
|
/** |
||||||
|
* @var boolean whether to select distinct rows of data only. If this is set true, |
||||||
|
* the SELECT clause would be changed to SELECT DISTINCT. |
||||||
|
*/ |
||||||
|
public $distinct; |
||||||
|
/** |
||||||
|
* @var array the index(es) to be selected from. For example, `['idx_user', 'idx_user_delta']`. |
||||||
|
* This is used to construct the FROM clause in a SQL statement. |
||||||
|
* @see from() |
||||||
|
*/ |
||||||
|
public $from; |
||||||
|
/** |
||||||
|
* @var string text, which should be searched in fulltext mode. |
||||||
|
* This value will be composed into MATCH operator inside the WHERE clause. |
||||||
|
*/ |
||||||
|
public $match; |
||||||
|
/** |
||||||
|
* @var array how to group the query results. For example, `['company', 'department']`. |
||||||
|
* This is used to construct the GROUP BY clause in a SQL statement. |
||||||
|
*/ |
||||||
|
public $groupBy; |
||||||
|
/** |
||||||
|
* @var string WITHIN GROUP ORDER BY clause. This is a Sphinx specific extension |
||||||
|
* that lets you control how the best row within a group will to be selected. |
||||||
|
* The possible value matches the [[orderBy]] one. |
||||||
|
*/ |
||||||
|
public $within; |
||||||
|
/** |
||||||
|
* @var array per-query options in format: optionName => optionValue |
||||||
|
* They will compose OPTION clause. This is a Sphinx specific extension |
||||||
|
* that lets you control a number of per-query options. |
||||||
|
*/ |
||||||
|
public $options; |
||||||
|
/** |
||||||
|
* @var array list of query parameter values indexed by parameter placeholders. |
||||||
|
* For example, `[':name' => 'Dan', ':age' => 31]`. |
||||||
|
*/ |
||||||
|
public $params; |
||||||
|
/** |
||||||
|
* @var callback PHP callback, which should be used to fetch source data for the snippets. |
||||||
|
* Such callback will receive array of query result rows as an argument and must return the |
||||||
|
* array of snippet source strings in the order, which match one of incoming rows. |
||||||
|
* For example: |
||||||
|
* ~~~ |
||||||
|
* $query = new Query; |
||||||
|
* $query->from('idx_item') |
||||||
|
* ->match('pencil') |
||||||
|
* ->snippetCallback(function ($rows) { |
||||||
|
* $result = []; |
||||||
|
* foreach ($rows as $row) { |
||||||
|
* $result[] = file_get_contents('/path/to/index/files/' . $row['id'] . '.txt'); |
||||||
|
* } |
||||||
|
* return $result; |
||||||
|
* }) |
||||||
|
* ->all(); |
||||||
|
* ~~~ |
||||||
|
*/ |
||||||
|
public $snippetCallback; |
||||||
|
/** |
||||||
|
* @var array query options for the call snippet. |
||||||
|
*/ |
||||||
|
public $snippetOptions; |
||||||
|
/** |
||||||
|
* @var Connection the Sphinx connection used to generate the SQL statements. |
||||||
|
*/ |
||||||
|
private $_connection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Connection $connection Sphinx connection instance |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function setConnection($connection) |
||||||
|
{ |
||||||
|
$this->_connection = $connection; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Connection Sphinx connection instance |
||||||
|
*/ |
||||||
|
public function getConnection() |
||||||
|
{ |
||||||
|
if ($this->_connection === null) { |
||||||
|
$this->_connection = $this->defaultConnection(); |
||||||
|
} |
||||||
|
return $this->_connection; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Connection default connection value. |
||||||
|
*/ |
||||||
|
protected function defaultConnection() |
||||||
|
{ |
||||||
|
return Yii::$app->getComponent('sphinx'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a Sphinx command that can be used to execute this query. |
||||||
|
* @param Connection $connection the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return Command the created Sphinx command instance. |
||||||
|
*/ |
||||||
|
public function createCommand($connection = null) |
||||||
|
{ |
||||||
|
$this->setConnection($connection); |
||||||
|
$connection = $this->getConnection(); |
||||||
|
list ($sql, $params) = $connection->getQueryBuilder()->build($this); |
||||||
|
return $connection->createCommand($sql, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns all results as an array. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return array the query results. If the query results in nothing, an empty array will be returned. |
||||||
|
*/ |
||||||
|
public function all($db = null) |
||||||
|
{ |
||||||
|
$rows = $this->createCommand($db)->queryAll(); |
||||||
|
$rows = $this->fillUpSnippets($rows); |
||||||
|
if ($this->indexBy === null) { |
||||||
|
return $rows; |
||||||
|
} |
||||||
|
$result = []; |
||||||
|
foreach ($rows as $row) { |
||||||
|
if (is_string($this->indexBy)) { |
||||||
|
$key = $row[$this->indexBy]; |
||||||
|
} else { |
||||||
|
$key = call_user_func($this->indexBy, $row); |
||||||
|
} |
||||||
|
$result[$key] = $row; |
||||||
|
} |
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns a single row of result. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component 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. |
||||||
|
*/ |
||||||
|
public function one($db = null) |
||||||
|
{ |
||||||
|
$row = $this->createCommand($db)->queryOne(); |
||||||
|
if ($row !== false) { |
||||||
|
list ($row) = $this->fillUpSnippets([$row]); |
||||||
|
} |
||||||
|
return $row; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the query result as a scalar value. |
||||||
|
* The value returned will be the first column in the first row of the query results. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return string|boolean the value of the first column in the first row of the query result. |
||||||
|
* False is returned if the query result is empty. |
||||||
|
*/ |
||||||
|
public function scalar($db = null) |
||||||
|
{ |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the query and returns the first column of the result. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return array the first column of the query result. An empty array is returned if the query results in nothing. |
||||||
|
*/ |
||||||
|
public function column($db = null) |
||||||
|
{ |
||||||
|
return $this->createCommand($db)->queryColumn(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the number of records. |
||||||
|
* @param string $q the COUNT expression. Defaults to '*'. |
||||||
|
* Make sure you properly quote column names in the expression. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return integer number of records |
||||||
|
*/ |
||||||
|
public function count($q = '*', $db = null) |
||||||
|
{ |
||||||
|
$this->select = ["COUNT($q)"]; |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the sum of the specified column values. |
||||||
|
* @param string $q the column name or expression. |
||||||
|
* Make sure you properly quote column names in the expression. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return integer the sum of the specified column values |
||||||
|
*/ |
||||||
|
public function sum($q, $db = null) |
||||||
|
{ |
||||||
|
$this->select = ["SUM($q)"]; |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the average of the specified column values. |
||||||
|
* @param string $q the column name or expression. |
||||||
|
* Make sure you properly quote column names in the expression. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return integer the average of the specified column values. |
||||||
|
*/ |
||||||
|
public function average($q, $db = null) |
||||||
|
{ |
||||||
|
$this->select = ["AVG($q)"]; |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the minimum of the specified column values. |
||||||
|
* @param string $q the column name or expression. |
||||||
|
* Make sure you properly quote column names in the expression. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return integer the minimum of the specified column values. |
||||||
|
*/ |
||||||
|
public function min($q, $db = null) |
||||||
|
{ |
||||||
|
$this->select = ["MIN($q)"]; |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the maximum of the specified column values. |
||||||
|
* @param string $q the column name or expression. |
||||||
|
* Make sure you properly quote column names in the expression. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return integer the maximum of the specified column values. |
||||||
|
*/ |
||||||
|
public function max($q, $db = null) |
||||||
|
{ |
||||||
|
$this->select = ["MAX($q)"]; |
||||||
|
return $this->createCommand($db)->queryScalar(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a value indicating whether the query result contains any row of data. |
||||||
|
* @param Connection $db the Sphinx connection used to generate the SQL statement. |
||||||
|
* If this parameter is not given, the `sphinx` application component will be used. |
||||||
|
* @return boolean whether the query result contains any row of data. |
||||||
|
*/ |
||||||
|
public function exists($db = null) |
||||||
|
{ |
||||||
|
$this->select = [new Expression('1')]; |
||||||
|
return $this->scalar($db) !== false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the SELECT part of the query. |
||||||
|
* @param string|array $columns the columns to be selected. |
||||||
|
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). |
||||||
|
* The method will automatically quote the column names unless a column contains some parenthesis |
||||||
|
* (which means the column contains a Sphinx expression). |
||||||
|
* @param string $option additional option that should be appended to the 'SELECT' keyword. |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function select($columns, $option = null) |
||||||
|
{ |
||||||
|
if (!is_array($columns)) { |
||||||
|
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); |
||||||
|
} |
||||||
|
$this->select = $columns; |
||||||
|
$this->selectOption = $option; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the value indicating whether to SELECT DISTINCT or not. |
||||||
|
* @param bool $value whether to SELECT DISTINCT or not. |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function distinct($value = true) |
||||||
|
{ |
||||||
|
$this->distinct = $value; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the FROM part of the query. |
||||||
|
* @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'idx_user'`) |
||||||
|
* or an array (e.g. `['idx_user', 'idx_user_delta']`) specifying one or several index names. |
||||||
|
* The method will automatically quote the table names unless it contains some parenthesis |
||||||
|
* (which means the table is given as a sub-query or Sphinx expression). |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function from($tables) |
||||||
|
{ |
||||||
|
if (!is_array($tables)) { |
||||||
|
$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY); |
||||||
|
} |
||||||
|
$this->from = $tables; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the fulltext query text. This text will be composed into |
||||||
|
* MATCH operator inside the WHERE clause. |
||||||
|
* @param string $query fulltext query text. |
||||||
|
* @return static the query object itself |
||||||
|
*/ |
||||||
|
public function match($query) |
||||||
|
{ |
||||||
|
$this->match = $query; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the WHERE part of the query. |
||||||
|
* |
||||||
|
* The method requires a $condition parameter, and optionally a $params parameter |
||||||
|
* specifying the values to be bound to the query. |
||||||
|
* |
||||||
|
* The $condition parameter should be either a string (e.g. 'id=1') or an array. |
||||||
|
* If the latter, it must be in one of the following two formats: |
||||||
|
* |
||||||
|
* - hash format: `['column1' => value1, 'column2' => value2, ...]` |
||||||
|
* - operator format: `[operator, operand1, operand2, ...]` |
||||||
|
* |
||||||
|
* A condition in hash format represents the following SQL expression in general: |
||||||
|
* `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, |
||||||
|
* an `IN` expression will be generated. And if a value is null, `IS NULL` will be used |
||||||
|
* in the generated expression. Below are some examples: |
||||||
|
* |
||||||
|
* - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`. |
||||||
|
* - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`. |
||||||
|
* - `['status' => null] generates `status IS NULL`. |
||||||
|
* |
||||||
|
* A condition in operator format generates the SQL expression according to the specified operator, which |
||||||
|
* can be one of the followings: |
||||||
|
* |
||||||
|
* - `and`: the operands should be concatenated together using `AND`. For example, |
||||||
|
* `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, |
||||||
|
* it will be converted into a string using the rules described here. For example, |
||||||
|
* `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`. |
||||||
|
* The method will NOT do any quoting or escaping. |
||||||
|
* |
||||||
|
* - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. |
||||||
|
* |
||||||
|
* - `between`: operand 1 should be the column name, and operand 2 and 3 should be the |
||||||
|
* starting and ending values of the range that the column is in. |
||||||
|
* For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. |
||||||
|
* |
||||||
|
* - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` |
||||||
|
* in the generated condition. |
||||||
|
* |
||||||
|
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing |
||||||
|
* the range of the values that the column or DB expression should be in. For example, |
||||||
|
* `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`. |
||||||
|
* The method will properly quote the column name and escape values in the range. |
||||||
|
* |
||||||
|
* - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. |
||||||
|
* |
||||||
|
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing |
||||||
|
* the values that the column or DB expression should be like. |
||||||
|
* For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. |
||||||
|
* When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated |
||||||
|
* using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate |
||||||
|
* `name LIKE '%test%' AND name LIKE '%sample%'`. |
||||||
|
* The method will properly quote the column name and escape values in the range. |
||||||
|
* |
||||||
|
* - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` |
||||||
|
* predicates when operand 2 is an array. |
||||||
|
* |
||||||
|
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` |
||||||
|
* in the generated condition. |
||||||
|
* |
||||||
|
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate |
||||||
|
* the `NOT LIKE` predicates. |
||||||
|
* |
||||||
|
* @param string|array $condition the conditions that should be put in the WHERE part. |
||||||
|
* @param array $params the parameters (name => value) to be bound to the query. |
||||||
|
* @return static the query object itself |
||||||
|
* @see andWhere() |
||||||
|
* @see orWhere() |
||||||
|
*/ |
||||||
|
public function where($condition, $params = []) |
||||||
|
{ |
||||||
|
$this->where = $condition; |
||||||
|
$this->addParams($params); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an additional WHERE condition to the existing one. |
||||||
|
* The new condition and the existing one will be joined using the 'AND' operator. |
||||||
|
* @param string|array $condition the new WHERE condition. Please refer to [[where()]] |
||||||
|
* on how to specify this parameter. |
||||||
|
* @param array $params the parameters (name => value) to be bound to the query. |
||||||
|
* @return static the query object itself |
||||||
|
* @see where() |
||||||
|
* @see orWhere() |
||||||
|
*/ |
||||||
|
public function andWhere($condition, $params = []) |
||||||
|
{ |
||||||
|
if ($this->where === null) { |
||||||
|
$this->where = $condition; |
||||||
|
} else { |
||||||
|
$this->where = ['and', $this->where, $condition]; |
||||||
|
} |
||||||
|
$this->addParams($params); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an additional WHERE condition to the existing one. |
||||||
|
* The new condition and the existing one will be joined using the 'OR' operator. |
||||||
|
* @param string|array $condition the new WHERE condition. Please refer to [[where()]] |
||||||
|
* on how to specify this parameter. |
||||||
|
* @param array $params the parameters (name => value) to be bound to the query. |
||||||
|
* @return static the query object itself |
||||||
|
* @see where() |
||||||
|
* @see andWhere() |
||||||
|
*/ |
||||||
|
public function orWhere($condition, $params = []) |
||||||
|
{ |
||||||
|
if ($this->where === null) { |
||||||
|
$this->where = $condition; |
||||||
|
} else { |
||||||
|
$this->where = ['or', $this->where, $condition]; |
||||||
|
} |
||||||
|
$this->addParams($params); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the GROUP BY part of the query. |
||||||
|
* @param string|array $columns the columns to be grouped by. |
||||||
|
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). |
||||||
|
* The method will automatically quote the column names unless a column contains some parenthesis |
||||||
|
* (which means the column contains a DB expression). |
||||||
|
* @return static the query object itself |
||||||
|
* @see addGroupBy() |
||||||
|
*/ |
||||||
|
public function groupBy($columns) |
||||||
|
{ |
||||||
|
if (!is_array($columns)) { |
||||||
|
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); |
||||||
|
} |
||||||
|
$this->groupBy = $columns; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds additional group-by columns to the existing ones. |
||||||
|
* @param string|array $columns additional columns to be grouped by. |
||||||
|
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). |
||||||
|
* The method will automatically quote the column names unless a column contains some parenthesis |
||||||
|
* (which means the column contains a DB expression). |
||||||
|
* @return static the query object itself |
||||||
|
* @see groupBy() |
||||||
|
*/ |
||||||
|
public function addGroupBy($columns) |
||||||
|
{ |
||||||
|
if (!is_array($columns)) { |
||||||
|
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); |
||||||
|
} |
||||||
|
if ($this->groupBy === null) { |
||||||
|
$this->groupBy = $columns; |
||||||
|
} else { |
||||||
|
$this->groupBy = array_merge($this->groupBy, $columns); |
||||||
|
} |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the parameters to be bound to the query. |
||||||
|
* @param array $params list of query parameter values indexed by parameter placeholders. |
||||||
|
* For example, `[':name' => 'Dan', ':age' => 31]`. |
||||||
|
* @return static the query object itself |
||||||
|
* @see addParams() |
||||||
|
*/ |
||||||
|
public function params($params) |
||||||
|
{ |
||||||
|
$this->params = $params; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds additional parameters to be bound to the query. |
||||||
|
* @param array $params list of query parameter values indexed by parameter placeholders. |
||||||
|
* For example, `[':name' => 'Dan', ':age' => 31]`. |
||||||
|
* @return static the query object itself |
||||||
|
* @see params() |
||||||
|
*/ |
||||||
|
public function addParams($params) |
||||||
|
{ |
||||||
|
if (!empty($params)) { |
||||||
|
if ($this->params === null) { |
||||||
|
$this->params = $params; |
||||||
|
} else { |
||||||
|
foreach ($params as $name => $value) { |
||||||
|
if (is_integer($name)) { |
||||||
|
$this->params[] = $value; |
||||||
|
} else { |
||||||
|
$this->params[$name] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the query options. |
||||||
|
* @param array $options query options in format: optionName => optionValue |
||||||
|
* @return static the query object itself |
||||||
|
* @see addOptions() |
||||||
|
*/ |
||||||
|
public function options($options) |
||||||
|
{ |
||||||
|
$this->options = $options; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds additional query options. |
||||||
|
* @param array $options query options in format: optionName => optionValue |
||||||
|
* @return static the query object itself |
||||||
|
* @see options() |
||||||
|
*/ |
||||||
|
public function addOptions($options) |
||||||
|
{ |
||||||
|
if (is_array($this->options)) { |
||||||
|
$this->options = array_merge($this->options, $options); |
||||||
|
} else { |
||||||
|
$this->options = $options; |
||||||
|
} |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the WITHIN GROUP ORDER BY part of the query. |
||||||
|
* @param string|array $columns the columns (and the directions) to find best row within a group. |
||||||
|
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array |
||||||
|
* (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`). |
||||||
|
* The method will automatically quote the column names unless a column contains some parenthesis |
||||||
|
* (which means the column contains a DB expression). |
||||||
|
* @return static the query object itself |
||||||
|
* @see addWithin() |
||||||
|
*/ |
||||||
|
public function within($columns) |
||||||
|
{ |
||||||
|
$this->within = $this->normalizeOrderBy($columns); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds additional WITHIN GROUP ORDER BY columns to the query. |
||||||
|
* @param string|array $columns the columns (and the directions) to find best row within a group. |
||||||
|
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array |
||||||
|
* (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`). |
||||||
|
* The method will automatically quote the column names unless a column contains some parenthesis |
||||||
|
* (which means the column contains a DB expression). |
||||||
|
* @return static the query object itself |
||||||
|
* @see within() |
||||||
|
*/ |
||||||
|
public function addWithin($columns) |
||||||
|
{ |
||||||
|
$columns = $this->normalizeOrderBy($columns); |
||||||
|
if ($this->within === null) { |
||||||
|
$this->within = $columns; |
||||||
|
} else { |
||||||
|
$this->within = array_merge($this->within, $columns); |
||||||
|
} |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the PHP callback, which should be used to retrieve the source data |
||||||
|
* for the snippets building. |
||||||
|
* @param callback $callback PHP callback, which should be used to fetch source data for the snippets. |
||||||
|
* @return static the query object itself |
||||||
|
* @see snippetCallback |
||||||
|
*/ |
||||||
|
public function snippetCallback($callback) |
||||||
|
{ |
||||||
|
$this->snippetCallback = $callback; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the call snippets query options. |
||||||
|
* @param array $options call snippet options in format: option_name => option_value |
||||||
|
* @return static the query object itself |
||||||
|
* @see snippetCallback |
||||||
|
*/ |
||||||
|
public function snippetOptions($options) |
||||||
|
{ |
||||||
|
$this->snippetOptions = $options; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fills the query result rows with the snippets built from source determined by |
||||||
|
* [[snippetCallback]] result. |
||||||
|
* @param array $rows raw query result rows. |
||||||
|
* @return array query result rows with filled up snippets. |
||||||
|
*/ |
||||||
|
protected function fillUpSnippets($rows) |
||||||
|
{ |
||||||
|
if ($this->snippetCallback === null) { |
||||||
|
return $rows; |
||||||
|
} |
||||||
|
$snippetSources = call_user_func($this->snippetCallback, $rows); |
||||||
|
$snippets = $this->callSnippets($snippetSources); |
||||||
|
$snippetKey = 0; |
||||||
|
foreach ($rows as $key => $row) { |
||||||
|
$rows[$key]['snippet'] = $snippets[$snippetKey]; |
||||||
|
$snippetKey++; |
||||||
|
} |
||||||
|
return $rows; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a snippets from provided source data. |
||||||
|
* @param array $source the source data to extract a snippet from. |
||||||
|
* @throws InvalidCallException in case [[match]] is not specified. |
||||||
|
* @return array snippets list. |
||||||
|
*/ |
||||||
|
protected function callSnippets(array $source) |
||||||
|
{ |
||||||
|
$connection = $this->getConnection(); |
||||||
|
$match = $this->match; |
||||||
|
if ($match === null) { |
||||||
|
throw new InvalidCallException('Unable to call snippets: "' . $this->className() . '::match" should be specified.'); |
||||||
|
} |
||||||
|
return $connection->createCommand() |
||||||
|
->callSnippets($this->from[0], $source, $match, $this->snippetOptions) |
||||||
|
->queryColumn(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,904 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\base\Object; |
||||||
|
use yii\db\Exception; |
||||||
|
use yii\db\Expression; |
||||||
|
|
||||||
|
/** |
||||||
|
* QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object. |
||||||
|
* |
||||||
|
* QueryBuilder can also be used to build SQL statements such as INSERT, REPLACE, UPDATE, DELETE, |
||||||
|
* from a [[Query]] object. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class QueryBuilder extends Object |
||||||
|
{ |
||||||
|
/** |
||||||
|
* The prefix for automatically generated query binding parameters. |
||||||
|
*/ |
||||||
|
const PARAM_PREFIX = ':qp'; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var Connection the Sphinx connection. |
||||||
|
*/ |
||||||
|
public $db; |
||||||
|
/** |
||||||
|
* @var string the separator between different fragments of a SQL statement. |
||||||
|
* Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. |
||||||
|
*/ |
||||||
|
public $separator = " "; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. |
||||||
|
* @param Connection $connection the Sphinx connection. |
||||||
|
* @param array $config name-value pairs that will be used to initialize the object properties |
||||||
|
*/ |
||||||
|
public function __construct($connection, $config = []) |
||||||
|
{ |
||||||
|
$this->db = $connection; |
||||||
|
parent::__construct($config); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a SELECT SQL statement from a [[Query]] object. |
||||||
|
* @param Query $query the [[Query]] object from which the SQL statement will be generated |
||||||
|
* @return array the generated SQL statement (the first array element) and the corresponding |
||||||
|
* parameters to be bound to the SQL statement (the second array element). |
||||||
|
*/ |
||||||
|
public function build($query) |
||||||
|
{ |
||||||
|
$params = $query->params; |
||||||
|
if ($query->match !== null) { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = (string)$query->match; |
||||||
|
$query->andWhere('MATCH(' . $phName . ')'); |
||||||
|
} |
||||||
|
$clauses = [ |
||||||
|
$this->buildSelect($query->select, $query->distinct, $query->selectOption), |
||||||
|
$this->buildFrom($query->from), |
||||||
|
$this->buildWhere($query->from, $query->where, $params), |
||||||
|
$this->buildGroupBy($query->groupBy), |
||||||
|
$this->buildWithin($query->within), |
||||||
|
$this->buildOrderBy($query->orderBy), |
||||||
|
$this->buildLimit($query->limit, $query->offset), |
||||||
|
$this->buildOption($query->options, $params), |
||||||
|
]; |
||||||
|
return [implode($this->separator, array_filter($clauses)), $params]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an INSERT SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $sql = $queryBuilder->insert('idx_user', [ |
||||||
|
* 'name' => 'Sam', |
||||||
|
* 'age' => 30, |
||||||
|
* 'id' => 10, |
||||||
|
* ], $params); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The method will properly escape the index and column names. |
||||||
|
* |
||||||
|
* @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. |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* They should be bound to the Sphinx command later. |
||||||
|
* @return string the INSERT SQL |
||||||
|
*/ |
||||||
|
public function insert($index, $columns, &$params) |
||||||
|
{ |
||||||
|
return $this->generateInsertReplace('INSERT', $index, $columns, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an REPLACE SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $sql = $queryBuilder->replace('idx_user', [ |
||||||
|
* 'name' => 'Sam', |
||||||
|
* 'age' => 30, |
||||||
|
* 'id' => 10, |
||||||
|
* ], $params); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The method will properly escape the index and column names. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be replaced. |
||||||
|
* @param array $columns the column data (name => value) to be replaced in the index. |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* They should be bound to the Sphinx command later. |
||||||
|
* @return string the INSERT SQL |
||||||
|
*/ |
||||||
|
public function replace($index, $columns, &$params) |
||||||
|
{ |
||||||
|
return $this->generateInsertReplace('REPLACE', $index, $columns, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates INSERT/REPLACE SQL statement. |
||||||
|
* @param string $statement statement ot be generated. |
||||||
|
* @param string $index the affected index name. |
||||||
|
* @param array $columns the column data (name => value). |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* @return string generated SQL |
||||||
|
*/ |
||||||
|
protected function generateInsertReplace($statement, $index, $columns, &$params) |
||||||
|
{ |
||||||
|
if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { |
||||||
|
$indexSchemas = [$indexSchema]; |
||||||
|
} else { |
||||||
|
$indexSchemas = []; |
||||||
|
} |
||||||
|
$names = []; |
||||||
|
$placeholders = []; |
||||||
|
foreach ($columns as $name => $value) { |
||||||
|
$names[] = $this->db->quoteColumnName($name); |
||||||
|
$placeholders[] = $this->composeColumnValue($indexSchemas, $name, $value, $params); |
||||||
|
} |
||||||
|
return $statement . ' INTO ' . $this->db->quoteIndexName($index) |
||||||
|
. ' (' . implode(', ', $names) . ') VALUES (' |
||||||
|
. implode(', ', $placeholders) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a batch INSERT SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->batchInsert('idx_user', ['id', 'name', 'age'], [ |
||||||
|
* [1, 'Tom', 30], |
||||||
|
* [2, 'Jane', 20], |
||||||
|
* [3, 'Linda', 25], |
||||||
|
* ])->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Note that the values in each row must match the corresponding column names. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be inserted into. |
||||||
|
* @param array $columns the column names |
||||||
|
* @param array $rows the rows to be batch inserted into the index |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* They should be bound to the Sphinx command later. |
||||||
|
* @return string the batch INSERT SQL statement |
||||||
|
*/ |
||||||
|
public function batchInsert($index, $columns, $rows, &$params) |
||||||
|
{ |
||||||
|
return $this->generateBatchInsertReplace('INSERT', $index, $columns, $rows, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a batch REPLACE SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $connection->createCommand()->batchReplace('idx_user', ['id', 'name', 'age'], [ |
||||||
|
* [1, 'Tom', 30], |
||||||
|
* [2, 'Jane', 20], |
||||||
|
* [3, 'Linda', 25], |
||||||
|
* ])->execute(); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* Note that the values in each row must match the corresponding column names. |
||||||
|
* |
||||||
|
* @param string $index the index that new rows will be replaced. |
||||||
|
* @param array $columns the column names |
||||||
|
* @param array $rows the rows to be batch replaced in the index |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* They should be bound to the Sphinx command later. |
||||||
|
* @return string the batch INSERT SQL statement |
||||||
|
*/ |
||||||
|
public function batchReplace($index, $columns, $rows, &$params) |
||||||
|
{ |
||||||
|
return $this->generateBatchInsertReplace('REPLACE', $index, $columns, $rows, $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a batch INSERT/REPLACE SQL statement. |
||||||
|
* @param string $statement statement ot be generated. |
||||||
|
* @param string $index the affected index name. |
||||||
|
* @param array $columns the column data (name => value). |
||||||
|
* @param array $rows the rows to be batch inserted into the index |
||||||
|
* @param array $params the binding parameters that will be generated by this method. |
||||||
|
* @return string generated SQL |
||||||
|
*/ |
||||||
|
protected function generateBatchInsertReplace($statement, $index, $columns, $rows, &$params) |
||||||
|
{ |
||||||
|
if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { |
||||||
|
$indexSchemas = [$indexSchema]; |
||||||
|
} else { |
||||||
|
$indexSchemas = []; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($columns as $i => $name) { |
||||||
|
$columns[$i] = $this->db->quoteColumnName($name); |
||||||
|
} |
||||||
|
|
||||||
|
$values = []; |
||||||
|
foreach ($rows as $row) { |
||||||
|
$vs = []; |
||||||
|
foreach ($row as $i => $value) { |
||||||
|
$vs[] = $this->composeColumnValue($indexSchemas, $columns[$i], $value, $params); |
||||||
|
} |
||||||
|
$values[] = '(' . implode(', ', $vs) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
return $statement . ' INTO ' . $this->db->quoteIndexName($index) |
||||||
|
. ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an UPDATE SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $params = []; |
||||||
|
* $sql = $queryBuilder->update('idx_user', ['status' => 1], 'age > 30', $params); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The method will properly escape the index and column names. |
||||||
|
* |
||||||
|
* @param string $index the index to be updated. |
||||||
|
* @param array $columns the column data (name => value) to be updated. |
||||||
|
* @param array|string $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 binding parameters that will be modified by this method |
||||||
|
* so that they can be bound to the Sphinx command later. |
||||||
|
* @param array $options list of options in format: optionName => optionValue |
||||||
|
* @return string the UPDATE SQL |
||||||
|
*/ |
||||||
|
public function update($index, $columns, $condition, &$params, $options) |
||||||
|
{ |
||||||
|
if (($indexSchema = $this->db->getIndexSchema($index)) !== null) { |
||||||
|
$indexSchemas = [$indexSchema]; |
||||||
|
} else { |
||||||
|
$indexSchemas = []; |
||||||
|
} |
||||||
|
|
||||||
|
$lines = []; |
||||||
|
foreach ($columns as $name => $value) { |
||||||
|
$lines[] = $this->db->quoteColumnName($name) . '=' . $this->composeColumnValue($indexSchemas, $name, $value, $params); |
||||||
|
} |
||||||
|
|
||||||
|
$sql = 'UPDATE ' . $this->db->quoteIndexName($index) . ' SET ' . implode(', ', $lines); |
||||||
|
$where = $this->buildWhere([$index], $condition, $params); |
||||||
|
if ($where !== '') { |
||||||
|
$sql = $sql . ' ' . $where; |
||||||
|
} |
||||||
|
$option = $this->buildOption($options, $params); |
||||||
|
if ($option !== '') { |
||||||
|
$sql = $sql . ' ' . $option; |
||||||
|
} |
||||||
|
return $sql; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a DELETE SQL statement. |
||||||
|
* For example, |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* $sql = $queryBuilder->delete('idx_user', 'status = 0'); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* The method will properly escape the index and column names. |
||||||
|
* |
||||||
|
* @param string $index the index where the data will be deleted from. |
||||||
|
* @param array|string $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 binding parameters that will be modified by this method |
||||||
|
* so that they can be bound to the Sphinx command later. |
||||||
|
* @return string the DELETE SQL |
||||||
|
*/ |
||||||
|
public function delete($index, $condition, &$params) |
||||||
|
{ |
||||||
|
$sql = 'DELETE FROM ' . $this->db->quoteIndexName($index); |
||||||
|
$where = $this->buildWhere([$index], $condition, $params); |
||||||
|
return $where === '' ? $sql : $sql . ' ' . $where; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a SQL statement for truncating an index. |
||||||
|
* @param string $index the index to be truncated. The name will be properly quoted by the method. |
||||||
|
* @return string the SQL statement for truncating an index. |
||||||
|
*/ |
||||||
|
public function truncateIndex($index) |
||||||
|
{ |
||||||
|
return 'TRUNCATE RTINDEX ' . $this->db->quoteIndexName($index); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a SQL statement for call snippet from provided data and query, using specified index settings. |
||||||
|
* @param string $index name of the index, from which to take the text processing settings. |
||||||
|
* @param string|array $source is the source data to extract a snippet from. |
||||||
|
* It could be either a single string or array of strings. |
||||||
|
* @param string $match the full-text query to build snippets for. |
||||||
|
* @param array $options list of options in format: optionName => optionValue |
||||||
|
* @param array $params the binding parameters that will be modified by this method |
||||||
|
* so that they can be bound to the Sphinx command later. |
||||||
|
* @return string the SQL statement for call snippets. |
||||||
|
*/ |
||||||
|
public function callSnippets($index, $source, $match, $options, &$params) |
||||||
|
{ |
||||||
|
if (is_array($source)) { |
||||||
|
$dataSqlParts = []; |
||||||
|
foreach ($source as $sourceRow) { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = $sourceRow; |
||||||
|
$dataSqlParts[] = $phName; |
||||||
|
} |
||||||
|
$dataSql = '(' . implode(',', $dataSqlParts) . ')'; |
||||||
|
} else { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = $source; |
||||||
|
$dataSql = $phName; |
||||||
|
} |
||||||
|
$indexParamName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$indexParamName] = $index; |
||||||
|
$matchParamName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$matchParamName] = $match; |
||||||
|
if (!empty($options)) { |
||||||
|
$optionParts = []; |
||||||
|
foreach ($options as $name => $value) { |
||||||
|
if ($value instanceof Expression) { |
||||||
|
$actualValue = $value->expression; |
||||||
|
} else { |
||||||
|
$actualValue = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$actualValue] = $value; |
||||||
|
} |
||||||
|
$optionParts[] = $actualValue . ' AS ' . $name; |
||||||
|
} |
||||||
|
$optionSql = ', ' . implode(', ', $optionParts); |
||||||
|
} else { |
||||||
|
$optionSql = ''; |
||||||
|
} |
||||||
|
return 'CALL SNIPPETS(' . $dataSql. ', ' . $indexParamName . ', ' . $matchParamName . $optionSql. ')'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds a SQL statement for returning tokenized and normalized forms of the keywords, and, |
||||||
|
* optionally, keyword statistics. |
||||||
|
* @param string $index the name of the index from which to take the text processing settings |
||||||
|
* @param string $text the text to break down to keywords. |
||||||
|
* @param boolean $fetchStatistic whether to return document and hit occurrence statistics |
||||||
|
* @param array $params the binding parameters that will be modified by this method |
||||||
|
* so that they can be bound to the Sphinx command later. |
||||||
|
* @return string the SQL statement for call keywords. |
||||||
|
*/ |
||||||
|
public function callKeywords($index, $text, $fetchStatistic, &$params) |
||||||
|
{ |
||||||
|
$indexParamName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$indexParamName] = $index; |
||||||
|
$textParamName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$textParamName] = $text; |
||||||
|
return 'CALL KEYWORDS(' . $textParamName . ', ' . $indexParamName . ($fetchStatistic ? ', 1' : '') . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $columns |
||||||
|
* @param boolean $distinct |
||||||
|
* @param string $selectOption |
||||||
|
* @return string the SELECT clause built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildSelect($columns, $distinct = false, $selectOption = null) |
||||||
|
{ |
||||||
|
$select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; |
||||||
|
if ($selectOption !== null) { |
||||||
|
$select .= ' ' . $selectOption; |
||||||
|
} |
||||||
|
|
||||||
|
if (empty($columns)) { |
||||||
|
return $select . ' *'; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($columns as $i => $column) { |
||||||
|
if (is_object($column)) { |
||||||
|
$columns[$i] = (string)$column; |
||||||
|
} elseif (strpos($column, '(') === false) { |
||||||
|
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) { |
||||||
|
$columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]); |
||||||
|
} else { |
||||||
|
$columns[$i] = $this->db->quoteColumnName($column); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (is_array($columns)) { |
||||||
|
$columns = implode(', ', $columns); |
||||||
|
} |
||||||
|
|
||||||
|
return $select . ' ' . $columns; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $indexes |
||||||
|
* @return string the FROM clause built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildFrom($indexes) |
||||||
|
{ |
||||||
|
if (empty($indexes)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($indexes as $i => $index) { |
||||||
|
if (strpos($index, '(') === false) { |
||||||
|
if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $index, $matches)) { // with alias |
||||||
|
$indexes[$i] = $this->db->quoteIndexName($matches[1]) . ' ' . $this->db->quoteIndexName($matches[2]); |
||||||
|
} else { |
||||||
|
$indexes[$i] = $this->db->quoteIndexName($index); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (is_array($indexes)) { |
||||||
|
$indexes = implode(', ', $indexes); |
||||||
|
} |
||||||
|
|
||||||
|
return 'FROM ' . $indexes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param string[] $indexes list of index names, which affected by query |
||||||
|
* @param string|array $condition |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the WHERE clause built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildWhere($indexes, $condition, &$params) |
||||||
|
{ |
||||||
|
if (empty($condition)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
$indexSchemas = []; |
||||||
|
if (!empty($indexes)) { |
||||||
|
foreach ($indexes as $indexName) { |
||||||
|
$index = $this->db->getIndexSchema($indexName); |
||||||
|
if ($index !== null) { |
||||||
|
$indexSchemas[] = $index; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
$where = $this->buildCondition($indexSchemas, $condition, $params); |
||||||
|
return $where === '' ? '' : 'WHERE ' . $where; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $columns |
||||||
|
* @return string the GROUP BY clause |
||||||
|
*/ |
||||||
|
public function buildGroupBy($columns) |
||||||
|
{ |
||||||
|
return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $columns |
||||||
|
* @return string the ORDER BY clause built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildOrderBy($columns) |
||||||
|
{ |
||||||
|
if (empty($columns)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
$orders = []; |
||||||
|
foreach ($columns as $name => $direction) { |
||||||
|
if (is_object($direction)) { |
||||||
|
$orders[] = (string)$direction; |
||||||
|
} else { |
||||||
|
$orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : 'ASC'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 'ORDER BY ' . implode(', ', $orders); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param integer $limit |
||||||
|
* @param integer $offset |
||||||
|
* @return string the LIMIT and OFFSET clauses built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildLimit($limit, $offset) |
||||||
|
{ |
||||||
|
$sql = ''; |
||||||
|
if ($limit !== null && $limit >= 0) { |
||||||
|
$sql = 'LIMIT ' . (int)$limit; |
||||||
|
} |
||||||
|
if ($offset > 0) { |
||||||
|
$sql .= ' OFFSET ' . (int)$offset; |
||||||
|
} |
||||||
|
return ltrim($sql); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes columns and properly quote them if necessary. |
||||||
|
* It will join all columns into a string with comma as separators. |
||||||
|
* @param string|array $columns the columns to be processed |
||||||
|
* @return string the processing result |
||||||
|
*/ |
||||||
|
public function buildColumns($columns) |
||||||
|
{ |
||||||
|
if (!is_array($columns)) { |
||||||
|
if (strpos($columns, '(') !== false) { |
||||||
|
return $columns; |
||||||
|
} else { |
||||||
|
$columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); |
||||||
|
} |
||||||
|
} |
||||||
|
foreach ($columns as $i => $column) { |
||||||
|
if (is_object($column)) { |
||||||
|
$columns[$i] = (string)$column; |
||||||
|
} elseif (strpos($column, '(') === false) { |
||||||
|
$columns[$i] = $this->db->quoteColumnName($column); |
||||||
|
} |
||||||
|
} |
||||||
|
return is_array($columns) ? implode(', ', $columns) : $columns; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses the condition specification and generates the corresponding SQL expression. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string|array $condition the condition specification. Please refer to [[Query::where()]] |
||||||
|
* on how to specify a condition. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
* @throws \yii\db\Exception if the condition is in bad format |
||||||
|
*/ |
||||||
|
public function buildCondition($indexes, $condition, &$params) |
||||||
|
{ |
||||||
|
static $builders = [ |
||||||
|
'AND' => 'buildAndCondition', |
||||||
|
'OR' => 'buildAndCondition', |
||||||
|
'BETWEEN' => 'buildBetweenCondition', |
||||||
|
'NOT BETWEEN' => 'buildBetweenCondition', |
||||||
|
'IN' => 'buildInCondition', |
||||||
|
'NOT IN' => 'buildInCondition', |
||||||
|
'LIKE' => 'buildLikeCondition', |
||||||
|
'NOT LIKE' => 'buildLikeCondition', |
||||||
|
'OR LIKE' => 'buildLikeCondition', |
||||||
|
'OR NOT LIKE' => 'buildLikeCondition', |
||||||
|
]; |
||||||
|
|
||||||
|
if (!is_array($condition)) { |
||||||
|
return (string)$condition; |
||||||
|
} elseif (empty($condition)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... |
||||||
|
$operator = strtoupper($condition[0]); |
||||||
|
if (isset($builders[$operator])) { |
||||||
|
$method = $builders[$operator]; |
||||||
|
array_shift($condition); |
||||||
|
return $this->$method($indexes, $operator, $condition, $params); |
||||||
|
} else { |
||||||
|
throw new Exception('Found unknown operator in query: ' . $operator); |
||||||
|
} |
||||||
|
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... |
||||||
|
return $this->buildHashCondition($indexes, $condition, $params); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a condition based on column-value pairs. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param array $condition the condition specification. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
*/ |
||||||
|
public function buildHashCondition($indexes, $condition, &$params) |
||||||
|
{ |
||||||
|
$parts = []; |
||||||
|
foreach ($condition as $column => $value) { |
||||||
|
if (is_array($value)) { // IN condition |
||||||
|
$parts[] = $this->buildInCondition($indexes, 'IN', [$column, $value], $params); |
||||||
|
} else { |
||||||
|
if (strpos($column, '(') === false) { |
||||||
|
$quotedColumn = $this->db->quoteColumnName($column); |
||||||
|
} else { |
||||||
|
$quotedColumn = $column; |
||||||
|
} |
||||||
|
if ($value === null) { |
||||||
|
$parts[] = "$quotedColumn IS NULL"; |
||||||
|
} else { |
||||||
|
$parts[] = $quotedColumn . '=' . $this->composeColumnValue($indexes, $column, $value, $params); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Connects two or more SQL expressions with the `AND` or `OR` operator. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $operator the operator to use for connecting the given operands |
||||||
|
* @param array $operands the SQL expressions to connect. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
*/ |
||||||
|
public function buildAndCondition($indexes, $operator, $operands, &$params) |
||||||
|
{ |
||||||
|
$parts = []; |
||||||
|
foreach ($operands as $operand) { |
||||||
|
if (is_array($operand)) { |
||||||
|
$operand = $this->buildCondition($indexes, $operand, $params); |
||||||
|
} |
||||||
|
if ($operand !== '') { |
||||||
|
$parts[] = $operand; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!empty($parts)) { |
||||||
|
return '(' . implode(") $operator (", $parts) . ')'; |
||||||
|
} else { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an SQL expressions with the `BETWEEN` operator. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) |
||||||
|
* @param array $operands the first operand is the column name. The second and third operands |
||||||
|
* describe the interval that column value should be in. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
* @throws Exception if wrong number of operands have been given. |
||||||
|
*/ |
||||||
|
public function buildBetweenCondition($indexes, $operator, $operands, &$params) |
||||||
|
{ |
||||||
|
if (!isset($operands[0], $operands[1], $operands[2])) { |
||||||
|
throw new Exception("Operator '$operator' requires three operands."); |
||||||
|
} |
||||||
|
|
||||||
|
list($column, $value1, $value2) = $operands; |
||||||
|
|
||||||
|
if (strpos($column, '(') === false) { |
||||||
|
$quotedColumn = $this->db->quoteColumnName($column); |
||||||
|
} else { |
||||||
|
$quotedColumn = $column; |
||||||
|
} |
||||||
|
$phName1 = $this->composeColumnValue($indexes, $column, $value1, $params); |
||||||
|
$phName2 = $this->composeColumnValue($indexes, $column, $value2, $params); |
||||||
|
|
||||||
|
return "$quotedColumn $operator $phName1 AND $phName2"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an SQL expressions with the `IN` operator. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $operator the operator to use (e.g. `IN` or `NOT IN`) |
||||||
|
* @param array $operands the first operand is the column name. If it is an array |
||||||
|
* a composite IN condition will be generated. |
||||||
|
* The second operand is an array of values that column value should be among. |
||||||
|
* If it is an empty array the generated expression will be a `false` value if |
||||||
|
* operator is `IN` and empty if operator is `NOT IN`. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
* @throws Exception if wrong number of operands have been given. |
||||||
|
*/ |
||||||
|
public function buildInCondition($indexes, $operator, $operands, &$params) |
||||||
|
{ |
||||||
|
if (!isset($operands[0], $operands[1])) { |
||||||
|
throw new Exception("Operator '$operator' requires two operands."); |
||||||
|
} |
||||||
|
|
||||||
|
list($column, $values) = $operands; |
||||||
|
|
||||||
|
$values = (array)$values; |
||||||
|
|
||||||
|
if (empty($values) || $column === []) { |
||||||
|
return $operator === 'IN' ? '0=1' : ''; |
||||||
|
} |
||||||
|
|
||||||
|
if (count($column) > 1) { |
||||||
|
return $this->buildCompositeInCondition($indexes, $operator, $column, $values, $params); |
||||||
|
} elseif (is_array($column)) { |
||||||
|
$column = reset($column); |
||||||
|
} |
||||||
|
foreach ($values as $i => $value) { |
||||||
|
if (is_array($value)) { |
||||||
|
$value = isset($value[$column]) ? $value[$column] : null; |
||||||
|
} |
||||||
|
$values[$i] = $this->composeColumnValue($indexes, $column, $value, $params); |
||||||
|
} |
||||||
|
if (strpos($column, '(') === false) { |
||||||
|
$column = $this->db->quoteColumnName($column); |
||||||
|
} |
||||||
|
|
||||||
|
if (count($values) > 1) { |
||||||
|
return "$column $operator (" . implode(', ', $values) . ')'; |
||||||
|
} else { |
||||||
|
$operator = $operator === 'IN' ? '=' : '<>'; |
||||||
|
return "$column$operator{$values[0]}"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $operator the operator to use (e.g. `IN` or `NOT IN`) |
||||||
|
* @param array $columns |
||||||
|
* @param array $values |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
*/ |
||||||
|
protected function buildCompositeInCondition($indexes, $operator, $columns, $values, &$params) |
||||||
|
{ |
||||||
|
$vss = []; |
||||||
|
foreach ($values as $value) { |
||||||
|
$vs = []; |
||||||
|
foreach ($columns as $column) { |
||||||
|
if (isset($value[$column])) { |
||||||
|
$vs[] = $this->composeColumnValue($indexes, $column, $value[$column], $params); |
||||||
|
} else { |
||||||
|
$vs[] = 'NULL'; |
||||||
|
} |
||||||
|
} |
||||||
|
$vss[] = '(' . implode(', ', $vs) . ')'; |
||||||
|
} |
||||||
|
foreach ($columns as $i => $column) { |
||||||
|
if (strpos($column, '(') === false) { |
||||||
|
$columns[$i] = $this->db->quoteColumnName($column); |
||||||
|
} |
||||||
|
} |
||||||
|
return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an SQL expressions with the `LIKE` operator. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) |
||||||
|
* @param array $operands the first operand is the column name. |
||||||
|
* The second operand is a single value or an array of values that column value |
||||||
|
* should be compared with. |
||||||
|
* If it is an empty array the generated expression will be a `false` value if |
||||||
|
* operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the generated SQL expression |
||||||
|
* @throws Exception if wrong number of operands have been given. |
||||||
|
*/ |
||||||
|
public function buildLikeCondition($indexes, $operator, $operands, &$params) |
||||||
|
{ |
||||||
|
if (!isset($operands[0], $operands[1])) { |
||||||
|
throw new Exception("Operator '$operator' requires two operands."); |
||||||
|
} |
||||||
|
|
||||||
|
list($column, $values) = $operands; |
||||||
|
|
||||||
|
$values = (array)$values; |
||||||
|
|
||||||
|
if (empty($values)) { |
||||||
|
return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : ''; |
||||||
|
} |
||||||
|
|
||||||
|
if ($operator === 'LIKE' || $operator === 'NOT LIKE') { |
||||||
|
$andor = ' AND '; |
||||||
|
} else { |
||||||
|
$andor = ' OR '; |
||||||
|
$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; |
||||||
|
} |
||||||
|
|
||||||
|
if (strpos($column, '(') === false) { |
||||||
|
$column = $this->db->quoteColumnName($column); |
||||||
|
} |
||||||
|
|
||||||
|
$parts = []; |
||||||
|
foreach ($values as $value) { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = $value; |
||||||
|
$parts[] = "$column $operator $phName"; |
||||||
|
} |
||||||
|
|
||||||
|
return implode($andor, $parts); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $columns |
||||||
|
* @return string the ORDER BY clause built from [[query]]. |
||||||
|
*/ |
||||||
|
public function buildWithin($columns) |
||||||
|
{ |
||||||
|
if (empty($columns)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
$orders = []; |
||||||
|
foreach ($columns as $name => $direction) { |
||||||
|
if (is_object($direction)) { |
||||||
|
$orders[] = (string)$direction; |
||||||
|
} else { |
||||||
|
$orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : ''); |
||||||
|
} |
||||||
|
} |
||||||
|
return 'WITHIN GROUP ORDER BY ' . implode(', ', $orders); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param array $options query options in format: optionName => optionValue |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string the OPTION clause build from [[query]] |
||||||
|
*/ |
||||||
|
public function buildOption($options, &$params) |
||||||
|
{ |
||||||
|
if (empty($options)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
$optionLines = []; |
||||||
|
foreach ($options as $name => $value) { |
||||||
|
if ($value instanceof Expression) { |
||||||
|
$actualValue = $value->expression; |
||||||
|
} else { |
||||||
|
if (is_array($value)) { |
||||||
|
$actualValueParts = []; |
||||||
|
foreach ($value as $key => $valuePart) { |
||||||
|
if (is_numeric($key)) { |
||||||
|
$actualValuePart = ''; |
||||||
|
} else { |
||||||
|
$actualValuePart = $key . ' = '; |
||||||
|
} |
||||||
|
if ($valuePart instanceof Expression) { |
||||||
|
$actualValuePart .= $valuePart->expression; |
||||||
|
} else { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = $valuePart; |
||||||
|
$actualValuePart .= $phName; |
||||||
|
} |
||||||
|
$actualValueParts[] = $actualValuePart; |
||||||
|
} |
||||||
|
$actualValue = '(' . implode(', ', $actualValueParts) . ')'; |
||||||
|
} else { |
||||||
|
$actualValue = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$actualValue] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
$optionLines[] = $name . ' = ' . $actualValue; |
||||||
|
} |
||||||
|
return 'OPTION ' . implode(', ', $optionLines); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Composes column value for SQL, taking in account the column type. |
||||||
|
* @param IndexSchema[] $indexes list of indexes, which affected by query |
||||||
|
* @param string $columnName name of the column |
||||||
|
* @param mixed $value raw column value |
||||||
|
* @param array $params the binding parameters to be populated |
||||||
|
* @return string SQL expression, which represents column value |
||||||
|
*/ |
||||||
|
protected function composeColumnValue($indexes, $columnName, $value, &$params) { |
||||||
|
if ($value === null) { |
||||||
|
return 'NULL'; |
||||||
|
} elseif ($value instanceof Expression) { |
||||||
|
$params = array_merge($params, $value->params); |
||||||
|
return $value->expression; |
||||||
|
} |
||||||
|
foreach ($indexes as $index) { |
||||||
|
$columnSchema = $index->getColumn($columnName); |
||||||
|
if ($columnSchema !== null) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (is_array($value)) { |
||||||
|
// MVA : |
||||||
|
$lineParts = []; |
||||||
|
foreach ($value as $subValue) { |
||||||
|
if ($subValue instanceof Expression) { |
||||||
|
$params = array_merge($params, $subValue->params); |
||||||
|
$lineParts[] = $subValue->expression; |
||||||
|
} else { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$lineParts[] = $phName; |
||||||
|
$params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($subValue) : $subValue; |
||||||
|
} |
||||||
|
} |
||||||
|
return '(' . implode(',', $lineParts) . ')'; |
||||||
|
} else { |
||||||
|
$phName = self::PARAM_PREFIX . count($params); |
||||||
|
$params[$phName] = (isset($columnSchema)) ? $columnSchema->typecast($value) : $value; |
||||||
|
return $phName; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
Yii 2.0 Public Preview - Sphinx Extension |
||||||
|
========================================= |
||||||
|
|
||||||
|
Thank you for choosing Yii - a high-performance component-based PHP framework. |
||||||
|
|
||||||
|
If you are looking for a production-ready PHP framework, please use |
||||||
|
[Yii v1.1](https://github.com/yiisoft/yii). |
||||||
|
|
||||||
|
Yii 2.0 is still under heavy development. We may make significant changes |
||||||
|
without prior notices. **Yii 2.0 is not ready for production use yet.** |
||||||
|
|
||||||
|
[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) |
||||||
|
|
||||||
|
This is the yii2-sphinx extension. |
||||||
|
|
||||||
|
|
||||||
|
Installation |
||||||
|
------------ |
||||||
|
|
||||||
|
The preferred way to install this extension is through [composer](http://getcomposer.org/download/). |
||||||
|
|
||||||
|
Either run |
||||||
|
``` |
||||||
|
php composer.phar require yiisoft/yii2-sphinx "*" |
||||||
|
``` |
||||||
|
|
||||||
|
or add |
||||||
|
``` |
||||||
|
"yiisoft/yii2-sphinx": "*" |
||||||
|
``` |
||||||
|
to the require section of your composer.json. |
||||||
|
|
||||||
|
|
||||||
|
*Note: You might have to run `php composer.phar selfupdate`* |
||||||
|
|
||||||
|
|
||||||
|
Usage & Documentation |
||||||
|
--------------------- |
||||||
|
|
||||||
|
This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii framework. |
||||||
|
This extension interact with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language. |
||||||
|
In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added: |
||||||
|
``` |
||||||
|
searchd |
||||||
|
{ |
||||||
|
listen = localhost:9306:mysql41 |
||||||
|
... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This extension supports all Sphinx features including [Runtime Indexes](http://sphinxsearch.com/docs/current.html#rt-indexes). |
||||||
|
Since this extension uses MySQL protocol to access Sphinx, it shares base approach and much code from the |
||||||
|
regular "yii\db" package. |
||||||
|
|
||||||
|
To use this extension, simply add the following code in your application configuration: |
||||||
|
|
||||||
|
```php |
||||||
|
return [ |
||||||
|
//.... |
||||||
|
'components' => [ |
||||||
|
'sphinx' => [ |
||||||
|
'class' => 'yii\sphinx\Connection', |
||||||
|
'dsn' => 'mysql:host=127.0.0.1;port=9306;', |
||||||
|
'username' => '', |
||||||
|
'password' => '', |
||||||
|
], |
||||||
|
], |
||||||
|
]; |
||||||
|
``` |
||||||
|
|
||||||
|
This extension provides ActiveRecord solution similar ot the [[\yii\db\ActiveRecord]]. |
||||||
|
To declare an ActiveRecord class you need to extend [[\yii\sphinx\ActiveRecord]] and |
||||||
|
implement the `indexName` method: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\sphinx\ActiveRecord; |
||||||
|
|
||||||
|
class Article extends ActiveRecord |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @return string the name of the index associated with this ActiveRecord class. |
||||||
|
*/ |
||||||
|
public static function indexName() |
||||||
|
{ |
||||||
|
return 'idx_article'; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
You can use [[\yii\data\ActiveDataProvider]] with the [[\yii\sphinx\Query]] and [[\yii\sphinx\ActiveQuery]]: |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\data\ActiveDataProvider; |
||||||
|
use yii\sphinx\Query; |
||||||
|
|
||||||
|
$query = new Query; |
||||||
|
$query->from('yii2_test_article_index')->match('development'); |
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => $query, |
||||||
|
'pagination' => [ |
||||||
|
'pageSize' => 10, |
||||||
|
] |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
``` |
||||||
|
|
||||||
|
```php |
||||||
|
use yii\data\ActiveDataProvider; |
||||||
|
use app\models\Article; |
||||||
|
|
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => Article::find(), |
||||||
|
'pagination' => [ |
||||||
|
'pageSize' => 10, |
||||||
|
] |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
``` |
@ -0,0 +1,489 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\sphinx; |
||||||
|
|
||||||
|
use yii\base\Object; |
||||||
|
use yii\caching\Cache; |
||||||
|
use Yii; |
||||||
|
use yii\caching\GroupDependency; |
||||||
|
|
||||||
|
/** |
||||||
|
* Schema represents the Sphinx schema information. |
||||||
|
* |
||||||
|
* @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only. |
||||||
|
* @property string[] $indexNames All index names in the Sphinx. This property is read-only. |
||||||
|
* @property string[] $indexTypes ALL index types in the Sphinx (index name => index type). |
||||||
|
* This property is read-only. |
||||||
|
* @property IndexSchema[] $tableSchemas The metadata for all indexes in the Sphinx. Each array element is an |
||||||
|
* instance of [[IndexSchema]] or its child class. This property is read-only. |
||||||
|
* |
||||||
|
* @author Paul Klimov <klimov.paul@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class Schema extends Object |
||||||
|
{ |
||||||
|
/** |
||||||
|
* The followings are the supported abstract column data types. |
||||||
|
*/ |
||||||
|
const TYPE_PK = 'pk'; |
||||||
|
const TYPE_STRING = 'string'; |
||||||
|
const TYPE_INTEGER = 'integer'; |
||||||
|
const TYPE_BIGINT = 'bigint'; |
||||||
|
const TYPE_FLOAT = 'float'; |
||||||
|
const TYPE_TIMESTAMP = 'timestamp'; |
||||||
|
const TYPE_BOOLEAN = 'boolean'; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var Connection the Sphinx connection |
||||||
|
*/ |
||||||
|
public $db; |
||||||
|
/** |
||||||
|
* @var array list of ALL index names in the Sphinx |
||||||
|
*/ |
||||||
|
private $_indexNames; |
||||||
|
/** |
||||||
|
* @var array list of ALL index types in the Sphinx (index name => index type) |
||||||
|
*/ |
||||||
|
private $_indexTypes; |
||||||
|
/** |
||||||
|
* @var array list of loaded index metadata (index name => IndexSchema) |
||||||
|
*/ |
||||||
|
private $_indexes = []; |
||||||
|
/** |
||||||
|
* @var QueryBuilder the query builder for this Sphinx connection |
||||||
|
*/ |
||||||
|
private $_builder; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var array mapping from physical column types (keys) to abstract column types (values) |
||||||
|
*/ |
||||||
|
public $typeMap = [ |
||||||
|
'field' => self::TYPE_STRING, |
||||||
|
'string' => self::TYPE_STRING, |
||||||
|
'ordinal' => self::TYPE_STRING, |
||||||
|
'integer' => self::TYPE_INTEGER, |
||||||
|
'int' => self::TYPE_INTEGER, |
||||||
|
'uint' => self::TYPE_INTEGER, |
||||||
|
'bigint' => self::TYPE_BIGINT, |
||||||
|
'timestamp' => self::TYPE_TIMESTAMP, |
||||||
|
'bool' => self::TYPE_BOOLEAN, |
||||||
|
'float' => self::TYPE_FLOAT, |
||||||
|
'mva' => self::TYPE_INTEGER, |
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* Loads the metadata for the specified index. |
||||||
|
* @param string $name index name |
||||||
|
* @return IndexSchema driver dependent index metadata. Null if the index does not exist. |
||||||
|
*/ |
||||||
|
protected function loadIndexSchema($name) |
||||||
|
{ |
||||||
|
$index = new IndexSchema; |
||||||
|
$this->resolveIndexNames($index, $name); |
||||||
|
$this->resolveIndexType($index); |
||||||
|
|
||||||
|
if ($this->findColumns($index)) { |
||||||
|
return $index; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolves the index name. |
||||||
|
* @param IndexSchema $index the index metadata object |
||||||
|
* @param string $name the index name |
||||||
|
*/ |
||||||
|
protected function resolveIndexNames($index, $name) |
||||||
|
{ |
||||||
|
$index->name = str_replace('`', '', $name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolves the index name. |
||||||
|
* @param IndexSchema $index the index metadata object |
||||||
|
*/ |
||||||
|
protected function resolveIndexType($index) |
||||||
|
{ |
||||||
|
$indexTypes = $this->getIndexTypes(); |
||||||
|
$index->type = array_key_exists($index->name, $indexTypes) ? $indexTypes[$index->name] : 'unknown'; |
||||||
|
$index->isRuntime = ($index->type == 'rt'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Obtains the metadata for the named index. |
||||||
|
* @param string $name index name. The index name may contain schema name if any. Do not quote the index name. |
||||||
|
* @param boolean $refresh whether to reload the index schema even if it is found in the cache. |
||||||
|
* @return IndexSchema index metadata. Null if the named index does not exist. |
||||||
|
*/ |
||||||
|
public function getIndexSchema($name, $refresh = false) |
||||||
|
{ |
||||||
|
if (isset($this->_indexes[$name]) && !$refresh) { |
||||||
|
return $this->_indexes[$name]; |
||||||
|
} |
||||||
|
|
||||||
|
$db = $this->db; |
||||||
|
$realName = $this->getRawIndexName($name); |
||||||
|
|
||||||
|
if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) { |
||||||
|
/** @var $cache Cache */ |
||||||
|
$cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache; |
||||||
|
if ($cache instanceof Cache) { |
||||||
|
$key = $this->getCacheKey($name); |
||||||
|
if ($refresh || ($index = $cache->get($key)) === false) { |
||||||
|
$index = $this->loadIndexSchema($realName); |
||||||
|
if ($index !== null) { |
||||||
|
$cache->set($key, $index, $db->schemaCacheDuration, new GroupDependency([ |
||||||
|
'group' => $this->getCacheGroup(), |
||||||
|
])); |
||||||
|
} |
||||||
|
} |
||||||
|
return $this->_indexes[$name] = $index; |
||||||
|
} |
||||||
|
} |
||||||
|
return $this->_indexes[$name] = $index = $this->loadIndexSchema($realName); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the cache key for the specified index name. |
||||||
|
* @param string $name the index name |
||||||
|
* @return mixed the cache key |
||||||
|
*/ |
||||||
|
protected function getCacheKey($name) |
||||||
|
{ |
||||||
|
return [ |
||||||
|
__CLASS__, |
||||||
|
$this->db->dsn, |
||||||
|
$this->db->username, |
||||||
|
$name, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the cache group name. |
||||||
|
* This allows [[refresh()]] to invalidate all cached index schemas. |
||||||
|
* @return string the cache group name |
||||||
|
*/ |
||||||
|
protected function getCacheGroup() |
||||||
|
{ |
||||||
|
return md5(serialize([ |
||||||
|
__CLASS__, |
||||||
|
$this->db->dsn, |
||||||
|
$this->db->username, |
||||||
|
])); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the metadata for all indexes in the database. |
||||||
|
* @param boolean $refresh whether to fetch the latest available index schemas. If this is false, |
||||||
|
* cached data may be returned if available. |
||||||
|
* @return IndexSchema[] the metadata for all indexes in the Sphinx. |
||||||
|
* Each array element is an instance of [[IndexSchema]] or its child class. |
||||||
|
*/ |
||||||
|
public function getIndexSchemas($refresh = false) |
||||||
|
{ |
||||||
|
$indexes = []; |
||||||
|
foreach ($this->getIndexNames($refresh) as $name) { |
||||||
|
if (($index = $this->getIndexSchema($name, $refresh)) !== null) { |
||||||
|
$indexes[] = $index; |
||||||
|
} |
||||||
|
} |
||||||
|
return $indexes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all index names in the Sphinx. |
||||||
|
* @param boolean $refresh whether to fetch the latest available index names. If this is false, |
||||||
|
* index names fetched previously (if available) will be returned. |
||||||
|
* @return string[] all index names in the Sphinx. |
||||||
|
*/ |
||||||
|
public function getIndexNames($refresh = false) |
||||||
|
{ |
||||||
|
if (!isset($this->_indexNames) || $refresh) { |
||||||
|
$this->initIndexesInfo(); |
||||||
|
} |
||||||
|
return $this->_indexNames; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all index types in the Sphinx. |
||||||
|
* @param boolean $refresh whether to fetch the latest available index types. If this is false, |
||||||
|
* index types fetched previously (if available) will be returned. |
||||||
|
* @return array all index types in the Sphinx in format: index name => index type. |
||||||
|
*/ |
||||||
|
public function getIndexTypes($refresh = false) |
||||||
|
{ |
||||||
|
if (!isset($this->_indexTypes) || $refresh) { |
||||||
|
$this->initIndexesInfo(); |
||||||
|
} |
||||||
|
return $this->_indexTypes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes information about name and type of all index in the Sphinx. |
||||||
|
*/ |
||||||
|
protected function initIndexesInfo() |
||||||
|
{ |
||||||
|
$this->_indexNames = []; |
||||||
|
$this->_indexTypes = []; |
||||||
|
$indexes = $this->findIndexes(); |
||||||
|
foreach ($indexes as $index) { |
||||||
|
$indexName = $index['Index']; |
||||||
|
$this->_indexNames[] = $indexName; |
||||||
|
$this->_indexTypes[$indexName] = $index['Type']; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all index names in the Sphinx. |
||||||
|
* @return array all index names in the Sphinx. |
||||||
|
*/ |
||||||
|
protected function findIndexes() |
||||||
|
{ |
||||||
|
$sql = 'SHOW TABLES'; |
||||||
|
return $this->db->createCommand($sql)->queryAll(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return QueryBuilder the query builder for this connection. |
||||||
|
*/ |
||||||
|
public function getQueryBuilder() |
||||||
|
{ |
||||||
|
if ($this->_builder === null) { |
||||||
|
$this->_builder = $this->createQueryBuilder(); |
||||||
|
} |
||||||
|
return $this->_builder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines the PDO type for the given PHP data value. |
||||||
|
* @param mixed $data the data whose PDO type is to be determined |
||||||
|
* @return integer the PDO type |
||||||
|
* @see http://www.php.net/manual/en/pdo.constants.php |
||||||
|
*/ |
||||||
|
public function getPdoType($data) |
||||||
|
{ |
||||||
|
static $typeMap = [ |
||||||
|
// php type => PDO type |
||||||
|
'boolean' => \PDO::PARAM_BOOL, |
||||||
|
'integer' => \PDO::PARAM_INT, |
||||||
|
'string' => \PDO::PARAM_STR, |
||||||
|
'resource' => \PDO::PARAM_LOB, |
||||||
|
'NULL' => \PDO::PARAM_NULL, |
||||||
|
]; |
||||||
|
$type = gettype($data); |
||||||
|
return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Refreshes the schema. |
||||||
|
* This method cleans up all cached index schemas so that they can be re-created later |
||||||
|
* to reflect the Sphinx schema change. |
||||||
|
*/ |
||||||
|
public function refresh() |
||||||
|
{ |
||||||
|
/** @var $cache Cache */ |
||||||
|
$cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache; |
||||||
|
if ($this->db->enableSchemaCache && $cache instanceof Cache) { |
||||||
|
GroupDependency::invalidate($cache, $this->getCacheGroup()); |
||||||
|
} |
||||||
|
$this->_indexNames = []; |
||||||
|
$this->_indexes = []; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a query builder for the Sphinx. |
||||||
|
* @return QueryBuilder query builder instance |
||||||
|
*/ |
||||||
|
public function createQueryBuilder() |
||||||
|
{ |
||||||
|
return new QueryBuilder($this->db); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a string value for use in a query. |
||||||
|
* Note that if the parameter is not a string, it will be returned without change. |
||||||
|
* @param string $str string to be quoted |
||||||
|
* @return string the properly quoted string |
||||||
|
* @see http://www.php.net/manual/en/function.PDO-quote.php |
||||||
|
*/ |
||||||
|
public function quoteValue($str) |
||||||
|
{ |
||||||
|
if (!is_string($str)) { |
||||||
|
return $str; |
||||||
|
} |
||||||
|
$this->db->open(); |
||||||
|
return $this->db->pdo->quote($str); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a index name for use in a query. |
||||||
|
* If the index name contains schema prefix, the prefix will also be properly quoted. |
||||||
|
* If the index name is already quoted or contains '(' or '{{', |
||||||
|
* then this method will do nothing. |
||||||
|
* @param string $name index name |
||||||
|
* @return string the properly quoted index name |
||||||
|
* @see quoteSimpleTableName |
||||||
|
*/ |
||||||
|
public function quoteIndexName($name) |
||||||
|
{ |
||||||
|
if (strpos($name, '(') !== false || strpos($name, '{{') !== false) { |
||||||
|
return $name; |
||||||
|
} |
||||||
|
return $this->quoteSimpleIndexName($name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a column name for use in a query. |
||||||
|
* If the column name contains prefix, the prefix will also be properly quoted. |
||||||
|
* If the column name is already quoted or contains '(', '[[' or '{{', |
||||||
|
* then this method will do nothing. |
||||||
|
* @param string $name column name |
||||||
|
* @return string the properly quoted column name |
||||||
|
* @see quoteSimpleColumnName |
||||||
|
*/ |
||||||
|
public function quoteColumnName($name) |
||||||
|
{ |
||||||
|
if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { |
||||||
|
return $name; |
||||||
|
} |
||||||
|
if (($pos = strrpos($name, '.')) !== false) { |
||||||
|
$prefix = $this->quoteIndexName(substr($name, 0, $pos)) . '.'; |
||||||
|
$name = substr($name, $pos + 1); |
||||||
|
} else { |
||||||
|
$prefix = ''; |
||||||
|
} |
||||||
|
return $prefix . $this->quoteSimpleColumnName($name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a index name for use in a query. |
||||||
|
* A simple index name has no schema prefix. |
||||||
|
* @param string $name index name |
||||||
|
* @return string the properly quoted index name |
||||||
|
*/ |
||||||
|
public function quoteSimpleIndexName($name) |
||||||
|
{ |
||||||
|
return strpos($name, "`") !== false ? $name : "`" . $name . "`"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Quotes a column name for use in a query. |
||||||
|
* A simple column name has no prefix. |
||||||
|
* @param string $name column name |
||||||
|
* @return string the properly quoted column name |
||||||
|
*/ |
||||||
|
public function quoteSimpleColumnName($name) |
||||||
|
{ |
||||||
|
return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the actual name of a given index name. |
||||||
|
* This method will strip off curly brackets from the given index name |
||||||
|
* and replace the percentage character '%' with [[Connection::indexPrefix]]. |
||||||
|
* @param string $name the index name to be converted |
||||||
|
* @return string the real name of the given index name |
||||||
|
*/ |
||||||
|
public function getRawIndexName($name) |
||||||
|
{ |
||||||
|
if (strpos($name, '{{') !== false) { |
||||||
|
$name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name); |
||||||
|
return str_replace('%', $this->db->tablePrefix, $name); |
||||||
|
} else { |
||||||
|
return $name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts the PHP type from abstract DB type. |
||||||
|
* @param ColumnSchema $column the column schema information |
||||||
|
* @return string PHP type name |
||||||
|
*/ |
||||||
|
protected function getColumnPhpType($column) |
||||||
|
{ |
||||||
|
static $typeMap = [ // abstract type => php type |
||||||
|
'smallint' => 'integer', |
||||||
|
'integer' => 'integer', |
||||||
|
'bigint' => 'integer', |
||||||
|
'boolean' => 'boolean', |
||||||
|
'float' => 'double', |
||||||
|
]; |
||||||
|
if (isset($typeMap[$column->type])) { |
||||||
|
if ($column->type === 'bigint') { |
||||||
|
return PHP_INT_SIZE == 8 ? 'integer' : 'string'; |
||||||
|
} elseif ($column->type === 'integer') { |
||||||
|
return PHP_INT_SIZE == 4 ? 'string' : 'integer'; |
||||||
|
} else { |
||||||
|
return $typeMap[$column->type]; |
||||||
|
} |
||||||
|
} else { |
||||||
|
return 'string'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Collects the metadata of index columns. |
||||||
|
* @param IndexSchema $index the index metadata |
||||||
|
* @return boolean whether the index exists in the database |
||||||
|
* @throws \Exception if DB query fails |
||||||
|
*/ |
||||||
|
protected function findColumns($index) |
||||||
|
{ |
||||||
|
$sql = 'DESCRIBE ' . $this->quoteSimpleIndexName($index->name); |
||||||
|
try { |
||||||
|
$columns = $this->db->createCommand($sql)->queryAll(); |
||||||
|
} catch (\Exception $e) { |
||||||
|
$previous = $e->getPrevious(); |
||||||
|
if ($previous instanceof \PDOException && $previous->getCode() == '42S02') { |
||||||
|
// index does not exist |
||||||
|
return false; |
||||||
|
} |
||||||
|
throw $e; |
||||||
|
} |
||||||
|
foreach ($columns as $info) { |
||||||
|
$column = $this->loadColumnSchema($info); |
||||||
|
$index->columns[$column->name] = $column; |
||||||
|
if ($column->isPrimaryKey) { |
||||||
|
$index->primaryKey = $column->name; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Loads the column information into a [[ColumnSchema]] object. |
||||||
|
* @param array $info column information |
||||||
|
* @return ColumnSchema the column schema object |
||||||
|
*/ |
||||||
|
protected function loadColumnSchema($info) |
||||||
|
{ |
||||||
|
$column = new ColumnSchema; |
||||||
|
|
||||||
|
$column->name = $info['Field']; |
||||||
|
$column->dbType = $info['Type']; |
||||||
|
|
||||||
|
$column->isPrimaryKey = ($column->name == 'id'); |
||||||
|
|
||||||
|
$type = $info['Type']; |
||||||
|
if (isset($this->typeMap[$type])) { |
||||||
|
$column->type = $this->typeMap[$type]; |
||||||
|
} else { |
||||||
|
$column->type = self::TYPE_STRING; |
||||||
|
} |
||||||
|
|
||||||
|
$column->isField = ($type == 'field'); |
||||||
|
$column->isAttribute = !$column->isField; |
||||||
|
|
||||||
|
$column->isMva = ($type == 'mva'); |
||||||
|
|
||||||
|
$column->phpType = $this->getColumnPhpType($column); |
||||||
|
|
||||||
|
return $column; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
{ |
||||||
|
"name": "yiisoft/yii2-sphinx", |
||||||
|
"description": "Sphinx full text search engine extension for the Yii framework", |
||||||
|
"keywords": ["yii", "sphinx", "search", "fulltext"], |
||||||
|
"type": "yii2-extension", |
||||||
|
"license": "BSD-3-Clause", |
||||||
|
"support": { |
||||||
|
"issues": "https://github.com/yiisoft/yii2/issues?state=open", |
||||||
|
"forum": "http://www.yiiframework.com/forum/", |
||||||
|
"wiki": "http://www.yiiframework.com/wiki/", |
||||||
|
"irc": "irc://irc.freenode.net/yii", |
||||||
|
"source": "https://github.com/yiisoft/yii2" |
||||||
|
}, |
||||||
|
"authors": [ |
||||||
|
{ |
||||||
|
"name": "Paul Klimov", |
||||||
|
"email": "klimov.paul@gmail.com" |
||||||
|
} |
||||||
|
], |
||||||
|
"minimum-stability": "dev", |
||||||
|
"require": { |
||||||
|
"yiisoft/yii2": "*", |
||||||
|
"ext-pdo": "*", |
||||||
|
"ext-pdo_mysql": "*" |
||||||
|
}, |
||||||
|
"autoload": { |
||||||
|
"psr-0": { "yii\\sphinx\\": "" } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test Sphinx ActiveRecord class |
||||||
|
*/ |
||||||
|
class ActiveRecord extends \yii\sphinx\ActiveRecord |
||||||
|
{ |
||||||
|
public static $db; |
||||||
|
|
||||||
|
public static function getDb() |
||||||
|
{ |
||||||
|
return self::$db; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
use yii\sphinx\ActiveRelation; |
||||||
|
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; |
||||||
|
|
||||||
|
class ArticleDb extends ActiveRecordDb |
||||||
|
{ |
||||||
|
public static function tableName() |
||||||
|
{ |
||||||
|
return 'yii2_test_article'; |
||||||
|
} |
||||||
|
|
||||||
|
public function getIndex() |
||||||
|
{ |
||||||
|
$config = [ |
||||||
|
'modelClass' => ArticleIndex::className(), |
||||||
|
'primaryModel' => $this, |
||||||
|
'link' => ['id' => 'id'], |
||||||
|
'multiple' => false, |
||||||
|
]; |
||||||
|
return new ActiveRelation($config); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
use yii\db\ActiveRelation; |
||||||
|
|
||||||
|
class ArticleIndex extends ActiveRecord |
||||||
|
{ |
||||||
|
public $custom_column; |
||||||
|
|
||||||
|
public static function indexName() |
||||||
|
{ |
||||||
|
return 'yii2_test_article_index'; |
||||||
|
} |
||||||
|
|
||||||
|
public static function favoriteAuthor($query) |
||||||
|
{ |
||||||
|
$query->andWhere('author_id=1'); |
||||||
|
} |
||||||
|
|
||||||
|
public function getSource() |
||||||
|
{ |
||||||
|
return $this->hasOne('db', ArticleDb::className(), ['id' => 'id']); |
||||||
|
} |
||||||
|
|
||||||
|
public function getTags() |
||||||
|
{ |
||||||
|
return $this->hasMany('db', TagDb::className(), ['id' => 'tag']); |
||||||
|
} |
||||||
|
|
||||||
|
public function getSnippetSource() |
||||||
|
{ |
||||||
|
return $this->source->content; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; |
||||||
|
|
||||||
|
class ItemDb extends ActiveRecordDb |
||||||
|
{ |
||||||
|
public static function tableName() |
||||||
|
{ |
||||||
|
return 'yii2_test_item'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
class ItemIndex extends ActiveRecord |
||||||
|
{ |
||||||
|
public static function indexName() |
||||||
|
{ |
||||||
|
return 'yii2_test_item_index'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
class RuntimeIndex extends ActiveRecord |
||||||
|
{ |
||||||
|
public static function indexName() |
||||||
|
{ |
||||||
|
return 'yii2_test_rt_index'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
<?php |
||||||
|
namespace yiiunit\data\sphinx\ar; |
||||||
|
|
||||||
|
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; |
||||||
|
|
||||||
|
class TagDb extends ActiveRecordDb |
||||||
|
{ |
||||||
|
public static function tableName() |
||||||
|
{ |
||||||
|
return 'yii2_test_tag'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
/** |
||||||
|
* This is the MySQL database schema for creation of the test Sphinx index sources. |
||||||
|
*/ |
||||||
|
|
||||||
|
DROP TABLE IF EXISTS yii2_test_article; |
||||||
|
DROP TABLE IF EXISTS yii2_test_item; |
||||||
|
DROP TABLE IF EXISTS yii2_test_tag; |
||||||
|
DROP TABLE IF EXISTS yii2_test_article_tag; |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `yii2_test_article` ( |
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT, |
||||||
|
`title` varchar(255) NOT NULL, |
||||||
|
`content` text NOT NULL, |
||||||
|
`author_id` int(11) NOT NULL, |
||||||
|
`create_date` datetime NOT NULL, |
||||||
|
PRIMARY KEY (`id`) |
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ; |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `yii2_test_item` ( |
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT, |
||||||
|
`name` varchar(255) NOT NULL, |
||||||
|
`description` text NOT NULL, |
||||||
|
`category_id` int(11) NOT NULL, |
||||||
|
`price` float NOT NULL, |
||||||
|
PRIMARY KEY (`id`) |
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3; |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `yii2_test_tag` ( |
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT, |
||||||
|
`name` varchar(255) NOT NULL, |
||||||
|
PRIMARY KEY (`id`) |
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=5; |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `yii2_test_article_tag` ( |
||||||
|
`article_id` int(11) NOT NULL, |
||||||
|
`tag_id` int(11) NOT NULL, |
||||||
|
PRIMARY KEY (`article_id`,`tag_id`) |
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8; |
||||||
|
|
||||||
|
INSERT INTO `yii2_test_article` (`id`, `title`, `content`, `author_id`, `create_date`) VALUES |
||||||
|
(1, 'About cats', 'This article is about cats', 1, '2013-10-23 00:00:00'), |
||||||
|
(2, 'About dogs', 'This article is about dogs', 2, '2013-11-15 00:00:00'); |
||||||
|
|
||||||
|
INSERT INTO `yii2_test_item` (`id`, `name`, `description`, `category_id`, `price`) VALUES |
||||||
|
(1, 'pencil', 'Simple pencil', 1, 2.5), |
||||||
|
(2, 'table', 'Wooden table', 2, 100); |
||||||
|
|
||||||
|
INSERT INTO `yii2_test_tag` (`id`, `name`) VALUES |
||||||
|
(1, 'tag1'), |
||||||
|
(2, 'tag2'), |
||||||
|
(3, 'tag3'), |
||||||
|
(4, 'tag4'); |
||||||
|
|
||||||
|
INSERT INTO `yii2_test_article_tag` (`article_id`, `tag_id`) VALUES |
||||||
|
(1, 1), |
||||||
|
(1, 2), |
||||||
|
(1, 3), |
||||||
|
(2, 3), |
||||||
|
(2, 4); |
@ -0,0 +1,125 @@ |
|||||||
|
# Sphinx configuration for the unit tests |
||||||
|
# |
||||||
|
# Setup test environment: |
||||||
|
# - initialize test database source: |
||||||
|
# mysql -D yii2test -u test < /path/to/yii/tests/unit/data/sphinx/source.sql |
||||||
|
# - setup test Sphinx indexes: |
||||||
|
# indexer --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf --all [--rotate] |
||||||
|
# - run the "searchd" daemon: |
||||||
|
# searchd --config /path/to/yii/tests/unit/data/sphinx/sphinx.conf |
||||||
|
|
||||||
|
|
||||||
|
source yii2_test_article_src |
||||||
|
{ |
||||||
|
type = mysql |
||||||
|
|
||||||
|
sql_host = localhost |
||||||
|
sql_user = |
||||||
|
sql_pass = |
||||||
|
sql_db = yii2test |
||||||
|
sql_port = 3306 # optional, default is 3306 |
||||||
|
|
||||||
|
sql_query = \ |
||||||
|
SELECT *, UNIX_TIMESTAMP(create_date) AS add_date \ |
||||||
|
FROM yii2_test_article |
||||||
|
|
||||||
|
sql_attr_uint = id |
||||||
|
sql_attr_uint = author_id |
||||||
|
sql_attr_timestamp = add_date |
||||||
|
sql_attr_multi = uint tag from query; SELECT article_id AS id, tag_id AS tag FROM yii2_test_article_tag |
||||||
|
|
||||||
|
sql_query_info = SELECT * FROM yii2_test_article WHERE id=$id |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
source yii2_test_item_src |
||||||
|
{ |
||||||
|
type = mysql |
||||||
|
|
||||||
|
sql_host = localhost |
||||||
|
sql_user = |
||||||
|
sql_pass = |
||||||
|
sql_db = yii2test |
||||||
|
sql_port = 3306 # optional, default is 3306 |
||||||
|
|
||||||
|
sql_query = \ |
||||||
|
SELECT *, CURRENT_TIMESTAMP() AS add_date \ |
||||||
|
FROM yii2_test_item \ |
||||||
|
WHERE id <= 100 |
||||||
|
|
||||||
|
sql_attr_uint = id |
||||||
|
sql_attr_uint = category_id |
||||||
|
sql_attr_float = price |
||||||
|
sql_attr_timestamp = add_date |
||||||
|
|
||||||
|
sql_query_info = SELECT * FROM yii2_test_item WHERE id=$id |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
source yii2_test_item_delta_src : yii2_test_item_src |
||||||
|
{ |
||||||
|
sql_query = \ |
||||||
|
SELECT *, CURRENT_TIMESTAMP() AS add_date \ |
||||||
|
FROM yii2_test_item \ |
||||||
|
WHERE id > 100 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
index yii2_test_article_index |
||||||
|
{ |
||||||
|
source = yii2_test_article_src |
||||||
|
path = /var/lib/sphinx/yii2_test_article |
||||||
|
docinfo = extern |
||||||
|
charset_type = sbcs |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
index yii2_test_item_index |
||||||
|
{ |
||||||
|
source = yii2_test_item_src |
||||||
|
path = /var/lib/sphinx/yii2_test_item |
||||||
|
docinfo = extern |
||||||
|
charset_type = sbcs |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
index yii2_test_item_delta_index : yii2_test_item_index |
||||||
|
{ |
||||||
|
source = yii2_test_item_delta_src |
||||||
|
path = /var/lib/sphinx/yii2_test_item_delta |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
index yii2_test_rt_index |
||||||
|
{ |
||||||
|
type = rt |
||||||
|
path = /var/lib/sphinx/yii2_test_rt |
||||||
|
rt_field = title |
||||||
|
rt_field = content |
||||||
|
rt_attr_uint = type_id |
||||||
|
rt_attr_multi = category |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
indexer |
||||||
|
{ |
||||||
|
mem_limit = 32M |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
searchd |
||||||
|
{ |
||||||
|
listen = 127.0.0.1:9312 |
||||||
|
listen = 9306:mysql41 |
||||||
|
log = /var/log/sphinx/searchd.log |
||||||
|
query_log = /var/log/sphinx/query.log |
||||||
|
read_timeout = 5 |
||||||
|
max_children = 30 |
||||||
|
pid_file = /var/run/sphinx/searchd.pid |
||||||
|
max_matches = 1000 |
||||||
|
seamless_rotate = 1 |
||||||
|
preopen_indexes = 1 |
||||||
|
unlink_old = 1 |
||||||
|
workers = threads # for RT to work |
||||||
|
binlog_path = /var/lib/sphinx |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\data\ActiveDataProvider; |
||||||
|
use yii\sphinx\Query; |
||||||
|
use yiiunit\data\sphinx\ar\ActiveRecord; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleIndex; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ActiveDataProviderTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
ActiveRecord::$db = $this->getConnection(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testQuery() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->from('yii2_test_article_index'); |
||||||
|
|
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => $query, |
||||||
|
'db' => $this->getConnection(), |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
$this->assertEquals(2, count($models)); |
||||||
|
|
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => $query, |
||||||
|
'db' => $this->getConnection(), |
||||||
|
'pagination' => [ |
||||||
|
'pageSize' => 1, |
||||||
|
] |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
$this->assertEquals(1, count($models)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testActiveQuery() |
||||||
|
{ |
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => ArticleIndex::find()->orderBy('id ASC'), |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
$this->assertEquals(2, count($models)); |
||||||
|
$this->assertTrue($models[0] instanceof ArticleIndex); |
||||||
|
$this->assertTrue($models[1] instanceof ArticleIndex); |
||||||
|
$this->assertEquals([1, 2], $provider->getKeys()); |
||||||
|
|
||||||
|
$provider = new ActiveDataProvider([ |
||||||
|
'query' => ArticleIndex::find(), |
||||||
|
'pagination' => [ |
||||||
|
'pageSize' => 1, |
||||||
|
] |
||||||
|
]); |
||||||
|
$models = $provider->getModels(); |
||||||
|
$this->assertEquals(1, count($models)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,238 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\sphinx\ActiveQuery; |
||||||
|
use yiiunit\data\sphinx\ar\ActiveRecord; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleIndex; |
||||||
|
use yiiunit\data\sphinx\ar\RuntimeIndex; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ActiveRecordTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
ActiveRecord::$db = $this->getConnection(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown() |
||||||
|
{ |
||||||
|
$this->truncateRuntimeIndex('yii2_test_rt_index'); |
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testFind() |
||||||
|
{ |
||||||
|
// find one |
||||||
|
$result = ArticleIndex::find(); |
||||||
|
$this->assertTrue($result instanceof ActiveQuery); |
||||||
|
$article = $result->one(); |
||||||
|
$this->assertTrue($article instanceof ArticleIndex); |
||||||
|
|
||||||
|
// find all |
||||||
|
$articles = ArticleIndex::find()->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles[0] instanceof ArticleIndex); |
||||||
|
$this->assertTrue($articles[1] instanceof ArticleIndex); |
||||||
|
|
||||||
|
// find fulltext |
||||||
|
$articles = ArticleIndex::find('cats'); |
||||||
|
$this->assertEquals(1, count($articles)); |
||||||
|
$this->assertTrue($articles[0] instanceof ArticleIndex); |
||||||
|
$this->assertEquals(1, $articles[0]->id); |
||||||
|
|
||||||
|
// find by column values |
||||||
|
$article = ArticleIndex::find(['id' => 2, 'author_id' => 2]); |
||||||
|
$this->assertTrue($article instanceof ArticleIndex); |
||||||
|
$this->assertEquals(2, $article->id); |
||||||
|
$this->assertEquals(2, $article->author_id); |
||||||
|
$article = ArticleIndex::find(['id' => 2, 'author_id' => 1]); |
||||||
|
$this->assertNull($article); |
||||||
|
|
||||||
|
// find by attributes |
||||||
|
$article = ArticleIndex::find()->where(['author_id' => 2])->one(); |
||||||
|
$this->assertTrue($article instanceof ArticleIndex); |
||||||
|
$this->assertEquals(2, $article->id); |
||||||
|
|
||||||
|
// find custom column |
||||||
|
$article = ArticleIndex::find()->select(['*', '(5*2) AS custom_column']) |
||||||
|
->where(['author_id' => 1])->one(); |
||||||
|
$this->assertEquals(1, $article->id); |
||||||
|
$this->assertEquals(10, $article->custom_column); |
||||||
|
|
||||||
|
// find count, sum, average, min, max, scalar |
||||||
|
$this->assertEquals(2, ArticleIndex::find()->count()); |
||||||
|
$this->assertEquals(1, ArticleIndex::find()->where('id=1')->count()); |
||||||
|
$this->assertEquals(3, ArticleIndex::find()->sum('id')); |
||||||
|
$this->assertEquals(1.5, ArticleIndex::find()->average('id')); |
||||||
|
$this->assertEquals(1, ArticleIndex::find()->min('id')); |
||||||
|
$this->assertEquals(2, ArticleIndex::find()->max('id')); |
||||||
|
$this->assertEquals(2, ArticleIndex::find()->select('COUNT(*)')->scalar()); |
||||||
|
|
||||||
|
// scope |
||||||
|
$this->assertEquals(1, ArticleIndex::find()->favoriteAuthor()->count()); |
||||||
|
|
||||||
|
// asArray |
||||||
|
$article = ArticleIndex::find()->where('id=2')->asArray()->one(); |
||||||
|
$this->assertEquals([ |
||||||
|
'id' => '2', |
||||||
|
'author_id' => '2', |
||||||
|
'add_date' => '1384466400', |
||||||
|
'tag' => '3,4', |
||||||
|
], $article); |
||||||
|
|
||||||
|
// indexBy |
||||||
|
$articles = ArticleIndex::find()->indexBy('author_id')->orderBy('id DESC')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles['1'] instanceof ArticleIndex); |
||||||
|
$this->assertTrue($articles['2'] instanceof ArticleIndex); |
||||||
|
|
||||||
|
// indexBy callable |
||||||
|
$articles = ArticleIndex::find()->indexBy(function ($article) { |
||||||
|
return $article->id . '-' . $article->author_id; |
||||||
|
})->orderBy('id DESC')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles['1-1'] instanceof ArticleIndex); |
||||||
|
$this->assertTrue($articles['2-2'] instanceof ArticleIndex); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindBySql() |
||||||
|
{ |
||||||
|
// find one |
||||||
|
$article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index ORDER BY id DESC')->one(); |
||||||
|
$this->assertTrue($article instanceof ArticleIndex); |
||||||
|
$this->assertEquals(2, $article->author_id); |
||||||
|
|
||||||
|
// find all |
||||||
|
$articles = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
|
||||||
|
// find with parameter binding |
||||||
|
$article = ArticleIndex::findBySql('SELECT * FROM yii2_test_article_index WHERE id=:id', [':id' => 2])->one(); |
||||||
|
$this->assertTrue($article instanceof ArticleIndex); |
||||||
|
$this->assertEquals(2, $article->author_id); |
||||||
|
} |
||||||
|
|
||||||
|
public function testInsert() |
||||||
|
{ |
||||||
|
$record = new RuntimeIndex; |
||||||
|
$record->id = 15; |
||||||
|
$record->title = 'test title'; |
||||||
|
$record->content = 'test content'; |
||||||
|
$record->type_id = 7; |
||||||
|
$record->category = [1, 2]; |
||||||
|
|
||||||
|
$this->assertTrue($record->isNewRecord); |
||||||
|
|
||||||
|
$record->save(); |
||||||
|
|
||||||
|
$this->assertEquals(15, $record->id); |
||||||
|
$this->assertFalse($record->isNewRecord); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testUpdate() |
||||||
|
{ |
||||||
|
$record = new RuntimeIndex; |
||||||
|
$record->id = 2; |
||||||
|
$record->title = 'test title'; |
||||||
|
$record->content = 'test content'; |
||||||
|
$record->type_id = 7; |
||||||
|
$record->category = [1, 2]; |
||||||
|
$record->save(); |
||||||
|
|
||||||
|
// save |
||||||
|
$record = RuntimeIndex::find(['id' => 2]); |
||||||
|
$this->assertTrue($record instanceof RuntimeIndex); |
||||||
|
$this->assertEquals(7, $record->type_id); |
||||||
|
$this->assertFalse($record->isNewRecord); |
||||||
|
|
||||||
|
$record->type_id = 9; |
||||||
|
$record->save(); |
||||||
|
$this->assertEquals(9, $record->type_id); |
||||||
|
$this->assertFalse($record->isNewRecord); |
||||||
|
$record2 = RuntimeIndex::find(['id' => 2]); |
||||||
|
$this->assertEquals(9, $record2->type_id); |
||||||
|
|
||||||
|
// replace |
||||||
|
$query = 'replace'; |
||||||
|
$rows = RuntimeIndex::find($query); |
||||||
|
$this->assertEmpty($rows); |
||||||
|
$record = RuntimeIndex::find(['id' => 2]); |
||||||
|
$record->content = 'Test content with ' . $query; |
||||||
|
$record->save(); |
||||||
|
$rows = RuntimeIndex::find($query); |
||||||
|
$this->assertNotEmpty($rows); |
||||||
|
|
||||||
|
// updateAll |
||||||
|
$pk = ['id' => 2]; |
||||||
|
$ret = RuntimeIndex::updateAll(['type_id' => 55], $pk); |
||||||
|
$this->assertEquals(1, $ret); |
||||||
|
$record = RuntimeIndex::find($pk); |
||||||
|
$this->assertEquals(55, $record->type_id); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testDelete() |
||||||
|
{ |
||||||
|
// delete |
||||||
|
$record = new RuntimeIndex; |
||||||
|
$record->id = 2; |
||||||
|
$record->title = 'test title'; |
||||||
|
$record->content = 'test content'; |
||||||
|
$record->type_id = 7; |
||||||
|
$record->category = [1, 2]; |
||||||
|
$record->save(); |
||||||
|
|
||||||
|
$record = RuntimeIndex::find(['id' => 2]); |
||||||
|
$record->delete(); |
||||||
|
$record = RuntimeIndex::find(['id' => 2]); |
||||||
|
$this->assertNull($record); |
||||||
|
|
||||||
|
// deleteAll |
||||||
|
$record = new RuntimeIndex; |
||||||
|
$record->id = 2; |
||||||
|
$record->title = 'test title'; |
||||||
|
$record->content = 'test content'; |
||||||
|
$record->type_id = 7; |
||||||
|
$record->category = [1, 2]; |
||||||
|
$record->save(); |
||||||
|
|
||||||
|
$ret = RuntimeIndex::deleteAll('id = 2'); |
||||||
|
$this->assertEquals(1, $ret); |
||||||
|
$records = RuntimeIndex::find()->all(); |
||||||
|
$this->assertEquals(0, count($records)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testCallSnippets() |
||||||
|
{ |
||||||
|
$query = 'pencil'; |
||||||
|
$source = 'Some data sentence about ' . $query; |
||||||
|
|
||||||
|
$snippet = ArticleIndex::callSnippets($source, $query); |
||||||
|
$this->assertNotEmpty($snippet, 'Unable to call snippets!'); |
||||||
|
$this->assertContains('<b>' . $query . '</b>', $snippet, 'Query not present in the snippet!'); |
||||||
|
|
||||||
|
$rows = ArticleIndex::callSnippets([$source], $query); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call snippets!'); |
||||||
|
$this->assertContains('<b>' . $query . '</b>', $rows[0], 'Query not present in the snippet!'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testCallKeywords() |
||||||
|
{ |
||||||
|
$text = 'table pencil'; |
||||||
|
$rows = ArticleIndex::callKeywords($text); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call keywords!'); |
||||||
|
$this->assertArrayHasKey('tokenized', $rows[0], 'No tokenized keyword!'); |
||||||
|
$this->assertArrayHasKey('normalized', $rows[0], 'No normalized keyword!'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yiiunit\data\sphinx\ar\ActiveRecord; |
||||||
|
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleIndex; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleDb; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ActiveRelationTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
ActiveRecord::$db = $this->getConnection(); |
||||||
|
ActiveRecordDb::$db = $this->getDbConnection(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testFindLazy() |
||||||
|
{ |
||||||
|
/** @var ArticleDb $article */ |
||||||
|
$article = ArticleDb::find(['id' => 2]); |
||||||
|
$this->assertFalse($article->isRelationPopulated('index')); |
||||||
|
$index = $article->index; |
||||||
|
$this->assertTrue($article->isRelationPopulated('index')); |
||||||
|
$this->assertTrue($index instanceof ArticleIndex); |
||||||
|
$this->assertEquals(1, count($article->populatedRelations)); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindEager() |
||||||
|
{ |
||||||
|
$articles = ArticleDb::find()->with('index')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles[0]->isRelationPopulated('index')); |
||||||
|
$this->assertTrue($articles[1]->isRelationPopulated('index')); |
||||||
|
$this->assertTrue($articles[0]->index instanceof ArticleIndex); |
||||||
|
$this->assertTrue($articles[1]->index instanceof ArticleIndex); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\sphinx\ColumnSchema; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ColumnSchemaTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Data provider for [[testTypeCast]] |
||||||
|
* @return array test data. |
||||||
|
*/ |
||||||
|
public function dataProviderTypeCast() |
||||||
|
{ |
||||||
|
return [ |
||||||
|
[ |
||||||
|
'integer', |
||||||
|
'integer', |
||||||
|
5, |
||||||
|
5 |
||||||
|
], |
||||||
|
[ |
||||||
|
'integer', |
||||||
|
'integer', |
||||||
|
'5', |
||||||
|
5 |
||||||
|
], |
||||||
|
[ |
||||||
|
'string', |
||||||
|
'string', |
||||||
|
5, |
||||||
|
'5' |
||||||
|
], |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dataProvider dataProviderTypeCast |
||||||
|
* |
||||||
|
* @param $type |
||||||
|
* @param $phpType |
||||||
|
* @param $value |
||||||
|
* @param $expectedResult |
||||||
|
*/ |
||||||
|
public function testTypeCast($type, $phpType, $value, $expectedResult) |
||||||
|
{ |
||||||
|
$columnSchema = new ColumnSchema(); |
||||||
|
$columnSchema->type = $type; |
||||||
|
$columnSchema->phpType = $phpType; |
||||||
|
$this->assertEquals($expectedResult, $columnSchema->typecast($value)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,409 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\db\DataReader; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class CommandTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
protected function tearDown() |
||||||
|
{ |
||||||
|
$this->truncateRuntimeIndex('yii2_test_rt_index'); |
||||||
|
parent::tearDown(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testConstruct() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(false); |
||||||
|
|
||||||
|
// null |
||||||
|
$command = $db->createCommand(); |
||||||
|
$this->assertEquals(null, $command->sql); |
||||||
|
|
||||||
|
// string |
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index'; |
||||||
|
$params = [ |
||||||
|
'name' => 'value' |
||||||
|
]; |
||||||
|
$command = $db->createCommand($sql, $params); |
||||||
|
$this->assertEquals($sql, $command->sql); |
||||||
|
$this->assertEquals($params, $command->params); |
||||||
|
} |
||||||
|
|
||||||
|
public function testGetSetSql() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(false); |
||||||
|
|
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$this->assertEquals($sql, $command->sql); |
||||||
|
|
||||||
|
$sql2 = 'SELECT * FROM yii2_test_item_index'; |
||||||
|
$command->sql = $sql2; |
||||||
|
$this->assertEquals($sql2, $command->sql); |
||||||
|
} |
||||||
|
|
||||||
|
public function testAutoQuoting() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(false); |
||||||
|
|
||||||
|
$sql = 'SELECT [[id]], [[t.name]] FROM {{yii2_test_item_index}} t'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$this->assertEquals("SELECT `id`, `t`.`name` FROM `yii2_test_item_index` t", $command->sql); |
||||||
|
} |
||||||
|
|
||||||
|
public function testPrepareCancel() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(false); |
||||||
|
|
||||||
|
$command = $db->createCommand('SELECT * FROM yii2_test_item_index'); |
||||||
|
$this->assertEquals(null, $command->pdoStatement); |
||||||
|
$command->prepare(); |
||||||
|
$this->assertNotEquals(null, $command->pdoStatement); |
||||||
|
$command->cancel(); |
||||||
|
$this->assertEquals(null, $command->pdoStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public function testExecute() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$sql = 'SELECT COUNT(*) FROM yii2_test_item_index WHERE MATCH(\'wooden\')'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$this->assertEquals(1, $command->queryScalar()); |
||||||
|
|
||||||
|
$command = $db->createCommand('bad SQL'); |
||||||
|
$this->setExpectedException('\yii\db\Exception'); |
||||||
|
$command->execute(); |
||||||
|
} |
||||||
|
|
||||||
|
public function testQuery() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
// query |
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index'; |
||||||
|
$reader = $db->createCommand($sql)->query(); |
||||||
|
$this->assertTrue($reader instanceof DataReader); |
||||||
|
|
||||||
|
// queryAll |
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_item_index')->queryAll(); |
||||||
|
$this->assertEquals(2, count($rows)); |
||||||
|
$row = $rows[1]; |
||||||
|
$this->assertEquals(2, $row['id']); |
||||||
|
$this->assertEquals(2, $row['category_id']); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_item_index WHERE id=10')->queryAll(); |
||||||
|
$this->assertEquals([], $rows); |
||||||
|
|
||||||
|
// queryOne |
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; |
||||||
|
$row = $db->createCommand($sql)->queryOne(); |
||||||
|
$this->assertEquals(1, $row['id']); |
||||||
|
$this->assertEquals(1, $row['category_id']); |
||||||
|
|
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$command->prepare(); |
||||||
|
$row = $command->queryOne(); |
||||||
|
$this->assertEquals(1, $row['id']); |
||||||
|
$this->assertEquals(1, $row['category_id']); |
||||||
|
|
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index WHERE id=10'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$this->assertFalse($command->queryOne()); |
||||||
|
|
||||||
|
// queryColumn |
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index'; |
||||||
|
$column = $db->createCommand($sql)->queryColumn(); |
||||||
|
$this->assertEquals(range(1, 2), $column); |
||||||
|
|
||||||
|
$command = $db->createCommand('SELECT id FROM yii2_test_item_index WHERE id=10'); |
||||||
|
$this->assertEquals([], $command->queryColumn()); |
||||||
|
|
||||||
|
// queryScalar |
||||||
|
$sql = 'SELECT * FROM yii2_test_item_index ORDER BY id ASC'; |
||||||
|
$this->assertEquals($db->createCommand($sql)->queryScalar(), 1); |
||||||
|
|
||||||
|
$sql = 'SELECT id FROM yii2_test_item_index ORDER BY id ASC'; |
||||||
|
$command = $db->createCommand($sql); |
||||||
|
$command->prepare(); |
||||||
|
$this->assertEquals(1, $command->queryScalar()); |
||||||
|
|
||||||
|
$command = $db->createCommand('SELECT id FROM yii2_test_item_index WHERE id=10'); |
||||||
|
$this->assertFalse($command->queryScalar()); |
||||||
|
|
||||||
|
$command = $db->createCommand('bad SQL'); |
||||||
|
$this->setExpectedException('\yii\db\Exception'); |
||||||
|
$command->query(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testQuery |
||||||
|
*/ |
||||||
|
public function testInsert() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$command = $db->createCommand()->insert('yii2_test_rt_index', [ |
||||||
|
'title' => 'Test title', |
||||||
|
'content' => 'Test content', |
||||||
|
'type_id' => 2, |
||||||
|
'category' => [1, 2], |
||||||
|
'id' => 1, |
||||||
|
]); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to execute insert!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals(1, count($rows), 'No row inserted!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testBatchInsert() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$command = $db->createCommand()->batchInsert( |
||||||
|
'yii2_test_rt_index', |
||||||
|
[ |
||||||
|
'title', |
||||||
|
'content', |
||||||
|
'type_id', |
||||||
|
'category', |
||||||
|
'id', |
||||||
|
], |
||||||
|
[ |
||||||
|
[ |
||||||
|
'Test title 1', |
||||||
|
'Test content 1', |
||||||
|
1, |
||||||
|
[1, 2], |
||||||
|
1, |
||||||
|
], |
||||||
|
[ |
||||||
|
'Test title 2', |
||||||
|
'Test content 2', |
||||||
|
2, |
||||||
|
[3, 4], |
||||||
|
2, |
||||||
|
], |
||||||
|
] |
||||||
|
); |
||||||
|
$this->assertEquals(2, $command->execute(), 'Unable to execute batch insert!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals(2, count($rows), 'No rows inserted!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testReplace() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$command = $db->createCommand()->replace('yii2_test_rt_index', [ |
||||||
|
'title' => 'Test title', |
||||||
|
'content' => 'Test content', |
||||||
|
'type_id' => 2, |
||||||
|
'category' => [1, 2], |
||||||
|
'id' => 1, |
||||||
|
]); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to execute replace!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals(1, count($rows), 'No row inserted!'); |
||||||
|
|
||||||
|
$newTypeId = 5; |
||||||
|
$command = $db->createCommand()->replace('yii2_test_rt_index',[ |
||||||
|
'type_id' => $newTypeId, |
||||||
|
'category' => [3, 4], |
||||||
|
'id' => 1, |
||||||
|
]); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to update via replace!'); |
||||||
|
|
||||||
|
list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testReplace |
||||||
|
*/ |
||||||
|
public function testBatchReplace() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$command = $db->createCommand()->batchReplace( |
||||||
|
'yii2_test_rt_index', |
||||||
|
[ |
||||||
|
'title', |
||||||
|
'content', |
||||||
|
'type_id', |
||||||
|
'category', |
||||||
|
'id', |
||||||
|
], |
||||||
|
[ |
||||||
|
[ |
||||||
|
'Test title 1', |
||||||
|
'Test content 1', |
||||||
|
1, |
||||||
|
[1, 2], |
||||||
|
1, |
||||||
|
], |
||||||
|
[ |
||||||
|
'Test title 2', |
||||||
|
'Test content 2', |
||||||
|
2, |
||||||
|
[3, 4], |
||||||
|
2, |
||||||
|
], |
||||||
|
] |
||||||
|
); |
||||||
|
$this->assertEquals(2, $command->execute(), 'Unable to execute batch replace!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals(2, count($rows), 'No rows inserted!'); |
||||||
|
|
||||||
|
$newTypeId = 5; |
||||||
|
$command = $db->createCommand()->replace('yii2_test_rt_index',[ |
||||||
|
'type_id' => $newTypeId, |
||||||
|
'id' => 1, |
||||||
|
]); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to update via replace!'); |
||||||
|
list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testUpdate() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$db->createCommand()->insert('yii2_test_rt_index', [ |
||||||
|
'title' => 'Test title', |
||||||
|
'content' => 'Test content', |
||||||
|
'type_id' => 2, |
||||||
|
'id' => 1, |
||||||
|
])->execute(); |
||||||
|
|
||||||
|
$newTypeId = 5; |
||||||
|
$command = $db->createCommand()->update( |
||||||
|
'yii2_test_rt_index', |
||||||
|
[ |
||||||
|
'type_id' => $newTypeId, |
||||||
|
'category' => [3, 4], |
||||||
|
], |
||||||
|
'id = 1' |
||||||
|
); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to execute update!'); |
||||||
|
|
||||||
|
list($row) = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals($newTypeId, $row['type_id'], 'Unable to update attribute value!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testUpdate |
||||||
|
*/ |
||||||
|
public function testUpdateWithOptions() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$db->createCommand()->insert('yii2_test_rt_index', [ |
||||||
|
'title' => 'Test title', |
||||||
|
'content' => 'Test content', |
||||||
|
'type_id' => 2, |
||||||
|
'id' => 1, |
||||||
|
])->execute(); |
||||||
|
|
||||||
|
$newTypeId = 5; |
||||||
|
$command = $db->createCommand()->update( |
||||||
|
'yii2_test_rt_index', |
||||||
|
[ |
||||||
|
'type_id' => $newTypeId, |
||||||
|
'non_existing_attribute' => 10, |
||||||
|
], |
||||||
|
'id = 1', |
||||||
|
[], |
||||||
|
[ |
||||||
|
'ignore_nonexistent_columns' => 1 |
||||||
|
] |
||||||
|
); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to execute update!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testInsert |
||||||
|
*/ |
||||||
|
public function testDelete() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$db->createCommand()->insert('yii2_test_rt_index', [ |
||||||
|
'title' => 'Test title', |
||||||
|
'content' => 'Test content', |
||||||
|
'type_id' => 2, |
||||||
|
'id' => 1, |
||||||
|
])->execute(); |
||||||
|
|
||||||
|
$command = $db->createCommand()->delete('yii2_test_rt_index', 'id = 1'); |
||||||
|
$this->assertEquals(1, $command->execute(), 'Unable to execute delete!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand('SELECT * FROM yii2_test_rt_index')->queryAll(); |
||||||
|
$this->assertEquals(0, count($rows), 'Unable to delete record!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testQuery |
||||||
|
*/ |
||||||
|
public function testCallSnippets() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$query = 'pencil'; |
||||||
|
$source = 'Some data sentence about ' . $query; |
||||||
|
|
||||||
|
$rows = $db->createCommand()->callSnippets('yii2_test_item_index', $source, $query)->queryColumn(); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call snippets!'); |
||||||
|
$this->assertContains('<b>' . $query . '</b>', $rows[0], 'Query not present in the snippet!'); |
||||||
|
|
||||||
|
$rows = $db->createCommand()->callSnippets('yii2_test_item_index', [$source], $query)->queryColumn(); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call snippets for array source!'); |
||||||
|
|
||||||
|
$options = [ |
||||||
|
'before_match' => '[', |
||||||
|
'after_match' => ']', |
||||||
|
'limit' => 20, |
||||||
|
]; |
||||||
|
$snippet = $db->createCommand()->callSnippets('yii2_test_item_index', $source, $query, $options)->queryScalar(); |
||||||
|
$this->assertContains($options['before_match'] . $query . $options['after_match'], $snippet, 'Unable to apply options!'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testQuery |
||||||
|
*/ |
||||||
|
public function testCallKeywords() |
||||||
|
{ |
||||||
|
$db = $this->getConnection(); |
||||||
|
|
||||||
|
$text = 'table pencil'; |
||||||
|
$rows = $db->createCommand()->callKeywords('yii2_test_item_index', $text)->queryAll(); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call keywords!'); |
||||||
|
$this->assertArrayHasKey('tokenized', $rows[0], 'No tokenized keyword!'); |
||||||
|
$this->assertArrayHasKey('normalized', $rows[0], 'No normalized keyword!'); |
||||||
|
|
||||||
|
$text = 'table pencil'; |
||||||
|
$rows = $db->createCommand()->callKeywords('yii2_test_item_index', $text, true)->queryAll(); |
||||||
|
$this->assertNotEmpty($rows, 'Unable to call keywords with statistic!'); |
||||||
|
$this->assertArrayHasKey('docs', $rows[0], 'No docs!'); |
||||||
|
$this->assertArrayHasKey('hits', $rows[0], 'No hits!'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\sphinx\Connection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ConnectionTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
public function testConstruct() |
||||||
|
{ |
||||||
|
$connection = $this->getConnection(false); |
||||||
|
$params = $this->sphinxConfig; |
||||||
|
|
||||||
|
$this->assertEquals($params['dsn'], $connection->dsn); |
||||||
|
$this->assertEquals($params['username'], $connection->username); |
||||||
|
$this->assertEquals($params['password'], $connection->password); |
||||||
|
} |
||||||
|
|
||||||
|
public function testOpenClose() |
||||||
|
{ |
||||||
|
$connection = $this->getConnection(false, false); |
||||||
|
|
||||||
|
$this->assertFalse($connection->isActive); |
||||||
|
$this->assertEquals(null, $connection->pdo); |
||||||
|
|
||||||
|
$connection->open(); |
||||||
|
$this->assertTrue($connection->isActive); |
||||||
|
$this->assertTrue($connection->pdo instanceof \PDO); |
||||||
|
|
||||||
|
$connection->close(); |
||||||
|
$this->assertFalse($connection->isActive); |
||||||
|
$this->assertEquals(null, $connection->pdo); |
||||||
|
|
||||||
|
$connection = new Connection; |
||||||
|
$connection->dsn = 'unknown::memory:'; |
||||||
|
$this->setExpectedException('yii\db\Exception'); |
||||||
|
$connection->open(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yiiunit\data\sphinx\ar\ActiveRecord; |
||||||
|
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleIndex; |
||||||
|
use yiiunit\data\sphinx\ar\ArticleDb; |
||||||
|
use yiiunit\data\sphinx\ar\TagDb; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class ExternalActiveRelationTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
ActiveRecord::$db = $this->getConnection(); |
||||||
|
ActiveRecordDb::$db = $this->getDbConnection(); |
||||||
|
} |
||||||
|
|
||||||
|
// Tests : |
||||||
|
|
||||||
|
public function testFindLazy() |
||||||
|
{ |
||||||
|
/** @var ArticleIndex $article */ |
||||||
|
$article = ArticleIndex::find(['id' => 2]); |
||||||
|
|
||||||
|
// has one : |
||||||
|
$this->assertFalse($article->isRelationPopulated('source')); |
||||||
|
$source = $article->source; |
||||||
|
$this->assertTrue($article->isRelationPopulated('source')); |
||||||
|
$this->assertTrue($source instanceof ArticleDb); |
||||||
|
$this->assertEquals(1, count($article->populatedRelations)); |
||||||
|
|
||||||
|
// has many : |
||||||
|
/*$this->assertFalse($article->isRelationPopulated('tags')); |
||||||
|
$tags = $article->tags; |
||||||
|
$this->assertTrue($article->isRelationPopulated('tags')); |
||||||
|
$this->assertEquals(3, count($tags)); |
||||||
|
$this->assertTrue($tags[0] instanceof TagDb);*/ |
||||||
|
} |
||||||
|
|
||||||
|
public function testFindEager() |
||||||
|
{ |
||||||
|
// has one : |
||||||
|
$articles = ArticleIndex::find()->with('source')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles[0]->isRelationPopulated('source')); |
||||||
|
$this->assertTrue($articles[1]->isRelationPopulated('source')); |
||||||
|
$this->assertTrue($articles[0]->source instanceof ArticleDb); |
||||||
|
$this->assertTrue($articles[1]->source instanceof ArticleDb); |
||||||
|
|
||||||
|
// has many : |
||||||
|
/*$articles = ArticleIndex::find()->with('tags')->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
$this->assertTrue($articles[0]->isRelationPopulated('tags')); |
||||||
|
$this->assertTrue($articles[1]->isRelationPopulated('tags'));*/ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testFindEager |
||||||
|
*/ |
||||||
|
public function testFindWithSnippets() |
||||||
|
{ |
||||||
|
$articles = ArticleIndex::find() |
||||||
|
->match('about') |
||||||
|
->with('source') |
||||||
|
->snippetByModel() |
||||||
|
->all(); |
||||||
|
$this->assertEquals(2, count($articles)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,187 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\sphinx\Query; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class QueryTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
public function testSelect() |
||||||
|
{ |
||||||
|
// default |
||||||
|
$query = new Query; |
||||||
|
$query->select('*'); |
||||||
|
$this->assertEquals(['*'], $query->select); |
||||||
|
$this->assertNull($query->distinct); |
||||||
|
$this->assertEquals(null, $query->selectOption); |
||||||
|
|
||||||
|
$query = new Query; |
||||||
|
$query->select('id, name', 'something')->distinct(true); |
||||||
|
$this->assertEquals(['id', 'name'], $query->select); |
||||||
|
$this->assertTrue($query->distinct); |
||||||
|
$this->assertEquals('something', $query->selectOption); |
||||||
|
} |
||||||
|
|
||||||
|
public function testFrom() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->from('tbl_user'); |
||||||
|
$this->assertEquals(['tbl_user'], $query->from); |
||||||
|
} |
||||||
|
|
||||||
|
public function testMatch() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$match = 'test match'; |
||||||
|
$query->match($match); |
||||||
|
$this->assertEquals($match, $query->match); |
||||||
|
|
||||||
|
$command = $query->createCommand($this->getConnection(false)); |
||||||
|
$this->assertContains('MATCH(', $command->getSql(), 'No MATCH operator present!'); |
||||||
|
$this->assertContains($match, $command->params, 'No match query among params!'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testWhere() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->where('id = :id', [':id' => 1]); |
||||||
|
$this->assertEquals('id = :id', $query->where); |
||||||
|
$this->assertEquals([':id' => 1], $query->params); |
||||||
|
|
||||||
|
$query->andWhere('name = :name', [':name' => 'something']); |
||||||
|
$this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); |
||||||
|
$this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); |
||||||
|
|
||||||
|
$query->orWhere('age = :age', [':age' => '30']); |
||||||
|
$this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); |
||||||
|
$this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); |
||||||
|
} |
||||||
|
|
||||||
|
public function testGroup() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->groupBy('team'); |
||||||
|
$this->assertEquals(['team'], $query->groupBy); |
||||||
|
|
||||||
|
$query->addGroupBy('company'); |
||||||
|
$this->assertEquals(['team', 'company'], $query->groupBy); |
||||||
|
|
||||||
|
$query->addGroupBy('age'); |
||||||
|
$this->assertEquals(['team', 'company', 'age'], $query->groupBy); |
||||||
|
} |
||||||
|
|
||||||
|
public function testOrder() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->orderBy('team'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC], $query->orderBy); |
||||||
|
|
||||||
|
$query->addOrderBy('company'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); |
||||||
|
|
||||||
|
$query->addOrderBy('age'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); |
||||||
|
|
||||||
|
$query->addOrderBy(['age' => SORT_DESC]); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); |
||||||
|
|
||||||
|
$query->addOrderBy('age ASC, company DESC'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); |
||||||
|
} |
||||||
|
|
||||||
|
public function testLimitOffset() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->limit(10)->offset(5); |
||||||
|
$this->assertEquals(10, $query->limit); |
||||||
|
$this->assertEquals(5, $query->offset); |
||||||
|
} |
||||||
|
|
||||||
|
public function testWithin() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$query->within('team'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC], $query->within); |
||||||
|
|
||||||
|
$query->addWithin('company'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->within); |
||||||
|
|
||||||
|
$query->addWithin('age'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->within); |
||||||
|
|
||||||
|
$query->addWithin(['age' => SORT_DESC]); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->within); |
||||||
|
|
||||||
|
$query->addWithin('age ASC, company DESC'); |
||||||
|
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->within); |
||||||
|
} |
||||||
|
|
||||||
|
public function testOptions() |
||||||
|
{ |
||||||
|
$query = new Query; |
||||||
|
$options = [ |
||||||
|
'cutoff' => 50, |
||||||
|
'max_matches' => 50, |
||||||
|
]; |
||||||
|
$query->options($options); |
||||||
|
$this->assertEquals($options, $query->options); |
||||||
|
|
||||||
|
$newMaxMatches = $options['max_matches'] + 10; |
||||||
|
$query->addOptions(['max_matches' => $newMaxMatches]); |
||||||
|
$this->assertEquals($newMaxMatches, $query->options['max_matches']); |
||||||
|
} |
||||||
|
|
||||||
|
public function testRun() |
||||||
|
{ |
||||||
|
$connection = $this->getConnection(); |
||||||
|
|
||||||
|
$query = new Query; |
||||||
|
$rows = $query->from('yii2_test_article_index') |
||||||
|
->match('about') |
||||||
|
->options([ |
||||||
|
'cutoff' => 50, |
||||||
|
'field_weights' => [ |
||||||
|
'title' => 10, |
||||||
|
'content' => 3, |
||||||
|
], |
||||||
|
]) |
||||||
|
->all($connection); |
||||||
|
$this->assertNotEmpty($rows); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @depends testRun |
||||||
|
*/ |
||||||
|
public function testSnippet() |
||||||
|
{ |
||||||
|
$connection = $this->getConnection(); |
||||||
|
|
||||||
|
$match = 'about'; |
||||||
|
$snippetPrefix = 'snippet#'; |
||||||
|
$snippetCallback = function() use ($match, $snippetPrefix) { |
||||||
|
return [ |
||||||
|
$snippetPrefix . '1: ' . $match, |
||||||
|
$snippetPrefix . '2: ' . $match, |
||||||
|
]; |
||||||
|
}; |
||||||
|
$snippetOptions = [ |
||||||
|
'before_match' => '[', |
||||||
|
'after_match' => ']', |
||||||
|
]; |
||||||
|
|
||||||
|
$query = new Query; |
||||||
|
$rows = $query->from('yii2_test_article_index') |
||||||
|
->match($match) |
||||||
|
->snippetCallback($snippetCallback) |
||||||
|
->snippetOptions($snippetOptions) |
||||||
|
->all($connection); |
||||||
|
$this->assertNotEmpty($rows); |
||||||
|
foreach ($rows as $row) { |
||||||
|
$this->assertContains($snippetPrefix, $row['snippet'], 'Snippet source not present!'); |
||||||
|
$this->assertContains($snippetOptions['before_match'] . $match, $row['snippet'] . $snippetOptions['after_match'], 'Options not applied!'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\caching\FileCache; |
||||||
|
use yii\sphinx\Schema; |
||||||
|
|
||||||
|
/** |
||||||
|
* @group sphinx |
||||||
|
*/ |
||||||
|
class SchemaTest extends SphinxTestCase |
||||||
|
{ |
||||||
|
public function testFindIndexNames() |
||||||
|
{ |
||||||
|
$schema = $this->getConnection()->schema; |
||||||
|
|
||||||
|
$indexes = $schema->getIndexNames(); |
||||||
|
$this->assertContains('yii2_test_article_index', $indexes); |
||||||
|
$this->assertContains('yii2_test_item_index', $indexes); |
||||||
|
$this->assertContains('yii2_test_rt_index', $indexes); |
||||||
|
} |
||||||
|
|
||||||
|
public function testGetIndexSchemas() |
||||||
|
{ |
||||||
|
$schema = $this->getConnection()->schema; |
||||||
|
|
||||||
|
$indexes = $schema->getIndexSchemas(); |
||||||
|
$this->assertEquals(count($schema->getIndexNames()), count($indexes)); |
||||||
|
foreach($indexes as $index) { |
||||||
|
$this->assertInstanceOf('yii\sphinx\IndexSchema', $index); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function testGetNonExistingIndexSchema() |
||||||
|
{ |
||||||
|
$this->assertNull($this->getConnection()->schema->getIndexSchema('non_existing_index')); |
||||||
|
} |
||||||
|
|
||||||
|
public function testSchemaRefresh() |
||||||
|
{ |
||||||
|
$schema = $this->getConnection()->schema; |
||||||
|
|
||||||
|
$schema->db->enableSchemaCache = true; |
||||||
|
$schema->db->schemaCache = new FileCache(); |
||||||
|
$noCacheIndex = $schema->getIndexSchema('yii2_test_rt_index', true); |
||||||
|
$cachedIndex = $schema->getIndexSchema('yii2_test_rt_index', true); |
||||||
|
$this->assertEquals($noCacheIndex, $cachedIndex); |
||||||
|
} |
||||||
|
|
||||||
|
public function testGetPDOType() |
||||||
|
{ |
||||||
|
$values = [ |
||||||
|
[null, \PDO::PARAM_NULL], |
||||||
|
['', \PDO::PARAM_STR], |
||||||
|
['hello', \PDO::PARAM_STR], |
||||||
|
[0, \PDO::PARAM_INT], |
||||||
|
[1, \PDO::PARAM_INT], |
||||||
|
[1337, \PDO::PARAM_INT], |
||||||
|
[true, \PDO::PARAM_BOOL], |
||||||
|
[false, \PDO::PARAM_BOOL], |
||||||
|
[$fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB], |
||||||
|
]; |
||||||
|
|
||||||
|
$schema = $this->getConnection()->schema; |
||||||
|
|
||||||
|
foreach($values as $value) { |
||||||
|
$this->assertEquals($value[1], $schema->getPdoType($value[0])); |
||||||
|
} |
||||||
|
fclose($fp); |
||||||
|
} |
||||||
|
|
||||||
|
public function testIndexType() |
||||||
|
{ |
||||||
|
$schema = $this->getConnection()->schema; |
||||||
|
|
||||||
|
$index = $schema->getIndexSchema('yii2_test_article_index'); |
||||||
|
$this->assertEquals('local', $index->type); |
||||||
|
$this->assertFalse($index->isRuntime); |
||||||
|
|
||||||
|
$index = $schema->getIndexSchema('yii2_test_rt_index'); |
||||||
|
$this->assertEquals('rt', $index->type); |
||||||
|
$this->assertTrue($index->isRuntime); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace yiiunit\extensions\sphinx; |
||||||
|
|
||||||
|
use yii\helpers\FileHelper; |
||||||
|
use yii\sphinx\Connection; |
||||||
|
use Yii; |
||||||
|
use yiiunit\TestCase as TestCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base class for the Sphinx test cases. |
||||||
|
*/ |
||||||
|
class SphinxTestCase extends TestCase |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var array Sphinx connection configuration. |
||||||
|
*/ |
||||||
|
protected $sphinxConfig = [ |
||||||
|
'dsn' => 'mysql:host=127.0.0.1;port=9306;', |
||||||
|
'username' => '', |
||||||
|
'password' => '', |
||||||
|
]; |
||||||
|
/** |
||||||
|
* @var Connection Sphinx connection instance. |
||||||
|
*/ |
||||||
|
protected $sphinx; |
||||||
|
/** |
||||||
|
* @var array Database connection configuration. |
||||||
|
*/ |
||||||
|
protected $dbConfig = [ |
||||||
|
'dsn' => 'mysql:host=127.0.0.1;', |
||||||
|
'username' => '', |
||||||
|
'password' => '', |
||||||
|
]; |
||||||
|
/** |
||||||
|
* @var \yii\db\Connection database connection instance. |
||||||
|
*/ |
||||||
|
protected $db; |
||||||
|
|
||||||
|
public static function setUpBeforeClass() |
||||||
|
{ |
||||||
|
static::loadClassMap(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function setUp() |
||||||
|
{ |
||||||
|
parent::setUp(); |
||||||
|
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) { |
||||||
|
$this->markTestSkipped('pdo and pdo_mysql extension are required.'); |
||||||
|
} |
||||||
|
$config = $this->getParam('sphinx'); |
||||||
|
if (!empty($config)) { |
||||||
|
$this->sphinxConfig = $config['sphinx']; |
||||||
|
$this->dbConfig = $config['db']; |
||||||
|
} |
||||||
|
$this->mockApplication(); |
||||||
|
static::loadClassMap(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown() |
||||||
|
{ |
||||||
|
if ($this->sphinx) { |
||||||
|
$this->sphinx->close(); |
||||||
|
} |
||||||
|
$this->destroyApplication(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds sphinx extension files to [[Yii::$classPath]], |
||||||
|
* avoiding the necessity of usage Composer autoloader. |
||||||
|
*/ |
||||||
|
protected static function loadClassMap() |
||||||
|
{ |
||||||
|
$baseNameSpace = 'yii/sphinx'; |
||||||
|
$basePath = realpath(__DIR__. '/../../../../extensions/sphinx'); |
||||||
|
$files = FileHelper::findFiles($basePath); |
||||||
|
foreach ($files as $file) { |
||||||
|
$classRelativePath = str_replace($basePath, '', $file); |
||||||
|
$classFullName = str_replace(['/', '.php'], ['\\', ''], $baseNameSpace . $classRelativePath); |
||||||
|
Yii::$classMap[$classFullName] = $file; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param bool $reset whether to clean up the test database |
||||||
|
* @param bool $open whether to open test database |
||||||
|
* @return \yii\sphinx\Connection |
||||||
|
*/ |
||||||
|
public function getConnection($reset = false, $open = true) |
||||||
|
{ |
||||||
|
if (!$reset && $this->sphinx) { |
||||||
|
return $this->sphinx; |
||||||
|
} |
||||||
|
$db = new Connection; |
||||||
|
$db->dsn = $this->sphinxConfig['dsn']; |
||||||
|
if (isset($this->sphinxConfig['username'])) { |
||||||
|
$db->username = $this->sphinxConfig['username']; |
||||||
|
$db->password = $this->sphinxConfig['password']; |
||||||
|
} |
||||||
|
if (isset($this->sphinxConfig['attributes'])) { |
||||||
|
$db->attributes = $this->sphinxConfig['attributes']; |
||||||
|
} |
||||||
|
if ($open) { |
||||||
|
$db->open(); |
||||||
|
} |
||||||
|
$this->sphinx = $db; |
||||||
|
return $db; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Truncates the runtime index. |
||||||
|
* @param string $indexName index name. |
||||||
|
*/ |
||||||
|
protected function truncateRuntimeIndex($indexName) |
||||||
|
{ |
||||||
|
if ($this->sphinx) { |
||||||
|
$this->sphinx->createCommand('TRUNCATE RTINDEX ' . $indexName)->execute(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param bool $reset whether to clean up the test database |
||||||
|
* @param bool $open whether to open and populate test database |
||||||
|
* @return \yii\db\Connection |
||||||
|
*/ |
||||||
|
public function getDbConnection($reset = true, $open = true) |
||||||
|
{ |
||||||
|
if (!$reset && $this->db) { |
||||||
|
return $this->db; |
||||||
|
} |
||||||
|
$db = new \yii\db\Connection; |
||||||
|
$db->dsn = $this->dbConfig['dsn']; |
||||||
|
if (isset($this->dbConfig['username'])) { |
||||||
|
$db->username = $this->dbConfig['username']; |
||||||
|
$db->password = $this->dbConfig['password']; |
||||||
|
} |
||||||
|
if (isset($this->dbConfig['attributes'])) { |
||||||
|
$db->attributes = $this->dbConfig['attributes']; |
||||||
|
} |
||||||
|
if ($open) { |
||||||
|
$db->open(); |
||||||
|
if (!empty($this->dbConfig['fixture'])) { |
||||||
|
$lines = explode(';', file_get_contents($this->dbConfig['fixture'])); |
||||||
|
foreach ($lines as $line) { |
||||||
|
if (trim($line) !== '') { |
||||||
|
$db->pdo->exec($line); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
$this->db = $db; |
||||||
|
return $db; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue