Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

QueryBuilder with fluent interface #76

Closed
wants to merge 9 commits into from

4 participants

@nacmartin

This a QueryBuilder with fluent interface that mimics the behaviour of the Doctrine DBAL QueryBuilder. It doesn't depend on other stuff of the DBAL, it depends only on PHPCR interfaces, so I think it can go either here or into PHPCR as "helper" code.

It is tested with unit tests and also this piece of "real code" using the QueryBuilder works:

https://github.com/nacmartin/SonataAdminBundle/blob/phpcr/Datagrid/ODM/PHPCR/ProxyQuery.php

PS: I have updated the jackalope submodule to current master to get the new names for andConstraint() and orConstraint() of the QOM.

@lsmith77
Owner

I think the ODM is the right place .. there might be an official JCR helper fluent interface and once that is out we should move something similar into PHPCR at which point we might decide to drop this one (or likely just adapt this one to their API and move the code).

lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((10 lines not shown))
+ * 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\PHPCR\Query;
+
+use PHPCR\Query\QOM\QueryObjectModelFactoryInterface;
+
+/**
+ * QueryBuilder clas ir responsible for dynamically create QOM queries.
@stof
stof added a note

the phpdoc needs to be fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((160 lines not shown))
+ *
+ * @return arrray Orderings to apply.
+ */
+ public function getOrderings()
+ {
+ return $this->orderings;
+ }
+
+ /**
+ * Adds an ordering to the query results.
+ *
+ * @param PHPCR\Query\QOM\DynamicOperandInterface $sort The ordering expression.
+ * @param string $order The ordering direction.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function addOrderBy(\PHPCR\Query\QOM\DynamicOperandInterface $sort, $order = null)
@stof
stof added a note

you should add use statements for the classes used for the type-hint

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((87 lines not shown))
+ private $params = array();
+
+ /**
+ * Initializes a new QueryBuilder
+ *
+ * @param PHPCR\Query\QOM\QueryObjectModelFactoryInterface $qomFactory
+ */
+ public function __construct(QueryObjectModelFactoryInterface $qomFactory)
+ {
+ $this->qomFactory = $qomFactory;
+ }
+
+ /**
+ * Get the associated QOMFactory for this query builder
+ *
+ * @return PHPCR\Query\QOM\QueryObjectModelFactoryInterface
@stof
stof added a note

I would prefer having FQCN starting with \ in the phpdoc to be more friendly with the IDEs. Thus, the convention used in the Symfony2 world (and which will be implemented in PhpStorm 3 AFAIK) is to consider the class name in the phpdoc as relative to the current namespace (excatly like it work for typehints for instance)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((186 lines not shown))
+
+ /**
+ * Specifies an ordering for the query results.
+ * Replaces any previously specified orderings, if any.
+ *
+ * @param PHPCR\Query\QOM\DynamicOperandInterface $sort The ordering expression.
+ * @param string $order The ordering direction.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function orderBy(\PHPCR\Query\QOM\DynamicOperandInterface $sort, $order = null)
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($order == 'ASC' ) {
+ $ordering = $this->qomFactory->ascending($sort);
+ } else {
+ $ordering = $this->qomFactory->descending($sort);
@stof
stof added a note

I think ASC should be used when $order is null (same previously). But it would even be better to change the default value to make it meaningful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((386 lines not shown))
+ */
+ public function leftJoin(\PHPCR\Query\QOM\SourceInterface $rightSource, \PHPCR\Query\QOM\JoinConditionInterface $joinCondition)
+ {
+ return $this->joinWithType($rightSource, \PHPCR\Query\QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER, $joinCondition);
+ }
+
+ /**
+ * Performs a right outer join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param string $joinType as specified in PHPCR\Query\QOM\QueryObjectModelConstantsInterface
+ * @param string $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function rightJoin(\PHPCR\Query\QOM\SourceInterface $rightSource, \PHPCR\Query\QOM\JoinConditionInterface $joinCondition)
@stof
stof added a note

the right join does not make sense in the ODM (think about the way you would map the result)

@lsmith77 Owner
@stof
stof added a note

for PHPCR, it makes sense. but here, the QueryBuilder is in the ODM repo where you are querying for objects. Let me know if you find a way to hydrate the relation of a null result.

@lsmith77 Owner
@stof
stof added a note

Well, if this QueryBuilder is about every PHPCR queries (more similar to the DBAL query builder than the ORM one), it should not be placed in the ODM but in PHPCR.

@lsmith77 Owner
@stof
stof added a note

DQL does not support right joins, exactly for the reason given here. Some DQL queries return a mixed result if you add scalar results along the object but they are hydrated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@lsmith77
Owner

but you can choose not to hydrate to objects. at any rate i am even hydrating to objects in one case and i am still using a left outer join, because it seems there are some limitations as to what is possible inside an ON clause with SQL2 (which is the serialization format for QOM).

@nacmartin

Added three commits to fix the issues pointed by Stof, except for the right join functionality.

@lsmith77
Owner

@bergie seems to think we could just move it to PHPCR .. i don't have a really solid opinion either way .. @dbu?

lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((82 lines not shown))
+ private $source = null;
+
+ /**
+ * @var \PHPCR\Query\QueryObjectModelInterface
+ */
+ private $query = null;
+
+ /**
+ * @var array The query parameters.
+ */
+ private $params = array();
+
+ /**
+ * Initializes a new QueryBuilder
+ *
+ * @return \PHPCR\Query\QOM\QueryObjectModelFactoryInterface $qomFactory
@stof
stof added a note

you should not have changed @param to @return as it is a param

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((201 lines not shown))
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($order == 'ASC' ) {
+ $ordering = $this->qomFactory->ascending($sort);
+ } else {
+ $ordering = $this->qomFactory->descending($sort);
+ }
+ $this->orderings = array($ordering);
+ return $this;
+ }
+
+ /**
+ * Specifies one restriction (may be simple or composed).
+ * Replaces any previously specified restrictions, if any.
+ *
+ * @return \PHPCR\Query\QOM\ConstraintInterface $constraint
@stof
stof added a note

the first one is a param, not a return

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((204 lines not shown))
+ $ordering = $this->qomFactory->ascending($sort);
+ } else {
+ $ordering = $this->qomFactory->descending($sort);
+ }
+ $this->orderings = array($ordering);
+ return $this;
+ }
+
+ /**
+ * Specifies one restriction (may be simple or composed).
+ * Replaces any previously specified restrictions, if any.
+ *
+ * @return \PHPCR\Query\QOM\ConstraintInterface $constraint
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function where($constraint)
@stof
stof added a note

what about type-hinting the constraint if it must be an object implementing the needed interface (according to the phpdoc) ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((234 lines not shown))
+ }
+
+ /**
+ * Creates a new constraint formed by applying a logical AND to the
+ * existing constraint and the new one
+ *
+ * Order of ands is important:
+ *
+ * Given $this->constraint = $constraint1
+ * running andWhere($constraint2)
+ * resulting constraint will be $constraint1 AND $constraint2
+ *
+ * If there is no previous constraint then it will simply store the
+ * provided one
+ *
+ * @return \PHPCR\Query\QOM\ConstraintInterface $constraint
@stof
stof added a note

same issue here for the phpdoc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
((261 lines not shown))
+ }
+
+ /**
+ * Creates a new constraint formed by applying a logical OR to the
+ * existing constraint and the new one
+ *
+ * Order of ands is important:
+ *
+ * Given $this->constraint = $constraint1
+ * running orWhere($constraint2)
+ * resulting constraint will be $constraint1 OR $constraint2
+ *
+ * If there is no previous constraint then it will simply store the
+ * provided one
+ *
+ * @return \PHPCR\Query\QOM\ConstraintInterface $constraint
@stof
stof added a note

and here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@nacmartin

fix fix, thanks @stof !

@dbu
Collaborator
dbu commented

as its building 100% on the interfaces, it does not add any burden to the phpcr implementors, so i think having it in phpcr is fine. even more so if the java world is going to add a fluent interface as well.

@lsmith77
Owner

ok@nacmartin .. could you add the fluent interface to PHPCR itself then? but where? in the Util namespace?
https://github.com/phpcr/phpcr/tree/master/src/PHPCR/Util

@dbu
Collaborator
dbu commented

i think PHPCR\Util is a good place. and then we should add a getQueryBuilder method to the QueryManagerInterface. this would allow implementations to replace the query builder if for some reason they want to replace it.

@nacmartin

Ok, fine. Closing this PR then

@nacmartin nacmartin closed this
@dbu
Collaborator
dbu commented

thanks nacmartin, sorry for the back and forth about where to add this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
515 lib/Doctrine/ODM/PHPCR/Query/QueryBuilder.php
@@ -0,0 +1,515 @@
+<?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\PHPCR\Query;
+
+use PHPCR\Query\QOM\QueryObjectModelFactoryInterface,
+ PHPCR\Query\QOM\DynamicOperandInterface,
+ PHPCR\Query\QOM\ConstraintInterface,
+ PHPCR\Query\QOM\SourceInterface,
+ PHPCR\Query\QOM\JoinConditionInterface,
+ PHPCR\Query\QOM\QueryObjectModelConstantsInterface;
+
+/**
+ * QueryBuilder class is responsible for dynamically create QOM queries.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @author Nacho Martín <nitram.ohcan@gmail.com>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class QueryBuilder
+{
+ /** The builder states. */
+ const STATE_DIRTY = 0;
+ const STATE_CLEAN = 1;
+
+ /**
+ * @var integer The state of the query object. Can be dirty or clean.
+ */
+ private $state = self::STATE_CLEAN;
+
+ /**
+ * @var \PHPCR\Query\QOM\QueryObjectModelFactoryInterface QOMFactory
+ */
+ private $qomFactory;
+
+ /**
+ * @var integer The maximum number of results to retrieve.
+ */
+ private $firstResult = null;
+
+ /**
+ * @var integer The maximum number of results to retrieve.
+ */
+ private $maxResults = null;
+
+ /**
+ * @var array with the orderings that determine the order of the result
+ */
+ private $orderings = array();
+
+ /**
+ * @var \PHPCR\Query\QOM\ConstraintInterface to apply to the query.
+ */
+ private $constraint = null;
+
+ /**
+ * @var array with the columns to be selected.
+ */
+ private $columns = array();
+
+ /**
+ * @var \PHPCR\Query\QOM\SourceInterface source of the query.
+ */
+ private $source = null;
+
+ /**
+ * @var \PHPCR\Query\QueryObjectModelInterface
+ */
+ private $query = null;
+
+ /**
+ * @var array The query parameters.
+ */
+ private $params = array();
+
+ /**
+ * Initializes a new QueryBuilder
+ *
+ * @param \PHPCR\Query\QOM\QueryObjectModelFactoryInterface $qomFactory
+ */
+ public function __construct(QueryObjectModelFactoryInterface $qomFactory)
+ {
+ $this->qomFactory = $qomFactory;
+ }
+
+ /**
+ * Get the associated QOMFactory for this query builder
+ *
+ * @return \PHPCR\Query\QOM\QueryObjectModelFactoryInterface
+ */
+ public function getQOMFactory()
+ {
+ return $this->qomFactory;
+ }
+
+ /**
+ * sets the position of the first result to retrieve (the "offset").
+ *
+ * @param integer $firstResult The First result to return.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function setFirstResult($firstResult)
+ {
+ $this->firstResult = $firstResult;
+ return $this;
+ }
+
+ /**
+ * getFirstResult
+ * Gets the position of the first result the query object was set to retrieve (the "offset").
+ * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
+ *
+ * @return integer The position of the first result.
+ */
+ public function getFirstResult()
+ {
+ return $this->firstResult;
+ }
+
+ /**
+ *
+ * Sets the maximum number of results to retrieve (the "limit").
+ *
+ * @param integer $maxResults The maximum number of results to retrieve.
+ * @return Doctrine\ODM\PHPCR\Query\QueryBuilder This QueryBuilder instance.
+ */
+ public function setMaxResults($maxResults)
+ {
+ $this->maxResults = $maxResults;
+ return $this;
+ }
+
+ /**
+ * Gets the maximum number of results the query object was set to retrieve (the "limit").
+ * Returns NULL if {@link setMaxResults} was not applied to this query builder.
+ *
+ * @return integer Maximum number of results.
+ */
+ public function getMaxResults()
+ {
+ return $this->maxResults;
+ }
+
+
+ /**
+ * Gets the array of orderings.
+ *
+ * @return arrray Orderings to apply.
+ */
+ public function getOrderings()
+ {
+ return $this->orderings;
+ }
+
+ /**
+ * Adds an ordering to the query results.
+ *
+ * @param \PHPCR\Query\QOM\DynamicOperandInterface $sort The ordering expression.
+ * @param string $order The ordering direction.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function addOrderBy(DynamicOperandInterface $sort, $order = 'ASC')
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($order == 'DESC' ) {
+ $ordering = $this->qomFactory->descending($sort);
+ } else {
+ $ordering = $this->qomFactory->ascending($sort);
+ }
+ $this->orderings[] = $ordering;
+ return $this;
+ }
+
+ /**
+ * Specifies an ordering for the query results.
+ * Replaces any previously specified orderings, if any.
+ *
+ * @param \PHPCR\Query\QOM\DynamicOperandInterface $sort The ordering expression.
+ * @param string $order The ordering direction.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function orderBy(DynamicOperandInterface $sort, $order = null)
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($order == 'ASC' ) {
+ $ordering = $this->qomFactory->ascending($sort);
+ } else {
+ $ordering = $this->qomFactory->descending($sort);
@stof
stof added a note

I think ASC should be used when $order is null (same previously). But it would even be better to change the default value to make it meaningful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+ $this->orderings = array($ordering);
+ return $this;
+ }
+
+ /**
+ * Specifies one restriction (may be simple or composed).
+ * Replaces any previously specified restrictions, if any.
+ *
+ * @param \PHPCR\Query\QOM\ConstraintInterface $constraint
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function where(ConstraintInterface $constraint)
+ {
+ $this->state = self::STATE_DIRTY;
+ $this->constraint = $constraint;
+ return $this;
+ }
+
+ /**
+ * Returns the constraint to apply
+ *
+ * @return \PHPCR\Query\QOM\ConstraintInterface the constraint to be applied
+ */
+ public function getConstraint()
+ {
+ return $this->constraint;
+ }
+
+ /**
+ * Creates a new constraint formed by applying a logical AND to the
+ * existing constraint and the new one
+ *
+ * Order of ands is important:
+ *
+ * Given $this->constraint = $constraint1
+ * running andWhere($constraint2)
+ * resulting constraint will be $constraint1 AND $constraint2
+ *
+ * If there is no previous constraint then it will simply store the
+ * provided one
+ *
+ * @param \PHPCR\Query\QOM\ConstraintInterface $constraint
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function andWhere(ConstraintInterface $constraint)
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($this->constraint) {
+ $this->constraint = $this->qomFactory->andConstraint($this->constraint, $constraint);
+ } else {
+ $this->constraint = $constraint;
+ }
+ return $this;
+ }
+
+ /**
+ * Creates a new constraint formed by applying a logical OR to the
+ * existing constraint and the new one
+ *
+ * Order of ands is important:
+ *
+ * Given $this->constraint = $constraint1
+ * running orWhere($constraint2)
+ * resulting constraint will be $constraint1 OR $constraint2
+ *
+ * If there is no previous constraint then it will simply store the
+ * provided one
+ *
+ * @param \PHPCR\Query\QOM\ConstraintInterface $constraint
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function orWhere(ConstraintInterface $constraint)
+ {
+ $this->state = self::STATE_DIRTY;
+ if ($this->constraint) {
+ $this->constraint = $this->qomFactory->orConstraint($this->constraint, $constraint);
+ } else {
+ $this->constraint = $constraint;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the columns to be selected
+ *
+ * @return array The columns to be selected
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Identifies a property in the specified or default selector to include in the tabular view of query results.
+ * Replaces any previously specified columns to be selected if any.
+ *
+ * @param string $propertyName
+ * @param string $columnName
+ * @param string $selectorName
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function select($propertyName, $columnName, $selectorName)
+ {
+ $this->state = self::STATE_DIRTY;
+ $this->columns = array($this->qomFactory->column($propertyName, $columnName, $selectorName));
+ return $this;
+ }
+
+ /**
+ * Adds a property in the specified or default selector to include in the tabular view of query results.
+ *
+ * @param string $propertyName
+ * @param string $columnName
+ * @param string $selectorName
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function addSelect($propertyName, $columnName, $selectorName)
+ {
+ $this->state = self::STATE_DIRTY;
+ $this->columns[] = $this->qomFactory->column($propertyName, $columnName, $selectorName);
+ return $this;
+ }
+
+ /**
+ * Sets the default Selector or the node-tuple Source. Can be a selector
+ * or a join.
+ *
+ * @param SourceInterface $source
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function from(SourceInterface $source)
+ {
+ $this->state = self::STATE_DIRTY;
+ $this->source = $source;
+ return $this;
+ }
+
+ /**
+ * Gets the default Selector.
+ *
+ * @return \PHPCR\Query\QOM\SourceInterface The default selector.
+ */
+ public function getSource()
+ {
+ return $this->source;
+ }
+
+ /**
+ * Performs an inner join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param \PHPCR\Query\QOM\JoinConditionInterface $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function join(SourceInterface $rightSource, JoinConditionInterface $joinCondition)
+ {
+ return $this->innerJoin($rightSource, $joinCondition);
+ }
+
+ /**
+ * Performs an inner join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param \PHPCR\Query\QOM\JoinConditionInterface $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function innerJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition)
+ {
+ return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER, $joinCondition);
+ }
+
+ /**
+ * Performs an left outer join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param \PHPCR\Query\QOM\JoinConditionInterface $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function leftJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition)
+ {
+ return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER, $joinCondition);
+ }
+
+ /**
+ * Performs a right outer join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param \PHPCR\Query\QOM\JoinConditionInterface $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function rightJoin(SourceInterface $rightSource, JoinConditionInterface $joinCondition)
+ {
+ return $this->joinWithType($rightSource, QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER, $joinCondition);
+ }
+
+ /**
+ * Performs an join between the stored source and the supplied source.
+ *
+ * @param \PHPCR\Query\QOM\SourceInterface $rightSource
+ * @param string $joinType as specified in PHPCR\Query\QOM\QueryObjectModelConstantsInterface
+ * @param \PHPCR\Query\QOM\JoinConditionInterface $joinCondition
+ * @return QueryBuilder This QueryBuilder instance.
+ * @trows RuntimeException if there is not an existing source.
+ */
+ public function joinWithType(SourceInterface $rightSource, $joinType, JoinConditionInterface $joinCondition)
+ {
+ if (!$this->source) {
+ throw new \RuntimeException('Cannot perform a join without a previous call to from');
+ }
+ $this->state = self::STATE_DIRTY;
+ $this->source = $this->qomFactory->join($this->source, $rightSource, $joinType, $joinCondition);
+ return $this;
+ }
+
+ /**
+ * Gets the query built
+ *
+ * @return \PHPCR\Query\QueryObjectModelInterface
+ */
+ public function getQuery()
+ {
+ if ($this->query !== null && $this->state === self::STATE_CLEAN) {
+ return $this->query;
+ }
+ $this->state = self::STATE_CLEAN;
+ $this->query = $this->qomFactory->createQuery($this->source, $this->constraint, $this->orderings, $this->columns);
+ return $this->query;
+ }
+
+ /**
+ * Executes the query setting firstResult and maxResults.
+ *
+ * @return \PHPCR\Query\QueryResultInterface
+ */
+ public function execute()
+ {
+ if ($this->query === null || $this->state === self::STATE_DIRTY) {
+ $this->query = $this->getQuery();
+ }
+
+ if ($this->firstResult) {
+ $this->query->setOffset($this->firstResult);
+ }
+
+ if ($this->maxResults) {
+ $this->query->setLimit($this->maxResults);
+ }
+
+ foreach ($this->params as $key => $value) {
+ $this->query->bindValue($key, $value);
+ }
+
+ $queryResult = $this->query->execute();
+
+ return $queryResult;
+ }
+
+ /**
+ * Sets a query parameter for the query being constructed.
+ *
+ * @param string $key The parameter name.
+ * @param mixed $value The parameter value.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function setParameter($key, $value)
+ {
+ $this->params[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Gets a (previously set) query parameter of the query being constructed.
+ *
+ * @param string $key The key (name) of the bound parameter.
+ * @return mixed The value of the bound parameter.
+ */
+ public function getParameter($key)
+ {
+ return isset($this->params[$key]) ? $this->params[$key] : null;
+ }
+
+ /**
+ * Sets a collection of query parameters for the query being constructed.
+ *
+ * @param array $params The query parameters to set.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function setParameters(array $params)
+ {
+ $this->params = $params;
+
+ return $this;
+ }
+
+ /**
+ * Gets all defined query parameters for the query being constructed.
+ *
+ * @return array The currently defined query parameters.
+ */
+ public function getParameters()
+ {
+ return $this->params;
+ }
+}
2  lib/vendor/jackalope
@@ -1 +1 @@
-Subproject commit afa4eb368f6369ac7547f9f60012bd1d1502e4c9
+Subproject commit 829acc9145edd5206c57aa8252d0dca2ddabdb46
View
244 tests/Doctrine/Tests/ODM/PHPCR/Query/QueryBuilderTest.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace Doctrine\Tests\ODM\PHPCR\Query;
+
+use Doctrine\ODM\PHPCR\Query\QueryBuilder;
+
+class QueryBuilderTest extends \PHPUnit_Framework_TestCase
+{
+ protected $qf;
+
+ public function setUp()
+ {
+ $this->qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array());
+ }
+
+ public function testSetFirstResult()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $qb->setFirstResult(15);
+ $this->assertEquals(15, $qb->getFirstResult());
+ }
+
+ public function testSetMaxResults()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $qb->setMaxResults(15);
+ $this->assertEquals(15, $qb->getMaxResults());
+ }
+
+ public function testAddOrderBy()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->addOrderBy($dynamicOperand, 'ASC');
+ $qb->addOrderBy($dynamicOperand, 'DESC');
+ $this->assertEquals(2, count($qb->getOrderings()));
+ $orderings = $qb->getOrderings();
+ }
+
+ public function testOrderBy()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->orderBy($dynamicOperand, 'ASC');
+ $qb->orderBy($dynamicOperand, 'ASC');
+ $this->assertEquals(1, count($qb->getOrderings()));
+ }
+
+ public function testOrderAscending()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('ascending')
+ ->with($this->equalTo($dynamicOperand));
+ $qb = new QueryBuilder($this->qf);
+ $qb->addOrderBy($dynamicOperand, 'ASC');
+ }
+
+ public function testOrderDescending()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('descending')
+ ->with($this->equalTo($dynamicOperand));
+ $qb = new QueryBuilder($this->qf);
+ $qb->addOrderBy($dynamicOperand, 'DESC');
+ }
+
+ public function testOrderAscendingIsDefault()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\DynamicOperandInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('ascending')
+ ->with($this->equalTo($dynamicOperand));
+ $qb = new QueryBuilder($this->qf);
+ $qb->addOrderBy($dynamicOperand);
+ }
+
+ public function testWhere()
+ {
+ $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->where($constraint);
+ $this->assertEquals($constraint, $qb->getConstraint());
+ }
+
+ public function testAndWhere()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $this->qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('andConstraint');
+
+ $constraint1 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $constraint2 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->where($constraint1);
+ $qb->andWhere($constraint2);
+ }
+
+ public function testOrWhere()
+ {
+ $dynamicOperand = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $this->qf = $this->getMock('PHPCR\Query\QOM\QueryObjectModelFactoryInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('orConstraint');
+
+ $constraint1 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $constraint2 = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->where($constraint1);
+ $qb->orWhere($constraint2);
+ }
+
+ public function testSelect()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $this->assertEquals(0, count($qb->getColumns()));
+ $qb->select('propertyName', 'columnName', 'selectorName');
+ $this->assertEquals(1, count($qb->getColumns()));
+ $qb->select('propertyName', 'columnName', 'selectorName');
+ $this->assertEquals(1, count($qb->getColumns()));
+ }
+
+ public function testAddSelect()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $this->assertEquals(0, count($qb->getColumns()));
+ $qb->addSelect('propertyName', 'columnName', 'selectorName');
+ $this->assertEquals(1, count($qb->getColumns()));
+ $qb->addSelect('propertyName', 'columnName', 'selectorName');
+ $this->assertEquals(2, count($qb->getColumns()));
+ }
+
+ public function testFrom()
+ {
+ $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source);
+ $this->assertEquals($source, $qb->getSource());
+ }
+
+ public function testInvalidJoin()
+ {
+ $this->setExpectedException('\RuntimeException');
+ $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->join($source, $joinCondition);
+ }
+
+ public function testJoin()
+ {
+ $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source1);
+ $qb->join($source2, $joinCondition);
+ }
+
+ public function testRightJoin()
+ {
+ $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('join')
+ ->with($source1, $source2, $this->equalTo(\PHPCR\Query\QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_RIGHT_OUTER), $joinCondition);
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source1);
+ $qb->rightJoin($source2, $joinCondition);
+ }
+
+ public function testLeftJoin()
+ {
+ $source1 = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $source2= $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $joinCondition = $this->getMock('PHPCR\Query\QOM\SameNodeJoinConditionInterface', array(), array());
+ $this->qf->expects($this->once())
+ ->method('join')
+ ->with($source1, $source2, $this->equalTo(\PHPCR\Query\QOM\QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_LEFT_OUTER), $joinCondition);
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source1);
+ $qb->leftJoin($source2, $joinCondition);
+ }
+
+ public function testGetQuery()
+ {
+ $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source);
+ $qb->where($constraint);
+ $this->qf->expects($this->once())
+ ->method('createQuery')
+ ->will($this->returnValue("true"));
+ $qb->getQuery();
+ //state is clean, query is stored
+ $qb->getQuery();
+ }
+
+ public function testSetParameter()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $key = "key";
+ $value = "value";
+ $qb->setParameter($key, $value);
+ $this->assertEquals($value, $qb->getParameter($key));
+ }
+
+ public function testSetParameters()
+ {
+ $qb = new QueryBuilder($this->qf);
+ $key1 = "key1";
+ $value1 = "value1";
+ $key2 = "key2";
+ $value2 = "value2";
+ $qb->setParameters(array($key1, $value1), array($key2, $value2));
+ $this->assertEquals("2", count($qb->getParameters()));
+ }
+
+ public function testExecute()
+ {
+ $source = $this->getMock('PHPCR\Query\QOM\SourceInterface', array(), array());
+ $constraint = $this->getMock('PHPCR\Query\QOM\ConstraintInterface', array(), array());
+ $query = $this->getMock('PHPCR\Query\QueryInterface', array(), array());
+ $query->expects($this->once())
+ ->method('execute');
+ $query->expects($this->once())
+ ->method('bindValue');
+ $this->qf->expects($this->once())
+ ->method('createQuery')
+ ->with($source, $constraint, array(), array())
+ ->will($this->returnValue($query));
+ $qb = new QueryBuilder($this->qf);
+ $qb->from($source)
+ ->where($constraint)
+ ->setFirstResult(10)
+ ->setMaxResults(10)
+ ->setParameter('Key', 'value')
+ ->execute();
+ }
+}
Something went wrong with that request. Please try again.