Browse Source

Mongo query condition composition composed.

tags/2.0.0-beta
Paul Klimov 11 years ago
parent
commit
9da7a80f0f
  1. 89
      extensions/mongo/Collection.php
  2. 64
      extensions/mongo/Query.php
  3. 110
      tests/unit/extensions/mongo/QueryRunTest.php
  4. 82
      tests/unit/extensions/mongo/QueryTest.php

89
extensions/mongo/Collection.php

@ -7,6 +7,7 @@
namespace yii\mongo; namespace yii\mongo;
use yii\base\InvalidParamException;
use yii\base\Object; use yii\base\Object;
use Yii; use Yii;
@ -32,23 +33,23 @@ class Collection extends Object
} }
/** /**
* @param array $query * @param array $condition
* @param array $fields * @param array $fields
* @return \MongoCursor * @return \MongoCursor
*/ */
public function find($query = [], $fields = []) public function find($condition = [], $fields = [])
{ {
return $this->mongoCollection->find($query, $fields); return $this->mongoCollection->find($this->buildCondition($condition), $fields);
} }
/** /**
* @param array $query * @param array $condition
* @param array $fields * @param array $fields
* @return array * @return array
*/ */
public function findAll($query = [], $fields = []) public function findAll($condition = [], $fields = [])
{ {
$cursor = $this->find($query, $fields); $cursor = $this->find($condition, $fields);
$result = []; $result = [];
foreach ($cursor as $data) { foreach ($cursor as $data) {
$result[] = $data; $result[] = $data;
@ -102,19 +103,19 @@ class Collection extends Object
/** /**
* Updates the rows, which matches given criteria by given data. * Updates the rows, which matches given criteria by given data.
* @param array $criteria description of the objects to update. * @param array $condition description of the objects to update.
* @param array $newData the object with which to update the matching records. * @param array $newData the object with which to update the matching records.
* @param array $options list of options in format: optionName => optionValue. * @param array $options list of options in format: optionName => optionValue.
* @return boolean whether operation was successful. * @return boolean whether operation was successful.
* @throws Exception on failure. * @throws Exception on failure.
*/ */
public function update($criteria, $newData, $options = []) public function update($condition, $newData, $options = [])
{ {
$token = 'Updating data in ' . $this->mongoCollection->getName(); $token = 'Updating data in ' . $this->mongoCollection->getName();
Yii::info($token, __METHOD__); Yii::info($token, __METHOD__);
try { try {
Yii::beginProfile($token, __METHOD__); Yii::beginProfile($token, __METHOD__);
$this->mongoCollection->update($criteria, $newData, $options); $this->mongoCollection->update($this->buildCondition($condition), $newData, $options);
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
return true; return true;
} catch (\Exception $e) { } catch (\Exception $e) {
@ -147,18 +148,18 @@ class Collection extends Object
/** /**
* Removes data from the collection. * Removes data from the collection.
* @param array $criteria description of records to remove. * @param array $condition description of records to remove.
* @param array $options list of options in format: optionName => optionValue. * @param array $options list of options in format: optionName => optionValue.
* @return boolean whether operation was successful. * @return boolean whether operation was successful.
* @throws Exception on failure. * @throws Exception on failure.
*/ */
public function remove($criteria = [], $options = []) public function remove($condition = [], $options = [])
{ {
$token = 'Removing data from ' . $this->mongoCollection->getName(); $token = 'Removing data from ' . $this->mongoCollection->getName();
Yii::info($token, __METHOD__); Yii::info($token, __METHOD__);
try { try {
Yii::beginProfile($token, __METHOD__); Yii::beginProfile($token, __METHOD__);
$this->tryResultError($this->mongoCollection->remove($criteria, $options)); $this->tryResultError($this->mongoCollection->remove($this->buildCondition($condition), $options));
Yii::endProfile($token, __METHOD__); Yii::endProfile($token, __METHOD__);
return true; return true;
} catch (\Exception $e) { } catch (\Exception $e) {
@ -182,4 +183,68 @@ class Collection extends Object
throw new Exception('Unknown error, use "w=1" option to enable error tracking'); throw new Exception('Unknown error, use "w=1" option to enable error tracking');
} }
} }
/**
* Converts user friendly condition keyword into actual Mongo condition keyword.
* @param string $key raw condition key.
* @return string actual key.
*/
protected function normalizeConditionKeyword($key)
{
static $map = [
'or' => '$or',
'>' => '$gt',
'>=' => '$gte',
'<' => '$lt',
'<=' => '$lte',
'!=' => '$ne',
'<>' => '$ne',
'in' => '$in',
'not in' => '$nin',
'all' => '$all',
'size' => '$size',
'type' => '$type',
'exists' => '$exists',
'notexists' => '$exists',
'elemmatch' => '$elemMatch',
'mod' => '$mod',
'%' => '$mod',
'=' => '$$eq',
'==' => '$$eq',
'where' => '$where'
];
$key = strtolower($key);
if (array_key_exists($key, $map)) {
return $map[$key];
} else {
return $key;
}
}
/**
* Builds up Mongo condition from user friendly condition.
* @param array $condition raw condition.
* @return array normalized Mongo condition.
* @throws \yii\base\InvalidParamException on invalid condition given.
*/
public function buildCondition($condition)
{
if (!is_array($condition)) {
throw new InvalidParamException('Condition should be an array.');
}
$result = [];
foreach ($condition as $key => $value) {
if (is_array($value)) {
$actualValue = $this->buildCondition($value);
} else {
$actualValue = $value;
}
if (is_numeric($key)) {
$result[] = $actualValue;
} else {
$result[$this->normalizeConditionKeyword($key)] = $actualValue;
}
}
return $result;
}
} }

64
extensions/mongo/Query.php

@ -74,26 +74,72 @@ class Query extends Component implements QueryInterface
} }
/** /**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition)
{
if (is_array($this->where)) {
$this->where = array_merge($this->where, $condition);
} else {
$this->where = $condition;
}
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 array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition)
{
if (is_array($this->where)) {
if (array_key_exists('or', $this->where) && count($this->where) == 1) {
$this->where['or'][] = $condition;
} else {
$this->where = [
'or' => [$this->where, $condition]
];
}
} else {
$this->where = $condition;
}
return $this;
}
/**
* @param Connection $db the database connection used to execute the query. * @param Connection $db the database connection used to execute the query.
* @return \MongoCursor mongo cursor instance. * @return \MongoCursor mongo cursor instance.
*/ */
protected function buildCursor($db = null) protected function buildCursor($db = null)
{ {
// TODO: compose query $where = $this->where;
$query = []; if (!is_array($where)) {
$where = [];
}
$selectFields = []; $selectFields = [];
if (!empty($this->select)) { if (!empty($this->select)) {
foreach ($this->select as $fieldName) { foreach ($this->select as $fieldName) {
$selectFields[$fieldName] = true; $selectFields[$fieldName] = true;
} }
} }
$cursor = $this->getCollection($db)->find($query, $selectFields); $cursor = $this->getCollection($db)->find($where, $selectFields);
if (!empty($this->orderBy)) { if (!empty($this->orderBy)) {
$sort = []; $sort = [];
foreach ($this->orderBy as $fieldName => $sortOrder) { foreach ($this->orderBy as $fieldName => $sortOrder) {
$sort[$fieldName] = $sortOrder === SORT_DESC ? \MongoCollection::DESCENDING : \MongoCollection::ASCENDING; $sort[$fieldName] = $sortOrder === SORT_DESC ? \MongoCollection::DESCENDING : \MongoCollection::ASCENDING;
} }
$cursor->sort($this->orderBy); $cursor->sort($sort);
} }
$cursor->limit($this->limit); $cursor->limit($this->limit);
$cursor->skip($this->offset); $cursor->skip($this->offset);
@ -109,17 +155,17 @@ class Query extends Component implements QueryInterface
public function all($db = null) public function all($db = null)
{ {
$cursor = $this->buildCursor($db); $cursor = $this->buildCursor($db);
if ($this->indexBy === null) { $result = [];
return iterator_to_array($cursor); foreach ($cursor as $row) {
} else { if ($this->indexBy !== null) {
$result = [];
foreach ($cursor as $row) {
if (is_string($this->indexBy)) { if (is_string($this->indexBy)) {
$key = $row[$this->indexBy]; $key = $row[$this->indexBy];
} else { } else {
$key = call_user_func($this->indexBy, $row); $key = call_user_func($this->indexBy, $row);
} }
$result[$key] = $row; $result[$key] = $row;
} else {
$result[] = $row;
} }
} }
return $result; return $result;

110
tests/unit/extensions/mongo/QueryRunTest.php

@ -0,0 +1,110 @@
<?php
namespace yiiunit\extensions\mongo;
use yii\mongo\Query;
/**
* @group mongo
*/
class QueryRunTest extends MongoTestCase
{
protected function setUp()
{
parent::setUp();
$this->setUpTestRows();
}
protected function tearDown()
{
$this->dropCollection('customer');
parent::tearDown();
}
/**
* Sets up test rows.
*/
protected function setUpTestRows()
{
$collection = $this->getConnection()->getCollection('customer');
$rows = [];
for ($i = 1; $i <= 10; $i++) {
$rows[] = [
'name' => 'name' . $i,
'address' => 'address' . $i,
];
}
$collection->batchInsert($rows);
}
// Tests :
public function testAll()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')->all($connection);
$this->assertEquals(10, count($rows));
}
public function testDirectMatch()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where(['name' => 'name1'])
->all($connection);
$this->assertEquals(1, count($rows));
$this->assertEquals('name1', $rows[0]['name']);
}
public function testIndexBy()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->indexBy('name')
->all($connection);
$this->assertEquals(10, count($rows));
$this->assertNotEmpty($rows['name1']);
}
public function testInCondition()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where([
'name' => [
'in' => ['name1', 'name5']
]
])
->all($connection);
$this->assertEquals(2, count($rows));
$this->assertEquals('name1', $rows[0]['name']);
$this->assertEquals('name5', $rows[1]['name']);
}
public function testOrCondition()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where(['name' => 'name1'])
->orWhere(['address' => 'address5'])
->all($connection);
$this->assertEquals(2, count($rows));
$this->assertEquals('name1', $rows[0]['name']);
$this->assertEquals('address5', $rows[1]['address']);
}
public function testOrder()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->orderBy(['name' => SORT_DESC])
->all($connection);
$this->assertEquals('name9', $rows[0]['name']);
}
}

82
tests/unit/extensions/mongo/QueryTest.php

@ -36,6 +36,88 @@ class QueryTest extends MongoTestCase
$this->assertEquals($from, $query->from); $this->assertEquals($from, $query->from);
} }
public function testWhere()
{
$query = new Query;
$query->where(['name' => 'name1']);
$this->assertEquals(['name' => 'name1'], $query->where);
$query->andWhere(['address' => 'address1']);
$this->assertEquals(
[
'name' => 'name1',
'address' => 'address1'
],
$query->where
);
$query->orWhere(['name' => 'name2']);
$this->assertEquals(
[
'or' => [
[
'name' => 'name1',
'address' => 'address1'
],
['name' => 'name2']
]
],
$query->where
);
$query->orWhere(['name' => 'name3']);
$this->assertEquals(
[
'or' => [
[
'name' => 'name1',
'address' => 'address1'
],
['name' => 'name2'],
['name' => 'name3']
]
],
$query->where
);
$query->andWhere(['address' => 'address2']);
$this->assertEquals(
[
'or' => [
[
'name' => 'name1',
'address' => 'address1'
],
['name' => 'name2'],
['name' => 'name3']
],
'address' => 'address2'
],
$query->where
);
$query->orWhere(['name' => 'name4']);
$this->assertEquals(
[
'or' => [
[
'or' => [
[
'name' => 'name1',
'address' => 'address1'
],
['name' => 'name2'],
['name' => 'name3']
],
'address' => 'address2'
],
['name' => 'name4']
],
],
$query->where
);
}
public function testOrder() public function testOrder()
{ {
$query = new Query; $query = new Query;

Loading…
Cancel
Save