From f608dd8a721ec9b7ec2e3cd82a22c14072f9544b Mon Sep 17 00:00:00 2001 From: jwage Date: Fri, 10 Jul 2009 17:53:48 +0000 Subject: [PATCH] [2.0] More work on the QueryBuilder and Expr classes. --- .../DBAL/Platforms/AbstractPlatform.php | 13 +++ lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php | 88 +++++++++++++++ .../DBAL/Platforms/OraclePlatform.php | 34 ++++++ lib/Doctrine/ORM/Query/Expr.php | 22 +++- lib/Doctrine/ORM/Query/Expr/Andx.php | 2 +- lib/Doctrine/ORM/Query/Expr/Base.php | 4 +- lib/Doctrine/ORM/Query/Expr/Comparison.php | 2 +- lib/Doctrine/ORM/Query/Expr/Func.php | 2 +- ...{CountDistinctFunction.php => GroupBy.php} | 18 +-- lib/Doctrine/ORM/Query/Expr/Math.php | 2 +- .../Expr/{SelectField.php => OrderBy.php} | 34 ++++-- lib/Doctrine/ORM/Query/Expr/Orx.php | 2 +- lib/Doctrine/ORM/Query/Expr/Select.php | 5 +- lib/Doctrine/ORM/Query/SqlWalker.php | 4 + lib/Doctrine/ORM/QueryBuilder.php | 103 +++++++++++++----- .../Tests/ORM/Functional/AllTests.php | 1 + .../Tests/ORM/Functional/QueryBuilderTest.php | 73 +++++++++++++ tests/Doctrine/Tests/ORM/Query/ExprTest.php | 45 +++++++- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 60 ++++++++-- 19 files changed, 433 insertions(+), 81 deletions(-) rename lib/Doctrine/ORM/Query/Expr/{CountDistinctFunction.php => GroupBy.php} (79%) rename lib/Doctrine/ORM/Query/Expr/{SelectField.php => OrderBy.php} (64%) create mode 100644 tests/Doctrine/Tests/ORM/Functional/QueryBuilderTest.php diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index e724d0308..4a747c8f9 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1538,6 +1538,19 @@ abstract class AbstractPlatform return 'H:i:s'; } + public function modifyLimitQuery($query, $max, $first) + { + if ( ! is_null($first)) { + $query .= ' OFFSET ' . $first; + } + + if ( ! is_null($max)) { + $query .= ' LIMIT ' . $max; + } + + return $query; + } + /** * Gets the SQL snippet used to declare a VARCHAR column type. * diff --git a/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php b/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php index 1c3f5a9f2..14705fe3d 100644 --- a/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php @@ -425,4 +425,92 @@ class MsSqlPlatform extends AbstractPlatform { return 'mssql'; } + + /** + * Adds an adapter-specific LIMIT clause to the SELECT statement. + * + * @param string $query + * @param mixed $limit + * @param mixed $offset + * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html + * @return string + */ + public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) + { + if ($limit > 0) { + $count = intval($limit); + $offset = intval($offset); + + if ($offset < 0) { + throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid"); + } + + $orderby = stristr($query, 'ORDER BY'); + + if ($orderby !== false) { + // Ticket #1835: Fix for ORDER BY alias + // Ticket #2050: Fix for multiple ORDER BY clause + $order = str_ireplace('ORDER BY', '', $orderby); + $orders = explode(',', $order); + + for ($i = 0; $i < count($orders); $i++) { + $sorts[$i] = (stripos($orders[$i], ' desc') !== false) ? 'desc' : 'asc'; + $orders[$i] = trim(preg_replace('/\s+(ASC|DESC)$/i', '', $orders[$i])); + + // find alias in query string + $helper_string = stristr($query, $orders[$i]); + + $from_clause_pos = strpos($helper_string, ' FROM '); + $fields_string = substr($helper_string, 0, $from_clause_pos + 1); + + $field_array = explode(',', $fields_string); + $field_array = array_shift($field_array); + $aux2 = spliti(' as ', $field_array); + + $aliases[$i] = trim(end($aux2)); + } + } + + // Ticket #1259: Fix for limit-subquery in MSSQL + $selectRegExp = 'SELECT\s+'; + $selectReplace = 'SELECT '; + + if (preg_match('/^SELECT(\s+)DISTINCT/i', $query)) { + $selectRegExp .= 'DISTINCT\s+'; + $selectReplace .= 'DISTINCT '; + } + + $query = preg_replace('/^'.$selectRegExp.'/i', $selectReplace . 'TOP ' . ($count + $offset) . ' ', $query); + $query = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $query . ') AS ' . $this->quoteIdentifier('inner_tbl'); + + if ($orderby !== false) { + $query .= ' ORDER BY '; + + for ($i = 0, $l = count($orders); $i < $l; $i++) { + if ($i > 0) { // not first order clause + $query .= ', '; + } + + $query .= $this->quoteIdentifier('inner_tbl') . '.' . $aliases[$i] . ' '; + $query .= (stripos($sorts[$i], 'asc') !== false) ? 'DESC' : 'ASC'; + } + } + + $query .= ') AS ' . $this->quoteIdentifier('outer_tbl'); + + if ($orderby !== false) { + $query .= ' ORDER BY '; + + for ($i = 0, $l = count($orders); $i < $l; $i++) { + if ($i > 0) { // not first order clause + $query .= ', '; + } + + $query .= $this->quoteIdentifier('outer_tbl') . '.' . $aliases[$i] . ' ' . $sorts[$i]; + } + } + } + + return $query; + } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php index 5abbc4c85..8fab5fee2 100644 --- a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -469,4 +469,38 @@ END;'; { return 'oracle'; } + + /** + * Adds an driver-specific LIMIT clause to the query + * + * @param string $query query to modify + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string the modified query + */ + public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) + { + $limit = (int) $limit; + $offset = (int) $offset; + if (preg_match('/^\s*SELECT/i', $query)) { + if ( ! preg_match('/\sFROM\s/i', $query)) { + $query .= " FROM dual"; + } + if ($limit > 0) { + $max = $offset + $limit; + $column = $column === null ? '*' : $this->quoteIdentifier($column); + if ($offset > 0) { + $min = $offset + 1; + $query = 'SELECT b.'.$column.' FROM ('. + 'SELECT a.*, ROWNUM AS doctrine_rownum FROM (' + . $query . ') a '. + ') b '. + 'WHERE doctrine_rownum BETWEEN ' . $min . ' AND ' . $max; + } else { + $query = 'SELECT a.'.$column.' FROM (' . $query .') a WHERE ROWNUM <= ' . $max; + } + } + } + return $query; + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr.php b/lib/Doctrine/ORM/Query/Expr.php index 35b702353..0cb917b6b 100644 --- a/lib/Doctrine/ORM/Query/Expr.php +++ b/lib/Doctrine/ORM/Query/Expr.php @@ -33,24 +33,34 @@ namespace Doctrine\ORM\Query; */ class Expr { - public static function andx() + public static function andx($x = null) { return new Expr\Andx(func_get_args()); } - public static function orx() + public static function orx($x = null) { return new Expr\Orx(func_get_args()); } - public static function select() + public static function select($select = null) { return new Expr\Select(func_get_args()); } - public static function selectField($field, $alias = null) + public static function orderBy($sort = null, $order = null) { - return new Expr\SelectField($field, $alias); + return new Expr\OrderBy($sort, $order); + } + + public static function groupBy($groupBy = null) + { + return new Expr\GroupBy(func_get_args()); + } + + public static function having($having = null) + { + return new Expr\Having(func_get_args()); } public static function eq($x, $y) @@ -105,7 +115,7 @@ class Expr public static function countDistinct() { - return new Expr\CountDistinctFunction(func_get_args()); + return 'COUNT(DISTINCT ' . implode(', ', func_get_args()) . ')'; } public static function exists($subquery) diff --git a/lib/Doctrine/ORM/Query/Expr/Andx.php b/lib/Doctrine/ORM/Query/Expr/Andx.php index 3bf8e100a..08f8fbddb 100644 --- a/lib/Doctrine/ORM/Query/Expr/Andx.php +++ b/lib/Doctrine/ORM/Query/Expr/Andx.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for building and clauses + * Expression class for building DQL and parts * * @author Jonathan H. Wage * @author Guilherme Blanco diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index 0a5e6a10a..9fe217236 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Abstract class for building DQL expressions + * Abstract base Expr class for building DQL parts * * @author Jonathan H. Wage * @author Guilherme Blanco @@ -68,7 +68,7 @@ abstract class Base return count($this->_parts); } - public function __tostring() + public function __toString() { return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator; } diff --git a/lib/Doctrine/ORM/Query/Expr/Comparison.php b/lib/Doctrine/ORM/Query/Expr/Comparison.php index 2d499caeb..2bc75ae75 100644 --- a/lib/Doctrine/ORM/Query/Expr/Comparison.php +++ b/lib/Doctrine/ORM/Query/Expr/Comparison.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for comparison statements + * Expression class for DQL comparison expressions * * @author Jonathan H. Wage * @license http://www.opensource.org/licenses/lgpl-license.php LGPL diff --git a/lib/Doctrine/ORM/Query/Expr/Func.php b/lib/Doctrine/ORM/Query/Expr/Func.php index 3c5fa4f22..72bea0e82 100644 --- a/lib/Doctrine/ORM/Query/Expr/Func.php +++ b/lib/Doctrine/ORM/Query/Expr/Func.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for building comparison clauses + * Expression class for generating DQL functions * * @author Jonathan H. Wage * @license http://www.opensource.org/licenses/lgpl-license.php LGPL diff --git a/lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php b/lib/Doctrine/ORM/Query/Expr/GroupBy.php similarity index 79% rename from lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php rename to lib/Doctrine/ORM/Query/Expr/GroupBy.php index 9de148ee7..454dbd3f6 100644 --- a/lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php +++ b/lib/Doctrine/ORM/Query/Expr/GroupBy.php @@ -22,25 +22,17 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for building comparison clauses + * Expression class for building DQL Group By parts * * @author Jonathan H. Wage + * @author Guilherme Blanco * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.phpdoctrine.org * @since 2.0 * @version $Revision$ */ -class CountDistinctFunction +class GroupBy extends Base { - private $_arguments; - - public function __construct($arguments) - { - $this->_arguments = $arguments; - } - - public function __toString() - { - return 'COUNT(DISTINCT ' . implode(', ', $this->_arguments) . ')'; - } + protected $_preSeparator = ''; + protected $_postSeparator = ''; } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Math.php b/lib/Doctrine/ORM/Query/Expr/Math.php index 7d2e82276..89de7a599 100644 --- a/lib/Doctrine/ORM/Query/Expr/Math.php +++ b/lib/Doctrine/ORM/Query/Expr/Math.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for math statements + * Expression class for DQL math statements * * @author Jonathan H. Wage * @license http://www.opensource.org/licenses/lgpl-license.php LGPL diff --git a/lib/Doctrine/ORM/Query/Expr/SelectField.php b/lib/Doctrine/ORM/Query/Expr/OrderBy.php similarity index 64% rename from lib/Doctrine/ORM/Query/Expr/SelectField.php rename to lib/Doctrine/ORM/Query/Expr/OrderBy.php index af69a47e6..f3558de34 100644 --- a/lib/Doctrine/ORM/Query/Expr/SelectField.php +++ b/lib/Doctrine/ORM/Query/Expr/OrderBy.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * This class is used for representing field in a select statement + * Expression class for building DQL Order By parts * * @author Jonathan H. Wage * @author Guilherme Blanco @@ -31,19 +31,35 @@ namespace Doctrine\ORM\Query\Expr; * @since 2.0 * @version $Revision$ */ -class SelectField +class OrderBy { - private $_field; - private $_alias; + protected $_preSeparator = ''; + protected $_separator = ', '; + protected $_postSeparator = ''; + protected $_allowedClasses = array(); - public function __construct($field, $alias = null) + private $_parts = array(); + + public function __construct($sort = null, $order = null) { - $this->_field = $field; - $this->_alias = $alias; + if ($sort) { + $this->add($sort, $order); + } } - public function __toString() + public function add($sort, $order = null) { - return $this->_field . (($this->_alias !== null) ? ' AS ' . $this->_alias : ''); + $order = ! $order ? 'ASC' : $order; + $this->_parts[] = $sort . ' '. $order; + } + + public function count() + { + return count($this->_parts); + } + + public function __tostring() + { + return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Orx.php b/lib/Doctrine/ORM/Query/Expr/Orx.php index c04855590..b4a8ef743 100644 --- a/lib/Doctrine/ORM/Query/Expr/Orx.php +++ b/lib/Doctrine/ORM/Query/Expr/Orx.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for building and clauses + * Expression class for building DQL OR clauses * * @author Jonathan H. Wage * @author Guilherme Blanco diff --git a/lib/Doctrine/ORM/Query/Expr/Select.php b/lib/Doctrine/ORM/Query/Expr/Select.php index a770c98e9..26144f209 100644 --- a/lib/Doctrine/ORM/Query/Expr/Select.php +++ b/lib/Doctrine/ORM/Query/Expr/Select.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\Expr; /** - * Expression class for building DQL select clauses + * Expression class for building DQL select statements * * @author Jonathan H. Wage * @author Guilherme Blanco @@ -35,7 +35,4 @@ class Select extends Base { protected $_preSeparator = ''; protected $_postSeparator = ''; - protected $_allowedClasses = array( - 'Doctrine\ORM\Query\Expr\SelectField' - ); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 526e72d8c..86264f689 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -134,6 +134,10 @@ class SqlWalker implements TreeWalker $sql .= $AST->getHavingClause() ? $this->walkHavingClause($AST->getHavingClause()) : ''; $sql .= $AST->getOrderByClause() ? $this->walkOrderByClause($AST->getOrderByClause()) : ''; + $q = $this->getQuery(); + $sql = $this->getConnection()->getDatabasePlatform() + ->modifyLimitQuery($sql, $q->getMaxResults(), $q->getFirstResult()); + return $sql; } diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 539ce802f..95f9bc1f7 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -49,41 +49,49 @@ class QueryBuilder /** * @var EntityManager $em Instance of an EntityManager to use for query */ - protected $_em; + private $_em; /** * @var array $dqlParts The array of DQL parts collected */ - protected $_dqlParts = array( + private $_dqlParts = array( 'select' => array(), 'from' => array(), 'where' => array(), 'groupBy' => array(), 'having' => array(), - 'orderBy' => array(), - 'limit' => array(), - 'offset' => array() + 'orderBy' => array() ); /** * @var integer $type The type of query this is. Can be select, update or delete */ - protected $_type = self::SELECT; + private $_type = self::SELECT; /** * @var integer $state The state of the query object. Can be dirty or clean. */ - protected $_state = self::STATE_CLEAN; + private $_state = self::STATE_CLEAN; /** * @var string $dql The complete DQL string for this query */ - protected $_dql; + private $_dql; /** * @var array $params Parameters of this query. */ - protected $_params = array(); + private $_params = array(); + + /** + * @var integer The first result to return (the "offset"). + */ + private $_firstResult = null; + + /** + * @var integer The maximum number of results to return (the "limit"). + */ + private $_maxResults = null; public function __construct(EntityManager $entityManager) { @@ -143,6 +151,8 @@ class QueryBuilder $q = new Query($this->_em); $q->setDql($this->getDql()); $q->setParameters($this->getParameters()); + $q->setFirstResult($this->getFirstResult()); + $q->setMaxResults($this->getMaxResults()); return $q; } @@ -224,7 +234,7 @@ class QueryBuilder return $this; } - public function select($select) + public function select($select = null) { $selects = func_get_args(); $this->_type = self::SELECT; @@ -349,12 +359,17 @@ class QueryBuilder public function groupBy($groupBy) { - return $this->add('groupBy', $groupBy, false); + return $this->add('groupBy', Expr::groupBy($groupBy), false); + } + + public function addGroupBy($groupBy) + { + return $this->add('groupBy', Expr::groupBy($groupBy), true); } public function having($having) { - return $this->add('having', $having, false); + return $this->add('having', Expr::having($having), false); } public function andHaving($having) @@ -363,7 +378,7 @@ class QueryBuilder $this->add('having', 'AND', true); } - return $this->add('having', $having, true); + return $this->add('having', Expr::having($having), true); } public function orHaving($having) @@ -372,27 +387,63 @@ class QueryBuilder $this->add('having', 'OR', true); } - return $this->add('having', $having, true); + return $this->add('having', Expr::having($having), true); } public function orderBy($sort, $order) { - return $this->add('orderBy', $sort . ' ' . $order, false); + return $this->add('orderBy', Expr::orderBy($sort, $order), false); } public function addOrderBy($sort, $order) { - return $this->add('orderBy', $sort . ' ' . $order, true); + return $this->add('orderBy', Expr::orderBy($sort, $order), true); } - public function limit($limit) + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param integer $firstResult The first result to return. + * @return Query This query object. + */ + public function setFirstResult($firstResult) { - return $this->add('limit', $limit); + $this->_firstResult = $firstResult; + return $this; + } + + /** + * 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 query. + * + * @return integer The position of the first result. + */ + public function getFirstResult() + { + return $this->_firstResult; } - public function offset($offset) + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param integer $maxResults + * @return Query This query object. + */ + public function setMaxResults($maxResults) { - return $this->add('offset', $offset); + $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. + * + * @return integer Maximum number of results. + */ + public function getMaxResults() + { + return $this->_maxResults; } /** @@ -414,9 +465,7 @@ class QueryBuilder return 'DELETE' . $this->_getReducedDqlQueryPart('from', array('pre' => ' ', 'separator' => ' ')) . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } /** @@ -439,9 +488,7 @@ class QueryBuilder . $this->_getReducedDqlQueryPart('from', array('pre' => ' ', 'separator' => ' ')) . $this->_getReducedDqlQueryPart('set', array('pre' => ' SET ', 'separator' => ', ')) . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } /** @@ -469,9 +516,7 @@ class QueryBuilder . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) . $this->_getReducedDqlQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', ')) . $this->_getReducedDqlQueryPart('having', array('pre' => ' HAVING ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) - . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) - . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } private function _getReducedDqlQueryPart($queryPartName, $options = array()) diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index 43fe7034f..3c53a7ee6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -34,6 +34,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToOneSelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManySelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Functional\QueryBuilderTest'); return $suite; } diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryBuilderTest.php new file mode 100644 index 000000000..30dde2729 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/QueryBuilderTest.php @@ -0,0 +1,73 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testExecute() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + + $results = $qb->execute(); + $this->assertEquals('Doctrine\Common\Collections\Collection', get_class($results)); + } + + public function testSetMaxResultsAndSetFirstResultZero() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->setMaxResults(10) + ->setFirstResult(0); + + $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ OFFSET 0 LIMIT 10', $qb->getQuery()->getSql()); + } + + public function testSetMaxResultsAndSetFirstResult() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->setMaxResults(10) + ->setFirstResult(10); + + $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ OFFSET 10 LIMIT 10', $qb->getQuery()->getSql()); + } + + public function testRemoveSetMaxResultsAndSetFirstResult() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->setMaxResults(10) + ->setFirstResult(0) + ->setMaxResults(null) + ->setFirstResult(null); + + $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_', $qb->getQuery()->getSql()); + } + + public function testOnlyFirstResult() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->setMaxResults(10); + + $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ LIMIT 10', $qb->getQuery()->getSql()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/ExprTest.php b/tests/Doctrine/Tests/ORM/Query/ExprTest.php index 6eeb301fc..3ae129ce9 100644 --- a/tests/Doctrine/Tests/ORM/Query/ExprTest.php +++ b/tests/Doctrine/Tests/ORM/Query/ExprTest.php @@ -245,9 +245,48 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testSelectExpr() { $selectExpr = Expr::select(); - $selectExpr->add(Expr::selectField('u.id')); - $selectExpr->add(Expr::selectField('u.username', 'my_test')); + $selectExpr->add('u.id'); + $selectExpr->add('u.username'); - $this->assertEquals('u.id, u.username AS my_test', (string) $selectExpr); + $this->assertEquals('u.id, u.username', (string) $selectExpr); + } + + public function testExprBaseCount() + { + $selectExpr = Expr::select(); + $selectExpr->add('u.id'); + $selectExpr->add('u.username'); + + $this->assertEquals($selectExpr->count(), 2); + } + + public function testOrderByCountExpr() + { + $orderByExpr = Expr::orderBy(); + $orderByExpr->add('u.username', 'DESC'); + + $this->assertEquals($orderByExpr->count(), 1); + $this->assertEquals('u.username DESC', (string) $orderByExpr); + } + + public function testOrderByOrder() + { + $orderByExpr = Expr::orderBy('u.username', 'DESC'); + $this->assertEquals('u.username DESC', (string) $orderByExpr); + } + + public function testOrderByDefaultOrderIsAsc() + { + $orderByExpr = Expr::orderBy('u.username'); + $this->assertEquals('u.username ASC', (string) $orderByExpr); + } + + /** + * @expectedException Doctrine\Common\DoctrineException + */ + public function testAddThrowsException() + { + $orExpr = Expr::orx(); + $orExpr->add(Expr::quot(5, 2)); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index d41a7e6f2..3164ed444 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -65,6 +65,15 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals($qb->getType(), QueryBuilder::SELECT); } + public function testEmptySelectSetsType() + { + $qb = QueryBuilder::create($this->_em) + ->delete('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->select(); + + $this->assertEquals($qb->getType(), QueryBuilder::SELECT); + } + public function testDeleteSetsType() { $qb = QueryBuilder::create($this->_em) @@ -210,9 +219,10 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $qb = QueryBuilder::create($this->_em) ->select('u') ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') - ->groupBy('u.id'); + ->groupBy('u.id') + ->addGroupBy('u.username'); - $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u.id'); + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u.id, u.username'); } public function testHaving() @@ -309,6 +319,29 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals($q->getParameters(), array('username' => 'jwage', 'username2' => 'jonwage')); } + + public function testGetParameters() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :id'); + + $qb->setParameters(array('id' => 1)); + $this->assertEquals(array('id' => 1, 'test' => 1), $qb->getParameters(array('test' => 1))); + } + + public function testGetParameter() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :id'); + + $qb->setParameters(array('id' => 1)); + $this->assertEquals(1, $qb->getParameter('id')); + } + public function testMultipleWhere() { $qb = QueryBuilder::create($this->_em) @@ -353,17 +386,24 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id = :uid3) OR (u.id IN(1)))'); } - public function testLimit() + public function testGetEntityManager() + { + $qb = QueryBuilder::create($this->_em); + $this->assertEquals($this->_em, $qb->getEntityManager()); + } + + public function testInitialStateIsClean() + { + $qb = QueryBuilder::create($this->_em); + $this->assertEquals(QueryBuilder::STATE_CLEAN, $qb->getState()); + } + + public function testAlteringQueryChangesStateToDirty() { - /* - TODO: Limit fails. Is this not implemented in the DQL parser? Will look tomorrow. $qb = QueryBuilder::create($this->_em) ->select('u') - ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') - ->limit(10) - ->offset(0); + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u LIMIT 10'); - */ + $this->assertEquals(QueryBuilder::STATE_DIRTY, $qb->getState()); } } \ No newline at end of file