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