diff --git a/lib/Doctrine/MongoDB/Collection.php b/lib/Doctrine/MongoDB/Collection.php index cba65ea3..4d967c55 100644 --- a/lib/Doctrine/MongoDB/Collection.php +++ b/lib/Doctrine/MongoDB/Collection.php @@ -44,7 +44,7 @@ class Collection * * @var Database */ - protected $db; + protected $database; /** * The event manager that is the central point of the event system. @@ -72,14 +72,14 @@ class Collection * for a given ClassMetadata instance. * * @param MongoCollection $mongoCollection The MongoCollection instance. - * @param Database $db The Database instance. + * @param Database $database The Database instance. * @param EventManager $evm The EventManager instance. * @param mixed $loggerCallable The logger callable. */ - public function __construct(\MongoCollection $mongoCollection, Database $db, EventManager $evm, $loggerCallable, $cmd) + public function __construct(\MongoCollection $mongoCollection, Database $database, EventManager $evm, $loggerCallable, $cmd) { $this->mongoCollection = $mongoCollection; - $this->db = $db; + $this->database = $database; $this->eventManager = $evm; $this->loggerCallable = $loggerCallable; $this->cmd = $cmd; @@ -92,7 +92,7 @@ public function __construct(\MongoCollection $mongoCollection, Database $db, Eve */ public function log(array $log) { - $log['db'] = $this->db->getName(); + $log['db'] = $this->database->getName(); $log['collection'] = $this->getName(); call_user_func_array($this->loggerCallable, array($log)); } @@ -109,7 +109,7 @@ public function getMongoCollection() public function getDatabase() { - return $this->db; + return $this->database; } /** @override */ @@ -248,7 +248,7 @@ public function findAndRemove(array $query, array $options = array()) $document = null; - $result = $this->db->command($command); + $result = $this->database->command($command); if (isset($result['value'])) { $document = $result['value']; if ($this->mongoCollection instanceof \MongoGridFS) { @@ -283,7 +283,7 @@ public function findAndModify(array $query, array $newObj, array $options = arra unset($options['new']); } $command['options'] = $options; - $result = $this->db->command($command); + $result = $this->database->command($command); $document = isset($result['value']) ? $result['value'] : null; if ($this->eventManager->hasListeners(Events::postFindAndModify)) { @@ -549,6 +549,11 @@ public function validate($scanData = false) return $this->mongoCollection->validate($scanData); } + public function createQueryBuilder() + { + return new Query\Builder($this->database, $this, $this->cmd); + } + /** @proxy */ public function __toString() { diff --git a/lib/Doctrine/MongoDB/Query/AbstractQuery.php b/lib/Doctrine/MongoDB/Query/AbstractQuery.php new file mode 100644 index 00000000..6f25863e --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/AbstractQuery.php @@ -0,0 +1,180 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +use Doctrine\MongoDB\Iterator; +use Doctrine\MongoDB\Database; +use Doctrine\MongoDB\Collection; + +/** + * Abstract executable query class for the different types of queries to implement. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +abstract class AbstractQuery implements QueryInterface, Iterator +{ + /** + * The Database instance. + * + * @var Database + */ + protected $database; + + /** + * The Collection instance. + * + * @var Collection + */ + protected $collection; + + /** + * Mongo command prefix + * + * @var string + */ + protected $cmd; + + /** + * @var Iterator + */ + protected $iterator; + + public function __construct(Database $database, Collection $collection, $cmd) + { + $this->database = $database; + $this->collection = $collection; + $this->cmd = $cmd; + } + + /** + * Gets an array of information about this query for debugging. + * + * @param string $name + * @return array $debug + */ + public function debug($name = null) + { + $debug = get_object_vars($this); + + unset($debug['database'], $debug['collection'], $debug['cmd']); + if ($name !== null) { + return $debug[$name]; + } + foreach ($debug as $key => $value) { + if ( ! $value) { + unset($debug[$key]); + } + } + return $debug; + } + + public function getIterator(array $options = array()) + { + if ($this->iterator === null) { + $iterator = $this->execute($options); + if ($iterator !== null && !$iterator instanceof Iterator) { + throw new \BadMethodCallException('Query execution did not return an iterator. This query may not support returning iterators. '); + } + $this->iterator = $iterator; + } + return $this->iterator; + } + + /** + * Count the number of results for this query. + * + * @param bool $all + * @return integer $count + */ + public function count($all = false) + { + return $this->getIterator()->count($all); + } + + /** + * Execute the query and get a single result + * + * @return object $document The single document. + */ + public function getSingleResult(array $options = array()) + { + return $this->getIterator($options)->getSingleResult(); + } + + /** + * Iterator over the query using the Cursor. + * + * @return Cursor $cursor + */ + public function iterate() + { + return $this->getIterator(); + } + + /** @inheritDoc */ + public function first() + { + return $this->getIterator()->first(); + } + + /** @inheritDoc */ + public function last() + { + return $this->getIterator()->last(); + } + + /** @inheritDoc */ + public function key() + { + return $this->getIterator()->key(); + } + + /** @inheritDoc */ + public function next() + { + return $this->getIterator()->next(); + } + + /** @inheritDoc */ + public function current() + { + return $this->getIterator()->current(); + } + + /** @inheritDoc */ + public function rewind() + { + return $this->getIterator()->rewind(); + } + + /** @inheritDoc */ + public function valid() + { + return $this->getIterator()->valid(); + } + + /** @inheritDoc */ + public function toArray() + { + return $this->getIterator()->toArray(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/Builder.php b/lib/Doctrine/MongoDB/Query/Builder.php new file mode 100644 index 00000000..f6fba67d --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/Builder.php @@ -0,0 +1,1046 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +use Doctrine\MongoDB\Query\Expr; +use Doctrine\MongoDB\Database; +use Doctrine\MongoDB\Collection; + +/** + * Fluent query builder interface. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class Builder +{ + const TYPE_FIND = 1; + const TYPE_FIND_AND_UPDATE = 2; + const TYPE_FIND_AND_REMOVE = 3; + const TYPE_INSERT = 4; + const TYPE_UPDATE = 5; + const TYPE_REMOVE = 6; + const TYPE_GROUP = 7; + const TYPE_MAP_REDUCE = 9; + const TYPE_DISTINCT_FIELD = 10; + const TYPE_GEO_LOCATION = 11; + + /** + * The Database instance. + * + * @var Database + */ + private $database; + + /** + * The Collection instance. + * + * @var Collection + */ + private $collection; + + /** + * The current field we are operating on. + * + * @var string + */ + private $currentField; + + /** + * Field to select distinct values of + * + * @var string + */ + private $distinctField; + + /** + * Array of fields to select + * + * @var array + */ + private $select = array(); + + /** + * Array of sort options + * + * @var array + */ + private $sort = array(); + + /** + * Limit number of records + * + * @var integer + */ + private $limit = null; + + /** + * Skip a specified number of records (offset) + * + * @var integer + */ + private $skip = null; + + /** + * Group information. + * + * @var array + */ + private $group = array(); + + /** + * Pass hints to the Cursor + * + * @var array + */ + private $hints = array(); + + /** + * Pass immortal to cursor + * + * @var bool + */ + private $immortal = false; + + /** + * Pass snapshot to cursor + * + * @var bool + */ + private $snapshot = false; + + /** + * Pass slaveOkay to cursor + * + * @var bool + */ + private $slaveOkay = false; + + /** + * Map reduce information + * + * @var array + */ + private $mapReduce = array(); + + /** + * Data to use with $near operator for geospatial indexes + * + * @var array + */ + private $near; + + /** + * The type of query + * + * @var integer + */ + private $type = self::TYPE_FIND; + + /** + * Mongo command prefix + * + * @var string + */ + private $cmd; + + /** + * Holds a Query\Expr instance used for generating query expressions using the operators. + * + * @var Query\Expr $expr + */ + private $expr; + + /** Refresh hint */ + const HINT_REFRESH = 1; + + /** + * Create a new query builder. + * + * @param Database $database + * @param Collection $collection + */ + public function __construct(Database $database, Collection $collection, $cmd) + { + $this->database = $database; + $this->collection = $collection; + $this->expr = new Expr($cmd); + $this->cmd = $cmd; + } + + /** + * Get the type of this query. + * + * @return string $type + */ + public function getType() + { + return $this->type; + } + + /** + * Set slave okaye. + * + * @param bool $bool + * @return Query + */ + public function slaveOkay($bool = true) + { + $this->slaveOkay = $bool; + return $this; + } + + /** + * Set snapshot. + * + * @param bool $bool + * @return Query + */ + public function snapshot($bool = true) + { + $this->snapshot = $bool; + return $this; + } + + /** + * Set immortal. + * + * @param bool $bool + * @return Query + */ + public function immortal($bool = true) + { + $this->immortal = $bool; + return $this; + } + + /** + * Pass a hint to the Cursor + * + * @param string $keyPattern + * @return Query + */ + public function hint($keyPattern) + { + $this->hints[] = $keyPattern; + return $this; + } + + /** + * Change the query type to find and optionally set and change the class being queried. + * + * @param string $className The Document class being queried. + * @return Query + */ + public function find() + { + $this->type = self::TYPE_FIND; + return $this; + } + + /** + * Sets a flag for the query to be executed as a findAndUpdate query query. + * + * @return Query + */ + public function findAndUpdate() + { + $this->type = self::TYPE_FIND_AND_UPDATE; + return $this; + } + + /** + * Sets a flag for the query to be executed as a findAndUpdate query query. + * + * @return Query + */ + public function findAndRemove() + { + $this->type = self::TYPE_FIND_AND_REMOVE; + return $this; + } + + /** + * Change the query type to update and optionally set and change the class being queried. + * + * @return Query + */ + public function update() + { + $this->type = self::TYPE_UPDATE; + return $this; + } + + /** + * Change the query type to insert and optionally set and change the class being queried. + * + * @return Query + */ + public function insert() + { + $this->type = self::TYPE_INSERT; + return $this; + } + + /** + * Change the query type to remove and optionally set and change the class being queried. + * + * @return Query + */ + public function remove() + { + $this->type = self::TYPE_REMOVE; + return $this; + } + + /** + * Perform an operation similar to SQL's GROUP BY command + * + * @param array $keys + * @param array $initial + * @param string $reduce + * @param array $condition + * @return Query + */ + public function group($keys, array $initial) + { + $this->group = array( + 'keys' => $keys, + 'initial' => $initial + ); + $this->type = self::TYPE_GROUP; + return $this; + } + + /** + * The distinct method queries for a list of distinct values for the given + * field for the document being queried for. + * + * @param string $field + * @return Query + */ + public function distinct($field) + { + $this->type = self::TYPE_DISTINCT_FIELD; + $this->distinctField = $field; + return $this; + } + + /** + * The fields to select. + * + * @param string $fieldName + * @return Query + */ + public function select($fieldName = null) + { + $select = func_get_args(); + foreach ($select as $fieldName) { + $this->select[] = $fieldName; + } + return $this; + } + + /** + * Select a slice of an embedded document. + * + * @param string $fieldName + * @param integer $skip + * @param integer $limit + * @return Query + */ + public function selectSlice($fieldName, $skip, $limit = null) + { + $slice = array($skip); + if ($limit !== null) { + $slice[] = $limit; + } + $this->select[$fieldName][$this->cmd . 'slice'] = $slice; + return $this; + } + + /** + * Add where near criteria. + * + * @param string $x + * @param string $y + * @return Query + */ + public function near($value) + { + $this->type = self::TYPE_GEO_LOCATION; + $this->near[$this->currentField] = $value; + return $this; + } + + /** + * Set the current field to operate on. + * + * @param string $field + * @return Query + */ + public function field($field) + { + $this->currentField = $field; + $this->expr->field($field); + return $this; + } + + /** + * Add a new where criteria erasing all old criteria. + * + * @param string $value + * @return Query + */ + public function equals($value) + { + $this->expr->equals($value); + return $this; + } + + /** + * Add $where javascript function to reduce result sets. + * + * @param string $javascript + * @return Query + */ + public function where($javascript) + { + $this->expr->where($javascript); + return $this; + } + + /** + * Add a new where in criteria. + * + * @param mixed $values + * @return Query + */ + public function in($values) + { + $this->expr->in($values); + return $this; + } + + /** + * Add where not in criteria. + * + * @param mixed $values + * @return Query + */ + public function notIn($values) + { + $this->expr->notIn($values); + return $this; + } + + /** + * Add where not equal criteria. + * + * @param string $value + * @return Query + */ + public function notEqual($value) + { + $this->expr->notEqual($value); + return $this; + } + + /** + * Add where greater than criteria. + * + * @param string $value + * @return Query + */ + public function gt($value) + { + $this->expr->gt($value); + return $this; + } + + /** + * Add where greater than or equal to criteria. + * + * @param string $value + * @return Query + */ + public function gte($value) + { + $this->expr->gte($value); + return $this; + } + + /** + * Add where less than criteria. + * + * @param string $value + * @return Query + */ + public function lt($value) + { + $this->expr->lt($value); + return $this; + } + + /** + * Add where less than or equal to criteria. + * + * @param string $value + * @return Query + */ + public function lte($value) + { + $this->expr->lte($value); + return $this; + } + + /** + * Add where range criteria. + * + * @param string $start + * @param string $end + * @return Query + */ + public function range($start, $end) + { + $this->expr->range($start, $end); + return $this; + } + + /** + * Add where size criteria. + * + * @param string $size + * @return Query + */ + public function size($size) + { + $this->expr->size($size); + return $this; + } + + /** + * Add where exists criteria. + * + * @param string $bool + * @return Query + */ + public function exists($bool) + { + $this->expr->exists($bool); + return $this; + } + + /** + * Add where type criteria. + * + * @param string $type + * @return Query + */ + public function type($type) + { + $this->expr->type($type); + return $this; + } + + /** + * Add where all criteria. + * + * @param mixed $values + * @return Query + */ + public function all($values) + { + $this->expr->all($values); + return $this; + } + + /** + * Add where mod criteria. + * + * @param string $mod + * @return Query + */ + public function mod($mod) + { + $this->expr->mod($mod); + return $this; + } + + /** + * Add where $within $box query. + * + * @param string $x1 + * @param string $y1 + * @param string $x2 + * @param string $y2 + * @return Query + */ + public function withinBox($x1, $y1, $x2, $y2) + { + $this->expr->withinBox($x1, $y1, $x2, $y2); + return $this; + } + + /** + * Add where $within $center query. + * + * @param string $x + * @param string $y + * @param string $radius + * @return Query + */ + public function withinCenter($x, $y, $radius) + { + $this->expr->withinCenter($x, $y, $radius); + return $this; + } + + /** + * Uses $elemMatch to limit results to documents that reference another document. + * + * @param mixed $document A document + * @return Query + */ + public function references($document) + { + $this->expr->references($document); + return $this; + } + + /** + * Set sort and erase all old sorts. + * + * @param string $order + * @return Query + */ + public function sort($fieldName, $order) + { + $this->sort[$fieldName] = strtolower($order) === 'asc' ? 1 : -1; + return $this; + } + + /** + * Set the Document limit for the Cursor + * + * @param string $limit + * @return Query + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Set the number of Documents to skip for the Cursor + * + * @param string $skip + * @return Query + */ + public function skip($skip) + { + $this->skip = $skip; + return $this; + } + + /** + * Specify a map reduce operation for this query. + * + * @param mixed $map + * @param mixed $reduce + * @param array $options + * @return Query + */ + public function mapReduce($map, $reduce, array $options = array()) + { + $this->mapReduce = array( + 'map' => $map, + 'reduce' => $reduce, + 'options' => $options + ); + return $this; + } + + /** + * Specify a map operation for this query. + * + * @param string $map + * @return Query + */ + public function map($map) + { + $this->mapReduce['map'] = $map; + return $this; + } + + /** + * Specify a reduce operation for this query. + * + * @param string $reduce + * @return Query + */ + public function reduce($reduce) + { + $this->mapReduce['reduce'] = $reduce; + return $this; + } + + /** + * Specify the map reduce array of options for this query. + * + * @param array $options + * @return Query + */ + public function mapReduceOptions(array $options) + { + $this->mapReduce['options'] = $options; + return $this; + } + + /** + * Set field to value. + * + * @param mixed $value + * @param boolean $atomic + * @return Query + */ + public function set($value, $atomic = true) + { + if ($this->type == self::TYPE_INSERT) { + $atomic = false; + } + $this->expr->set($value, $atomic); + return $this; + } + + /** + * Increment field by the number value if field is present in the document, + * otherwise sets field to the number value. + * + * @param integer $value + * @return Query + */ + public function inc($value) + { + $this->expr->inc($value); + return $this; + } + + /** + * Deletes a given field. + * + * @return Query + */ + public function unsetField() + { + $this->expr->unsetField(); + return $this; + } + + /** + * Appends value to field, if field is an existing array, otherwise sets + * field to the array [value] if field is not present. If field is present + * but is not an array, an error condition is raised. + * + * @param mixed $value + * @return Query + */ + public function push($value) + { + $this->expr->push($value); + return $this; + } + + /** + * Appends each value in valueArray to field, if field is an existing + * array, otherwise sets field to the array valueArray if field is not + * present. If field is present but is not an array, an error condition is + * raised. + * + * @param array $valueArray + * @return Query + */ + public function pushAll(array $valueArray) + { + $this->expr->pushAll($valueArray); + return $this; + } + + /** + * Adds value to the array only if its not in the array already. + * + * @param mixed $value + * @return Query + */ + public function addToSet($value) + { + $this->expr->addToSet($value); + return $this; + } + + /** + * Adds values to the array only they are not in the array already. + * + * @param array $values + * @return Query + */ + public function addManyToSet(array $values) + { + $this->expr->addManyToSet($values); + return $this; + } + + /** + * Removes first element in an array + * + * @return Query + */ + public function popFirst() + { + $this->expr->popFirst(); + return $this; + } + + /** + * Removes last element in an array + * + * @return Query + */ + public function popLast() + { + $this->expr->popLast(); + return $this; + } + + /** + * Removes all occurrences of value from field, if field is an array. + * If field is present but is not an array, an error condition is raised. + * + * @param mixed $value + * @return Query + */ + public function pull($value) + { + $this->expr->pull($value); + return $this; + } + + /** + * Removes all occurrences of each value in value_array from field, if + * field is an array. If field is present but is not an array, an error + * condition is raised. + * + * @param array $valueArray + * @return Query + */ + public function pullAll(array $valueArray) + { + $this->expr->pullAll($valueArray); + return $this; + } + + /** + * Adds an "or" expression to the current query. + * + * You can create the expression using the expr() method: + * + * $qb = $this->createQueryBuilder('User'); + * $qb + * ->addOr($qb->expr()->field('first_name')->equals('Kris')) + * ->addOr($qb->expr()->field('first_name')->equals('Chris')); + * + * @param array|QueryBuilder $expression + * @return Query + */ + public function addOr($expression) + { + $this->expr->addOr($expression); + return $this; + } + + /** + * Adds an "elemMatch" expression to the current query. + * + * You can create the expression using the expr() method: + * + * $qb = $this->createQueryBuilder('User'); + * $qb + * ->field('phonenumbers') + * ->elemMatch($qb->expr()->field('phonenumber')->equals('6155139185')); + * + * @param array|QueryBuilder $expression + * @return Query + */ + public function elemMatch($expression) + { + $this->expr->elemMatch($expression); + return $this; + } + + /** + * Adds a "not" expression to the current query. + * + * You can create the expression using the expr() method: + * + * $qb = $this->createQueryBuilder('User'); + * $qb->field('id')->not($qb->expr()->in(1)); + * + * @param array|QueryBuilder $expression + * @return Query + */ + public function not($expression) + { + $this->expr->not($expression); + return $this; + } + + /** + * Create a new Query\Expr instance that can be used as an expression with the QueryBuilder + * + * @return Query\Expr $expr + */ + public function expr() + { + return new Expr($this->cmd); + } + + public function getQueryArray() + { + return $this->expr->getQuery(); + } + + public function getNewObj() + { + return $this->expr->getNewObj(); + } + + /** + * Gets the Query executable. + * + * @param array $options + * @return QueryInterface $query + */ + public function getQuery() + { + switch ($this->type) { + case self::TYPE_GEO_LOCATION; + $query = new GeoLocationFindQuery($this->database, $this->collection, $this->cmd); + $query->setQuery($this->expr->getQuery()); + $query->setNear($this->near); + $query->setLimit($this->limit); + return $query; + case self::TYPE_DISTINCT_FIELD; + $query = new DistinctFieldQuery($this->database, $this->collection, $this->cmd); + $query->setDistinctField($this->distinctField); + $query->setQuery($this->expr->getQuery()); + return $query; + case self::TYPE_MAP_REDUCE; + $query = new MapReduceQuery($this->database, $this->collection, $this->cmd); + $query->setQuery($this->expr->getQuery()); + $query->setMap(isset($this->mapReduce['map']) ? $this->mapReduce['map'] : null); + $query->setReduce(isset($this->mapReduce['reduce']) ? $this->mapReduce['reduce'] : null); + $query->setOptions(isset($this->mapReduce['options']) ? $this->mapReduce['options'] : array()); + $query->setSelect($this->select); + $query->setQuery($this->expr->getQuery()); + $query->setLimit($this->limit); + $query->setSkip($this->skip); + $query->setSort($this->sort); + $query->setImmortal($this->immortal); + $query->setSlaveOkay($this->slaveOkay); + $query->setSnapshot($this->snapshot); + $query->setHints($this->hints); + return $query; + case self::TYPE_FIND; + $query = new FindQuery($this->database, $this->collection, $this->cmd); + $query->setReduce(isset($this->mapReduce['reduce']) ? $this->mapReduce['reduce'] : null); + $query->setSelect($this->select); + $query->setQuery($this->expr->getQuery()); + $query->setLimit($this->limit); + $query->setSkip($this->skip); + $query->setSort($this->sort); + $query->setImmortal($this->immortal); + $query->setSlaveOkay($this->slaveOkay); + $query->setSnapshot($this->snapshot); + $query->setHints($this->hints); + return $query; + case self::TYPE_FIND_AND_REMOVE; + $query = new FindAndRemoveQuery($this->database, $this->collection, $this->cmd); + $query->setSelect($this->select); + $query->setQuery($this->expr->getQuery()); + $query->setSort($this->sort); + $query->setLimit($this->limit); + return $query; + case self::TYPE_FIND_AND_UPDATE; + $query = new FindAndUpdateQuery($this->database, $this->collection, $this->cmd); + $query->setSelect($this->select); + $query->setQuery($this->expr->getQuery()); + $query->setNewObj($this->expr->getNewObj()); + $query->setSort($this->sort); + $query->setUpsert(isset($this->findAndUpdate['upsert'])); + $query->setNew(isset($this->findAndUpdate['new'])); + $query->setLimit($this->limit); + return $query; + case self::TYPE_REMOVE; + $query = new RemoveQuery($this->database, $this->collection, $this->cmd); + $query->setQuery($this->expr->getQuery()); + return $query; + case self::TYPE_UPDATE; + $query = new UpdateQuery($this->database, $this->collection, $this->cmd); + $query->setQuery($this->expr->getQuery()); + $query->setNewObj($this->expr->getNewObj()); + return $query; + case self::TYPE_INSERT; + $query = new InsertQuery($this->database, $this->collection, $this->cmd); + $query->setNewObj($this->expr->getNewObj()); + return $query; + case self::TYPE_GROUP; + $query = new GroupQuery($this->database, $this->collection, $this->cmd); + $query->setKeys(isset($this->group['keys']) ? $this->group['keys'] : null); + $query->setInitial(isset($this->group['initial']) ? $this->group['initial'] : array()); + $query->setReduce(isset($this->mapReduce['reduce']) ? $this->mapReduce['reduce'] : null); + $query->setQuery($this->expr->getQuery()); + return $query; + } + } + + /** + * Gets an array of information about this query builder for debugging. + * + * @param string $name + * @return array $debug + */ + public function debug($name = null) + { + $debug = get_object_vars($this); + + unset($debug['database'], $debug['collection'], $debug['expr']); + if ($name !== null) { + return $debug[$name]; + } + foreach ($debug as $key => $value) { + if ( ! $value) { + unset($debug[$key]); + } + } + return $debug; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/DistinctFieldQuery.php b/lib/Doctrine/MongoDB/Query/DistinctFieldQuery.php new file mode 100644 index 00000000..f8dcc936 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/DistinctFieldQuery.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +use Doctrine\MongoDB\ArrayIterator; + +/** + * DistinctFieldQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class DistinctFieldQuery extends AbstractQuery +{ + protected $distinctField; + protected $query; + + public function setDistinctField($distinctField) + { + $this->distinctField = $distinctField; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function execute(array $options = array()) + { + $result = $this->database->command(array( + 'distinct' => $this->collection->getName(), + 'key' => $this->distinctField, + 'query' => $this->query + )); + return new ArrayIterator($result['values']); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/Expr.php b/lib/Doctrine/MongoDB/Query/Expr.php new file mode 100644 index 00000000..c4455165 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/Expr.php @@ -0,0 +1,347 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * Expression builder class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class Expr +{ + /** + * Mongo command prefix + * + * @var string + */ + private $cmd; + + /** + * The query array built by this expression class. + * + * @var string + */ + private $query = array(); + + /** + * The new object array containing a whole new document or a query containing + * atomic operators to update a document. + * + * @var array + */ + private $newObj = array(); + + /** + * The current field we are operating on. + * + * @var string + */ + private $currentField; + + public function __construct($cmd) + { + $this->cmd = $cmd; + } + + public function getQuery() + { + return $this->query; + } + + public function getNewObj() + { + return $this->newObj; + } + + public function getCurrentField() + { + return $this->currentField; + } + + public function field($field) + { + $this->currentField = $field; + return $this; + } + + public function equals($value, array $options = array()) + { + if ($this->currentField) { + $this->query[$this->currentField] = $value; + } else { + $this->query = $value; + } + return $this; + } + + public function where($javascript) + { + return $this->field($this->cmd . 'where')->equals($javascript); + } + + public function operator($operator, $value) + { + if ($this->currentField) { + $this->query[$this->currentField][$operator] = $value; + } else { + $this->query[$operator] = $value; + } + return $this; + } + + public function in($values) + { + return $this->operator($this->cmd . 'in', $values); + } + + public function notIn($values) + { + return $this->operator($this->cmd . 'nin', (array) $values); + } + + public function notEqual($value) + { + return $this->operator($this->cmd . 'ne', $value); + } + + public function gt($value) + { + return $this->operator($this->cmd . 'gt', $value); + } + + public function gte($value) + { + return $this->operator($this->cmd . 'gte', $value); + } + + public function lt($value) + { + return $this->operator($this->cmd . 'lt', $value); + } + + public function lte($value) + { + return $this->operator($this->cmd . 'lte', $value); + } + + public function range($start, $end) + { + return $this->operator($this->cmd . 'gte', $start)->operator($this->cmd . 'lt', $end); + } + + public function size($size) + { + return $this->operator($this->cmd . 'size', $size); + } + + public function exists($bool) + { + return $this->operator($this->cmd . 'exists', $bool); + } + + public function type($type) + { + $map = array( + 'double' => 1, + 'string' => 2, + 'object' => 3, + 'array' => 4, + 'binary' => 5, + 'undefined' => 6, + 'objectid' => 7, + 'boolean' => 8, + 'date' => 9, + 'null' => 10, + 'regex' => 11, + 'jscode' => 13, + 'symbol' => 14, + 'jscodewithscope' => 15, + 'integer32' => 16, + 'timestamp' => 17, + 'integer64' => 18, + 'minkey' => 255, + 'maxkey' => 127 + ); + if (is_string($type) && isset($map[$type])) { + $type = $map[$type]; + } + return $this->operator($this->cmd . 'type', $type); + } + + public function all($values) + { + return $this->operator($this->cmd . 'all', (array) $values); + } + + public function mod($mod) + { + return $this->operator($this->cmd . 'mod', $mod); + } + + public function withinBox($x1, $y1, $x2, $y2) + { + if ($this->currentField) { + $this->query[$this->currentField][$this->cmd . 'within'][$this->cmd . 'box'] = array(array($x1, $y1), array($x2, $y2)); + } else { + $this->query[$this->cmd . 'within'][$this->cmd . 'box'] = array(array($x1, $y1), array($x2, $y2)); + } + return $this; + } + + public function withinCenter($x, $y, $radius) + { + if ($this->currentField) { + $this->query[$this->currentField][$this->cmd . 'within'][$this->cmd . 'center'] = array(array($x, $y), $radius); + } else { + $this->query[$this->cmd . 'within'][$this->cmd . 'center'] = array(array($x, $y), $radius); + } + return $this; + } + + public function set($value, $atomic = true) + { + $this->requiresCurrentField(); + if ($atomic === true) { + $this->newObj[$this->cmd . 'set'][$this->currentField] = $value; + } else { + if (strpos($this->currentField, '.') !== false) { + $e = explode('.', $this->currentField); + $current = &$this->newObj; + foreach ($e as $v) { + $current[$v] = null; + $current = &$current[$v]; + } + $current = $value; + } else { + $this->newObj[$this->currentField] = $value; + } + } + return $this; + } + + public function inc($value) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'inc'][$this->currentField] = $value; + return $this; + } + + public function unsetField() + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'unset'][$this->currentField] = 1; + return $this; + } + + public function push($value) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'push'][$this->currentField] = $value; + return $this; + } + + public function pushAll(array $valueArray) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'pushAll'][$this->currentField] = $valueArray; + return $this; + } + + public function addToSet($value) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'addToSet'][$this->currentField] = $value; + return $this; + } + + public function addManyToSet(array $values) + { + $this->requiresCurrentField(); + if ( ! isset($this->newObj[$this->cmd . 'addToSet'][$this->currentField])) { + $this->newObj[$this->cmd . 'addToSet'][$this->currentField][$this->cmd . 'each'] = array(); + } + if ( ! is_array($this->newObj[$this->cmd . 'addToSet'][$this->currentField])) { + $this->newObj[$this->cmd . 'addToSet'][$this->currentField] = array($this->cmd . 'each' => array($this->newObj[$this->cmd . 'addToSet'][$this->currentField])); + } + $this->newObj[$this->cmd . 'addToSet'][$this->currentField][$this->cmd . 'each'] = array_merge_recursive($this->newObj[$this->cmd . 'addToSet'][$this->currentField][$this->cmd . 'each'], $values); + } + + public function popFirst() + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'pop'][$this->currentField] = 1; + return $this; + } + + public function popLast() + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'pop'][$this->currentField] = -1; + return $this; + } + + public function pull($value) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'pull'][$this->currentField] = $value; + return $this; + } + + public function pullAll(array $valueArray) + { + $this->requiresCurrentField(); + $this->newObj[$this->cmd . 'pullAll'][$this->currentField] = $valueArray; + return $this; + } + + public function addOr($expression) + { + if ($expression instanceof Expr) { + $expression = $expression->getQuery(); + } + $this->query[$this->cmd . 'or'][] = $expression; + return $this; + } + + public function elemMatch($expression) + { + if ($expression instanceof Expr) { + $expression = $expression->getQuery(); + } + return $this->operator($this->cmd . 'elemMatch', $expression); + } + + public function not($expression) + { + if ($expression instanceof Expr) { + $expression = $expression->getQuery(); + } + return $this->operator($this->cmd . 'not', $expression); + } + + private function requiresCurrentField() + { + if ( ! $this->currentField) { + throw new \LogicException('This method requires you set a current field using field().'); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/FindAndRemoveQuery.php b/lib/Doctrine/MongoDB/Query/FindAndRemoveQuery.php new file mode 100644 index 00000000..f7662e8d --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/FindAndRemoveQuery.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * FindAndRemoveQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class FindAndRemoveQuery extends AbstractQuery +{ + protected $select = array(); + protected $query = array(); + protected $sort; + protected $upsert; + protected $new; + protected $limit; + + public function setSelect(array $select) + { + $this->select = $select; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setSort($sort) + { + $this->sort = $sort; + } + + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function execute(array $options = array()) + { + $command = array(); + $command['findandmodify'] = $this->collection->getName(); + if ($this->query) { + $command['query'] = $this->query; + } + if ($this->sort) { + $command['sort'] = $this->sort; + } + if ($this->select) { + $command['fields'] = $this->select; + } + $command['remove'] = true; + if ($this->limit) { + $command['num'] = $this->limit; + } + $result = $this->database->command($command); + if (isset($result['value'])) { + return $result['value']; + } + return $result; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/FindAndUpdateQuery.php b/lib/Doctrine/MongoDB/Query/FindAndUpdateQuery.php new file mode 100644 index 00000000..bb99d50e --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/FindAndUpdateQuery.php @@ -0,0 +1,103 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * FindAndUpdateQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class FindAndUpdateQuery extends AbstractQuery +{ + protected $select = array(); + protected $query = array(); + protected $newObj = array(); + protected $sort; + protected $upsert; + protected $new; + protected $limit; + + public function setSelect(array $select) + { + $this->select = $select; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setNewObj(array $newObj) + { + $this->newObj = $newObj; + } + + public function setSort($sort) + { + $this->sort = $sort; + } + + public function setUpsert($upsert) + { + $this->upsert = $upsert; + } + + public function setNew($new) + { + $this->new = $new; + } + + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function execute(array $options = array()) + { + $command = array(); + $command['findandmodify'] = $this->collection->getName(); + if ($this->query) { + $command['query'] = $this->query; + } + if ($this->sort) { + $command['sort'] = $this->sort; + } + if ($this->select) { + $command['fields'] = $this->select; + } + $command['update'] = $this->newObj; + if ($this->upsert) { + $command['upsert'] = true; + } + if ($this->new) { + $command['new'] = true; + } + if ($this->limit) { + $command['num'] = $this->limit; + } + $result = $this->database->command($command); + if (isset($result['value'])) { + return $result['value']; + } + return $result; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/FindQuery.php b/lib/Doctrine/MongoDB/Query/FindQuery.php new file mode 100644 index 00000000..70675e2e --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/FindQuery.php @@ -0,0 +1,113 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +use Doctrine\MongoDB\Cursor; + +/** + * FindQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class FindQuery extends AbstractQuery +{ + protected $reduce; + protected $select = array(); + protected $query; + protected $limit; + protected $skip; + protected $sort; + protected $immortal; + protected $slaveOkay; + protected $snapshot; + protected $hints = array(); + + public function setReduce($reduce) + { + $this->reduce = $reduce; + } + + public function setSelect($select) + { + $this->select = $select; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function setSkip($skip) + { + $this->skip = $skip; + } + + public function setSort($sort) + { + $this->sort = $sort; + } + + public function setImmortal($immortal) + { + $this->immortal = $immortal; + } + + public function setSlaveOkay($slaveOkay) + { + $this->slaveOkay = $slaveOkay; + } + + public function setSnapshot($snapshot) + { + $this->snapshot = $snapshot; + } + + public function setHints(array $hints) + { + $this->hints = $hints; + } + + public function execute(array $options = array()) + { + if ($this->reduce) { + $this->query[$this->cmd . 'where'] = $this->reduce; + } + $cursor = $this->collection->find($this->query, $this->select, $options); + $cursor->limit($this->limit); + $cursor->skip($this->skip); + $cursor->sort($this->sort); + $cursor->immortal($this->immortal); + $cursor->slaveOkay($this->slaveOkay); + if ($this->snapshot) { + $cursor->snapshot(); + } + foreach ($this->hints as $keyPattern) { + $cursor->hint($keyPattern); + } + return $cursor; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/GeoLocationFindQuery.php b/lib/Doctrine/MongoDB/Query/GeoLocationFindQuery.php new file mode 100644 index 00000000..648fecc4 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/GeoLocationFindQuery.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +use Doctrine\MongoDB\ArrayIterator; + +/** + * GeoLocationFindQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class GeoLocationFindQuery extends AbstractQuery +{ + protected $query; + protected $near; + protected $limit; + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setNear($near) + { + $this->near = $near; + } + + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function execute(array $options = array()) + { + $command = array( + 'geoNear' => $this->collection->getName(), + 'near' => $this->near, + 'query' => $this->query + ); + if ($this->limit) { + $command['num'] = $this->limit; + } + $result = $this->database->command($command); + $results = isset($result['results']) ? $result['results'] : array(); + return new ArrayIterator($results); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/GroupQuery.php b/lib/Doctrine/MongoDB/Query/GroupQuery.php new file mode 100644 index 00000000..f74124b9 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/GroupQuery.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * GroupQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class GroupQuery extends AbstractQuery +{ + protected $keys = array(); + protected $initial; + protected $reduce; + protected $query = array(); + + public function setKeys(array $keys) + { + $this->keys = $keys; + } + + public function setInitial($initial) + { + $this->initial = $initial; + } + + public function setReduce($reduce) + { + $this->reduce = $reduce; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function execute(array $options = array()) + { + return $this->collection->group($this->keys, $this->initial, $this->reduce, $this->query); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/InsertQuery.php b/lib/Doctrine/MongoDB/Query/InsertQuery.php new file mode 100644 index 00000000..522a2e2c --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/InsertQuery.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * InsertQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class InsertQuery extends AbstractQuery +{ + protected $newObj = array(); + + public function setNewObj(array $newObj) + { + $this->newObj = $newObj; + } + + public function execute(array $options = array()) + { + return $this->collection->insert($this->newObj); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/MapReduceQuery.php b/lib/Doctrine/MongoDB/Query/MapReduceQuery.php new file mode 100644 index 00000000..345e7ead --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/MapReduceQuery.php @@ -0,0 +1,145 @@ +. + */ + +namespace Doctrine\ODM\MongoDB\Query; + +use Doctrine\ODM\MongoDB\Cursor; + +/** + * InsertQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class MapReduceQuery extends AbstractQuery +{ + protected $select = array(); + protected $query; + protected $hydrate; + protected $limit; + protected $skip; + protected $sort; + protected $immortal; + protected $slaveOkay; + protected $snapshot; + protected $hints = array(); + protected $map; + protected $reduce; + protected $options = array(); + + public function setSelect($select) + { + $this->select = $select; + } + + public function setHydrate($hydrate) + { + $this->hydrate = $hydrate; + } + + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function setSkip($skip) + { + $this->skip = $skip; + } + + public function setSort($sort) + { + $this->sort = $sort; + } + + public function setImmortal($immortal) + { + $this->immortal = $immortal; + } + + public function setSlaveOkay($slaveOkay) + { + $this->slaveOkay = $slaveOkay; + } + + public function setSnapshot($snapshot) + { + $this->snapshot = $snapshot; + } + + public function setHints(array $hints) + { + $this->hints = $hints; + } + + public function setMap($map) + { + $this->map = $map; + } + + public function setReduce($reduce) + { + $this->reduce = $reduce; + } + + public function setOptions(array $options) + { + $this->options = $options; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function execute(array $options = array()) + { + if (is_string($this->map)) { + $this->map = new \MongoCode($this->map); + } + if (is_string($this->reduce)) { + $this->reduce = new \MongoCode($this->reduce); + } + $command = array( + 'mapreduce' => $this->class->getCollection(), + 'map' => $this->map, + 'reduce' => $this->reduce, + 'query' => $this->query + ); + $command = array_merge($command, $options); + $result = $this->database->command($command); + if ( ! $result['ok']) { + throw new \RuntimeException($result['errmsg']); + } + $cursor = $this->database->selectCollection($result['result'])->find(); + $cursor->limit($this->limit); + $cursor->skip($this->skip); + $cursor->sort($this->sort); + $cursor->immortal($this->immortal); + $cursor->slaveOkay($this->slaveOkay); + if ($this->snapshot) { + $cursor->snapshot(); + } + foreach ($this->hints as $keyPattern) { + $cursor->hint($keyPattern); + } + return $cursor; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/QueryInterface.php b/lib/Doctrine/MongoDB/Query/QueryInterface.php new file mode 100644 index 00000000..bcc61b74 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/QueryInterface.php @@ -0,0 +1,25 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +interface QueryInterface +{ + function execute(array $options = array()); +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/RemoveQuery.php b/lib/Doctrine/MongoDB/Query/RemoveQuery.php new file mode 100644 index 00000000..4a161894 --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/RemoveQuery.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * RemoveQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class RemoveQuery extends AbstractQuery +{ + protected $query = array(); + + public function setQuery(array $query = array()) + { + $this->query = $query; + } + + public function execute(array $options = array()) + { + return $this->collection->remove($this->query, $options); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Query/UpdateQuery.php b/lib/Doctrine/MongoDB/Query/UpdateQuery.php new file mode 100644 index 00000000..a313b07b --- /dev/null +++ b/lib/Doctrine/MongoDB/Query/UpdateQuery.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\MongoDB\Query; + +/** + * UpdateQuery + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class UpdateQuery extends AbstractQuery +{ + protected $query = array(); + protected $newObj = array(); + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setNewObj(array $newObj) + { + $this->newObj = $newObj; + } + + public function execute(array $options = array()) + { + return $this->collection->update($this->query, $this->newObj); + } +} \ No newline at end of file diff --git a/tests/Doctrine/MongoDB/Tests/Query/BuilderTest.php b/tests/Doctrine/MongoDB/Tests/Query/BuilderTest.php new file mode 100644 index 00000000..94146cfd --- /dev/null +++ b/tests/Doctrine/MongoDB/Tests/Query/BuilderTest.php @@ -0,0 +1,279 @@ +getTestQueryBuilder() + ->distinct('count') + ->field('username')->equals('distinct_test'); + + $expected = array( + 'username' => 'distinct_test' + ); + $this->assertInstanceOf('Doctrine\MongoDB\Query\DistinctFieldQuery', $qb->getQuery()); + $this->assertEquals($expected, $qb->getQueryArray()); + $this->assertInstanceof('Doctrine\MongoDB\ArrayIterator', $qb->getQuery()->execute()); + } + + public function testFindAndRemoveQuery() + { + $qb = $this->getTestQueryBuilder() + ->findAndRemove() + ->field('username')->equals('jwage'); + + $this->assertEquals(Builder::TYPE_FIND_AND_REMOVE, $qb->getType()); + $expected = array( + 'username' => 'jwage' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + $this->assertTrue(is_array($qb->getQuery()->execute())); + } + + public function testFindAndUpdateQuery() + { + $qb = $this->getTestQueryBuilder() + ->findAndRemove() + ->field('username')->equals('jwage'); + + $this->assertEquals(Builder::TYPE_FIND_AND_REMOVE, $qb->getType()); + $expected = array( + 'username' => 'jwage' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\FindAndRemoveQuery', $qb->getQuery()); + $this->assertTrue(is_array($qb->getQuery()->execute())); + } + + public function testGeoLocationQuery() + { + $qb = $this->getTestQueryBuilder() + ->field('x')->near(1) + ->field('y')->near(2) + ->field('username')->equals('jwage'); + + $this->assertEquals(Builder::TYPE_GEO_LOCATION, $qb->getType()); + $expected = array( + 'username' => 'jwage' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\GeoLocationFindQuery', $qb->getQuery()); + $this->assertInstanceOf('Doctrine\MongoDB\ArrayIterator', $qb->getQuery()->execute()); + } + + public function testGroupQuery() + { + $qb = $this->getTestQueryBuilder() + ->group(array(), array()); + + $this->assertEquals(Builder::TYPE_GROUP, $qb->getType()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\GroupQuery', $qb->getQuery()); + $this->assertInstanceOf('Doctrine\MongoDB\ArrayIterator', $qb->getQuery()->execute()); + } + + public function testInsertQuery() + { + $qb = $this->getTestQueryBuilder() + ->insert() + ->field('username')->set('jwage'); + + $expected = array( + 'username' => 'jwage' + ); + $this->assertEquals($expected, $qb->getNewObj()); + $this->assertEquals(Builder::TYPE_INSERT, $qb->getType()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\InsertQuery', $qb->getQuery()); + $this->assertTrue($qb->getQuery()->execute()); + } + + public function testUpdateQuery() + { + $qb = $this->getTestQueryBuilder() + ->update() + ->field('username')->set('jwage'); + + $expected = array( + '$set' => array( + 'username' => 'jwage' + ) + ); + $this->assertEquals($expected, $qb->getNewObj()); + $this->assertEquals(Builder::TYPE_UPDATE, $qb->getType()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\UpdateQuery', $qb->getQuery()); + $this->assertTrue($qb->getQuery()->execute()); + } + + public function testRemoveQuery() + { + $qb = $this->getTestQueryBuilder() + ->remove() + ->field('username')->equals('jwage'); + + $this->assertEquals(Builder::TYPE_REMOVE, $qb->getType()); + $this->assertInstanceOf('Doctrine\MongoDB\Query\RemoveQuery', $qb->getQuery()); + $this->assertTrue($qb->getQuery()->execute()); + } + + public function testThatOrAcceptsAnotherQuery() + { + $coll = $this->conn->selectCollection('db', 'users'); + + $expression1 = array('firstName' => 'Kris'); + $expression2 = array('firstName' => 'Chris'); + + $qb = $coll->createQueryBuilder(); + $qb->addOr($qb->expr()->field('firstName')->equals('Kris')); + $qb->addOr($qb->expr()->field('firstName')->equals('Chris')); + + $this->assertEquals(array('$or' => array( + array('firstName' => 'Kris'), + array('firstName' => 'Chris') + )), $qb->getQueryArray()); + } + + public function testAddElemMatch() + { + $qb = $this->getTestQueryBuilder(); + $qb->field('phonenumbers')->elemMatch($qb->expr()->field('phonenumber')->equals('6155139185')); + $expected = array('phonenumbers' => array( + '$elemMatch' => array('phonenumber' => '6155139185') + )); + $this->assertEquals($expected, $qb->getQueryArray()); + } + + public function testAddNot() + { + $qb = $this->getTestQueryBuilder(); + $qb->field('username')->not($qb->expr()->in(array('boo'))); + $expected = array( + 'username' => array( + '$not' => array( + '$in' => array('boo') + ) + ) + ); + $this->assertEquals($expected, $qb->getQueryArray()); + } + + public function testFindQuery() + { + $qb = $this->getTestQueryBuilder() + ->where("function() { return this.username == 'boo' }"); + $expected = array( + '$where' => "function() { return this.username == 'boo' }" + ); + $this->assertEquals($expected, $qb->getQueryArray()); + } + + public function testComplexUpdateQuery() + { + $qb = $this->getTestQueryBuilder() + ->update() + ->field('username') + ->set('jwage') + ->equals('boo'); + + $this->assertInstanceOf('Doctrine\MongoDB\Query\UpdateQuery', $qb->getQuery()); + + $expected = array( + 'username' => 'boo' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + + $expected = array('$set' => array( + 'username' => 'jwage' + )); + $this->assertEquals($expected, $qb->getNewObj()); + } + + public function testIncUpdateQuery() + { + $qb = $this->getTestQueryBuilder() + ->update() + ->field('hits')->inc(5) + ->field('username')->equals('boo'); + $query = $qb->getQuery(); + + $this->assertInstanceOf('Doctrine\MongoDB\Query\UpdateQuery', $qb->getQuery()); + + $expected = array( + 'username' => 'boo' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + + $expected = array('$inc' => array( + 'hits' => 5 + )); + $this->assertEquals($expected, $qb->getNewObj()); + } + + public function testUnsetField() + { + $qb = $this->getTestQueryBuilder() + ->update() + ->field('hits')->unsetField() + ->field('username')->equals('boo'); + + $expected = array( + 'username' => 'boo' + ); + $this->assertEquals($expected, $qb->getQueryArray()); + + $expected = array('$unset' => array( + 'hits' => 1 + )); + $this->assertEquals($expected, $qb->getNewObj()); + } + + public function testGroup() + { + $qb = $this->getTestQueryBuilder() + ->group(array(), array('count' => 0)) + ->reduce('function (obj, prev) { prev.count++; }'); + + $expected = array( + 'initial' => array( + 'count' => 0 + ), + 'keys' => array() + ); + $this->assertEquals($expected, $qb->debug('group')); + + $expected = array('reduce' => 'function (obj, prev) { prev.count++; }'); + $this->assertEquals($expected, $qb->debug('mapReduce')); + } + + public function testDateRange() + { + $start = new \MongoDate(strtotime('1985-09-01 01:00:00')); + $end = new \MongoDate(strtotime('1985-09-04')); + $qb = $this->getTestQueryBuilder(); + $qb->field('createdAt')->range($start, $end); + + $expected = array( + 'createdAt' => array( + '$gte' => $start, + '$lt' => $end + ) + ); + $this->assertEquals($expected, $qb->getQueryArray()); + } + + public function testQueryIsIterable() + { + $qb = $this->getTestQueryBuilder(); + $query = $qb->getQuery(); + $this->assertInstanceOf('Iterator', $query); + $this->assertInstanceOf('Doctrine\MongoDB\Iterator', $query); + } + + private function getTestQueryBuilder() + { + return $this->conn->selectCollection('db', 'users')->createQueryBuilder(); + } +} \ No newline at end of file