Permalink
Browse files

Improve query index checking.

  • Loading branch information...
jwage committed Mar 23, 2012
1 parent ca8be60 commit bba938ee566f9942bb0adf6fb07f77e94671df2b
@@ -0,0 +1,96 @@
+<?php
+/*
+ * 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.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\Query;
+
+/**
+ * Class responsible for extracting an array of field names that are involved in
+ * a given mongodb query. Used for checking if query is indexed.
+ *
+ * @see Doctrine\ODM\MongoDB\Query::isIndexed()
+ */
+class FieldExtractor
+{
+ private $query;
+ private $sort;
+ private $cmd;
+
+ public function __construct(array $query, array $sort = array(), $cmd = '$')
+ {
+ $this->query = $query;
+ $this->sort = $sort;
+ $this->cmd = $cmd;
+ }
+
+ public function getFields()
+ {
+ $fields = array();
+
+ foreach ($this->query as $k => $v) {
+ if (is_array($v) && isset($v[$this->cmd.'elemMatch'])) {
+ $elemMatchFields = $this->getFieldsFromElemMatch($v[$this->cmd.'elemMatch']);
+ foreach ($elemMatchFields as $field) {
+ $fields[] = $k.'.'.$field;
+ }
+ } else if ($this->isOperator($k, array('and', 'or'))) {
+ foreach ($v as $q) {
+ $test = new self($q);
+ $fields = array_merge($fields, $test->getFields());
+ }
+ } else if ($k[0] !== $this->cmd) {
+ $fields[] = $k;
+ }
+ }
+ $fields = array_unique(array_merge($fields, array_keys($this->sort)));
+ return $fields;
+ }
+
+ private function getFieldsFromElemMatch($elemMatch)
+ {
+ $fields = array();
+ foreach ($elemMatch as $fieldName => $value) {
+ if ($this->isOperator($fieldName, 'where')) {
+ continue;
+ }
+
+ if ($this->isOperator($fieldName, array('and', 'or'))) {
+ foreach ($value as $q) {
+ $test = new self($q);
+ $fields = array_merge($fields, $test->getFields());
+ }
+ } else {
+ $fields[] = $fieldName;
+ }
+ }
+ return $fields;
+ }
+
+ private function isOperator($fieldName, $operator)
+ {
+ if (!is_array($operator)) {
+ $operator = array($operator);
+ }
+ foreach ($operator as $op) {
+ if ($fieldName === $this->cmd.$op) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
@@ -137,26 +137,15 @@ public function setRefresh($bool)
$this->refresh = $bool;
}
+ /**
+ * Gets the fields involved in this query.
+ *
+ * @return array $fields An array of fields names used in this query.
+ */
public function getFieldsInQuery()
{
- $fields = array();
- foreach ($this->query['query'] as $fieldName => $value) {
- if (is_array($value)) {
- foreach ($value as $k => $v) {
- if ($k === $this->cmd.'elemMatch') {
- foreach (array_keys($v) as $field) {
- $fields[] = $fieldName.'.'.$field;
- }
- } else {
- $fields[] = $fieldName;
- }
- }
- } else {
- $fields[] = $fieldName;
- }
- }
- $fields = array_unique(array_merge($fields, array_keys($this->query['sort'])));
- return $fields;
+ $extractor = new FieldExtractor($this->query['query'], $this->query['sort'], $this->cmd);
+ return $extractor->getFields();
}
/**
@@ -22,6 +22,14 @@ public function testQueries()
), $qb->getQueryArray());
}
+ public function testGetFieldsInCoordinatesQuery()
+ {
+ $qb = $this->dm->createQueryBuilder(__NAMESPACE__.'\City');
+ $qb->field('coordinates')->withinBox(41, 41, 72, 72);
+ $query = $qb->getQuery();
+ $this->assertEquals(array('coordinates'), $query->getFieldsInQuery());
+ }
+
public function testGeoSpatial1()
{
$this->dm->getSchemaManager()->ensureDocumentIndexes(__NAMESPACE__.'\City');
@@ -14,7 +14,25 @@ public function setUp()
$this->dm->getSchemaManager()->ensureDocumentIndexes('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
}
- public function testGetFieldsInQuery()
+ public function testGetFieldsInQueryWithSimpleEquals()
+ {
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('test')->equals('test');
+ $query = $qb->getQuery();
+ $this->assertEquals(array('test'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryIgnoresWhereOperator()
+ {
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->where('this.test > 0');
+ $qb->addOr($qb->expr()->where('this.ok > 1'));
+ $qb->addAnd($qb->expr()->field('username')->equals('jwage'));
+ $query = $qb->getQuery();
+ $this->assertEquals(array('username'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithElemMatch()
{
$date = new \DateTime();
$qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
@@ -26,13 +44,106 @@ public function testGetFieldsInQuery()
'flashes.startDate',
'flashes.endDate'
), $query->getFieldsInQuery());
+ }
+ public function testGetFieldsInQueryWithElemMatchAndOr()
+ {
$date = new \DateTime();
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('flashes')->elemMatch(
+ $qb->expr()->field('startDate')->lt($date)->field('endDate')->gte($date)->field('startDate')
+ ->addOr($qb->expr()->field('something')->equals($date))
+
+ ->where('this.id > 0')
+ )->addAnd($qb->expr()->field('flashes.id')->equals('foo'));
+ $query = $qb->getQuery();
+ $this->assertEquals(array(
+ 'flashes.startDate',
+ 'flashes.endDate',
+ 'flashes.something',
+ 'flashes.id'
+ ), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithOrAndIn()
+ {
+ $date = new \DateTime();
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->addOr($qb->expr()->field('field1')->in(array(1)));
+ $qb->addOr($qb->expr()->field('field2')->in(array(1)));
+ $query = $qb->getQuery();
+ $this->assertEquals(array('field1', 'field2'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithComplexQuery()
+ {
+ $date = new \DateTime();
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->addOr($qb->expr()->field('field1')->in(array(1)));
+ $qb->addAnd($qb->expr()->field('field2')->equals(1));
+ $qb->field('field3')->elemMatch($qb->expr()->field('embedded')->range(1, 2));
+ $qb->field('field4')->elemMatch($qb->expr()->addOr($qb->expr()->field('embedded')->equals($date)));
+ $qb->field('field5')->equals('test');
+ $query = $qb->getQuery();
+ $this->assertEquals(array(
+ 'field1',
+ 'field2',
+ 'field3.embedded',
+ 'field4.embedded',
+ 'field5'
+ ), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithIn()
+ {
$qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
$qb->field('test')->in(array(1));
$query = $qb->getQuery();
$this->assertEquals(array('test'), $query->getFieldsInQuery());
+ }
+ public function testGetFieldsInQueryWithNotIn()
+ {
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('test')->notIn(array(1));
+ $query = $qb->getQuery();
+ $this->assertEquals(array('test'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithNotEqual()
+ {
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('test')->notEqual(1);
+ $query = $qb->getQuery();
+ $this->assertEquals(array('test'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithNot()
+ {
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('test')->not(1);
+ $query = $qb->getQuery();
+ $this->assertEquals(array('test'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithReferences()
+ {
+ $reference = new DoesNotRequireIndexesDocument();
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('reference')->references($reference);
+ $qb->field('simpleReference')->references($reference);
+ $query = $qb->getQuery();
+ $this->assertEquals(array('reference.$id', 'simpleReference'), $query->getFieldsInQuery());
+ }
+
+ public function testGetFieldsInQueryWithIncludesReferences()
+ {
+ $reference = new DoesNotRequireIndexesDocument();
+ $qb = $this->dm->createQueryBuilder('Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesDocument');
+ $qb->field('reference')->includesReferenceTo($reference);
+ $qb->field('simpleReference')->includesReferenceTo($reference);
+ $query = $qb->getQuery();
+ $this->assertEquals(array('reference.$id', 'simpleReference'), $query->getFieldsInQuery());
}
public function testIsIndexedTrue()
@@ -170,6 +281,12 @@ class RequireIndexesDocument
/** @ODM\EmbedMany(targetDocument="Doctrine\ODM\MongoDB\Tests\Functional\RequireIndexesEmbeddedDocument") */
public $embedMany;
+
+ /** @ODM\ReferenceOne(targetDocument="Doctrine\ODM\MongoDB\Tests\Functional\DoesNotRequireIndexesDocument") */
+ public $reference;
+
+ /** @ODM\ReferenceOne(targetDocument="Doctrine\ODM\MongoDB\Tests\Functional\DoesNotRequireIndexesDocument", simple=true) */
+ public $simpleReference;
}
/**
@@ -0,0 +1,88 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Query;
+
+use Doctrine\ODM\MongoDB\Query\FieldExtractor;
+
+class FieldExtractorTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
+{
+ /**
+ * @dataProvider getQueriesAndFields
+ **/
+ public function testFieldExtractor($query, $fields)
+ {
+ $this->assertFieldsExtracted($query, $fields);
+ }
+
+ public function getQueriesAndFields()
+ {
+ return array(
+ array(
+ array('fieldName' => 1),
+ array('fieldName')
+ ),
+ array(
+ array('fieldName' => array(
+ '$elemMatch' => array(
+ 'embedded' => 1
+ )
+ )),
+ array('fieldName.embedded')
+ ),
+ array(
+ array('fieldName' => array(
+ '$in' => array(1)
+ )),
+ array('fieldName')
+ ),
+ array(
+ array('fieldName' => array(
+ '$gt' => 1
+ )),
+ array('fieldName')
+ ),
+ array(
+ array('$or' => array(
+ array(
+ 'fieldName1' => array(
+ '$in' => array(1)
+ )
+ ),
+ array(
+ 'fieldName2' => array(
+ '$in' => array(1)
+ )
+ ),
+ array(
+ 'fieldName3' => 1
+ )
+ )),
+ array('fieldName1', 'fieldName2', 'fieldName3')
+ ),
+ array(
+ array('$and' => array(
+ array(
+ 'fieldName1' => array(
+ '$in' => array(1)
+ )
+ ),
+ array(
+ 'fieldName2' => array(
+ '$in' => array(1)
+ )
+ ),
+ array(
+ 'fieldName3' => 1
+ )
+ )),
+ array('fieldName1', 'fieldName2', 'fieldName3')
+ )
+ );
+ }
+
+ private function assertFieldsExtracted(array $query, array $fields)
+ {
+ $extractor = new FieldExtractor($query);
+ $this->assertEquals($fields, $extractor->getFields());
+ }
+}

0 comments on commit bba938e

Please sign in to comment.