Changeset 6052
- Timestamp:
- 07/10/09 13:53:48 (7 months ago)
- Location:
- trunk
- Files:
-
- 3 added
- 2 removed
- 16 modified
-
lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (modified) (1 diff)
-
lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php (modified) (1 diff)
-
lib/Doctrine/DBAL/Platforms/OraclePlatform.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr.php (modified) (2 diffs)
-
lib/Doctrine/ORM/Query/Expr/Andx.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr/Base.php (modified) (2 diffs)
-
lib/Doctrine/ORM/Query/Expr/Comparison.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php (deleted)
-
lib/Doctrine/ORM/Query/Expr/Func.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr/GroupBy.php (added)
-
lib/Doctrine/ORM/Query/Expr/Math.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr/OrderBy.php (added)
-
lib/Doctrine/ORM/Query/Expr/Orx.php (modified) (1 diff)
-
lib/Doctrine/ORM/Query/Expr/Select.php (modified) (2 diffs)
-
lib/Doctrine/ORM/Query/Expr/SelectField.php (deleted)
-
lib/Doctrine/ORM/Query/SqlWalker.php (modified) (1 diff)
-
lib/Doctrine/ORM/QueryBuilder.php (modified) (11 diffs)
-
tests/Doctrine/Tests/ORM/Functional/AllTests.php (modified) (1 diff)
-
tests/Doctrine/Tests/ORM/Functional/QueryBuilderTest.php (added)
-
tests/Doctrine/Tests/ORM/Query/ExprTest.php (modified) (1 diff)
-
tests/Doctrine/Tests/ORM/QueryBuilderTest.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php
r5909 r6052 1539 1539 } 1540 1540 1541 public function modifyLimitQuery($query, $max, $first) 1542 { 1543 if ( ! is_null($first)) { 1544 $query .= ' OFFSET ' . $first; 1545 } 1546 1547 if ( ! is_null($max)) { 1548 $query .= ' LIMIT ' . $max; 1549 } 1550 1551 return $query; 1552 } 1553 1541 1554 /** 1542 1555 * Gets the SQL snippet used to declare a VARCHAR column type. -
trunk/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php
r5898 r6052 426 426 return 'mssql'; 427 427 } 428 429 /** 430 * Adds an adapter-specific LIMIT clause to the SELECT statement. 431 * 432 * @param string $query 433 * @param mixed $limit 434 * @param mixed $offset 435 * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html 436 * @return string 437 */ 438 public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) 439 { 440 if ($limit > 0) { 441 $count = intval($limit); 442 $offset = intval($offset); 443 444 if ($offset < 0) { 445 throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid"); 446 } 447 448 $orderby = stristr($query, 'ORDER BY'); 449 450 if ($orderby !== false) { 451 // Ticket #1835: Fix for ORDER BY alias 452 // Ticket #2050: Fix for multiple ORDER BY clause 453 $order = str_ireplace('ORDER BY', '', $orderby); 454 $orders = explode(',', $order); 455 456 for ($i = 0; $i < count($orders); $i++) { 457 $sorts[$i] = (stripos($orders[$i], ' desc') !== false) ? 'desc' : 'asc'; 458 $orders[$i] = trim(preg_replace('/\s+(ASC|DESC)$/i', '', $orders[$i])); 459 460 // find alias in query string 461 $helper_string = stristr($query, $orders[$i]); 462 463 $from_clause_pos = strpos($helper_string, ' FROM '); 464 $fields_string = substr($helper_string, 0, $from_clause_pos + 1); 465 466 $field_array = explode(',', $fields_string); 467 $field_array = array_shift($field_array); 468 $aux2 = spliti(' as ', $field_array); 469 470 $aliases[$i] = trim(end($aux2)); 471 } 472 } 473 474 // Ticket #1259: Fix for limit-subquery in MSSQL 475 $selectRegExp = 'SELECT\s+'; 476 $selectReplace = 'SELECT '; 477 478 if (preg_match('/^SELECT(\s+)DISTINCT/i', $query)) { 479 $selectRegExp .= 'DISTINCT\s+'; 480 $selectReplace .= 'DISTINCT '; 481 } 482 483 $query = preg_replace('/^'.$selectRegExp.'/i', $selectReplace . 'TOP ' . ($count + $offset) . ' ', $query); 484 $query = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $query . ') AS ' . $this->quoteIdentifier('inner_tbl'); 485 486 if ($orderby !== false) { 487 $query .= ' ORDER BY '; 488 489 for ($i = 0, $l = count($orders); $i < $l; $i++) { 490 if ($i > 0) { // not first order clause 491 $query .= ', '; 492 } 493 494 $query .= $this->quoteIdentifier('inner_tbl') . '.' . $aliases[$i] . ' '; 495 $query .= (stripos($sorts[$i], 'asc') !== false) ? 'DESC' : 'ASC'; 496 } 497 } 498 499 $query .= ') AS ' . $this->quoteIdentifier('outer_tbl'); 500 501 if ($orderby !== false) { 502 $query .= ' ORDER BY '; 503 504 for ($i = 0, $l = count($orders); $i < $l; $i++) { 505 if ($i > 0) { // not first order clause 506 $query .= ', '; 507 } 508 509 $query .= $this->quoteIdentifier('outer_tbl') . '.' . $aliases[$i] . ' ' . $sorts[$i]; 510 } 511 } 512 } 513 514 return $query; 515 } 428 516 } -
trunk/lib/Doctrine/DBAL/Platforms/OraclePlatform.php
r5899 r6052 470 470 return 'oracle'; 471 471 } 472 473 /** 474 * Adds an driver-specific LIMIT clause to the query 475 * 476 * @param string $query query to modify 477 * @param integer $limit limit the number of rows 478 * @param integer $offset start reading from given offset 479 * @return string the modified query 480 */ 481 public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) 482 { 483 $limit = (int) $limit; 484 $offset = (int) $offset; 485 if (preg_match('/^\s*SELECT/i', $query)) { 486 if ( ! preg_match('/\sFROM\s/i', $query)) { 487 $query .= " FROM dual"; 488 } 489 if ($limit > 0) { 490 $max = $offset + $limit; 491 $column = $column === null ? '*' : $this->quoteIdentifier($column); 492 if ($offset > 0) { 493 $min = $offset + 1; 494 $query = 'SELECT b.'.$column.' FROM ('. 495 'SELECT a.*, ROWNUM AS doctrine_rownum FROM (' 496 . $query . ') a '. 497 ') b '. 498 'WHERE doctrine_rownum BETWEEN ' . $min . ' AND ' . $max; 499 } else { 500 $query = 'SELECT a.'.$column.' FROM (' . $query .') a WHERE ROWNUM <= ' . $max; 501 } 502 } 503 } 504 return $query; 505 } 472 506 } -
trunk/lib/Doctrine/ORM/Query/Expr.php
r6048 r6052 34 34 class Expr 35 35 { 36 public static function andx( )36 public static function andx($x = null) 37 37 { 38 38 return new Expr\Andx(func_get_args()); 39 39 } 40 40 41 public static function orx( )41 public static function orx($x = null) 42 42 { 43 43 return new Expr\Orx(func_get_args()); 44 44 } 45 45 46 public static function select( )46 public static function select($select = null) 47 47 { 48 48 return new Expr\Select(func_get_args()); 49 49 } 50 50 51 public static function selectField($field, $alias = null) 52 { 53 return new Expr\SelectField($field, $alias); 51 public static function orderBy($sort = null, $order = null) 52 { 53 return new Expr\OrderBy($sort, $order); 54 } 55 56 public static function groupBy($groupBy = null) 57 { 58 return new Expr\GroupBy(func_get_args()); 59 } 60 61 public static function having($having = null) 62 { 63 return new Expr\Having(func_get_args()); 54 64 } 55 65 … … 106 116 public static function countDistinct() 107 117 { 108 return new Expr\CountDistinctFunction(func_get_args());118 return 'COUNT(DISTINCT ' . implode(', ', func_get_args()) . ')'; 109 119 } 110 120 -
trunk/lib/Doctrine/ORM/Query/Expr/Andx.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for building and clauses25 * Expression class for building DQL and parts 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> -
trunk/lib/Doctrine/ORM/Query/Expr/Base.php
r6048 r6052 23 23 24 24 /** 25 * Abstract class for building DQL expressions25 * Abstract base Expr class for building DQL parts 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> … … 69 69 } 70 70 71 public function __to string()71 public function __toString() 72 72 { 73 73 return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator; -
trunk/lib/Doctrine/ORM/Query/Expr/Comparison.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for comparison statements25 * Expression class for DQL comparison expressions 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> -
trunk/lib/Doctrine/ORM/Query/Expr/Func.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for building comparison clauses25 * Expression class for generating DQL functions 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> -
trunk/lib/Doctrine/ORM/Query/Expr/Math.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for math statements25 * Expression class for DQL math statements 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> -
trunk/lib/Doctrine/ORM/Query/Expr/Orx.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for building andclauses25 * Expression class for building DQL OR clauses 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> -
trunk/lib/Doctrine/ORM/Query/Expr/Select.php
r6048 r6052 23 23 24 24 /** 25 * Expression class for building DQL select clauses25 * Expression class for building DQL select statements 26 26 * 27 27 * @author Jonathan H. Wage <jonwage@gmail.com> … … 36 36 protected $_preSeparator = ''; 37 37 protected $_postSeparator = ''; 38 protected $_allowedClasses = array(39 'Doctrine\ORM\Query\Expr\SelectField'40 );41 38 } -
trunk/lib/Doctrine/ORM/Query/SqlWalker.php
r5973 r6052 135 135 $sql .= $AST->getOrderByClause() ? $this->walkOrderByClause($AST->getOrderByClause()) : ''; 136 136 137 $q = $this->getQuery(); 138 $sql = $this->getConnection()->getDatabasePlatform() 139 ->modifyLimitQuery($sql, $q->getMaxResults(), $q->getFirstResult()); 140 137 141 return $sql; 138 142 } -
trunk/lib/Doctrine/ORM/QueryBuilder.php
r6048 r6052 50 50 * @var EntityManager $em Instance of an EntityManager to use for query 51 51 */ 52 pr otected$_em;52 private $_em; 53 53 54 54 /** 55 55 * @var array $dqlParts The array of DQL parts collected 56 56 */ 57 pr otected$_dqlParts = array(57 private $_dqlParts = array( 58 58 'select' => array(), 59 59 'from' => array(), … … 61 61 'groupBy' => array(), 62 62 'having' => array(), 63 'orderBy' => array(), 64 'limit' => array(), 65 'offset' => array() 63 'orderBy' => array() 66 64 ); 67 65 … … 69 67 * @var integer $type The type of query this is. Can be select, update or delete 70 68 */ 71 pr otected$_type = self::SELECT;69 private $_type = self::SELECT; 72 70 73 71 /** 74 72 * @var integer $state The state of the query object. Can be dirty or clean. 75 73 */ 76 pr otected$_state = self::STATE_CLEAN;74 private $_state = self::STATE_CLEAN; 77 75 78 76 /** 79 77 * @var string $dql The complete DQL string for this query 80 78 */ 81 pr otected$_dql;79 private $_dql; 82 80 83 81 /** 84 82 * @var array $params Parameters of this query. 85 83 */ 86 protected $_params = array(); 84 private $_params = array(); 85 86 /** 87 * @var integer The first result to return (the "offset"). 88 */ 89 private $_firstResult = null; 90 91 /** 92 * @var integer The maximum number of results to return (the "limit"). 93 */ 94 private $_maxResults = null; 87 95 88 96 public function __construct(EntityManager $entityManager) … … 144 152 $q->setDql($this->getDql()); 145 153 $q->setParameters($this->getParameters()); 154 $q->setFirstResult($this->getFirstResult()); 155 $q->setMaxResults($this->getMaxResults()); 146 156 147 157 return $q; … … 225 235 } 226 236 227 public function select($select )237 public function select($select = null) 228 238 { 229 239 $selects = func_get_args(); … … 350 360 public function groupBy($groupBy) 351 361 { 352 return $this->add('groupBy', $groupBy, false); 362 return $this->add('groupBy', Expr::groupBy($groupBy), false); 363 } 364 365 public function addGroupBy($groupBy) 366 { 367 return $this->add('groupBy', Expr::groupBy($groupBy), true); 353 368 } 354 369 355 370 public function having($having) 356 371 { 357 return $this->add('having', $having, false);372 return $this->add('having', Expr::having($having), false); 358 373 } 359 374 … … 364 379 } 365 380 366 return $this->add('having', $having, true);381 return $this->add('having', Expr::having($having), true); 367 382 } 368 383 … … 373 388 } 374 389 375 return $this->add('having', $having, true);390 return $this->add('having', Expr::having($having), true); 376 391 } 377 392 378 393 public function orderBy($sort, $order) 379 394 { 380 return $this->add('orderBy', $sort . ' ' . $order, false);395 return $this->add('orderBy', Expr::orderBy($sort, $order), false); 381 396 } 382 397 383 398 public function addOrderBy($sort, $order) 384 399 { 385 return $this->add('orderBy', $sort . ' ' . $order, true); 386 } 387 388 public function limit($limit) 389 { 390 return $this->add('limit', $limit); 391 } 392 393 public function offset($offset) 394 { 395 return $this->add('offset', $offset); 400 return $this->add('orderBy', Expr::orderBy($sort, $order), true); 401 } 402 403 /** 404 * Sets the position of the first result to retrieve (the "offset"). 405 * 406 * @param integer $firstResult The first result to return. 407 * @return Query This query object. 408 */ 409 public function setFirstResult($firstResult) 410 { 411 $this->_firstResult = $firstResult; 412 return $this; 413 } 414 415 /** 416 * Gets the position of the first result the query object was set to retrieve (the "offset"). 417 * Returns NULL if {@link setFirstResult} was not applied to this query. 418 * 419 * @return integer The position of the first result. 420 */ 421 public function getFirstResult() 422 { 423 return $this->_firstResult; 424 } 425 426 /** 427 * Sets the maximum number of results to retrieve (the "limit"). 428 * 429 * @param integer $maxResults 430 * @return Query This query object. 431 */ 432 public function setMaxResults($maxResults) 433 { 434 $this->_maxResults = $maxResults; 435 return $this; 436 } 437 438 /** 439 * Gets the maximum number of results the query object was set to retrieve (the "limit"). 440 * Returns NULL if {@link setMaxResults} was not applied to this query. 441 * 442 * @return integer Maximum number of results. 443 */ 444 public function getMaxResults() 445 { 446 return $this->_maxResults; 396 447 } 397 448 … … 415 466 . $this->_getReducedDqlQueryPart('from', array('pre' => ' ', 'separator' => ' ')) 416 467 . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) 417 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) 418 . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) 419 . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); 468 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); 420 469 } 421 470 … … 440 489 . $this->_getReducedDqlQueryPart('set', array('pre' => ' SET ', 'separator' => ', ')) 441 490 . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) 442 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) 443 . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) 444 . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); 491 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); 445 492 } 446 493 … … 470 517 . $this->_getReducedDqlQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', ')) 471 518 . $this->_getReducedDqlQueryPart('having', array('pre' => ' HAVING ', 'separator' => ' ')) 472 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')) 473 . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) 474 . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); 519 . $this->_getReducedDqlQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); 475 520 } 476 521 -
trunk/tests/Doctrine/Tests/ORM/Functional/AllTests.php
r6007 r6052 35 35 $suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManySelfReferentialAssociationTest'); 36 36 $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest'); 37 $suite->addTestSuite('Doctrine\Tests\ORM\Functional\QueryBuilderTest'); 37 38 38 39 return $suite; -
trunk/tests/Doctrine/Tests/ORM/Query/ExprTest.php
r6048 r6052 246 246 { 247 247 $selectExpr = Expr::select(); 248 $selectExpr->add(Expr::selectField('u.id')); 249 $selectExpr->add(Expr::selectField('u.username', 'my_test')); 250 251 $this->assertEquals('u.id, u.username AS my_test', (string) $selectExpr); 248 $selectExpr->add('u.id'); 249 $selectExpr->add('u.username'); 250 251 $this->assertEquals('u.id, u.username', (string) $selectExpr); 252 } 253 254 public function testExprBaseCount() 255 { 256 $selectExpr = Expr::select(); 257 $selectExpr->add('u.id'); 258 $selectExpr->add('u.username'); 259 260 $this->assertEquals($selectExpr->count(), 2); 261 } 262 263 public function testOrderByCountExpr() 264 { 265 $orderByExpr = Expr::orderBy(); 266 $orderByExpr->add('u.username', 'DESC'); 267 268 $this->assertEquals($orderByExpr->count(), 1); 269 $this->assertEquals('u.username DESC', (string) $orderByExpr); 270 } 271 272 public function testOrderByOrder() 273 { 274 $orderByExpr = Expr::orderBy('u.username', 'DESC'); 275 $this->assertEquals('u.username DESC', (string) $orderByExpr); 276 } 277 278 public function testOrderByDefaultOrderIsAsc() 279 { 280 $orderByExpr = Expr::orderBy('u.username'); 281 $this->assertEquals('u.username ASC', (string) $orderByExpr); 282 } 283 284 /** 285 * @expectedException Doctrine\Common\DoctrineException 286 */ 287 public function testAddThrowsException() 288 { 289 $orExpr = Expr::orx(); 290 $orExpr->add(Expr::quot(5, 2)); 252 291 } 253 292 } -
trunk/tests/Doctrine/Tests/ORM/QueryBuilderTest.php
r6048 r6052 66 66 } 67 67 68 public function testEmptySelectSetsType() 69 { 70 $qb = QueryBuilder::create($this->_em) 71 ->delete('Doctrine\Tests\Models\CMS\CmsUser', 'u') 72 ->select(); 73 74 $this->assertEquals($qb->getType(), QueryBuilder::SELECT); 75 } 76 68 77 public function testDeleteSetsType() 69 78 { … … 211 220 ->select('u') 212 221 ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') 213 ->groupBy('u.id'); 214 215 $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u.id'); 222 ->groupBy('u.id') 223 ->addGroupBy('u.username'); 224 225 $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u.id, u.username'); 216 226 } 217 227 … … 310 320 } 311 321 322 323 public function testGetParameters() 324 { 325 $qb = QueryBuilder::create($this->_em) 326 ->select('u') 327 ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') 328 ->where('u.id = :id'); 329 330 $qb->setParameters(array('id' => 1)); 331 $this->assertEquals(array('id' => 1, 'test' => 1), $qb->getParameters(array('test' => 1))); 332 } 333 334 public function testGetParameter() 335 { 336 $qb = QueryBuilder::create($this->_em) 337 ->select('u') 338 ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') 339 ->where('u.id = :id'); 340 341 $qb->setParameters(array('id' => 1)); 342 $this->assertEquals(1, $qb->getParameter('id')); 343 } 344 312 345 public function testMultipleWhere() 313 346 { … … 354 387 } 355 388 356 public function testLimit() 357 { 358 /* 359 TODO: Limit fails. Is this not implemented in the DQL parser? Will look tomorrow. 360 $qb = QueryBuilder::create($this->_em) 361 ->select('u') 362 ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') 363 ->limit(10) 364 ->offset(0); 365 366 $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u LIMIT 10'); 367 */ 389 public function testGetEntityManager() 390 { 391 $qb = QueryBuilder::create($this->_em); 392 $this->assertEquals($this->_em, $qb->getEntityManager()); 393 } 394 395 public function testInitialStateIsClean() 396 { 397 $qb = QueryBuilder::create($this->_em); 398 $this->assertEquals(QueryBuilder::STATE_CLEAN, $qb->getState()); 399 } 400 401 public function testAlteringQueryChangesStateToDirty() 402 { 403 $qb = QueryBuilder::create($this->_em) 404 ->select('u') 405 ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); 406 407 $this->assertEquals(QueryBuilder::STATE_DIRTY, $qb->getState()); 368 408 } 369 409 }