Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

428 lines
13 KiB

<?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\InvalidCallException;
use yii\base\NotSupportedException;
use yii\db\Expression;
/**
* 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, group_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!
*
* @property Connection $connection Sphinx connection instance.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Query extends \yii\db\Query
{
/**
* @var string|Expression text, which should be searched in fulltext mode.
* This value will be composed into MATCH operator inside the WHERE clause.
* Note: this value will be processed by [[Connection::escapeMatchValue()]],
* if you need to compose complex match condition use [[Expression]],
* see [[match()]] for details.
*/
public $match;
/**
* @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 callable 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->get('sphinx');
}
/**
* Creates a Sphinx command that can be used to execute this query.
* @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 Command the created Sphinx command instance.
*/
public function createCommand($db = null)
{
$this->setConnection($db);
$db = $this->getConnection();
list ($sql, $params) = $db->getQueryBuilder()->build($this);
return $db->createCommand($sql, $params);
}
/**
* @inheritdoc
*/
public function populate($rows)
{
return parent::populate($this->fillUpSnippets($rows));
}
/**
* @inheritdoc
*/
public function one($db = null)
{
$row = parent::one($db);
if ($row !== false) {
list ($row) = $this->fillUpSnippets([$row]);
}
return $row;
}
/**
* Sets the fulltext query text. This text will be composed into
* MATCH operator inside the WHERE clause.
* Note: this value will be processed by [[Connection::escapeMatchValue()]],
* if you need to compose complex match condition use [[Expression]]:
* ~~~
* $query = new Query;
* $query->from('my_index')
* ->match(new Expression(':match', ['match' => '@(content) ' . Yii::$app->sphinx->escapeMatchValue($matchValue)]))
* ->all();
* ~~~
*
* @param string $query fulltext query text.
* @return static the query object itself
*/
public function match($query)
{
$this->match = $query;
return $this;
}
/**
* @inheritdoc
*/
public function join($type, $table, $on = '', $params = [])
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function innerJoin($table, $on = '', $params = [])
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function leftJoin($table, $on = '', $params = [])
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* @inheritdoc
*/
public function rightJoin($table, $on = '', $params = [])
{
throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
}
/**
* 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 callable $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|ActiveRecord[] 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)
{
return $this->callSnippetsInternal($source, $this->from[0]);
}
/**
* Builds a snippets from provided source data by the given index.
* @param array $source the source data to extract a snippet from.
* @param string $from name of the source index.
* @return array snippets list.
* @throws InvalidCallException in case [[match]] is not specified.
*/
protected function callSnippetsInternal(array $source, $from)
{
$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($from, $source, $match, $this->snippetOptions)
->queryColumn();
}
/**
* @inheritdoc
*/
protected function queryScalar($selectExpression, $db)
{
$select = $this->select;
$limit = $this->limit;
$offset = $this->offset;
$this->select = [$selectExpression];
$this->limit = null;
$this->offset = null;
$command = $this->createCommand($db);
$this->select = $select;
$this->limit = $limit;
$this->offset = $offset;
if (empty($this->groupBy) && empty($this->union) && !$this->distinct) {
return $command->queryScalar();
} else {
return (new Query)->select([$selectExpression])
->from(['c' => $this])
->createCommand($command->db)
->queryScalar();
}
}
/**
* Creates a new Query object and copies its property values from an existing one.
* The properties being copies are the ones to be used by query builders.
* @param Query $from the source query object
* @return Query the new Query object
*/
public static function create($from)
{
return new self([
'where' => $from->where,
'limit' => $from->limit,
'offset' => $from->offset,
'orderBy' => $from->orderBy,
'indexBy' => $from->indexBy,
'select' => $from->select,
'selectOption' => $from->selectOption,
'distinct' => $from->distinct,
'from' => $from->from,
'groupBy' => $from->groupBy,
'join' => $from->join,
'having' => $from->having,
'union' => $from->union,
'params' => $from->params,
// Sphinx specifics :
'options' => $from->options,
'within' => $from->within,
'match' => $from->match,
'snippetCallback' => $from->snippetCallback,
'snippetOptions' => $from->snippetOptions,
]);
}
}