diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php
index 0d76355..dceddb0 100644
--- a/extensions/sphinx/Query.php
+++ b/extensions/sphinx/Query.php
@@ -30,10 +30,14 @@ class Query extends Component
 	 */
 	const SORT_DESC = true;
 
+	/**
+	 * @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. For example,
-	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+	 * @var string additional option that should be appended to the 'SELECT' keyword.
 	 */
 	public $selectOption;
 	/**
@@ -41,15 +45,45 @@ class Query extends Component
 	 * 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_post']`.
+	 * This is used to construct the FROM clause in a SQL statement.
+	 * @see from()
+	 */
 	public $from;
+	/**
+	 * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
+	 * For example, `MATCH('ipod') AND team = 1`.
+	 * @see where()
+	 */
 	public $where;
+	/**
+	 * @var integer maximum number of records to be returned.
+	 * Note: if not set implicit LIMIT 0,20 is present by default.
+	 */
 	public $limit;
+	/**
+	 * @var integer zero-based offset from where the records are to be returned. If not set or
+	 * less than 0, it means starting from the beginning.
+	 * Note: implicit LIMIT 0,20 is present by default.
+	 */
 	public $offset;
+	/**
+	 * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
+	 * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
+	 * can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
+	 * If that is the case, the expressions will be converted into strings without any change.
+	 */
 	public $orderBy;
+	/**
+	 * @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;
 	/**
@@ -502,13 +536,25 @@ class Query extends Component
 		return $this;
 	}
 
-	public function options(array $options)
+	/**
+	 * 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;
 	}
 
-	public function addOptions(array $options)
+	/**
+	 * 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);
@@ -518,12 +564,32 @@ class Query extends Component
 		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);
diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php
index e6d110e..91e3b62 100644
--- a/extensions/sphinx/QueryBuilder.php
+++ b/extensions/sphinx/QueryBuilder.php
@@ -62,7 +62,7 @@ class QueryBuilder extends Object
 			$this->buildWithin($query->within),
 			$this->buildOrderBy($query->orderBy),
 			$this->buildLimit($query->limit, $query->offset),
-			$this->buildOption($query->options),
+			$this->buildOption($query->options, $params),
 		];
 		return [implode($this->separator, array_filter($clauses)), $params];
 	}
@@ -311,9 +311,13 @@ class QueryBuilder extends Object
 		if (!empty($options)) {
 			$optionParts = [];
 			foreach ($options as $name => $value) {
-				$phName = self::PARAM_PREFIX . count($params);
-				$params[$phName] = $value;
-				$optionParts[] = $phName . ' AS ' . $name;
+				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 {
@@ -768,17 +772,24 @@ class QueryBuilder extends Object
 	}
 
 	/**
-	 * @param array $options
+	 * @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(array $options)
+	public function buildOption($options, &$params)
 	{
 		if (empty($options)) {
 			return '';
 		}
 		$optionLines = [];
 		foreach ($options as $name => $value) {
-			$optionLines[] = $name . ' = ' . $value;
+			if ($value instanceof Expression) {
+				$actualValue = $value->expression;
+			} else {
+				$actualValue = self::PARAM_PREFIX . count($params);
+				$params[$actualValue] = $value;
+			}
+			$optionLines[] = $name . ' = ' . $actualValue;
 		}
 		return 'OPTION ' . implode(', ', $optionLines);
 	}
diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php
new file mode 100644
index 0000000..36bf95d
--- /dev/null
+++ b/tests/unit/extensions/sphinx/QueryTest.php
@@ -0,0 +1,138 @@
+<?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 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' => false], $query->orderBy);
+
+		$query->addOrderBy('company');
+		$this->assertEquals(['team' => false, 'company' => false], $query->orderBy);
+
+		$query->addOrderBy('age');
+		$this->assertEquals(['team' => false, 'company' => false, 'age' => false], $query->orderBy);
+
+		$query->addOrderBy(['age' => true]);
+		$this->assertEquals(['team' => false, 'company' => false, 'age' => true], $query->orderBy);
+
+		$query->addOrderBy('age ASC, company DESC');
+		$this->assertEquals(['team' => false, 'company' => true, 'age' => false], $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' => false], $query->within);
+
+		$query->addWithin('company');
+		$this->assertEquals(['team' => false, 'company' => false], $query->within);
+
+		$query->addWithin('age');
+		$this->assertEquals(['team' => false, 'company' => false, 'age' => false], $query->within);
+
+		$query->addWithin(['age' => true]);
+		$this->assertEquals(['team' => false, 'company' => false, 'age' => true], $query->within);
+
+		$query->addWithin('age ASC, company DESC');
+		$this->assertEquals(['team' => false, 'company' => true, 'age' => false], $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')
+			->where("MATCH('about')")
+			->options([
+				'cutoff' => 50,
+			])
+			->all($connection);
+		$this->assertNotEmpty($rows);
+	}
+}
\ No newline at end of file