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.** | ||||
| 
 | ||||
| [](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