-
-
Save doctrinebot/05f78980b5c88d1e967f to your computer and use it in GitHub Desktop.
Attachments to Doctrine Jira Issue DDC-680 - https://github.com/doctrine/doctrine2/issues/5191
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php | |
index 2a2fa70..d9a1cfe 100644 | |
--- a/lib/Doctrine/ORM/Query/Parser.php | |
+++ b/lib/Doctrine/ORM/Query/Parser.php | |
@@ -398,6 +398,20 @@ class Parser | |
} | |
/** | |
+ * Checks if the given token indicates a mathematical operator. | |
+ * | |
+ * @return boolean TRUE is the token is a mathematical operator, FALSE otherwise. | |
+ */ | |
+ private function _isMathOperator($token) | |
+ { | |
+ if (in_array($token['value'], array("+", "-", "/", "*"))) { | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+ } | |
+ | |
+ /** | |
* Checks if the next-next (after lookahead) token starts a function. | |
* | |
* @return boolean TRUE if the next-next tokens start a function, FALSE otherwise. | |
@@ -1537,7 +1551,7 @@ class Parser | |
$peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' | |
$this->_lexer->resetPeek(); | |
- if ($peek['value'] == '+' || $peek['value'] == '-' || $peek['value'] == '/' || $peek['value'] == '*') { | |
+ if ($this->_isMathOperator($peek)) { | |
return $this->SimpleArithmeticExpression(); | |
} | |
@@ -1545,6 +1559,14 @@ class Parser | |
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) { | |
return $this->SimpleArithmeticExpression(); | |
} else if ($this->_isFunction()) { | |
+ // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator) | |
+ $this->_lexer->peek(); // "(" | |
+ $peek = $this->_peekBeyondClosingParenthesis(); | |
+ | |
+ if ($this->_isMathOperator($peek)) { | |
+ return $this->SimpleArithmeticExpression(); | |
+ } | |
+ | |
return $this->FunctionDeclaration(); | |
} else if ($lookahead == Lexer::T_STRING) { | |
return $this->StringPrimary(); | |
@@ -1603,7 +1625,12 @@ class Parser | |
$expression = $this->SimpleArithmeticExpression(); | |
} | |
} else if ($this->_isFunction()) { | |
- if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { | |
+ $this->_lexer->peek(); // "(" | |
+ $beyond = $this->_peekBeyondClosingParenthesis(); | |
+ | |
+ if ($this->_isMathOperator($beyond)) { | |
+ $expression = $this->ScalarExpression(); | |
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { | |
$expression = $this->AggregateExpression(); | |
} else { | |
// Shortcut: ScalarExpression => Function | |
@@ -1644,26 +1671,51 @@ class Parser | |
} | |
/** | |
- * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] AliasResultVariable]) | |
+ * SimpleSelectExpression ::= | |
+ * StateFieldPathExpression | IdentificationVariable | | |
+ * ((AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]) | |
* | |
* @return \Doctrine\ORM\Query\AST\SimpleSelectExpression | |
*/ | |
public function SimpleSelectExpression() | |
{ | |
- if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { | |
+ $peek = $this->_lexer->peek(); | |
+ | |
+ if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { | |
// SingleValuedPathExpression | IdentificationVariable | |
- $glimpse = $this->_lexer->glimpse(); | |
+ $this->_lexer->resetPeek(); | |
- if ($glimpse['type'] == Lexer::T_DOT) { | |
- return new AST\SimpleSelectExpression($this->StateFieldPathExpression()); | |
+ if ($peek['value'] == '.') { | |
+ $expression = $this->StateFieldPathExpression(); | |
+ } else { | |
+ $expression = $this->IdentificationVariable(); | |
} | |
- $this->match(Lexer::T_IDENTIFIER); | |
+ return new AST\SimpleSelectExpression($expression); | |
+ } else if ($this->_lexer->lookahead['value'] == '(') { | |
+ if ($peek['type'] == Lexer::T_SELECT) { | |
+ // Subselect | |
+ $this->match(Lexer::T_OPEN_PARENTHESIS); | |
+ $expression = $this->Subselect(); | |
+ $this->match(Lexer::T_CLOSE_PARENTHESIS); | |
+ } else { | |
+ $expression = $this->SimpleArithmeticExpression(); | |
+ } | |
- return new AST\SimpleSelectExpression($this->_lexer->token['value']); | |
+ return new AST\SimpleSelectExpression($expression); | |
+ } | |
+ | |
+ $beyond = $this->_peekBeyondClosingParenthesis(); | |
+ | |
+ if ($this->_isMathOperator($beyond)) { | |
+ $expression = $this->ScalarExpression(); | |
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { | |
+ $expression = $this->AggregateExpression(); | |
+ } else { | |
+ $expression = $this->FunctionDeclaration(); | |
} | |
- $expr = new AST\SimpleSelectExpression($this->AggregateExpression()); | |
+ $expr = new AST\SimpleSelectExpression($expression); | |
if ($this->_lexer->isNextToken(Lexer::T_AS)) { | |
$this->match(Lexer::T_AS); | |
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php | |
index baaafcf..e43965e 100644 | |
--- a/lib/Doctrine/ORM/Query/SqlWalker.php | |
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php | |
@@ -1106,7 +1106,7 @@ class SqlWalker implements TreeWalker | |
$expr = $simpleSelectExpression->expression; | |
if ($expr instanceof AST\PathExpression) { | |
- $sql .= ' ' . $this->walkPathExpression($expr); | |
+ $sql .= $this->walkPathExpression($expr); | |
} else if ($expr instanceof AST\AggregateExpression) { | |
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { | |
$alias = $this->_scalarResultCounter++; | |
@@ -1114,17 +1114,55 @@ class SqlWalker implements TreeWalker | |
$alias = $simpleSelectExpression->fieldIdentificationVariable; | |
} | |
- $sql .= ' ' . $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; | |
+ $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; | |
+ } else if ($expr instanceof AST\Subselect) { | |
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) { | |
+ $alias = $this->_scalarResultCounter++; | |
+ } else { | |
+ $alias = $simpleSelectExpression->fieldIdentificationVariable; | |
+ } | |
+ | |
+ $columnAlias = 'sclr' . $this->_aliasCounter++; | |
+ $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; | |
+ $this->_scalarResultAliasMap[$alias] = $columnAlias; | |
+ } else if ($expr instanceof AST\Functions\FunctionNode) { | |
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) { | |
+ $alias = $this->_scalarResultCounter++; | |
+ } else { | |
+ $alias = $simpleSelectExpression->fieldIdentificationVariable; | |
+ } | |
+ | |
+ $columnAlias = 'sclr' . $this->_aliasCounter++; | |
+ $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; | |
+ $this->_scalarResultAliasMap[$alias] = $columnAlias; | |
+ } else if ( | |
+ $expr instanceof AST\SimpleArithmeticExpression || | |
+ $expr instanceof AST\ArithmeticTerm || | |
+ $expr instanceof AST\ArithmeticFactor || | |
+ $expr instanceof AST\ArithmeticPrimary | |
+ ) { | |
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) { | |
+ $alias = $this->_scalarResultCounter++; | |
+ } else { | |
+ $alias = $simpleSelectExpression->fieldIdentificationVariable; | |
+ } | |
+ | |
+ $columnAlias = 'sclr' . $this->_aliasCounter++; | |
+ $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; | |
+ $this->_scalarResultAliasMap[$alias] = $columnAlias; | |
} else { | |
// IdentificationVariable | |
- // FIXME: Composite key support, or select all columns? Does that make sense | |
- // in a subquery? | |
$class = $this->_queryComponents[$expr]['metadata']; | |
- $sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.' | |
- . $class->getQuotedColumnName($class->identifier[0], $this->_platform); | |
+ $tableAlias = $this->getSqlTableAlias($class->getTableName(), $expr); | |
+ $first = true; | |
+ | |
+ foreach ($class->identifier as $identifier) { | |
+ if ($first) $first = false; else $sql .= ', '; | |
+ $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); | |
+ } | |
} | |
- return $sql; | |
+ return ' ' . $sql; | |
} | |
/** | |
diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php | |
index 757d542..ab27441 100644 | |
--- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php | |
+++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php | |
@@ -225,15 +225,15 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase | |
$this->assertValidDql("SELECT u.name, (SELECT COUNT(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234) pcount FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'"); | |
} | |
- /*public function testSubselectInSelectPart2() | |
+ public function testArithmeticExpressionInSelectPart() | |
{ | |
$this->assertValidDql("SELECT SUM(u.id) / COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u"); | |
- }*/ | |
+ } | |
- /*public function testSubselectInSelectPart3() | |
+ public function testArithmeticExpressionInSubselectPart() | |
{ | |
$this->assertValidDql("SELECT (SELECT SUM(u.id) / COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) value FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'"); | |
- }*/ | |
+ } | |
public function testPositionalInputParameter() | |
{ | |
@@ -377,6 +377,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase | |
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id > SOME (SELECT u2.id FROM Doctrine\Tests\Models\CMS\CmsUser u2 WHERE u2.name = u.name)'); | |
} | |
+ public function testArithmeticExpressionWithoutParenthesisInWhereClause() | |
+ { | |
+ $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.phonenumbers) + 1 > 10'); | |
+ } | |
+ | |
public function testMemberOfExpression() | |
{ | |
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers'); | |
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php | |
index 7a664b4..c2d35cb 100644 | |
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php | |
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php | |
@@ -102,6 +102,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase | |
); | |
} | |
+ public function testSelectCorrelatedSubqueryComplexMathematicalExpression() | |
+ { | |
+ $this->assertSqlGeneration( | |
+ 'SELECT (SELECT (count(p.phonenumber)+5)*10 FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p JOIN p.user ui WHERE ui.id = u.id) AS c FROM Doctrine\Tests\Models\CMS\CmsUser u', | |
+ 'SELECT (SELECT (count(c0_.phonenumber) + 5) * 10 AS sclr1 FROM cms_phonenumbers c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id WHERE c1_.id = c2_.id) AS sclr0 FROM cms_users c2_' | |
+ ); | |
+ } | |
+ | |
+ public function testSelectComplexMathematicalExpression() | |
+ { | |
+ $this->assertSqlGeneration( | |
+ 'SELECT (count(p.phonenumber)+5)*10 FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p JOIN p.user ui WHERE ui.id = ?1', | |
+ 'SELECT (count(c0_.phonenumber) + 5) * 10 AS sclr0 FROM cms_phonenumbers c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id WHERE c1_.id = ?' | |
+ ); | |
+ } | |
+ | |
public function testSupportsOrderByWithAscAsDefault() | |
{ | |
$this->assertSqlGeneration( |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment