Commit 13f6a112 by Qiang Xue

Merge pull request #1259 from klimov-paul/sphinx

Sphinx fulltext search engine integration
parents 4b353c7b 5a8afcf7
......@@ -18,7 +18,7 @@ before_script:
- tests/unit/data/travis/cubrid-setup.sh
script:
- phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
- phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor,sphinx
after_script:
- php vendor/bin/coveralls
<?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;
}
}
\ No newline at end of file
<?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\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\base\Model;
use yii\base\ModelEvent;
use yii\base\NotSupportedException;
use yii\base\UnknownMethodException;
use yii\db\ActiveRelationInterface;
use yii\db\StaleObjectException;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
use Yii;
/**
* ActiveRecord is the base class for classes representing relational data in terms of objects.
*
* @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
* read-only.
* @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
* @property array $oldAttributes The old attribute values (name-value pairs).
* @property integer $oldPrimaryKey The old primary key value. This property is read-only.
* @property array $populatedRelations An array of relation data indexed by relation names. This property is
* read-only.
* @property integer $primaryKey The primary key value. This property is read-only.
* @property string $snippet current snippet value for this Active Record instance..
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
abstract class ActiveRecord extends Model
{
/**
* @event Event an event that is triggered when the record is initialized via [[init()]].
*/
const EVENT_INIT = 'init';
/**
* @event Event an event that is triggered after the record is created and populated with query result.
*/
const EVENT_AFTER_FIND = 'afterFind';
/**
* @event ModelEvent an event that is triggered before inserting a record.
* You may set [[ModelEvent::isValid]] to be false to stop the insertion.
*/
const EVENT_BEFORE_INSERT = 'beforeInsert';
/**
* @event Event an event that is triggered after a record is inserted.
*/
const EVENT_AFTER_INSERT = 'afterInsert';
/**
* @event ModelEvent an event that is triggered before updating a record.
* You may set [[ModelEvent::isValid]] to be false to stop the update.
*/
const EVENT_BEFORE_UPDATE = 'beforeUpdate';
/**
* @event Event an event that is triggered after a record is updated.
*/
const EVENT_AFTER_UPDATE = 'afterUpdate';
/**
* @event ModelEvent an event that is triggered before deleting a record.
* You may set [[ModelEvent::isValid]] to be false to stop the deletion.
*/
const EVENT_BEFORE_DELETE = 'beforeDelete';
/**
* @event Event an event that is triggered after a record is deleted.
*/
const EVENT_AFTER_DELETE = 'afterDelete';
/**
* The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
*/
const OP_INSERT = 0x01;
/**
* The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
*/
const OP_UPDATE = 0x02;
/**
* The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
*/
const OP_DELETE = 0x04;
/**
* All three operations: insert, update, delete.
* This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
*/
const OP_ALL = 0x07;
/**
* @var array attribute values indexed by attribute names
*/
private $_attributes = [];
/**
* @var array old attribute values indexed by attribute names.
*/
private $_oldAttributes;
/**
* @var array related models indexed by the relation names
*/
private $_related = [];
/**
* @var string current snippet value for this Active Record instance.
* It will be filled up automatically when instance found using [[Query::snippetCallback]]
* or [[ActiveQuery::snippetByModel()]].
*/
private $_snippet;
/**
* Returns the Sphinx connection used by this AR class.
* By default, the "sphinx" application component is used as the Sphinx connection.
* You may override this method if you want to use a different Sphinx connection.
* @return Connection the Sphinx connection used by this AR class.
*/
public static function getDb()
{
return \Yii::$app->getComponent('sphinx');
}
/**
* Creates an [[ActiveQuery]] instance for query purpose.
*
* @param mixed $q the query parameter. This can be one of the followings:
*
* - a string: fulltext query by a query string and return the list
* of matching records.
* - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
* - null: return a new [[ActiveQuery]] object for further query purpose.
*
* @return ActiveQuery|ActiveRecord[]|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
* is returned; when `$q` is a string, an array of ActiveRecord objects matching it will be returned;
* when `$q` is an array, an ActiveRecord object matching it will be returned (null
* will be returned if there is no matching).
* @see createQuery()
*/
public static function find($q = null)
{
$query = static::createQuery();
if (is_array($q)) {
return $query->where($q)->one();
} elseif ($q !== null) {
return $query->match($q)->all();
}
return $query;
}
/**
* Creates an [[ActiveQuery]] instance with a given SQL statement.
*
* Note that because the SQL statement is already specified, calling additional
* query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
* instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
* still fine.
*
* Below is an example:
*
* ~~~
* $customers = Article::findBySql("SELECT * FROM `idx_article` WHERE MATCH('development')")->all();
* ~~~
*
* @param string $sql the SQL statement to be executed
* @param array $params parameters to be bound to the SQL statement during execution.
* @return ActiveQuery the newly created [[ActiveQuery]] instance
*/
public static function findBySql($sql, $params = [])
{
$query = static::createQuery();
$query->sql = $sql;
return $query->params($params);
}
/**
* Updates the whole table using the provided attribute values and conditions.
* For example, to change the status to be 1 for all articles which status is 2:
*
* ~~~
* Article::updateAll(['status' => 1], 'status = 2');
* ~~~
*
* @param array $attributes attribute values (name-value pairs) to be saved into the table
* @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
* @return integer the number of rows updated
*/
public static function updateAll($attributes, $condition = '', $params = [])
{
$command = static::getDb()->createCommand();
$command->update(static::indexName(), $attributes, $condition, $params);
return $command->execute();
}
/**
* Deletes rows in the index using the provided conditions.
*
* For example, to delete all articles whose status is 3:
*
* ~~~
* Article::deleteAll('status = 3');
* ~~~
*
* @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
* @return integer the number of rows deleted
*/
public static function deleteAll($condition = '', $params = [])
{
$command = static::getDb()->createCommand();
$command->delete(static::indexName(), $condition, $params);
return $command->execute();
}
/**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query.
* You may override this method to return a customized query (e.g. `ArticleQuery` specified
* written for querying `Article` purpose.)
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
{
return new ActiveQuery(['modelClass' => get_called_class()]);
}
/**
* Declares the name of the Sphinx index associated with this AR class.
* By default this method returns the class name as the index name by calling [[Inflector::camel2id()]].
* For example, 'Article' becomes 'article', and 'StockItem' becomes
* 'stock_item'. You may override this method if the index is not named after this convention.
* @return string the index name
*/
public static function indexName()
{
return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
}
/**
* Returns the schema information of the Sphinx index associated with this AR class.
* @return IndexSchema the schema information of the Sphinx index associated with this AR class.
* @throws InvalidConfigException if the index for the AR class does not exist.
*/
public static function getIndexSchema()
{
$schema = static::getDb()->getIndexSchema(static::indexName());
if ($schema !== null) {
return $schema;
} else {
throw new InvalidConfigException("The index does not exist: " . static::indexName());
}
}
/**
* Returns the primary key name for this AR class.
* The default implementation will return the primary key as declared
* in the Sphinx index, which is associated with this AR class.
*
* Note that an array should be returned even for a table with single primary key.
*
* @return string[] the primary keys of the associated Sphinx index.
*/
public static function primaryKey()
{
return [static::getIndexSchema()->primaryKey];
}
/**
* Builds a snippet from provided data and query, using specified index 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 string|array built snippet in case "source" is a string, list of built snippets
* in case "source" is an array.
*/
public static function callSnippets($source, $match, $options = [])
{
$command = static::getDb()->createCommand();
$command->callSnippets(static::indexName(), $source, $match, $options);
if (is_array($source)) {
return $command->queryColumn();
} else {
return $command->queryScalar();
}
}
/**
* Returns tokenized and normalized forms of the keywords, and, optionally, keyword statistics.
* @param string $text the text to break down to keywords.
* @param boolean $fetchStatistic whether to return document and hit occurrence statistics
* @return array keywords and statistics
*/
public static function callKeywords($text, $fetchStatistic = false)
{
$command = static::getDb()->createCommand();
$command->callKeywords(static::indexName(), $text, $fetchStatistic);
return $command->queryAll();
}
/**
* @param string $snippet
*/
public function setSnippet($snippet)
{
$this->_snippet = $snippet;
}
/**
* Returns current snippet value or generates new one from given match.
* @param string $match snippet source query
* @param array $options list of options in format: optionName => optionValue
* @return string snippet value
*/
public function getSnippet($match = null, $options = [])
{
if ($match !== null) {
$this->_snippet = $this->fetchSnippet($match, $options);
}
return $this->_snippet;
}
/**
* Builds up the snippet value from the given query.
* @param string $match the full-text query to build snippets for.
* @param array $options list of options in format: optionName => optionValue
* @return string snippet value.
*/
protected function fetchSnippet($match, $options = [])
{
return static::callSnippets($this->getSnippetSource(), $match, $options);
}
/**
* Returns the string, which should be used as a source to create snippet for this
* Active Record instance.
* Child classes must implement this method to return the actual snippet source text.
* For example:
* ~~~
* public function getSnippetSource()
* {
* return $this->snippetSourceRelation->content;
* }
* ~~~
* @return string snippet source string.
* @throws \yii\base\NotSupportedException if this is not supported by the Active Record class
*/
public function getSnippetSource()
{
throw new NotSupportedException($this->className() . ' does not provide snippet source.');
}
/**
* Returns the name of the column that stores the lock version for implementing optimistic locking.
*
* Optimistic locking allows multiple users to access the same record for edits and avoids
* potential conflicts. In case when a user attempts to save the record upon some staled data
* (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
* and the update or deletion is skipped.
*
* Optimistic locking is only supported by [[update()]] and [[delete()]].
*
* To use optimistic locking:
*
* 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
* Override this method to return the name of this column.
* 2. In the Web form that collects the user input, add a hidden field that stores
* the lock version of the recording being updated.
* 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
* and implement necessary business logic (e.g. merging the changes, prompting stated data)
* to resolve the conflict.
*
* Warning: optimistic lock will NOT work in case of updating fields (not attributes) for the
* runtime indexes!
*
* @return string the column name that stores the lock version of a table row.
* If null is returned (default implemented), optimistic locking will not be supported.
*/
public function optimisticLock()
{
return null;
}
/**
* Declares which operations should be performed within a transaction in different scenarios.
* The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
* which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
* By default, these methods are NOT enclosed in a transaction.
*
* In some scenarios, to ensure data consistency, you may want to enclose some or all of them
* in transactions. You can do so by overriding this method and returning the operations
* that need to be transactional. For example,
*
* ~~~
* return [
* 'admin' => self::OP_INSERT,
* 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
* // the above is equivalent to the following:
* // 'api' => self::OP_ALL,
*
* ];
* ~~~
*
* The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
* should be done in a transaction; and in the "api" scenario, all the operations should be done
* in a transaction.
*
* @return array the declarations of transactional operations. The array keys are scenarios names,
* and the array values are the corresponding transaction operations.
*/
public function transactions()
{
return [];
}
/**
* PHP getter magic method.
* This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name
* @return mixed property value
* @see getAttribute
*/
public function __get($name)
{
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
return $this->_attributes[$name];
} elseif ($this->hasAttribute($name)) {
return null;
} else {
if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
return $this->_related[$name];
}
$value = parent::__get($name);
if ($value instanceof ActiveRelationInterface) {
return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
} else {
return $value;
}
}
}
/**
* PHP setter magic method.
* This method is overridden so that AR attributes can be accessed like properties.
* @param string $name property name
* @param mixed $value property value
*/
public function __set($name, $value)
{
if ($this->hasAttribute($name)) {
$this->_attributes[$name] = $value;
} else {
parent::__set($name, $value);
}
}
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking if the named attribute is null or not.
* @param string $name the property name or the event name
* @return boolean whether the property value is null
*/
public function __isset($name)
{
try {
return $this->__get($name) !== null;
} catch (\Exception $e) {
return false;
}
}
/**
* Sets a component property to be null.
* This method overrides the parent implementation by clearing
* the specified attribute value.
* @param string $name the property name or the event name
*/
public function __unset($name)
{
if ($this->hasAttribute($name)) {
unset($this->_attributes[$name]);
} else {
if (isset($this->_related[$name])) {
unset($this->_related[$name]);
} else {
parent::__unset($name);
}
}
}
/**
* Declares a `has-one` relation.
* The declaration is returned in terms of an [[ActiveRelationInterface]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-one` relation means that there is at most one related record matching
* the criteria set by this relation, e.g., a particular index has one source.
*
* For example, to declare the `source` relation for `ArticleIndex` class, we can write
* the following code in the `ArticleIndex` class:
*
* ~~~
* public function getSource()
* {
* return $this->hasOne('db', ArticleContent::className(), ['article_id' => 'id']);
* }
* ~~~
*
* Note that in the above, the 'article_id' key in the `$link` parameter refers to an attribute name
* in the related class `ArticleContent`, while the 'id' value refers to an attribute name
* in the current AR class.
*
* @param string $type relation type or class name.
* - if value contains backslash ("\"), it is treated as full active relation class name,
* for example: "app\mydb\ActiveRelation"
* - if value does not contain backslash ("\"), the active relation class name will be composed
* by pattern: "yii\{type}\ActiveRelation", for example: type "db" refers "yii\db\ActiveRelation",
* type "sphinx" - "yii\sphinx\ActiveRelation"
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the attributes in the `$class` model, while the values of the array refer to the corresponding
* attributes in the index associated with this AR class.
* @return ActiveRelationInterface the relation object.
*/
public function hasOne($type, $class, $link)
{
return $this->createActiveRelation($type, [
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
'multiple' => false,
]);
}
/**
* Declares a `has-many` relation.
* The declaration is returned in terms of an [[ActiveRelationInterface]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-many` relation means that there are multiple related records matching
* the criteria set by this relation, e.g., an article has many tags.
*
* For example, to declare the `tags` relation for `ArticleIndex` class, we can write
* the following code in the `ArticleIndex` class:
*
* ~~~
* public function getOrders()
* {
* return $this->hasMany('db', Tag::className(), ['id' => 'tag_id']);
* }
* ~~~
*
* Note that in the above, the 'id' key in the `$link` parameter refers to
* an attribute name in the related class `Tag`, while the 'tag_id' value refers to
* a multi value attribute name in the current AR class.
*
* @param string $type relation type or class name.
* - if value contains backslash ("\"), it is treated as full active relation class name,
* for example: "app\mydb\ActiveRelation"
* - if value does not contain backslash ("\"), the active relation class name will be composed
* by pattern: "yii\{type}\ActiveRelation", for example: type "db" refers "yii\db\ActiveRelation",
* type "sphinx" - "yii\sphinx\ActiveRelation"
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelationInterface the relation object.
*/
public function hasMany($type, $class, $link)
{
return $this->createActiveRelation($type, [
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
'multiple' => true,
]);
}
/**
* Creates an [[ActiveRelationInterface]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param string $type relation type or class name.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelationInterface the newly created [[ActiveRelation]] instance.
*/
protected function createActiveRelation($type, $config = [])
{
if (strpos($type, '\\') === false) {
$class = "yii\\{$type}\\ActiveRelation";
} else {
$class = $type;
}
$config['class'] = $class;
return Yii::createObject($config);
}
/**
* Populates the named relation with the related records.
* Note that this method does not check if the relation exists or not.
* @param string $name the relation name (case-sensitive)
* @param ActiveRecord|array|null the related records to be populated into the relation.
*/
public function populateRelation($name, $records)
{
$this->_related[$name] = $records;
}
/**
* Check whether the named relation has been populated with records.
* @param string $name the relation name (case-sensitive)
* @return bool whether relation has been populated with records.
*/
public function isRelationPopulated($name)
{
return array_key_exists($name, $this->_related);
}
/**
* Returns all populated relations.
* @return array an array of relation data indexed by relation names.
*/
public function getPopulatedRelations()
{
return $this->_related;
}
/**
* Returns the list of all attribute names of the model.
* The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names.
*/
public function attributes()
{
return array_keys($this->getIndexSchema()->columns);
}
/**
* Returns a value indicating whether the model has an attribute with the specified name.
* @param string $name the name of the attribute
* @return boolean whether the model has an attribute with the specified name.
*/
public function hasAttribute($name)
{
return isset($this->_attributes[$name]) || isset($this->getIndexSchema()->columns[$name]);
}
/**
* Returns the named attribute value.
* If this record is the result of a query and the attribute is not loaded,
* null will be returned.
* @param string $name the attribute name
* @return mixed the attribute value. Null if the attribute is not set or does not exist.
* @see hasAttribute
*/
public function getAttribute($name)
{
return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
}
/**
* Sets the named attribute value.
* @param string $name the attribute name
* @param mixed $value the attribute value.
* @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute
*/
public function setAttribute($name, $value)
{
if ($this->hasAttribute($name)) {
$this->_attributes[$name] = $value;
} else {
throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
}
}
/**
* Returns the old attribute values.
* @return array the old attribute values (name-value pairs)
*/
public function getOldAttributes()
{
return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
}
/**
* Sets the old attribute values.
* All existing old attribute values will be discarded.
* @param array $values old attribute values to be set.
*/
public function setOldAttributes($values)
{
$this->_oldAttributes = $values;
}
/**
* Returns the old value of the named attribute.
* If this record is the result of a query and the attribute is not loaded,
* null will be returned.
* @param string $name the attribute name
* @return mixed the old attribute value. Null if the attribute is not loaded before
* or does not exist.
* @see hasAttribute
*/
public function getOldAttribute($name)
{
return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
}
/**
* Sets the old value of the named attribute.
* @param string $name the attribute name
* @param mixed $value the old attribute value.
* @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute
*/
public function setOldAttribute($name, $value)
{
if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
$this->_oldAttributes[$name] = $value;
} else {
throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
}
}
/**
* Returns a value indicating whether the named attribute has been changed.
* @param string $name the name of the attribute
* @return boolean whether the attribute has been changed
*/
public function isAttributeChanged($name)
{
if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
return $this->_attributes[$name] !== $this->_oldAttributes[$name];
} else {
return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
}
}
/**
* Returns the attribute values that have been modified since they are loaded or saved most recently.
* @param string[]|null $names the names of the attributes whose values may be returned if they are
* changed recently. If null, [[attributes()]] will be used.
* @return array the changed attribute values (name-value pairs)
*/
public function getDirtyAttributes($names = null)
{
if ($names === null) {
$names = $this->attributes();
}
$names = array_flip($names);
$attributes = [];
if ($this->_oldAttributes === null) {
foreach ($this->_attributes as $name => $value) {
if (isset($names[$name])) {
$attributes[$name] = $value;
}
}
} else {
foreach ($this->_attributes as $name => $value) {
if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
$attributes[$name] = $value;
}
}
}
return $attributes;
}
/**
* Saves the current record.
*
* This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
* when [[isNewRecord]] is false.
*
* For example, to save an article record:
*
* ~~~
* $customer = new Article; // or $customer = Article::find(['id' => $id]);
* $customer->id = $id;
* $customer->genre_id = $genreId;
* $customer->content = $email;
* $customer->save();
* ~~~
*
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be saved.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from index will be saved.
* @return boolean whether the saving succeeds
*/
public function save($runValidation = true, $attributes = null)
{
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributes);
} else {
return $this->update($runValidation, $attributes) !== false;
}
}
/**
* Inserts a row into the associated Sphinx index using the attribute values of this record.
*
* This method performs the following steps in order:
*
* 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
* fails, it will skip the rest of the steps;
* 2. call [[afterValidate()]] when `$runValidation` is true.
* 3. call [[beforeSave()]]. If the method returns false, it will skip the
* rest of the steps;
* 4. insert the record into index. If this fails, it will skip the rest of the steps;
* 5. call [[afterSave()]];
*
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
* [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
* will be raised by the corresponding methods.
*
* Only the [[changedAttributes|changed attribute values]] will be inserted.
*
* For example, to insert an article record:
*
* ~~~
* $article = new Article;
* $article->id = $id;
* $article->genre_id = $genreId;
* $article->content = $content;
* $article->insert();
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from index will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws \Exception in case insert failed.
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
$db = static::getDb();
if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) {
$transaction = $db->beginTransaction();
try {
$result = $this->insertInternal($attributes);
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
throw $e;
}
} else {
$result = $this->insertInternal($attributes);
}
return $result;
}
/**
* @see ActiveRecord::insert()
*/
private function insertInternal($attributes = null)
{
if (!$this->beforeSave(true)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
foreach ($this->primaryKey() as $key) {
$values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null;
}
}
$db = static::getDb();
$command = $db->createCommand()->insert($this->indexName(), $values);
if (!$command->execute()) {
return false;
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value;
}
$this->afterSave(true);
return true;
}
/**
* Saves the changes to this active record into the associated Sphinx index.
*
* This method performs the following steps in order:
*
* 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
* fails, it will skip the rest of the steps;
* 2. call [[afterValidate()]] when `$runValidation` is true.
* 3. call [[beforeSave()]]. If the method returns false, it will skip the
* rest of the steps;
* 4. save the record into index. If this fails, it will skip the rest of the steps;
* 5. call [[afterSave()]];
*
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
* [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
* will be raised by the corresponding methods.
*
* Only the [[changedAttributes|changed attribute values]] will be saved into database.
*
* For example, to update an article record:
*
* ~~~
* $article = Article::find(['id' => $id]);
* $article->genre_id = $genreId;
* $article->group_id = $groupId;
* $article->update();
* ~~~
*
* Note that it is possible the update does not affect any row in the table.
* In this case, this method will return 0. For this reason, you should use the following
* code to check if update() is successful or not:
*
* ~~~
* if ($this->update() !== false) {
* // update successful
* } else {
* // update failed
* }
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted into the database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return integer|boolean the number of rows affected, or false if validation fails
* or [[beforeSave()]] stops the updating process.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being updated is outdated.
* @throws \Exception in case update failed.
*/
public function update($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
$db = static::getDb();
if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) {
$transaction = $db->beginTransaction();
try {
$result = $this->updateInternal($attributes);
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
throw $e;
}
} else {
$result = $this->updateInternal($attributes);
}
return $result;
}
/**
* @see CActiveRecord::update()
* @throws StaleObjectException
*/
private function updateInternal($attributes = null)
{
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false);
return 0;
}
// Replace is supported only by runtime indexes and necessary only for field update
$useReplace = false;
$indexSchema = $this->getIndexSchema();
if ($this->getIndexSchema()->isRuntime) {
foreach ($values as $name => $value) {
$columnSchema = $indexSchema->getColumn($name);
if ($columnSchema->isField) {
$useReplace = true;
break;
}
}
}
if ($useReplace) {
$values = array_merge($values, $this->getOldPrimaryKey(true));
$command = static::getDb()->createCommand();
$command->replace(static::indexName(), $values);
// We do not check the return value of replace because it's possible
// that the REPLACE statement doesn't change anything and thus returns 0.
$rows = $command->execute();
} else {
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
if (!isset($values[$lock])) {
$values[$lock] = $this->$lock + 1;
}
$condition[$lock] = $this->$lock;
}
// We do not check the return value of updateAll() because it's possible
// that the UPDATE statement doesn't change anything and thus returns 0.
$rows = $this->updateAll($values, $condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterSave(false);
return $rows;
}
/**
* Deletes the index entry corresponding to this active record.
*
* This method performs the following steps in order:
*
* 1. call [[beforeDelete()]]. If the method returns false, it will skip the
* rest of the steps;
* 2. delete the record from the index;
* 3. call [[afterDelete()]].
*
* In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
* will be raised by the corresponding methods.
*
* @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
* Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being deleted is outdated.
* @throws \Exception in case delete failed.
*/
public function delete()
{
$db = static::getDb();
$transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
try {
$result = false;
if ($this->beforeDelete()) {
// we do not check the return value of deleteAll() because it's possible
// the record is already deleted in the database and thus the method will return 0
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$result = $this->deleteAll($condition);
if ($lock !== null && !$result) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->_oldAttributes = null;
$this->afterDelete();
}
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
} else {
$transaction->commit();
}
}
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
}
throw $e;
}
return $result;
}
/**
* Returns a value indicating whether the current record is new.
* @return boolean whether the record is new and should be inserted when calling [[save()]].
*/
public function getIsNewRecord()
{
return $this->_oldAttributes === null;
}
/**
* Sets the value indicating whether the record is new.
* @param boolean $value whether the record is new and should be inserted when calling [[save()]].
* @see getIsNewRecord
*/
public function setIsNewRecord($value)
{
$this->_oldAttributes = $value ? null : $this->_attributes;
}
/**
* Initializes the object.
* This method is called at the end of the constructor.
* The default implementation will trigger an [[EVENT_INIT]] event.
* If you override this method, make sure you call the parent implementation at the end
* to ensure triggering of the event.
*/
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
}
/**
* This method is called when the AR object is created and populated with the query result.
* The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
* When overriding this method, make sure you call the parent implementation to ensure the
* event is triggered.
*/
public function afterFind()
{
$this->trigger(self::EVENT_AFTER_FIND);
}
/**
* This method is called at the beginning of inserting or updating a record.
* The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
* or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
* When overriding this method, make sure you call the parent implementation like the following:
*
* ~~~
* public function beforeSave($insert)
* {
* if (parent::beforeSave($insert)) {
* // ...custom code here...
* return true;
* } else {
* return false;
* }
* }
* ~~~
*
* @param boolean $insert whether this method called while inserting a record.
* If false, it means the method is called while updating a record.
* @return boolean whether the insertion or updating should continue.
* If false, the insertion or updating will be cancelled.
*/
public function beforeSave($insert)
{
$event = new ModelEvent;
$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
return $event->isValid;
}
/**
* This method is called at the end of inserting or updating a record.
* The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
* or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
* When overriding this method, make sure you call the parent implementation so that
* the event is triggered.
* @param boolean $insert whether this method called while inserting a record.
* If false, it means the method is called while updating a record.
*/
public function afterSave($insert)
{
$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
}
/**
* This method is invoked before deleting a record.
* The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
* When overriding this method, make sure you call the parent implementation like the following:
*
* ~~~
* public function beforeDelete()
* {
* if (parent::beforeDelete()) {
* // ...custom code here...
* return true;
* } else {
* return false;
* }
* }
* ~~~
*
* @return boolean whether the record should be deleted. Defaults to true.
*/
public function beforeDelete()
{
$event = new ModelEvent;
$this->trigger(self::EVENT_BEFORE_DELETE, $event);
return $event->isValid;
}
/**
* This method is invoked after deleting a record.
* The default implementation raises the [[EVENT_AFTER_DELETE]] event.
* You may override this method to do postprocessing after the record is deleted.
* Make sure you call the parent implementation so that the event is raised properly.
*/
public function afterDelete()
{
$this->trigger(self::EVENT_AFTER_DELETE);
}
/**
* Repopulates this active record with the latest data.
* @return boolean whether the row still exists in the database. If true, the latest data
* will be populated to this active record. Otherwise, this record will remain unchanged.
*/
public function refresh()
{
$record = $this->find($this->getPrimaryKey(true));
if ($record === null) {
return false;
}
foreach ($this->attributes() as $name) {
$this->_attributes[$name] = $record->_attributes[$name];
}
$this->_oldAttributes = $this->_attributes;
$this->_related = [];
return true;
}
/**
* Returns a value indicating whether the given active record is the same as the current one.
* The comparison is made by comparing the index names and the primary key values of the two active records.
* @param ActiveRecord $record record to compare to
* @return boolean whether the two active records refer to the same row in the same index.
*/
public function equals($record)
{
return $this->indexName() === $record->indexName() && $this->getPrimaryKey() === $record->getPrimaryKey();
}
/**
* Returns the primary key value.
* @param boolean $asArray whether to return the primary key value as an array. If true,
* the return value will be an array with column names as keys and column values as values.
* @return mixed the primary key value. An array (column name => column value) is returned
* if `$asArray` is true. A string is returned otherwise (null will be returned if
* the key value is null).
*/
public function getPrimaryKey($asArray = false)
{
$keys = $this->primaryKey();
if (count($keys) === 1 && !$asArray) {
return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
} else {
$values = [];
foreach ($keys as $name) {
$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
}
return $values;
}
}
/**
* Returns the old primary key value.
* This refers to the primary key value that is populated into the record
* after executing a find method (e.g. find(), findAll()).
* The value remains unchanged even if the primary key attribute is manually assigned with a different value.
* @param boolean $asArray whether to return the primary key value as an array. If true,
* the return value will be an array with column name as key and column value as value.
* If this is false (default), a scalar value will be returned.
* @return mixed the old primary key value. An array (column name => column value) is returned if
* `$asArray` is true. A string is returned otherwise (null will be returned if
* the key value is null).
*/
public function getOldPrimaryKey($asArray = false)
{
$keys = $this->primaryKey();
if (count($keys) === 1 && !$asArray) {
return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
} else {
$values = [];
foreach ($keys as $name) {
$values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
}
return $values;
}
}
/**
* Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
*/
public static function create($row)
{
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$column = $columns[$name];
if ($column->isMva) {
$value = explode(',', $value);
}
$record->_attributes[$name] = $value;
} else {
$record->$name = $value;
}
}
$record->_oldAttributes = $record->_attributes;
$record->afterFind();
return $record;
}
/**
* Creates an active record instance.
* This method is called by [[create()]].
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* @param array $row row data to be populated into the record.
* @return ActiveRecord the newly created active record
*/
public static function instantiate($row)
{
return new static;
}
/**
* Returns whether there is an element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean whether there is an element at the specified offset.
*/
public function offsetExists($offset)
{
return $this->__isset($offset);
}
/**
* Returns the relation object with the specified name.
* A relation is defined by a getter method which returns an [[ActiveRelationInterface]] object.
* It can be declared in either the Active Record class itself or one of its behaviors.
* @param string $name the relation name
* @return ActiveRelationInterface the relation object
* @throws InvalidParamException if the named relation does not exist.
*/
public function getRelation($name)
{
$getter = 'get' . $name;
try {
$relation = $this->$getter();
if ($relation instanceof ActiveRelationInterface) {
return $relation;
} else {
return null;
}
} catch (UnknownMethodException $e) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
}
}
/**
* Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
* @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
* @return boolean whether the specified operation is transactional in the current [[scenario]].
*/
public function isTransactional($operation)
{
$scenario = $this->getScenario();
$transactions = $this->transactions();
return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
}
/**
* Sets the element at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `$model[$offset] = $item;`.
* @param integer $offset the offset to set element
* @param mixed $item the element value
* @throws \Exception on failure
*/
public function offsetSet($offset, $item)
{
// Bypass relation owner restriction to 'yii\db\ActiveRecord' at [[yii\db\ActiveRelationTrait::findWith()]]:
try {
$relation = $this->getRelation($offset);
if (is_object($relation)) {
$this->populateRelation($offset, $item);
return;
}
} catch (UnknownMethodException $e) {
throw $e->getPrevious();
}
parent::offsetSet($offset, $item);
}
}
\ No newline at end of file
<?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;
}
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
<?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.');
}
}
\ No newline at end of file
<?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.');
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 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);
}
}
\ No newline at end of file
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.
<?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();
}
}
\ No newline at end of file
<?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;
}
}
}
\ No newline at end of file
Yii 2.0 Public Preview - Sphinx Extension
=========================================
Thank you for choosing Yii - a high-performance component-based PHP framework.
If you are looking for a production-ready PHP framework, please use
[Yii v1.1](https://github.com/yiisoft/yii).
Yii 2.0 is still under heavy development. We may make significant changes
without prior notices. **Yii 2.0 is not ready for production use yet.**
[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2)
This is the yii2-sphinx extension.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require yiisoft/yii2-sphinx "*"
```
or add
```
"yiisoft/yii2-sphinx": "*"
```
to the require section of your composer.json.
*Note: You might have to run `php composer.phar selfupdate`*
Usage & Documentation
---------------------
This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii framework.
This extension interact with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language.
In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added:
```
searchd
{
listen = localhost:9306:mysql41
...
}
```
This extension supports all Sphinx features including [Runtime Indexes](http://sphinxsearch.com/docs/current.html#rt-indexes).
Since this extension uses MySQL protocol to access Sphinx, it shares base approach and much code from the
regular "yii\db" package.
To use this extension, simply add the following code in your application configuration:
```php
return [
//....
'components' => [
'sphinx' => [
'class' => 'yii\sphinx\Connection',
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
],
],
];
```
This extension provides ActiveRecord solution similar ot the [[\yii\db\ActiveRecord]].
To declare an ActiveRecord class you need to extend [[\yii\sphinx\ActiveRecord]] and
implement the `indexName` method:
```php
use yii\sphinx\ActiveRecord;
class Article extends ActiveRecord
{
/**
* @return string the name of the index associated with this ActiveRecord class.
*/
public static function indexName()
{
return 'idx_article';
}
}
```
You can use [[\yii\data\ActiveDataProvider]] with the [[\yii\sphinx\Query]] and [[\yii\sphinx\ActiveQuery]]:
```php
use yii\data\ActiveDataProvider;
use yii\sphinx\Query;
$query = new Query;
$query->from('yii2_test_article_index')->match('development');
$provider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
]
]);
$models = $provider->getModels();
```
```php
use yii\data\ActiveDataProvider;
use app\models\Article;
$provider = new ActiveDataProvider([
'query' => Article::find(),
'pagination' => [
'pageSize' => 10,
]
]);
$models = $provider->getModels();
```
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
{
"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\\": "" }
}
}
......@@ -34,4 +34,17 @@ return [
'password' => null,
],
],
'sphinx' => [
'sphinx' => [
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
],
'db' => [
'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest',
'username' => 'travis',
'password' => '',
'fixture' => __DIR__ . '/sphinx/source.sql',
],
]
];
<?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;
}
}
\ No newline at end of file
<?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);
}
}
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
<?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';
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
class ItemIndex extends ActiveRecord
{
public static function indexName()
{
return 'yii2_test_item_index';
}
}
\ No newline at end of file
<?php
namespace yiiunit\data\sphinx\ar;
class RuntimeIndex extends ActiveRecord
{
public static function indexName()
{
return 'yii2_test_rt_index';
}
}
\ No newline at end of file
<?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';
}
}
\ No newline at end of file
/**
* 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);
\ No newline at end of file
# 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
}
<?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));
}
}
\ No newline at end of file
<?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!');
}
}
\ No newline at end of file
<?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);
}
}
\ No newline at end of file
<?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));
}
}
\ No newline at end of file
<?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!');
}
}
\ No newline at end of file
<?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();
}
}
\ No newline at end of file
<?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));
}
}
\ No newline at end of file
<?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!');
}
}
}
\ No newline at end of file
<?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);
}
}
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment