diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index a707577d7..77d71dfc0 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -833,15 +833,11 @@ class ClassMetadataInfo implements ClassMetadata // Cascades $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array(); + if (in_array('all', $cascades)) { - $cascades = array( - 'remove', - 'persist', - 'refresh', - 'merge', - 'detach' - ); + $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach'); } + $mapping['cascade'] = $cascades; $mapping['isCascadeRemove'] = in_array('remove', $cascades); $mapping['isCascadePersist'] = in_array('persist', $cascades); diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 3953eb0dd..ee7db54ac 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -97,6 +97,16 @@ class AnnotationDriver implements Driver return $this->_paths; } + /** + * Retrieve the current annotation reader + * + * @return AnnotationReader + */ + public function getReader() + { + return $this->_reader; + } + /** * Get the file extension used to look for mapping files under * diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 19da2e200..328e3f091 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -26,6 +26,7 @@ use PDO, Doctrine\ORM\ORMException, Doctrine\ORM\OptimisticLockException, Doctrine\ORM\EntityManager, + Doctrine\ORM\UnitOfWork, Doctrine\ORM\Query, Doctrine\ORM\PersistentCollection, Doctrine\ORM\Mapping\MappingException, @@ -1213,7 +1214,7 @@ class BasicEntityPersister } else { $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; } - + $conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name']; } else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, @@ -1224,6 +1225,7 @@ class BasicEntityPersister } else { throw ORMException::unrecognizedField($field); } + $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); } return $conditionSql; @@ -1313,18 +1315,96 @@ class BasicEntityPersister continue; // skip null values. } - $type = null; - if (isset($this->_class->fieldMappings[$field])) { + $types[] = $this->getType($field, $value); + $params[] = $this->getValue($value); + } + + return array($params, $types); + } + + /** + * Infer field type to be used by parameter type casting. + * + * @param string $field + * @param mixed $value + * @return integer + */ + private function getType($field, $value) + { + switch (true) { + case (isset($this->_class->fieldMappings[$field])): $type = Type::getType($this->_class->fieldMappings[$field]['type'])->getBindingType(); - } - if (is_array($value)) { - $type += Connection::ARRAY_PARAM_OFFSET; + break; + + case (isset($this->_class->associationMappings[$field])): + $assoc = $this->_class->associationMappings[$field]; + + if (count($assoc['sourceToTargetKeyColumns']) > 1) { + throw Query\QueryException::associationPathCompositeKeyNotSupported(); + } + + $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); + $targetColumn = $assoc['joinColumns'][0]['referencedColumnName']; + $type = null; + + if (isset($targetClass->fieldNames[$targetColumn])) { + $type = Type::getType($targetClass->fieldMappings[$targetClass->fieldNames[$targetColumn]]['type'])->getBindingType(); + } + + break; + + default: + $type = null; + } + + if (is_array($value)) { + $type += Connection::ARRAY_PARAM_OFFSET; + } + + return $type; + } + + /** + * Retrieve parameter value + * + * @param mixed $value + * @return mixed + */ + private function getValue($value) + { + if (is_array($value)) { + $newValue = array(); + + foreach ($value as $itemValue) { + $newValue[] = $this->getIndividualValue($itemValue); } - $params[] = $value; - $types[] = $type; + return $newValue; } - return array($params, $types); + + return $this->getIndividualValue($value); + } + + /** + * Retrieve an invidiual parameter value + * + * @param mixed $value + * @return mixed + */ + private function getIndividualValue($value) + { + if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { + if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) { + $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + } else { + $class = $this->_em->getClassMetadata(get_class($value)); + $idValues = $class->getIdentifierValues($value); + } + + $value = $idValues[key($idValues)]; + } + + return $value; } /** diff --git a/lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php b/lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php new file mode 100644 index 000000000..e9e49f070 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * + * @since 2.2 + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class GeneralCaseExpression extends Node +{ + public $whenClauses = array(); + public $elseScalarExpression = null; + + public function __construct(array $whenClauses, $elseScalarExpression) + { + $this->whenClauses = $whenClauses; + $this->elseScalarExpression = $elseScalarExpression; + } + + public function dispatch($sqlWalker) + { + return $sqlWalker->walkGeneralCaseExpression($this); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php b/lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php new file mode 100644 index 000000000..9fd5e22ee --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * + * @since 2.2 + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class SimpleCaseExpression extends Node +{ + public $caseOperand = null; + public $simpleWhenClauses = array(); + public $elseScalarExpression = null; + + public function __construct($caseOperand, array $simpleWhenClauses, $elseScalarExpression) + { + $this->caseOperand = $caseOperand; + $this->simpleWhenClauses = $simpleWhenClauses; + $this->elseScalarExpression = $elseScalarExpression; + } + + public function dispatch($sqlWalker) + { + return $sqlWalker->walkSimpleCaseExpression($this); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php b/lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php new file mode 100644 index 000000000..1cb654f21 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * + * @since 2.2 + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class SimpleWhenClause extends Node +{ + public $caseScalarExpression = null; + public $thenScalarExpression = null; + + public function __construct($caseScalarExpression, $thenScalarExpression) + { + $this->caseScalarExpression = $caseScalarExpression; + $this->thenScalarExpression = $thenScalarExpression; + } + + public function dispatch($sqlWalker) + { + return $sqlWalker->walkWhenClauseExpression($this); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/WhenClause.php b/lib/Doctrine/ORM/Query/AST/WhenClause.php new file mode 100644 index 000000000..c20bad9f5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/WhenClause.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * + * @since 2.2 + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class WhenClause extends Node +{ + public $caseConditionExpression = null; + public $thenScalarExpression = null; + + public function __construct($caseConditionExpression, $thenScalarExpression) + { + $this->caseConditionExpression = $caseConditionExpression; + $this->thenScalarExpression = $thenScalarExpression; + } + + public function dispatch($sqlWalker) + { + return $sqlWalker->walkWhenClauseExpression($this); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Expr/Andx.php b/lib/Doctrine/ORM/Query/Expr/Andx.php index c26055c2a..c5cf1f3ab 100644 --- a/lib/Doctrine/ORM/Query/Expr/Andx.php +++ b/lib/Doctrine/ORM/Query/Expr/Andx.php @@ -39,5 +39,6 @@ class Andx extends Composite 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', 'Doctrine\ORM\Query\Expr\Orx', + 'Doctrine\ORM\Query\Expr\Andx', ); } \ 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 7eb66535c..742e499f7 100644 --- a/lib/Doctrine/ORM/Query/Expr/Orx.php +++ b/lib/Doctrine/ORM/Query/Expr/Orx.php @@ -36,8 +36,9 @@ class Orx extends Composite { protected $_separator = ' OR '; protected $_allowedClasses = array( - 'Doctrine\ORM\Query\Expr\Andx', 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', + 'Doctrine\ORM\Query\Expr\Andx', + 'Doctrine\ORM\Query\Expr\Orx', ); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 70c8b7db6..9d20689cf 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -67,46 +67,49 @@ class Lexer extends \Doctrine\Common\Lexer const T_DELETE = 113; const T_DESC = 114; const T_DISTINCT = 115; - const T_EMPTY = 116; - const T_ESCAPE = 117; - const T_EXISTS = 118; - const T_FALSE = 119; - const T_FROM = 120; - const T_GROUP = 121; - const T_HAVING = 122; - const T_IN = 123; - const T_INDEX = 124; - const T_INNER = 125; - const T_INSTANCE = 126; - const T_IS = 127; - const T_JOIN = 128; - const T_LEADING = 129; - const T_LEFT = 130; - const T_LIKE = 131; - const T_MAX = 132; - const T_MEMBER = 133; - const T_MIN = 134; - const T_NOT = 135; - const T_NULL = 136; - const T_NULLIF = 137; - const T_OF = 138; - const T_OR = 139; - const T_ORDER = 140; - const T_OUTER = 141; - const T_SELECT = 142; - const T_SET = 143; - const T_SIZE = 144; - const T_SOME = 145; - const T_SUM = 146; - const T_TRAILING = 147; - const T_TRUE = 148; - const T_UPDATE = 149; - const T_WHEN = 150; - const T_WHERE = 151; - const T_WITH = 153; - const T_PARTIAL = 154; - const T_MOD = 155; - + const T_ELSE = 116; + const T_EMPTY = 117; + const T_END = 118; + const T_ESCAPE = 119; + const T_EXISTS = 120; + const T_FALSE = 121; + const T_FROM = 122; + const T_GROUP = 123; + const T_HAVING = 124; + const T_IN = 125; + const T_INDEX = 126; + const T_INNER = 127; + const T_INSTANCE = 128; + const T_IS = 129; + const T_JOIN = 130; + const T_LEADING = 131; + const T_LEFT = 132; + const T_LIKE = 133; + const T_MAX = 134; + const T_MEMBER = 135; + const T_MIN = 136; + const T_NOT = 137; + const T_NULL = 138; + const T_NULLIF = 139; + const T_OF = 140; + const T_OR = 141; + const T_ORDER = 142; + const T_OUTER = 143; + const T_SELECT = 144; + const T_SET = 145; + const T_SIZE = 146; + const T_SOME = 147; + const T_SUM = 148; + const T_THEN = 149; + const T_TRAILING = 150; + const T_TRUE = 151; + const T_UPDATE = 152; + const T_WHEN = 153; + const T_WHERE = 154; + const T_WITH = 155; + const T_PARTIAL = 156; + const T_MOD = 157; + /** * Creates a new query scanner object. * diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 20a5122e6..0f7c88538 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1342,10 +1342,7 @@ class Parser } /** - * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] - * - * @todo Post 2.0 release. Support general SingleValuedPathExpression instead - * of only StateFieldPathExpression. + * OrderByItem ::= (ResultVariable | SingleValuedPathExpression) ["ASC" | "DESC"] * * @return \Doctrine\ORM\Query\AST\OrderByItem */ @@ -1360,7 +1357,7 @@ class Parser $token = $this->_lexer->lookahead; $expr = $this->ResultVariable(); } else { - $expr = $this->StateFieldPathExpression(); + $expr = $this->SingleValuedPathExpression(); } $item = new AST\OrderByItem($expr); @@ -1624,7 +1621,7 @@ class Parser /** * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | * StateFieldPathExpression | BooleanPrimary | CaseExpression | - * EntityTypeExpression + * InstanceOfExpression * * @return mixed One of the possible expressions or subexpressions. */ @@ -1659,9 +1656,9 @@ class Parser if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return $this->AggregateExpression(); - } else { - return $this->FunctionDeclaration(); } + + return $this->FunctionDeclaration(); } else if ($lookahead == Lexer::T_STRING) { return $this->StringPrimary(); } else if ($lookahead == Lexer::T_INPUT_PARAMETER) { @@ -1674,25 +1671,42 @@ class Parser } } + /** + * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + * + * @return mixed One of the possible expressions or subexpressions. + */ public function CaseExpression() { $lookahead = $this->_lexer->lookahead['type']; - // if "CASE" "WHEN" => GeneralCaseExpression - // else if "CASE" => SimpleCaseExpression - // [DONE] else if "COALESCE" => CoalesceExpression - // [DONE] else if "NULLIF" => NullifExpression switch ($lookahead) { case Lexer::T_NULLIF: return $this->NullIfExpression(); case Lexer::T_COALESCE: return $this->CoalesceExpression(); + + case Lexer::T_CASE: + $peek = $this->_lexer->peek(); + + return ($peek['type'] === Lexer::T_WHEN) + ? $this->GeneralCaseExpression() + : $this->SimpleCaseExpression(); default: - $this->semanticalError('CaseExpression not yet supported.'); - return null; + // Do nothing + break; } + + $this->syntaxError(); } /** @@ -1722,7 +1736,7 @@ class Parser /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * - * @return Doctrine\ORM\Query\AST\ExistsExpression + * @return Doctrine\ORM\Query\AST\NullIfExpression */ public function NullIfExpression() { @@ -1737,6 +1751,80 @@ class Parser return new AST\NullIfExpression($firstExpression, $secondExpression); } + + /** + * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + * + * @return Doctrine\ORM\Query\AST\GeneralExpression + */ + public function GeneralCaseExpression() + { + $this->match(Lexer::T_CASE); + + // Process WhenClause (1..N) + $whenClauses = array(); + + do { + $whenClauses[] = $this->WhenClause(); + } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); + + $this->match(Lexer::T_ELSE); + $scalarExpression = $this->ScalarExpression(); + $this->match(Lexer::T_END); + + return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); + } + + /** + * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + */ + public function SimpleCaseExpression() + { + $this->match(Lexer::T_CASE); + $caseOperand = $this->StateFieldPathExpression(); + + // Process SimpleWhenClause (1..N) + $simpleWhenClauses = array(); + + do { + $simpleWhenClauses[] = $this->SimpleWhenClause(); + } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); + + $this->match(Lexer::T_ELSE); + $scalarExpression = $this->ScalarExpression(); + $this->match(Lexer::T_END); + + return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); + } + + /** + * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + * + * @return Doctrine\ORM\Query\AST\WhenExpression + */ + public function WhenClause() + { + $this->match(Lexer::T_WHEN); + $conditionalExpression = $this->ConditionalExpression(); + $this->match(Lexer::T_THEN); + + return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); + } + + /** + * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + * + * @return Doctrine\ORM\Query\AST\SimpleWhenExpression + */ + public function SimpleWhenClause() + { + $this->match(Lexer::T_WHEN); + $conditionalExpression = $this->ScalarExpression(); + $this->match(Lexer::T_THEN); + + return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); + } /** * SelectExpression ::= @@ -1782,7 +1870,7 @@ class Parser $expression = $this->ScalarExpression(); } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { $expression = $this->AggregateExpression(); - } else if (in_array ($lookaheadType, array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { + } else if (in_array($lookaheadType, array(Lexer::T_COALESCE, Lexer::T_NULLIF))) { $expression = $this->CaseExpression(); } else { // Shortcut: ScalarExpression => Function @@ -1797,10 +1885,13 @@ class Parser $this->_lexer->lookahead['type'] == Lexer::T_STRING) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); + } else if ($this->_lexer->lookahead['type'] == Lexer::T_CASE) { + $expression = $this->CaseExpression(); } else { - $this->syntaxError('IdentificationVariable | StateFieldPathExpression' - . ' | AggregateExpression | "(" Subselect ")" | ScalarExpression', - $this->_lexer->lookahead); + $this->syntaxError( + 'IdentificationVariable | StateFieldPathExpression | AggregateExpression | "(" Subselect ")" | ScalarExpression', + $this->_lexer->lookahead + ); } if ($supportsAlias) { @@ -2290,7 +2381,7 @@ class Parser /** * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - * | FunctionsReturningDatetime | IdentificationVariable + * | FunctionsReturningDatetime | IdentificationVariable | CaseExpression */ public function ArithmeticPrimary() { @@ -2304,6 +2395,11 @@ class Parser } switch ($this->_lexer->lookahead['type']) { + case Lexer::T_COALESCE: + case Lexer::T_NULLIF: + case Lexer::T_CASE: + return $this->CaseExpression(); + case Lexer::T_IDENTIFIER: $peek = $this->_lexer->glimpse(); @@ -2359,7 +2455,7 @@ class Parser } /** - * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression + * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression */ public function StringPrimary() { @@ -2382,6 +2478,8 @@ class Parser return $this->InputParameter(); } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return $this->AggregateExpression(); + } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { + return $this->CaseExpression(); } $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index b4e8b3f15..cc387543c 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -479,20 +479,20 @@ class SqlWalker implements TreeWalker $assoc = $class->associationMappings[$fieldName]; - if ($assoc['isOwningSide']) { - // COMPOSITE KEYS NOT (YET?) SUPPORTED - if (count($assoc['sourceToTargetKeyColumns']) > 1) { - throw QueryException::associationPathCompositeKeyNotSupported(); - } - - if ($this->_useSqlTableAliases) { - $sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.'; - } - - $sql .= reset($assoc['targetToSourceKeyColumns']); - } else { + if ( ! $assoc['isOwningSide']) { throw QueryException::associationPathInverseSideNotSupported(); } + + // COMPOSITE KEYS NOT (YET?) SUPPORTED + if (count($assoc['sourceToTargetKeyColumns']) > 1) { + throw QueryException::associationPathCompositeKeyNotSupported(); + } + + if ($this->_useSqlTableAliases) { + $sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.'; + } + + $sql .= reset($assoc['targetToSourceKeyColumns']); break; default: @@ -858,6 +858,32 @@ class SqlWalker implements TreeWalker return $sql; } + /** + * Walks down a CaseExpression AST node and generates the corresponding SQL. + * + * @param CoalesceExpression|NullIfExpression|GeneralCaseExpression|SimpleCaseExpression $expression + * @return string The SQL. + */ + public function walkCaseExpression($expression) + { + switch (true) { + case ($expression instanceof AST\CoalesceExpression): + return $this->walkCoalesceExpression($expression); + + case ($expression instanceof AST\NullIfExpression): + return $this->walkNullIfExpression($expression); + + case ($expression instanceof AST\GeneralCaseExpression): + return $this->walkGeneralCaseExpression($expression); + + case ($expression instanceof AST\SimpleCaseExpression): + return $this->walkSimpleCaseExpression($expression); + + default: + return ''; + } + } + /** * Walks down a CoalesceExpression AST node and generates the corresponding SQL. * @@ -879,20 +905,6 @@ class SqlWalker implements TreeWalker return $sql; } - public function walkCaseExpression($expression) - { - switch (true) { - case ($expression instanceof AST\CoalesceExpression): - return $this->walkCoalesceExpression($expression); - - case ($expression instanceof AST\NullIfExpression): - return $this->walkNullIfExpression($expression); - - default: - return ''; - } - } - /** * Walks down a NullIfExpression AST node and generates the corresponding SQL. * @@ -911,6 +923,46 @@ class SqlWalker implements TreeWalker return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; } + + /** + * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL. + * + * @param GeneralCaseExpression $generalCaseExpression + * @return string The SQL. + */ + public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression) + { + $sql = 'CASE'; + + foreach ($generalCaseExpression->whenClauses as $whenClause) { + $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression); + $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression); + } + + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END'; + + return $sql; + } + + /** + * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL. + * + * @param SimpleCaseExpression $simpleCaseExpression + * @return string The SQL. + */ + public function walkSimpleCaseExpression($simpleCaseExpression) + { + $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand); + + foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) { + $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression); + $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression); + } + + $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END'; + + return $sql; + } /** * Walks down a SelectExpression AST node and generates the corresponding SQL. @@ -924,36 +976,35 @@ class SqlWalker implements TreeWalker $expr = $selectExpression->expression; if ($expr instanceof AST\PathExpression) { - if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) { - $fieldName = $expr->field; - $dqlAlias = $expr->identificationVariable; - $qComp = $this->_queryComponents[$dqlAlias]; - $class = $qComp['metadata']; - - if ( ! $selectExpression->fieldIdentificationVariable) { - $resultAlias = $fieldName; - } else { - $resultAlias = $selectExpression->fieldIdentificationVariable; - } - - if ($class->isInheritanceTypeJoined()) { - $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); - } else { - $tableName = $class->getTableName(); - } - - $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); - $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); - - $columnAlias = $this->getSQLColumnAlias($columnName); - $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } else { + if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { throw QueryException::invalidPathExpression($expr->type); } - } - else if ($expr instanceof AST\AggregateExpression) { + + $fieldName = $expr->field; + $dqlAlias = $expr->identificationVariable; + $qComp = $this->_queryComponents[$dqlAlias]; + $class = $qComp['metadata']; + + if ( ! $selectExpression->fieldIdentificationVariable) { + $resultAlias = $fieldName; + } else { + $resultAlias = $selectExpression->fieldIdentificationVariable; + } + + if ($class->isInheritanceTypeJoined()) { + $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); + } else { + $tableName = $class->getTableName(); + } + + $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); + $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); + + $columnAlias = $this->getSQLColumnAlias($columnName); + $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; + $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } else if ($expr instanceof AST\AggregateExpression) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; } else { @@ -966,8 +1017,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - else if ($expr instanceof AST\Subselect) { + } else if ($expr instanceof AST\Subselect) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; } else { @@ -980,8 +1030,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); $this->_rsm->addScalarResult($columnAlias, $resultAlias); - } - else if ($expr instanceof AST\Functions\FunctionNode) { + } else if ($expr instanceof AST\Functions\FunctionNode) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; } else { @@ -1022,7 +1071,8 @@ class SqlWalker implements TreeWalker } else if ( $expr instanceof AST\NullIfExpression || $expr instanceof AST\CoalesceExpression || - $expr instanceof AST\CaseExpression + $expr instanceof AST\GeneralCaseExpression || + $expr instanceof AST\SimpleCaseExpression ) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index 40f1bf16e..74a2c924c 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit 40f1bf16e84ddc5291a6a63aa00b9879c40e3500 +Subproject commit 74a2c924cd08b30785877808b1fb519b4b2e60b1 diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 0127ee98a..be3790059 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 0127ee98a4301f2f6e3463c824adc3a3687f901f +Subproject commit be3790059cc43b674a55548eb42d5d25846ea6a9 diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 029d55252..16d3b7bb4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -3,8 +3,8 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\Tests\Models\CMS\CmsUser; -use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsAddress; +use Doctrine\Tests\Models\CMS\CmsPhonenumber; require_once __DIR__ . '/../../TestInit.php'; @@ -18,6 +18,12 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase parent::setUp(); } + public function tearDown() + { + $this->_em->getConfiguration()->setEntityNamespaces(array()); + parent::tearDown(); + } + public function loadFixture() { $user = new CmsUser; @@ -33,13 +39,66 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($user2); $this->_em->flush(); + $user1Id = $user->getId(); + unset($user); unset($user2); + $this->_em->clear(); return $user1Id; } + + public function loadAssociatedFixture() + { + $address = new CmsAddress(); + $address->city = "Berlin"; + $address->country = "Germany"; + $address->street = "Foostreet"; + $address->zip = "12345"; + + $user = new CmsUser(); + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'freak'; + $user->setAddress($address); + + $this->_em->persist($user); + $this->_em->persist($address); + $this->_em->flush(); + $this->_em->clear(); + + return array($user->id, $address->id); + } + + public function buildUser($name, $username, $status, $address) + { + $user = new CmsUser(); + $user->name = $name; + $user->username = $username; + $user->status = $status; + $user->setAddress($address); + + $this->_em->persist($user); + $this->_em->flush(); + + return $user; + } + + public function buildAddress($country, $city, $street, $zip) + { + $address = new CmsAddress(); + $address->country = $country; + $address->city = $city; + $address->street = $street; + $address->zip = $zip; + + $this->_em->persist($address); + $this->_em->flush(); + + return $address; + } public function testBasicFind() { @@ -64,6 +123,53 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('dev', $users[0]->status); } + public function testFindByAssociationWithIntegerAsParameter() + { + $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); + $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); + + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); + $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); + + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); + $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); + + unset($address1); + unset($address2); + unset($address3); + + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); + $addresses = $repository->findBy(array('user' => array($user1->getId(), $user2->getId()))); + + $this->assertEquals(2, count($addresses)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); + } + + public function testFindByAssociationWithObjectAsParameter() + { + $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); + $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); + + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); + $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); + + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); + $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); + + unset($address1); + unset($address2); + unset($address3); + + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); + $addresses = $repository->findBy(array('user' => array($user1, $user2))); + + $this->assertEquals(2, count($addresses)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); + } public function testFindFieldByMagicCall() { @@ -99,12 +205,6 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(2, count($users)); } - public function tearDown() - { - $this->_em->getConfiguration()->setEntityNamespaces(array()); - parent::tearDown(); - } - /** * @expectedException \Doctrine\ORM\ORMException */ @@ -201,28 +301,6 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos->foo(); } - public function loadAssociatedFixture() - { - $address = new CmsAddress(); - $address->city = "Berlin"; - $address->country = "Germany"; - $address->street = "Foostreet"; - $address->zip = "12345"; - - $user = new CmsUser(); - $user->name = 'Roman'; - $user->username = 'romanb'; - $user->status = 'freak'; - $user->setAddress($address); - - $this->_em->persist($user); - $this->_em->persist($address); - $this->_em->flush(); - $this->_em->clear(); - - return array($user->id, $address->id); - } - /** * @group DDC-817 */ diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6319f85fd..503807594 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -39,7 +39,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query->setHint($name, $value); } - parent::assertEquals($sqlToBeConfirmed, $query->getSql()); + parent::assertEquals($sqlToBeConfirmed, $query->getSQL()); $query->free(); } catch (\Exception $e) { $this->fail($e->getMessage() ."\n".$e->getTraceAsString()); @@ -678,6 +678,23 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, (SELECT COUNT(*) FROM cms_articles c1_ WHERE c1_.user_id = c0_.id) AS sclr4 FROM cms_users c0_ ORDER BY sclr4 ASC" ); } + + public function testOrderBySupportsSingleValuedPathExpressionOwningSide() + { + $this->assertSqlGeneration( + "select a from Doctrine\Tests\Models\CMS\CmsArticle a order by a.user", + "SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2, c0_.version AS version3 FROM cms_articles c0_ ORDER BY c0_.user_id ASC" + ); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + */ + public function testOrderBySupportsSingleValuedPathExpressionInverseSide() + { + $q = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u order by u.address"); + $q->getSQL(); + } public function testBooleanLiteralInWhereOnSqlite() { @@ -976,6 +993,38 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id" ); } + + public function testGeneralCaseWithSingleWhenClause() + { + $this->assertSqlGeneration( + "SELECT g.id, CASE WHEN ((g.id / 2) > 18) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 > 18) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" + ); + } + + public function testGeneralCaseWithMultipleWhenClause() + { + $this->assertSqlGeneration( + "SELECT g.id, CASE WHEN (g.id / 2 < 10) THEN 2 WHEN ((g.id / 2) > 20) THEN 1 ELSE 0 END AS test FROM Doctrine\Tests\Models\CMS\CmsGroup g", + "SELECT c0_.id AS id0, CASE WHEN (c0_.id / 2 < 10) THEN 2 WHEN (c0_.id / 2 > 20) THEN 1 ELSE 0 END AS sclr1 FROM cms_groups c0_" + ); + } + + public function testSimpleCaseWithSingleWhenClause() + { + $this->assertSqlGeneration( + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = CASE g.name WHEN 'admin' THEN 1 ELSE 2 END", + "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 ELSE 2 END" + ); + } + + public function testSimpleCaseWithMultipleWhenClause() + { + $this->assertSqlGeneration( + "SELECT g FROM Doctrine\Tests\Models\CMS\CmsGroup g WHERE g.id = (CASE g.name WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END)", + "SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_groups c0_ WHERE c0_.id = CASE c0_.name WHEN admin THEN 1 WHEN moderator THEN 2 ELSE 3 END" + ); + } } @@ -988,9 +1037,7 @@ class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( - $this->simpleArithmeticExpression - ) . ')'; + return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) . ')'; } /**