1
0
mirror of synced 2025-02-02 21:41:45 +03:00

Some extra refinement over patch

This commit is contained in:
Guilherme Blanco 2015-11-08 16:05:18 +00:00
parent b7bd42638d
commit 443259f629
7 changed files with 129 additions and 87 deletions

View File

@ -49,68 +49,70 @@ class Lexer extends \Doctrine\Common\Lexer
const T_PLUS = 17;
const T_OPEN_CURLY_BRACE = 18;
const T_CLOSE_CURLY_BRACE = 19;
const T_ALIASED_NAME = 20;
const T_FULLY_QUALIFIED_NAME = 21;
// All tokens that are also identifiers should be >= 100
const T_IDENTIFIER = 100;
const T_ALL = 101;
const T_AND = 102;
const T_ANY = 103;
const T_AS = 104;
const T_ASC = 105;
const T_AVG = 106;
const T_BETWEEN = 107;
const T_BOTH = 108;
const T_BY = 109;
const T_CASE = 110;
const T_COALESCE = 111;
const T_COUNT = 112;
const T_DELETE = 113;
const T_DESC = 114;
const T_DISTINCT = 115;
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_HIDDEN = 125;
const T_IN = 126;
const T_INDEX = 127;
const T_INNER = 128;
const T_INSTANCE = 129;
const T_IS = 130;
const T_JOIN = 131;
const T_LEADING = 132;
const T_LEFT = 133;
const T_LIKE = 134;
const T_MAX = 135;
const T_MEMBER = 136;
const T_MIN = 137;
const T_NOT = 138;
const T_NULL = 139;
const T_NULLIF = 140;
const T_OF = 141;
const T_OR = 142;
const T_ORDER = 143;
const T_OUTER = 144;
const T_SELECT = 145;
const T_SET = 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_NEW = 157;
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
const T_ALIASED_NAME = 100;
const T_FULLY_QUALIFIED_NAME = 101;
const T_IDENTIFIER = 102;
// All keyword tokens should be >= 200
const T_ALL = 200;
const T_AND = 201;
const T_ANY = 202;
const T_AS = 203;
const T_ASC = 204;
const T_AVG = 205;
const T_BETWEEN = 206;
const T_BOTH = 207;
const T_BY = 208;
const T_CASE = 209;
const T_COALESCE = 210;
const T_COUNT = 211;
const T_DELETE = 212;
const T_DESC = 213;
const T_DISTINCT = 214;
const T_ELSE = 215;
const T_EMPTY = 216;
const T_END = 217;
const T_ESCAPE = 218;
const T_EXISTS = 219;
const T_FALSE = 220;
const T_FROM = 221;
const T_GROUP = 222;
const T_HAVING = 223;
const T_HIDDEN = 224;
const T_IN = 225;
const T_INDEX = 226;
const T_INNER = 227;
const T_INSTANCE = 228;
const T_IS = 229;
const T_JOIN = 230;
const T_LEADING = 231;
const T_LEFT = 232;
const T_LIKE = 233;
const T_MAX = 234;
const T_MEMBER = 235;
const T_MIN = 236;
const T_NEW = 237;
const T_NOT = 238;
const T_NULL = 239;
const T_NULLIF = 240;
const T_OF = 241;
const T_OR = 242;
const T_ORDER = 243;
const T_OUTER = 244;
const T_PARTIAL = 245;
const T_SELECT = 246;
const T_SET = 247;
const T_SOME = 248;
const T_SUM = 249;
const T_THEN = 250;
const T_TRAILING = 251;
const T_TRUE = 252;
const T_UPDATE = 253;
const T_WHEN = 254;
const T_WHERE = 255;
const T_WITH = 256;
/**
* Creates a new query scanner object.
@ -129,7 +131,7 @@ class Lexer extends \Doctrine\Common\Lexer
{
return array(
'[a-z_][a-z0-9_]*\:[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // aliased name
'[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name
'[a-z_\\\][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name
'(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers
"'(?:[^']|'')*'", // quoted strings
'\?[0-9]*|:[a-z_][a-z0-9_]*' // parameters
@ -167,7 +169,7 @@ class Lexer extends \Doctrine\Common\Lexer
return self::T_STRING;
// Recognize identifiers, aliased or qualified names
case (ctype_alpha($value[0]) || $value[0] === '_'):
case (ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\'):
$name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
if (defined($name)) {
@ -181,9 +183,11 @@ class Lexer extends \Doctrine\Common\Lexer
if (strpos($value, ':') !== false) {
return self::T_ALIASED_NAME;
}
if (strpos($value, '\\') !== false) {
return self::T_FULLY_QUALIFIED_NAME;
}
return self::T_IDENTIFIER;
// Recognize input parameters
@ -206,9 +210,6 @@ class Lexer extends \Doctrine\Common\Lexer
case ($value === '{'): return self::T_OPEN_CURLY_BRACE;
case ($value === '}'): return self::T_CLOSE_CURLY_BRACE;
case (strrpos($value, ':') !== false):
return self::T_ALIASED_NAME;
// Default
default:
// Do nothing

View File

@ -311,14 +311,22 @@ class Parser
{
$lookaheadType = $this->lexer->lookahead['type'];
/*
* The following condition means:
* - If next token matches expectation -> ok.
* - If expectation is to get T_IDENTIFIER and we find one of the tokens that are reserved words *but* would work as identifiers as well (e. g. "FROM", DDC-505) -> ok.
* - Else fail.
*/
if ($lookaheadType !== $token && ($token !== Lexer::T_IDENTIFIER || $lookaheadType <= Lexer::T_IDENTIFIER)) {
$this->syntaxError($this->lexer->getLiteral($token));
// Short-circuit on first condition, usually types match
if ($lookaheadType !== $token) {
// If parameter is not identifier (1-99) must be exact match
if ($token < Lexer::T_IDENTIFIER) {
$this->syntaxError($this->lexer->getLiteral($token));
}
// If parameter is keyword (200+) must be exact match
if ($token > Lexer::T_IDENTIFIER) {
$this->syntaxError($this->lexer->getLiteral($token));
}
// If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
$this->syntaxError($this->lexer->getLiteral($token));
}
}
$this->lexer->moveNext();
@ -962,13 +970,17 @@ class Parser
{
if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
$this->match(Lexer::T_FULLY_QUALIFIED_NAME);
$schemaName = $this->lexer->token['value'];
} else if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$schemaName = $this->lexer->token['value'];
} else {
$this->match(Lexer::T_ALIASED_NAME);
list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
$schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
}
@ -1216,8 +1228,10 @@ class Parser
public function UpdateClause()
{
$this->match(Lexer::T_UPDATE);
$token = $this->lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName();
$this->validateAbstractSchemaName($abstractSchemaName);
if ($this->lexer->isNextToken(Lexer::T_AS)) {
@ -1272,7 +1286,9 @@ class Parser
$token = $this->lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName();
$this->validateAbstractSchemaName($abstractSchemaName);
$deleteClause = new AST\DeleteClause($abstractSchemaName);
if ($this->lexer->isNextToken(Lexer::T_AS)) {
@ -1718,6 +1734,7 @@ class Parser
public function RangeVariableDeclaration()
{
$abstractSchemaName = $this->AbstractSchemaName();
$this->validateAbstractSchemaName($abstractSchemaName);
if ($this->lexer->isNextToken(Lexer::T_AS)) {
@ -2242,9 +2259,12 @@ class Parser
}
// [["AS"] ["HIDDEN"] AliasResultVariable]
$mustHaveAliasResultVariable = false;
if ($this->lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
$mustHaveAliasResultVariable = true;
}
$hiddenAliasResultVariable = false;
@ -2257,7 +2277,7 @@ class Parser
$aliasResultVariable = null;
if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$token = $this->lexer->lookahead;
$aliasResultVariable = $this->AliasResultVariable();
@ -3150,6 +3170,7 @@ class Parser
}
$abstractSchemaName = $this->AbstractSchemaName();
$this->validateAbstractSchemaName($abstractSchemaName);
return $abstractSchemaName;

View File

@ -129,7 +129,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->clear();
$user2 = $this->_em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.id=?1')
$user2 = $this->_em->createQuery('select u from \Doctrine\Tests\Models\CMS\CmsUser u where u.id=?1')
->setParameter(1, $userId)
->getSingleResult();

View File

@ -1046,21 +1046,21 @@ class NewOperatorTest extends \Doctrine\Tests\OrmFunctionalTestCase
/**
* @expectedException Doctrine\ORM\Query\QueryException
* @expectedExceptionMessage [Semantical Error] line 0, col 11 near 'InvalidClass(u.name)': Error: Class "InvalidClass" is not defined.
* @expectedExceptionMessage [Semantical Error] line 0, col 11 near '\InvalidClass(u.name)': Error: Class "\InvalidClass" is not defined.
*/
public function testInvalidClassException()
{
$dql = "SELECT new InvalidClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u";
$dql = "SELECT new \InvalidClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u";
$this->_em->createQuery($dql)->getResult();
}
/**
* @expectedException Doctrine\ORM\Query\QueryException
* @expectedExceptionMessage [Semantical Error] line 0, col 11 near 'stdClass(u.name)': Error: Class "stdClass" has not a valid constructor.
* @expectedExceptionMessage [Semantical Error] line 0, col 11 near '\stdClass(u.name)': Error: Class "\stdClass" has not a valid constructor.
*/
public function testInvalidClassConstructorException()
{
$dql = "SELECT new stdClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u";
$dql = "SELECT new \stdClass(u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u";
$this->_em->createQuery($dql)->getResult();
}

View File

@ -84,7 +84,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{
$this->setExpectedException('\Doctrine\ORM\Query\QueryException');
$this->_em->getConfiguration()->setEntityNamespaces(array('Unknown' => 'Unknown', 'CMS' => 'Doctrine\Tests\Models\CMS'));
$this->_em->getConfiguration()->setEntityNamespaces(array(
'Unknown' => 'Unknown',
'CMS' => 'Doctrine\Tests\Models\CMS'
));
$this->parseDql($dql);
}
@ -92,6 +96,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{
return array(
array('SELECT \'foo\' AS foo\bar FROM Doctrine\Tests\Models\CMS\CmsUser u'),
/* Checks for invalid IdentificationVariables and AliasIdentificationVariables */
array('SELECT \foo FROM Doctrine\Tests\Models\CMS\CmsUser \foo'),
array('SELECT foo\ FROM Doctrine\Tests\Models\CMS\CmsUser foo\\'),
@ -118,7 +123,6 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
/* Checks for invalid AliasResultVariable */
array('SELECT \'foo\' AS \foo FROM Doctrine\Tests\Models\CMS\CmsUser u'),
array('SELECT \'foo\' AS \foo\bar FROM Doctrine\Tests\Models\CMS\CmsUser u'),
array('SELECT \'foo\' AS foo\bar FROM Doctrine\Tests\Models\CMS\CmsUser u'),
array('SELECT \'foo\' AS foo\ FROM Doctrine\Tests\Models\CMS\CmsUser u'),
array('SELECT \'foo\' AS foo\\\\bar FROM Doctrine\Tests\Models\CMS\CmsUser u'),
array('SELECT \'foo\' AS foo: FROM Doctrine\Tests\Models\CMS\CmsUser u'),

View File

@ -24,12 +24,11 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
* @covers Doctrine\ORM\Query\Parser::AbstractSchemaName
* @group DDC-3715
*/
public function testAbstractSchemaNameFailsOnClassnamesWithLeadingBackslash()
public function testAbstractSchemaNameSupportsClassnamesWithLeadingBackslash()
{
$this->setExpectedException('\Doctrine\ORM\Query\QueryException');
$parser = $this->createParser('\Doctrine\Tests\Models\CMS\CmsUser');
$parser->AbstractSchemaName();
$this->assertEquals('\Doctrine\Tests\Models\CMS\CmsUser', $parser->AbstractSchemaName());
}
/**
@ -39,6 +38,7 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
public function testAbstractSchemaNameSupportsIdentifier()
{
$parser = $this->createParser('stdClass');
$this->assertEquals('stdClass', $parser->AbstractSchemaName());
}
@ -49,6 +49,7 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
public function testAbstractSchemaNameSupportsNamespaceAlias()
{
$parser = $this->createParser('CMS:CmsUser');
$parser->getEntityManager()->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $parser->AbstractSchemaName());
@ -61,6 +62,7 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
public function testAbstractSchemaNameSupportsNamespaceAliasWithRelativeClassname()
{
$parser = $this->createParser('Model:CMS\CmsUser');
$parser->getEntityManager()->getConfiguration()->addEntityNamespace('Model', 'Doctrine\Tests\Models');
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $parser->AbstractSchemaName());
@ -74,7 +76,9 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
public function testMatch($expectedToken, $inputString)
{
$parser = $this->createParser($inputString);
$parser->match($expectedToken); // throws exception if not matched
$this->addToAssertionCount(1);
}
@ -88,6 +92,7 @@ class ParserTest extends \Doctrine\Tests\OrmTestCase
$this->setExpectedException('\Doctrine\ORM\Query\QueryException');
$parser = $this->createParser($inputString);
$parser->match($expectedToken);
}

View File

@ -491,11 +491,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
{
// This also uses FQCNs starting with or without a backslash in the INSTANCE OF parameter
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF (Doctrine\Tests\Models\Company\CompanyEmployee, Doctrine\Tests\Models\Company\CompanyManager)",
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF (Doctrine\Tests\Models\Company\CompanyEmployee, \Doctrine\Tests\Models\Company\CompanyManager)",
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('employee', 'manager')"
);
}
/**
* @group DDC-1194
*/
public function testSupportsInstanceOfExpressionsInWherePartPrefixedSlash()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF \Doctrine\Tests\Models\Company\CompanyEmployee",
"SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.discr AS discr_2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')"
);
}
/**
* @group DDC-1194
*/