From 312d347d2de335cbbd809a54f4c3ee2335f43e6f Mon Sep 17 00:00:00 2001 From: jwage Date: Fri, 10 Jul 2009 14:02:06 +0000 Subject: [PATCH] [2.0] More work on the QueryBuilder and Expr classes --- lib/Doctrine/ORM/EntityManager.php | 2 +- lib/Doctrine/ORM/Query/Expr.php | 619 +++++------------- lib/Doctrine/ORM/Query/Expr/Andx.php | 42 ++ lib/Doctrine/ORM/Query/Expr/Base.php | 75 +++ lib/Doctrine/ORM/Query/Expr/Comparison.php | 50 ++ .../ORM/Query/Expr/CountDistinctFunction.php | 46 ++ lib/Doctrine/ORM/Query/Expr/Func.php | 48 ++ lib/Doctrine/ORM/Query/Expr/Math.php | 50 ++ lib/Doctrine/ORM/Query/Expr/Orx.php | 42 ++ lib/Doctrine/ORM/Query/Expr/Select.php | 41 ++ lib/Doctrine/ORM/Query/Expr/SelectField.php | 49 ++ lib/Doctrine/ORM/QueryBuilder.php | 95 ++- tests/Doctrine/Tests/ORM/Query/ExprTest.php | 78 +-- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 122 +++- 14 files changed, 799 insertions(+), 560 deletions(-) create mode 100644 lib/Doctrine/ORM/Query/Expr/Andx.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Base.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Comparison.php create mode 100644 lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Func.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Math.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Orx.php create mode 100644 lib/Doctrine/ORM/Query/Expr/Select.php create mode 100644 lib/Doctrine/ORM/Query/Expr/SelectField.php diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 862db24f2..0f35ee869 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -226,7 +226,7 @@ class EntityManager } return $query; } - + /** * Creates a DQL query with the specified name. * diff --git a/lib/Doctrine/ORM/Query/Expr.php b/lib/Doctrine/ORM/Query/Expr.php index e6923d180..35b702353 100644 --- a/lib/Doctrine/ORM/Query/Expr.php +++ b/lib/Doctrine/ORM/Query/Expr.php @@ -33,508 +33,197 @@ namespace Doctrine\ORM\Query; */ class Expr { - private static $_methodMap = array( - 'avg' => '_avgExpr', - 'max' => '_maxExpr', - 'min' => '_minExpr', - 'count' => '_countExpr', - 'countDistinct' => '_countDistinctExpr', - 'exists' => '_existsExpr', - 'all' => '_allExpr', - 'some' => '_someExpr', - 'any' => '_anyExpr', - 'not' => '_notExpr', - 'and' => '_andExpr', - 'or' => '_orExpr', - 'abs' => '_absExpr', - 'prod' => '_prodExpr', - 'diff' => '_diffExpr', - 'sum' => '_sumExpr', - 'quot' => '_quotientExpr', - 'sqrt' => '_squareRootExpr', - 'eq' => '_equalExpr', - 'in' => '_inExpr', - 'notIn' => '_notInExpr', - 'notEqual' => '_notEqualExpr', - 'like' => '_likeExpr', - 'concat' => '_concatExpr', - 'substr' => '_substrExpr', - 'lower' => '_lowerExpr', - 'upper' => '_upperExpr', - 'length' => '_lengthExpr', - 'gt' => '_greaterThanExpr', - 'lt' => '_lessThanExpr', - 'path' => '_pathExpr', - 'literal' => '_literalExpr', - 'gtoet' => '_greaterThanOrEqualToExpr', - 'ltoet' => '_lessThanOrEqualToExpr', - 'between' => '_betweenExpr', - 'trim' => '_trimExpr', - 'on' => '_onExpr', - 'with' => '_withExpr', - 'from' => '_fromExpr', - 'innerJoin' => '_innerJoinExpr', - 'leftJoin' => '_leftJoinExpr' - ); - - private $_type; - private $_parts; - - protected function __construct($type, array $parts) + public static function andx() { - $this->_type = $type; - $this->_parts = $parts; + return new Expr\Andx(func_get_args()); } - public function getDql() + public static function orx() { - return $this->{self::$_methodMap[$this->_type]}(); + return new Expr\Orx(func_get_args()); } - public function __toString() + public static function select() { - return $this->getDql(); + return new Expr\Select(func_get_args()); } - private function _avgExpr() + public static function selectField($field, $alias = null) { - return 'AVG(' . $this->_parts[0] . ')'; - } - - private function _maxExpr() - { - return 'MAX(' . $this->_parts[0] . ')'; - } - - private function _minExpr() - { - return 'MIN(' . $this->_parts[0] . ')'; - } - - private function _countExpr() - { - return 'COUNT(' . $this->_parts[0] . ')'; - } - - private function _countDistinctExpr() - { - return 'COUNT(DISTINCT ' . $this->_parts[0] . ')'; - } - - private function _existsExpr() - { - return 'EXISTS(' . $this->_parts[0] . ')'; - } - - private function _allExpr() - { - return 'ALL(' . $this->_parts[0] . ')'; - } - - private function _someExpr() - { - return 'SOME(' . $this->_parts[0] . ')'; - } - - private function _anyExpr() - { - return 'ANY(' . $this->_parts[0] . ')'; - } - - private function _notExpr() - { - return 'NOT(' . $this->_parts[0] . ')'; - } - - private function _andExpr() - { - return '(' . $this->_parts[0] . ' AND ' . $this->_parts[1] . ')'; - } - - private function _orExpr() - { - return '(' . $this->_parts[0] . ' OR ' . $this->_parts[1] . ')'; - } - - private function _absExpr() - { - return 'ABS(' . $this->_parts[0] . ')'; - } - - private function _prodExpr() - { - return '(' . $this->_parts[0] . ' * ' . $this->_parts[1] . ')'; - } - - private function _diffExpr() - { - return '(' . $this->_parts[0] . ' - ' . $this->_parts[1] . ')'; - } - - private function _sumExpr() - { - return '(' . $this->_parts[0] . ' + ' . $this->_parts[1] . ')'; - } - - private function _quotientExpr() - { - return '(' . $this->_parts[0] . ' / ' . $this->_parts[1] . ')'; - } - - private function _squareRootExpr() - { - return 'SQRT(' . $this->_parts[0] . ')'; - } - - private function _equalExpr() - { - return $this->_parts[0] . ' = ' . $this->_parts[1]; - } - - private function _inExpr() - { - return $this->_parts[0] . ' IN(' . implode(', ', $this->_parts[1]) . ')'; - } - - private function _notInExpr() - { - return $this->_parts[0] . ' NOT IN(' . implode(', ', $this->_parts[1]) . ')'; - } - - private function _notEqualExpr() - { - return $this->_parts[0] . ' != ' . $this->_parts[1]; - } - - private function _likeExpr() - { - // TODO: How should we use $escapeChar which is in $this->_parts[2] - return '(' . $this->_parts[0] . ' LIKE ' . $this->_parts[1] . ')'; - } - - private function _concatExpr() - { - return 'CONCAT(' . $this->_parts[0] . ', ' . $this->_parts[1] . ')'; - } - - private function _substrExpr() - { - return 'SUBSTR(' . $this->_parts[0] . ', ' . $this->_parts[1] . ', ' . $this->_parts[2] . ')'; - } - - private function _lowerExpr() - { - return 'LOWER(' . $this->_parts[0] . ')'; - } - - private function _upperExpr() - { - return 'UPPER(' . $this->_parts[0] . ')'; - } - - private function _lengthExpr() - { - return 'LENGTH(' . $this->_parts[0] . ')'; - } - - private function _greaterThanExpr() - { - return $this->_parts[0] . ' > ' . $this->_parts[1]; - } - - private function _lessThanExpr() - { - return $this->_parts[0] . ' < ' . $this->_parts[1]; - } - - private function _pathExpr() - { - // TODO: What is this? - } - - private function _literalExpr() - { - if (is_numeric($this->_parts[0])) { - return (string) $this->_parts[0]; - } else { - return "'" . $this->_parts[0] . "'"; - } - } - - private function _greaterThanOrEqualToExpr() - { - return $this->_parts[0] . ' >= ' . $this->_parts[1]; - } - - private function _lessThanOrEqualToExpr() - { - return $this->_parts[0] . ' <= ' . $this->_parts[1]; - } - - private function _betweenExpr() - { - return 'BETWEEN(' . $this->_parts[0] . ', ' . $this->_parts[1] . ', ' . $this->_parts[2] . ')'; - } - - private function _ltExpr() - { - return '(' . $this->_parts[0] . ' < ' . $this->_parts[1] . ')'; - } - - private function _trimExpr() - { - return 'TRIM(' . $this->_parts[0] . ')'; - } - - private function _onExpr() - { - return 'ON ' . $this->_parts[0]; - } - - private function _withExpr() - { - return 'WITH ' . $this->_parts[0]; - } - - private function _fromExpr() - { - return $this->_parts[0] . ' ' . $this->_parts[1]; - } - - private function _leftJoinExpr() - { - return 'LEFT JOIN ' . $this->_parts[0] . '.' . $this->_parts[1] . ' ' - . $this->_parts[2] . (isset($this->_parts[3]) ? ' ' . $this->_parts[3] : null); - } - - private function _innerJoinExpr() - { - return 'INNER JOIN ' . $this->_parts[0] . '.' . $this->_parts[1] . ' ' - . $this->_parts[2] . (isset($this->_parts[3]) ? ' ' . $this->_parts[3] : null); - } - - public static function avg($x) - { - return new self('avg', array($x)); - } - - public static function max($x) - { - return new self('max', array($x)); - } - - public static function min($x) - { - return new self('min', array($x)); - } - - public static function count($x) - { - return new self('count', array($x)); - } - - public static function countDistinct($x) - { - return new self('countDistinct', array($x)); - } - - public static function exists($subquery) - { - return new self('exists', array($subquery)); - } - - public static function all($subquery) - { - return new self('all', array($subquery)); - } - - public static function some($subquery) - { - return new self('some', array($subquery)); - } - - public static function any($subquery) - { - return new self('any', array($subquery)); - } - - public static function not($restriction) - { - return new self('not', array($restriction)); - } - - public static function andx($x, $y) - { - return new self('and', array($x, $y)); - } - - public static function orx($x, $y) - { - return new self('or', array($x, $y)); - } - - public static function abs($x) - { - return new self('abs', array($x)); - } - - public static function prod($x, $y) - { - return new self('prod', array($x, $y)); - } - - public static function diff($x, $y) - { - return new self('diff', array($x, $y)); - } - - public static function sum($x, $y) - { - return new self('sum', array($x, $y)); - } - - public static function quot($x, $y) - { - return new self('quot', array($x, $y)); - } - - public static function sqrt($x) - { - return new self('sqrt', array($x)); + return new Expr\SelectField($field, $alias); } public static function eq($x, $y) { - return new self('eq', array($x, $y)); + return new Expr\Comparison($x, '=', $y); } - public static function in($x, $y) + public static function neq($x, $y) { - return new self('in', array($x, $y)); - } - - public static function notIn($x, $y) - { - return new self('notIn', array($x, $y)); - } - - public static function notEqual($x, $y) - { - return new self('notEqual', array($x, $y)); - } - - public static function like($x, $pattern, $escapeChar = null) - { - return new self('like', array($x, $pattern, $escapeChar)); - } - - public static function concat($x, $y) - { - return new self('concat', array($x, $y)); - } - - public static function substr($x, $from = null, $len = null) - { - return new self('substr', array($x, $from, $len)); - } - - public static function lower($x) - { - return new self('lower', array($x)); - } - - public static function upper($x) - { - return new self('upper', array($x)); - } - - public static function length($x) - { - return new self('length', array($x)); - } - - public static function gt($x, $y) - { - return new self('gt', array($x, $y)); - } - - public static function greaterThan($x, $y) - { - return new self('gt', array($x, $y)); + return new Expr\Comparison($x, '<>', $y); } public static function lt($x, $y) { - return new self('lt', array($x, $y)); + return new Expr\Comparison($x, '<', $y); } - public static function lessThan($x, $y) + public static function lte($x, $y) { - return new self('lt', array($x, $y)); + return new Expr\Comparison($x, '<=', $y); } - public static function path($path) + public static function gt($x, $y) { - return new self('path', array($path)); + return new Expr\Comparison($x, '>', $y); + } + + public static function gte($x, $y) + { + return new Expr\Comparison($x, '>=', $y); + } + + public static function avg($x) + { + return new Expr\Func('AVG', array($x)); + } + + public static function max($x) + { + return new Expr\Func('MAX', array($x)); + } + + public static function min($x) + { + return new Expr\Func('MIN', array($x)); + } + + public static function count($x) + { + return new Expr\Func('COUNT', array($x)); + } + + public static function countDistinct() + { + return new Expr\CountDistinctFunction(func_get_args()); + } + + public static function exists($subquery) + { + return new Expr\Func('EXISTS', array($subquery)); + } + + public static function all($subquery) + { + return new Expr\Func('ALL', array($subquery)); + } + + public static function some($subquery) + { + return new Expr\Func('SOME', array($subquery)); + } + + public static function any($subquery) + { + return new Expr\Func('ANY', array($subquery)); + } + + public static function not($restriction) + { + return new Expr\Func('NOT', array($restriction)); + } + + public static function abs($x) + { + return new Expr\Func('ABS', array($x)); + } + + public static function prod($x, $y) + { + return new Expr\Math($x, '*', $y); + } + + public static function diff($x, $y) + { + return new Expr\Math($x, '-', $y); + } + + public static function sum($x, $y) + { + return new Expr\Math($x, '+', $y); + } + + public static function quot($x, $y) + { + return new Expr\Math($x, '/', $y); + } + + public static function sqrt($x) + { + return new Expr\Func('SQRT', array($x)); + } + + public static function in($x, $y) + { + return new Expr\Func($x . ' IN', (array) $y); + } + + public static function notIn($x, $y) + { + return new Expr\Func($x . ' NOT IN', (array) $y); + } + + public static function notEqual($x, $y) + { + return new Expr\Comparison($x, '!=', $y); + } + + public static function like($x, $y) + { + return new Expr\Math($x, 'LIKE', $y); + } + + public static function concat($x, $y) + { + return new Expr\Func('CONCAT', array($x, $y)); + } + + public static function substr($x, $from, $len) + { + return new Expr\Func('SUBSTR', array($x, $from, $len)); + } + + public static function lower($x) + { + return new Expr\Func('LOWER', array($x)); + } + + public static function upper($x) + { + return new Expr\Func('UPPER', array($x)); + } + + public static function length($x) + { + return new Expr\Func('LENGTH', array($x)); } public static function literal($literal) { - return new self('literal', array($literal)); - } - - public static function greaterThanOrEqualTo($x, $y) - { - return new self('gtoet', array($x, $y)); - } - - public static function gtoet($x, $y) - { - return new self('gtoet', array($x, $y)); - } - - public static function lessThanOrEqualTo($x, $y) - { - return new self('ltoet', array($x, $y)); - } - - public static function ltoet($x, $y) - { - return new self('ltoet', array($x, $y)); + if (is_numeric($literal)) { + return (string) $literal; + } else { + return "'" . $literal . "'"; + } } public static function between($val, $x, $y) { - return new self('between', array($val, $x, $y)); + return new Expr\Func('BETWEEN', array($val, $x, $y)); } - public static function trim($val, $spec = null, $char = null) + public static function trim() { - return new self('trim', array($val, $spec, $char)); - } - - public static function on($x) - { - return new self('on', array($x)); - } - - public static function with($x) - { - return new self('with', array($x)); - } - - public static function from($from, $alias) - { - return new self('from', array($from, $alias)); - } - - public static function leftJoin($parentAlias, $join, $alias, $condition = null) - { - return new self('leftJoin', array($parentAlias, $join, $alias, $condition)); - } - - public static function innerJoin($parentAlias, $join, $alias, $condition = null) - { - return new self('innerJoin', array($parentAlias, $join, $alias, $condition)); + return new Expr\Func('TRIM', func_get_args()); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Andx.php b/lib/Doctrine/ORM/Query/Expr/Andx.php new file mode 100644 index 000000000..3bf8e100a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Andx.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for building and clauses + * + * @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 Andx extends Base +{ + protected $_separator = ') AND ('; + protected $_allowedClasses = array( + 'Doctrine\ORM\Query\Expr\Comparison', + 'Doctrine\ORM\Query\Expr\Orx', + 'Doctrine\ORM\Query\Expr\Func' + ); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php new file mode 100644 index 000000000..0a5e6a10a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Abstract class for building DQL expressions + * + * @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$ + */ +abstract class Base +{ + protected $_preSeparator = '('; + protected $_separator = ', '; + protected $_postSeparator = ')'; + protected $_allowedClasses = array(); + + private $_parts = array(); + + public function __construct($args = array()) + { + foreach ($args as $arg) { + $this->add($arg); + } + } + + public function add($arg) + { + if ( ! empty($arg) || ($arg instanceof self && $arg->count() > 0)) { + // If we decide to keep Expr\Base instances, we can use this check + if ( ! is_string($arg)) { + $class = get_class($arg); + + if ( ! in_array($class, $this->_allowedClasses)) { + throw \Doctrine\Common\DoctrineException::updateMe("Class '{$class}' is not allowed in " . get_class($this) . " instance."); + } + } + + $this->_parts[] = $arg; + } + } + + 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/Comparison.php b/lib/Doctrine/ORM/Query/Expr/Comparison.php new file mode 100644 index 000000000..2d499caeb --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Comparison.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for comparison statements + * + * @author Jonathan H. Wage + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Comparison +{ + private $_leftExpr; + private $_operator; + private $_rightExpr; + + public function __construct($leftExpr, $operator, $rightExpr) + { + $this->_leftExpr = $leftExpr; + $this->_operator = $operator; + $this->_rightExpr = $rightExpr; + } + + public function __toString() + { + return $this->_leftExpr . ' ' . $this->_operator . ' ' . $this->_rightExpr; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php b/lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php new file mode 100644 index 000000000..9de148ee7 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/CountDistinctFunction.php @@ -0,0 +1,46 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for building comparison clauses + * + * @author Jonathan H. Wage + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class CountDistinctFunction +{ + private $_arguments; + + public function __construct($arguments) + { + $this->_arguments = $arguments; + } + + public function __toString() + { + return 'COUNT(DISTINCT ' . implode(', ', $this->_arguments) . ')'; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Func.php b/lib/Doctrine/ORM/Query/Expr/Func.php new file mode 100644 index 000000000..3c5fa4f22 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Func.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for building comparison clauses + * + * @author Jonathan H. Wage + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Func +{ + private $_name; + private $_arguments; + + public function __construct($name, $arguments) + { + $this->_name = $name; + $this->_arguments = $arguments; + } + + public function __toString() + { + return $this->_name . '(' . implode(', ', $this->_arguments) . ')'; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Math.php b/lib/Doctrine/ORM/Query/Expr/Math.php new file mode 100644 index 000000000..7d2e82276 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Math.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for math statements + * + * @author Jonathan H. Wage + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Math +{ + private $_leftExpr; + private $_operator; + private $_rightExpr; + + public function __construct($leftExpr, $operator, $rightExpr) + { + $this->_leftExpr = $leftExpr; + $this->_operator = $operator; + $this->_rightExpr = $rightExpr; + } + + public function __toString() + { + return $this->_leftExpr . ' ' . $this->_operator . ' ' . $this->_rightExpr; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Orx.php b/lib/Doctrine/ORM/Query/Expr/Orx.php new file mode 100644 index 000000000..c04855590 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Orx.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for building and clauses + * + * @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 Orx extends Base +{ + protected $_separator = ') OR ('; + protected $_allowedClasses = array( + 'Doctrine\ORM\Query\Expr\Comparison', + 'Doctrine\ORM\Query\Expr\Andx', + 'Doctrine\ORM\Query\Expr\Func' + ); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Select.php b/lib/Doctrine/ORM/Query/Expr/Select.php new file mode 100644 index 000000000..a770c98e9 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/Select.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * Expression class for building DQL select clauses + * + * @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 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/Expr/SelectField.php b/lib/Doctrine/ORM/Query/Expr/SelectField.php new file mode 100644 index 000000000..af69a47e6 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Expr/SelectField.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\ORM\Query\Expr; + +/** + * This class is used for representing field in a select statement + * + * @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 SelectField +{ + private $_field; + private $_alias; + + public function __construct($field, $alias = null) + { + $this->_field = $field; + $this->_alias = $alias; + } + + public function __toString() + { + return $this->_field . (($this->_alias !== null) ? ' AS ' . $this->_alias : ''); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 4a394c4e3..539ce802f 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -224,7 +224,7 @@ class QueryBuilder return $this; } - public function select() + public function select($select) { $selects = func_get_args(); $this->_type = self::SELECT; @@ -233,7 +233,8 @@ class QueryBuilder return $this; } - return $this->add('select', implode(', ', $selects), true); + $select = call_user_func_array(array('Doctrine\ORM\Query\Expr', 'select'), $selects); + return $this->add('select', $select, true); } public function delete($delete = null, $alias = null) @@ -244,7 +245,7 @@ class QueryBuilder return $this; } - return $this->add('from', Expr::from($delete, $alias)); + return $this->add('from', $delete . ' ' . $alias); } public function update($update = null, $alias = null) @@ -255,7 +256,7 @@ class QueryBuilder return $this; } - return $this->add('from', Expr::from($update, $alias)); + return $this->add('from', $update . ' ' . $alias); } public function set($key, $value) @@ -265,24 +266,87 @@ class QueryBuilder public function from($from, $alias) { - return $this->add('from', Expr::from($from, $alias), true); + return $this->add('from', $from . ' ' . $alias, true); } public function innerJoin($parentAlias, $join, $alias, $condition = null) { - return $this->add('from', Expr::innerJoin($parentAlias, $join, $alias, $condition), true); + $join = 'INNER JOIN ' . $parentAlias . '.' . $join . ' ' + . $alias . (isset($condition) ? ' ' . $condition : null); + + return $this->add('from', $join, true); } public function leftJoin($parentAlias, $join, $alias, $condition = null) { - return $this->add('from', Expr::leftJoin($parentAlias, $join, $alias, $condition), true); + $join = 'LEFT JOIN ' . $parentAlias . '.' . $join . ' ' + . $alias . (isset($condition) ? ' ' . $condition : null); + + return $this->add('from', $join, true); } public function where($where) { + $where = call_user_func_array(array('Doctrine\ORM\Query\Expr', 'andx'), func_get_args()); return $this->add('where', $where, false); } + public function andWhere($where) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'AND', true); + } + + $where = call_user_func_array(array('Doctrine\ORM\Query\Expr', 'andx'), func_get_args()); + return $this->add('where', $where, true); + } + + public function orWhere($where) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'OR', true); + } + + $where = call_user_func_array(array('Doctrine\ORM\Query\Expr', 'orx'), func_get_args()); + return $this->add('where', $where, true); + } + + public function andWhereIn($expr, $params) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'AND', true); + } + + return $this->add('where', Expr::in($expr, $params), true); + } + + public function orWhereIn($expr, $params = array(), $not = false) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'OR', true); + } + + return $this->add('where', Expr::in($expr, $params), true); + } + + public function andWhereNotIn($expr, $params = array()) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'AND', true); + } + + return $this->add('where', Expr::notIn($expr, $params), true); + } + + public function orWhereNotIn($expr, $params = array()) + { + if (count($this->_getDqlQueryPart('where')) > 0) { + $this->add('where', 'OR', true); + } + + return $this->add('where', Expr::notIn($expr, $params), true); + } + public function groupBy($groupBy) { return $this->add('groupBy', $groupBy, false); @@ -427,21 +491,4 @@ class QueryBuilder { return $this->_dqlParts[$queryPartName]; } - - /** - * Proxy method calls to the Expr class to ease the syntax of the query builder - * - * @param string $method The method name called - * @param string $arguments The arguments passed to the method - * @return void - * @throws \Doctrine\Common\DoctrineException Throws exception if method could not be proxied - */ - public function __call($method, $arguments) - { - $class = 'Doctrine\ORM\Query\Expr'; - if (method_exists($class, $method)) { - return call_user_func_array(array('Doctrine\ORM\Query\Expr', $method), $arguments); - } - throw \Doctrine\Common\DoctrineException::notImplemented($method, get_class($this)); - } } \ 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 c04ad7158..6eeb301fc 100644 --- a/tests/Doctrine/Tests/ORM/Query/ExprTest.php +++ b/tests/Doctrine/Tests/ORM/Query/ExprTest.php @@ -97,12 +97,12 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testAndExpr() { - $this->assertEquals('(1 = 1 AND 2 = 2)', (string) Expr::andx((string) Expr::eq(1, 1), (string) Expr::eq(2, 2))); + $this->assertEquals('(1 = 1) AND (2 = 2)', (string) Expr::andx((string) Expr::eq(1, 1), (string) Expr::eq(2, 2))); } public function testOrExpr() { - $this->assertEquals('(1 = 1 OR 2 = 2)', (string) Expr::orx((string) Expr::eq(1, 1), (string) Expr::eq(2, 2))); + $this->assertEquals('(1 = 1) OR (2 = 2)', (string) Expr::orx((string) Expr::eq(1, 1), (string) Expr::eq(2, 2))); } public function testAbsExpr() @@ -112,22 +112,22 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testProdExpr() { - $this->assertEquals('(1 * 2)', (string) Expr::prod(1, 2)); + $this->assertEquals('1 * 2', (string) Expr::prod(1, 2)); } public function testDiffExpr() { - $this->assertEquals('(1 - 2)', (string) Expr::diff(1, 2)); + $this->assertEquals('1 - 2', (string) Expr::diff(1, 2)); } public function testSumExpr() { - $this->assertEquals('(1 + 2)', (string) Expr::sum(1, 2)); + $this->assertEquals('1 + 2', (string) Expr::sum(1, 2)); } public function testQuotientExpr() { - $this->assertEquals('(10 / 2)', (string) Expr::quot(10, 2)); + $this->assertEquals('10 / 2', (string) Expr::quot(10, 2)); } public function testSquareRootExpr() @@ -147,7 +147,7 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testLikeExpr() { - $this->assertEquals('(a.description LIKE :description)', (string) Expr::like('a.description', ':description')); + $this->assertEquals('a.description LIKE :description', (string) Expr::like('a.description', ':description')); } public function testConcatExpr() @@ -178,18 +178,11 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testGreaterThanExpr() { $this->assertEquals('5 > 2', (string) Expr::gt(5, 2)); - $this->assertEquals('5 > 2', (string) Expr::greaterThan(5, 2)); } public function testLessThanExpr() { $this->assertEquals('2 < 5', (string) Expr::lt(2, 5)); - $this->assertEquals('2 < 5', (string) Expr::lessThan(2, 5)); - } - - public function testPathExpr() - { - // TODO: This functionality still needs to be written and tested } public function testStringLiteralExpr() @@ -204,14 +197,12 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase public function testGreaterThanOrEqualToExpr() { - $this->assertEquals('5 >= 2', (string) Expr::gtoet(5, 2)); - $this->assertEquals('5 >= 2', (string) Expr::greaterThanOrEqualTo(5, 2)); + $this->assertEquals('5 >= 2', (string) Expr::gte(5, 2)); } public function testLessThanOrEqualTo() { - $this->assertEquals('2 <= 5', (string) Expr::ltoet(2, 5)); - $this->assertEquals('2 <= 5', (string) Expr::lessThanOrEqualTo(2, 5)); + $this->assertEquals('2 <= 5', (string) Expr::lte(2, 5)); } public function testBetweenExpr() @@ -229,43 +220,34 @@ class ExprTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('u.id IN(1, 2, 3)', (string) Expr::in('u.id', array(1, 2, 3))); } - public function testOnExpr() + public function testAndxOrxExpr() { - $this->assertEquals('ON 1 = 1', (string) Expr::on(Expr::eq(1, 1))); + $andExpr = Expr::andx(); + $andExpr->add(Expr::eq(1, 1)); + $andExpr->add(Expr::lt(1, 5)); + + $orExpr = Expr::orx(); + $orExpr->add($andExpr); + $orExpr->add(Expr::eq(1, 1)); + + $this->assertEquals('((1 = 1) AND (1 < 5)) OR (1 = 1)', (string) $orExpr); } - public function testWithExpr() + public function testOrxExpr() { - $this->assertEquals('WITH 1 = 1', (string) Expr::with(Expr::eq(1, 1))); + $orExpr = Expr::orx(); + $orExpr->add(Expr::eq(1, 1)); + $orExpr->add(Expr::lt(1, 5)); + + $this->assertEquals('(1 = 1) OR (1 < 5)', (string) $orExpr); } - public function testLeftJoinExpr() + public function testSelectExpr() { - $this->assertEquals('LEFT JOIN u.Profile p', (string) Expr::leftJoin('u', 'Profile', 'p')); - } + $selectExpr = Expr::select(); + $selectExpr->add(Expr::selectField('u.id')); + $selectExpr->add(Expr::selectField('u.username', 'my_test')); - public function testLeftJoinOnConditionExpr() - { - $this->assertEquals('LEFT JOIN u.Profile p ON p.user_id = u.id', (string) Expr::leftJoin('u', 'Profile', 'p', Expr::on(Expr::eq('p.user_id', 'u.id')))); - } - - public function testLeftJoinWithConditionExpr() - { - $this->assertEquals('LEFT JOIN u.Profile p WITH p.user_id = u.id', (string) Expr::leftJoin('u', 'Profile', 'p', Expr::with(Expr::eq('p.user_id', 'u.id')))); - } - - public function testInnerJoinExpr() - { - $this->assertEquals('INNER JOIN u.Profile p', (string) Expr::innerJoin('u', 'Profile', 'p')); - } - - public function testInnerJoinOnConditionExpr() - { - $this->assertEquals('INNER JOIN u.Profile p ON p.user_id = u.id', (string) Expr::innerJoin('u', 'Profile', 'p', Expr::on(Expr::eq('p.user_id', 'u.id')))); - } - - public function testInnerJoinWithConditionExpr() - { - $this->assertEquals('INNER JOIN u.Profile p WITH p.user_id = u.id', (string) Expr::innerJoin('u', 'Profile', 'p', Expr::with(Expr::eq('p.user_id', 'u.id')))); + $this->assertEquals('u.id, u.username AS my_test', (string) $selectExpr); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index 03a8a78a1..d41a7e6f2 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -52,17 +52,6 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase { $dql = $qb->getDql(); $q = $qb->getQuery(); - - //FIXME: QueryBuilder tests should not test the Parser or SQL building, so - // this block should probably be removed. - try { - $q->getSql(); - } catch (\Exception $e) { - echo $dql . "\n"; - echo $e->getTraceAsString(); - $this->fail($e->getMessage()); - } - //-- $this->assertEquals($expectedDql, $dql); } @@ -147,7 +136,73 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->where('u.id = :uid'); - $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :uid'); + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid)'); + } + + public function testAndWhere() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->andWhere('u.id = :uid2'); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) AND (u.id = :uid2)'); + } + + public function testOrWhere() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->orWhere('u.id = :uid2'); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) OR (u.id = :uid2)'); + } + + public function testAndWhereIn() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->andWhereIn('u.id', array(1, 2, 3)); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) AND u.id IN(1, 2, 3)'); + } + + public function testOrWhereIn() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->orWhereIn('u.id', array(1, 2, 3)); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) OR u.id IN(1, 2, 3)'); + } + + public function testAndWhereNotIn() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->andWhereNotIn('u.id', array(1, 2, 3)); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) AND u.id NOT IN(1, 2, 3)'); + } + + public function testOrWhereNotIn() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid') + ->OrWhereNotIn('u.id', array(1, 2, 3)); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) OR u.id NOT IN(1, 2, 3)'); } public function testGroupBy() @@ -254,25 +309,48 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals($q->getParameters(), array('username' => 'jwage', 'username2' => 'jonwage')); } - public function testExprProxyWithMagicCall() + public function testMultipleWhere() { $qb = QueryBuilder::create($this->_em) ->select('u') - ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $qb->where($qb->eq('u.id', 1)); + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where('u.id = :uid', 'u.id = :uid2'); - $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = 1'); + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) AND (u.id = :uid2)'); } - /** - * @expectedException \Doctrine\Common\DoctrineException - */ - public function testInvalidQueryBuilderMethodThrowsException() + public function testMultipleAndWhere() { $qb = QueryBuilder::create($this->_em) ->select('u') - ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); - $qb->where($qb->blah('u.id', 1)); + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->andWhere('u.id = :uid', 'u.id = :uid2'); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) AND (u.id = :uid2)'); + } + + public function testMultipleOrWhere() + { + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->orWhere('u.id = :uid', Expr::eq('u.id', ':uid2')); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE (u.id = :uid) OR (u.id = :uid2)'); + } + + public function testComplexWhere() + { + $orExpr = Expr::orx(); + $orExpr->add(Expr::eq('u.id', ':uid3')); + $orExpr->add(Expr::in('u.id', array(1))); + + $qb = QueryBuilder::create($this->_em) + ->select('u') + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->where($orExpr); + + $this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id = :uid3) OR (u.id IN(1)))'); } public function testLimit()