Merge pull request #551 from FabioBatSilva/DDC-2234
[DDC-2234] FUNCTION() IS NULL comparison
This commit is contained in:
commit
916424af49
@ -2400,76 +2400,92 @@ class Parser
|
|||||||
*/
|
*/
|
||||||
public function SimpleConditionalExpression()
|
public function SimpleConditionalExpression()
|
||||||
{
|
{
|
||||||
$token = $this->lexer->lookahead;
|
if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
|
||||||
|
return $this->ExistsExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->lexer->lookahead;
|
||||||
|
$peek = $this->lexer->glimpse();
|
||||||
|
$lookahead = $token;
|
||||||
|
|
||||||
if ($this->lexer->isNextToken(Lexer::T_NOT)) {
|
if ($this->lexer->isNextToken(Lexer::T_NOT)) {
|
||||||
$token = $this->lexer->glimpse();
|
$token = $this->lexer->glimpse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token['type'] === Lexer::T_EXISTS) {
|
if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
|
||||||
return $this->ExistsExpression();
|
// Peek beyond the matching closing paranthesis.
|
||||||
}
|
$beyond = $this->lexer->peek();
|
||||||
|
|
||||||
$peek = $this->lexer->glimpse();
|
switch ($peek['value']) {
|
||||||
|
case '(':
|
||||||
|
//Peeks beyond the matched closing parenthesis.
|
||||||
|
$token = $this->peekBeyondClosingParenthesis(false);
|
||||||
|
|
||||||
if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) {
|
if ($token['type'] === Lexer::T_NOT) {
|
||||||
if ($peek['value'] == '(') {
|
$token = $this->lexer->peek();
|
||||||
// Peek beyond the matching closing paranthesis ')'
|
}
|
||||||
$this->lexer->peek();
|
|
||||||
$token = $this->peekBeyondClosingParenthesis(false);
|
|
||||||
|
|
||||||
if ($token['type'] === Lexer::T_NOT) {
|
if ($token['type'] === Lexer::T_IS) {
|
||||||
$token = $this->lexer->peek();
|
$lookahead = $this->lexer->peek();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
$this->lexer->resetPeek();
|
default:
|
||||||
} else {
|
// Peek beyond the PathExpression or InputParameter.
|
||||||
// Peek beyond the PathExpression (or InputParameter)
|
$token = $beyond;
|
||||||
$peek = $this->lexer->peek();
|
|
||||||
|
|
||||||
while ($peek['value'] === '.') {
|
while ($token['value'] === '.') {
|
||||||
$this->lexer->peek();
|
$this->lexer->peek();
|
||||||
$peek = $this->lexer->peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also peek beyond a NOT if there is one
|
$token = $this->lexer->peek();
|
||||||
if ($peek['type'] === Lexer::T_NOT) {
|
}
|
||||||
$peek = $this->lexer->peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = $peek;
|
// Also peek beyond a NOT if there is one.
|
||||||
|
if ($token['type'] === Lexer::T_NOT) {
|
||||||
|
$token = $this->lexer->peek();
|
||||||
|
}
|
||||||
|
|
||||||
// We need to go even further in case of IS (differenciate between NULL and EMPTY)
|
// We need to go even further in case of IS (differenciate between NULL and EMPTY)
|
||||||
$lookahead = $this->lexer->peek();
|
|
||||||
|
|
||||||
// Also peek beyond a NOT if there is one
|
|
||||||
if ($lookahead['type'] === Lexer::T_NOT) {
|
|
||||||
$lookahead = $this->lexer->peek();
|
$lookahead = $this->lexer->peek();
|
||||||
}
|
|
||||||
|
|
||||||
$this->lexer->resetPeek();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also peek beyond a NOT if there is one.
|
||||||
|
if ($lookahead['type'] === Lexer::T_NOT) {
|
||||||
|
$lookahead = $this->lexer->peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lexer->resetPeek();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($token['type']) {
|
if ($token['type'] === Lexer::T_BETWEEN) {
|
||||||
case Lexer::T_BETWEEN:
|
return $this->BetweenExpression();
|
||||||
return $this->BetweenExpression();
|
|
||||||
case Lexer::T_LIKE:
|
|
||||||
return $this->LikeExpression();
|
|
||||||
case Lexer::T_IN:
|
|
||||||
return $this->InExpression();
|
|
||||||
case Lexer::T_INSTANCE:
|
|
||||||
return $this->InstanceOfExpression();
|
|
||||||
case Lexer::T_IS:
|
|
||||||
if ($lookahead['type'] == Lexer::T_NULL) {
|
|
||||||
return $this->NullComparisonExpression();
|
|
||||||
}
|
|
||||||
return $this->EmptyCollectionComparisonExpression();
|
|
||||||
case Lexer::T_MEMBER:
|
|
||||||
return $this->CollectionMemberExpression();
|
|
||||||
default:
|
|
||||||
return $this->ComparisonExpression();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_LIKE) {
|
||||||
|
return $this->LikeExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_IN) {
|
||||||
|
return $this->InExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_INSTANCE) {
|
||||||
|
return $this->InstanceOfExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_MEMBER) {
|
||||||
|
return $this->CollectionMemberExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
|
||||||
|
return $this->NullComparisonExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
|
||||||
|
return $this->EmptyCollectionComparisonExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ComparisonExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3085,24 +3101,43 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
|
* NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | SingleValuedPathExpression) "IS" ["NOT"] "NULL"
|
||||||
*
|
*
|
||||||
* @return \Doctrine\ORM\Query\AST\NullComparisonExpression
|
* @return \Doctrine\ORM\Query\AST\NullComparisonExpression
|
||||||
*/
|
*/
|
||||||
public function NullComparisonExpression()
|
public function NullComparisonExpression()
|
||||||
{
|
{
|
||||||
if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
|
switch (true) {
|
||||||
$this->match(Lexer::T_INPUT_PARAMETER);
|
case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
|
||||||
$expr = new AST\InputParameter($this->lexer->token['value']);
|
$this->match(Lexer::T_INPUT_PARAMETER);
|
||||||
} else {
|
|
||||||
$expr = $this->SingleValuedPathExpression();
|
$expr = new AST\InputParameter($this->lexer->token['value']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case $this->lexer->isNextToken(Lexer::T_NULLIF):
|
||||||
|
$expr = $this->NullIfExpression();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case $this->lexer->isNextToken(Lexer::T_COALESCE):
|
||||||
|
$expr = $this->CoalesceExpression();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case $this->isFunction():
|
||||||
|
$expr = $this->FunctionDeclaration();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$expr = $this->SingleValuedPathExpression();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$nullCompExpr = new AST\NullComparisonExpression($expr);
|
$nullCompExpr = new AST\NullComparisonExpression($expr);
|
||||||
|
|
||||||
$this->match(Lexer::T_IS);
|
$this->match(Lexer::T_IS);
|
||||||
|
|
||||||
if ($this->lexer->isNextToken(Lexer::T_NOT)) {
|
if ($this->lexer->isNextToken(Lexer::T_NOT)) {
|
||||||
$this->match(Lexer::T_NOT);
|
$this->match(Lexer::T_NOT);
|
||||||
|
|
||||||
$nullCompExpr->not = true;
|
$nullCompExpr->not = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1874,20 +1874,16 @@ class SqlWalker implements TreeWalker
|
|||||||
*/
|
*/
|
||||||
public function walkNullComparisonExpression($nullCompExpr)
|
public function walkNullComparisonExpression($nullCompExpr)
|
||||||
{
|
{
|
||||||
$sql = '';
|
$expression = $nullCompExpr->expression;
|
||||||
$innerExpr = $nullCompExpr->expression;
|
$comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
|
||||||
|
|
||||||
if ($innerExpr instanceof AST\InputParameter) {
|
if ($expression instanceof AST\InputParameter) {
|
||||||
$dqlParamKey = $innerExpr->name;
|
$this->parserResult->addParameterMapping($expression->name, $this->sqlParamIndex++);
|
||||||
$this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
|
|
||||||
$sql .= ' ?';
|
return '?' . $comparison;
|
||||||
} else {
|
|
||||||
$sql .= $this->walkPathExpression($innerExpr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
|
return $expression->dispatch($this) . $comparison;
|
||||||
|
|
||||||
return $sql;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,6 +119,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNotExistsExpression()
|
||||||
|
{
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE NOT EXISTS (SELECT p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234)',
|
||||||
|
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE NOT EXISTS (SELECT c1_.phonenumber FROM cms_phonenumbers c1_ WHERE c1_.phonenumber = 1234)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSupportsSelectForMultipleColumnsOfASingleComponent()
|
public function testSupportsSelectForMultipleColumnsOfASingleComponent()
|
||||||
{
|
{
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
@ -1654,6 +1662,47 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-2234
|
||||||
|
*/
|
||||||
|
public function testWhereFunctionIsNullComparisonExpression()
|
||||||
|
{
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE IDENTITY(u.email) IS NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.email_id IS NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE NULLIF(u.name, 'FabioBatSilva') IS NULL AND IDENTITY(u.email) IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE NULLIF(c0_.name, 'FabioBatSilva') IS NULL AND c0_.email_id IS NOT NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE IDENTITY(u.email) IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.email_id IS NOT NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE NULLIF(u.name, 'FabioBatSilva') IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE NULLIF(c0_.name, 'FabioBatSilva') IS NOT NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE COALESCE(u.name, u.id) IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE COALESCE(c0_.name, c0_.id) IS NOT NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE COALESCE(u.id, IDENTITY(u.email)) IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE COALESCE(c0_.id, c0_.email_id) IS NOT NULL"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE COALESCE(IDENTITY(u.email), NULLIF(u.name, 'FabioBatSilva')) IS NOT NULL",
|
||||||
|
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE COALESCE(c0_.email_id, NULLIF(c0_.name, 'FabioBatSilva')) IS NOT NULL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testCustomTypeValueSql()
|
public function testCustomTypeValueSql()
|
||||||
{
|
{
|
||||||
if (DBALType::hasType('negative_to_positive')) {
|
if (DBALType::hasType('negative_to_positive')) {
|
||||||
|
Loading…
Reference in New Issue
Block a user