Skip to content

Instantly share code, notes, and snippets.

@doctrinebot
Created December 13, 2015 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doctrinebot/05f78980b5c88d1e967f to your computer and use it in GitHub Desktop.
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
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