From 55bb4055e44be68a51edf914378b748f097f61b5 Mon Sep 17 00:00:00 2001 From: jepso Date: Tue, 22 Jan 2008 19:01:18 +0000 Subject: [PATCH] - Replaced NUMERIC token with INTEGER and FLOAT tokens - LIMIT and OFFSET clauses allow only integers now - Added support for INDEX BY - Added more tests, fixed failing tests, etc. --- draft/Doctrine/Query/Parser/Exception.php | 33 +++++++++- draft/Doctrine/Query/Production/Atom.php | 9 ++- .../Query/Production/ComparisonExpression.php | 2 +- .../IdentificationVariableDeclaration.php | 8 +++ draft/Doctrine/Query/Production/IndexBy.php | 41 +++++++++++++ draft/Doctrine/Query/Production/Join.php | 6 +- .../Doctrine/Query/Production/LimitClause.php | 4 +- .../Query/Production/OffsetClause.php | 4 +- draft/Doctrine/Query/Production/Primary.php | 3 +- .../SimpleConditionalExpression.php | 2 +- draft/Doctrine/Query/Scanner.php | 6 +- draft/Doctrine/Query/Token.php | 5 +- draft/query-language.txt | 12 ++-- .../Query/LanguageRecognitionTestCase.php | 61 +++++++++++++++---- draft/tests/Query/ScannerTestCase.php | 8 +-- 15 files changed, 163 insertions(+), 41 deletions(-) create mode 100644 draft/Doctrine/Query/Production/IndexBy.php diff --git a/draft/Doctrine/Query/Parser/Exception.php b/draft/Doctrine/Query/Parser/Exception.php index d906f0988..9e5c449a3 100644 --- a/draft/Doctrine/Query/Parser/Exception.php +++ b/draft/Doctrine/Query/Parser/Exception.php @@ -1,4 +1,35 @@ . + */ + +/** + * Doctrine_Query_Parser_Exception + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Query_Parser_Exception extends Doctrine_Exception { } diff --git a/draft/Doctrine/Query/Production/Atom.php b/draft/Doctrine/Query/Production/Atom.php index d0ab05d9c..68e30e617 100644 --- a/draft/Doctrine/Query/Production/Atom.php +++ b/draft/Doctrine/Query/Production/Atom.php @@ -20,7 +20,7 @@ */ /** - * Atom = string | numeric | input_parameter + * Atom = string | integer | float | input_parameter * * @package Doctrine * @subpackage Query @@ -38,8 +38,11 @@ class Doctrine_Query_Production_Atom extends Doctrine_Query_Production case Doctrine_Query_Token::T_STRING: $this->_parser->match(Doctrine_Query_Token::T_STRING); break; - case Doctrine_Query_Token::T_NUMERIC: - $this->_parser->match(Doctrine_Query_Token::T_NUMERIC); + case Doctrine_Query_Token::T_INTEGER: + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); + break; + case Doctrine_Query_Token::T_FLOAT: + $this->_parser->match(Doctrine_Query_Token::T_FLOAT); break; case Doctrine_Query_Token::T_INPUT_PARAMETER: $this->_parser->match(Doctrine_Query_Token::T_INPUT_PARAMETER); diff --git a/draft/Doctrine/Query/Production/ComparisonExpression.php b/draft/Doctrine/Query/Production/ComparisonExpression.php index 590c1a6c4..b7462149d 100644 --- a/draft/Doctrine/Query/Production/ComparisonExpression.php +++ b/draft/Doctrine/Query/Production/ComparisonExpression.php @@ -43,8 +43,8 @@ class Doctrine_Query_Production_ComparisonExpression extends Doctrine_Query_Prod } else { switch ($this->_parser->lookahead['type']) { case Doctrine_Query_Token::T_ALL: + case Doctrine_Query_Token::T_ANY: case Doctrine_Query_Token::T_SOME: - case Doctrine_Query_Token::T_NONE: $this->QuantifiedExpression(); break; default: diff --git a/draft/Doctrine/Query/Production/IdentificationVariableDeclaration.php b/draft/Doctrine/Query/Production/IdentificationVariableDeclaration.php index e5543f220..df093c542 100644 --- a/draft/Doctrine/Query/Production/IdentificationVariableDeclaration.php +++ b/draft/Doctrine/Query/Production/IdentificationVariableDeclaration.php @@ -36,11 +36,19 @@ class Doctrine_Query_Production_IdentificationVariableDeclaration extends Doctri { $rangeVarDecl = $this->RangeVariableDeclaration(); + if ($this->_isNextToken(Doctrine_Query_Token::T_INDEX)) { + $this->IndexBy(); + } + while ($this->_isNextToken(Doctrine_Query_Token::T_LEFT) || $this->_isNextToken(Doctrine_Query_Token::T_INNER) || $this->_isNextToken(Doctrine_Query_Token::T_JOIN)) { $this->Join(); + + if ($this->_isNextToken(Doctrine_Query_Token::T_INDEX)) { + $this->IndexBy(); + } } } } diff --git a/draft/Doctrine/Query/Production/IndexBy.php b/draft/Doctrine/Query/Production/IndexBy.php new file mode 100644 index 000000000..3c5f13ee6 --- /dev/null +++ b/draft/Doctrine/Query/Production/IndexBy.php @@ -0,0 +1,41 @@ +. + */ + +/** + * IndexBy = "INDEX" "BY" PathExpression + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_IndexBy extends Doctrine_Query_Production +{ + public function execute(array $params = array()) + { + $this->_parser->match(Doctrine_Query_Token::T_INDEX); + $this->_parser->match(Doctrine_Query_Token::T_BY); + $this->PathExpression(); + } +} diff --git a/draft/Doctrine/Query/Production/Join.php b/draft/Doctrine/Query/Production/Join.php index e6d47c1ed..1d70f7aeb 100644 --- a/draft/Doctrine/Query/Production/Join.php +++ b/draft/Doctrine/Query/Production/Join.php @@ -20,7 +20,7 @@ */ /** - * Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] [IndexBy] + * Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] * * @package Doctrine * @subpackage Query @@ -51,9 +51,5 @@ class Doctrine_Query_Production_Join extends Doctrine_Query_Production $this->_parser->match(Doctrine_Query_Token::T_WITH); $this->ConditionalExpression(); } - - if ($this->_isNextToken(Doctrine_Query_Token::T_INDEX)) { - $this->IndexBy(); - } } } diff --git a/draft/Doctrine/Query/Production/LimitClause.php b/draft/Doctrine/Query/Production/LimitClause.php index 8fec6409a..f680ecb6b 100644 --- a/draft/Doctrine/Query/Production/LimitClause.php +++ b/draft/Doctrine/Query/Production/LimitClause.php @@ -20,7 +20,7 @@ */ /** - * LimitClause = "LIMIT" Expression + * LimitClause = "LIMIT" integer * * @package Doctrine * @subpackage Query @@ -35,6 +35,6 @@ class Doctrine_Query_Production_LimitClause extends Doctrine_Query_Production public function execute(array $params = array()) { $this->_parser->match(Doctrine_Query_Token::T_LIMIT); - $this->Expression(); + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); } } diff --git a/draft/Doctrine/Query/Production/OffsetClause.php b/draft/Doctrine/Query/Production/OffsetClause.php index f5b762eec..3b894ce90 100644 --- a/draft/Doctrine/Query/Production/OffsetClause.php +++ b/draft/Doctrine/Query/Production/OffsetClause.php @@ -20,7 +20,7 @@ */ /** - * OffsetClause = "OFFSET" Expression + * OffsetClause = "OFFSET" integer * * @package Doctrine * @subpackage Query @@ -35,6 +35,6 @@ class Doctrine_Query_Production_OffsetClause extends Doctrine_Query_Production public function execute(array $params = array()) { $this->_parser->match(Doctrine_Query_Token::T_OFFSET); - $this->Expression(); + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); } } diff --git a/draft/Doctrine/Query/Production/Primary.php b/draft/Doctrine/Query/Production/Primary.php index 793ea174c..0a755ef98 100644 --- a/draft/Doctrine/Query/Production/Primary.php +++ b/draft/Doctrine/Query/Production/Primary.php @@ -44,7 +44,8 @@ class Doctrine_Query_Production_Primary extends Doctrine_Query_Production } break; case Doctrine_Query_Token::T_STRING: - case Doctrine_Query_Token::T_NUMERIC: + case Doctrine_Query_Token::T_INTEGER: + case Doctrine_Query_Token::T_FLOAT: case Doctrine_Query_Token::T_INPUT_PARAMETER: $this->Atom(); break; diff --git a/draft/Doctrine/Query/Production/SimpleConditionalExpression.php b/draft/Doctrine/Query/Production/SimpleConditionalExpression.php index 7b4c1ae4d..aa13536be 100644 --- a/draft/Doctrine/Query/Production/SimpleConditionalExpression.php +++ b/draft/Doctrine/Query/Production/SimpleConditionalExpression.php @@ -70,7 +70,7 @@ class Doctrine_Query_Production_SimpleConditionalExpression extends Doctrine_Que $this->ComparisonExpression(); break; default: - $this->_parser->syntaxError(); + $this->_parser->logError(); } } diff --git a/draft/Doctrine/Query/Scanner.php b/draft/Doctrine/Query/Scanner.php index e24aef085..4af887375 100644 --- a/draft/Doctrine/Query/Scanner.php +++ b/draft/Doctrine/Query/Scanner.php @@ -101,7 +101,11 @@ class Doctrine_Query_Scanner $value = $match[0]; if (is_numeric($value)) { - $type = Doctrine_Query_Token::T_NUMERIC; + if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { + $type = Doctrine_Query_Token::T_FLOAT; + } else { + $type = Doctrine_Query_Token::T_INTEGER; + } } elseif ($value[0] === "'" && $value[strlen($value) - 1] === "'") { $type = Doctrine_Query_Token::T_STRING; } elseif (ctype_alpha($value[0]) || $value[0] === '_') { diff --git a/draft/Doctrine/Query/Token.php b/draft/Doctrine/Query/Token.php index cf27df218..dbde9be11 100644 --- a/draft/Doctrine/Query/Token.php +++ b/draft/Doctrine/Query/Token.php @@ -34,9 +34,10 @@ final class Doctrine_Query_Token { const T_NONE = 1; const T_IDENTIFIER = 2; - const T_NUMERIC = 3; + const T_INTEGER = 3; const T_STRING = 4; const T_INPUT_PARAMETER = 5; + const T_FLOAT = 6; const T_ALL = 101; const T_AND = 102; @@ -80,6 +81,8 @@ final class Doctrine_Query_Token const T_UPDATE = 140; const T_WHERE = 141; const T_WITH = 142; + const T_TRUE = 143; + const T_FALSE = 144; private function __construct() {} } diff --git a/draft/query-language.txt b/draft/query-language.txt index a5082023a..2d8100c96 100644 --- a/draft/query-language.txt +++ b/draft/query-language.txt @@ -26,18 +26,18 @@ FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVa HavingClause = "HAVING" ConditionalExpression GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} -LimitClause = "LIMIT" Expression -OffsetClause = "OFFSET" Expression +LimitClause = "LIMIT" integer +OffsetClause = "OFFSET" integer UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem} OrderByItem = Expression ["ASC" | "DESC"] GroupByItem = PathExpression UpdateItem = PathExpression "=" (Expression | "NULL") -IdentificationVariableDeclaration = RangeVariableDeclaration {Join} +IdentificationVariableDeclaration = RangeVariableDeclaration [IndexBy] {Join [IndexBy]} RangeVariableDeclaration = PathExpression [["AS"] IdentificationVariable] -Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] [IndexBy] +Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] IndexBy = "INDEX" "BY" PathExpression ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm} @@ -48,7 +48,7 @@ SimpleConditionalExpression = Expression (ComparisonExpression | BetweenExpression | LikeExpression | InExpression | NullComparisonExpression) | ExistsExpression -Atom = string_literal | numeric_constant | input_parameter +Atom = string | integer | float | boolean | input_parameter Expression = Term {("+" | "-") Term} Term = Factor {("*" | "/") Factor} @@ -66,7 +66,7 @@ QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")" BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" ) InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")" -LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string_literal] +LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string] NullComparisonExpression = "IS" ["NOT"] "NULL" ExistsExpression = "EXISTS" "(" Subselect ")" diff --git a/draft/tests/Query/LanguageRecognitionTestCase.php b/draft/tests/Query/LanguageRecognitionTestCase.php index 02092cfba..f0621e18d 100644 --- a/draft/tests/Query/LanguageRecognitionTestCase.php +++ b/draft/tests/Query/LanguageRecognitionTestCase.php @@ -92,12 +92,12 @@ class Doctrine_Query_LanguageRecognition_TestCase extends Doctrine_UnitTestCase public function testExistsExpressionSupportedInWherePart() { - $this->assertValidDql('FROM User WHERE EXISTS (SELECT g.id FROM Groupuser g WHERE g.user_id = u.id)'); + $this->assertValidDql('FROM User WHERE EXISTS (SELECT g.id FROM UserGroupuser g WHERE g.user_id = u.id)'); } public function testNotExistsExpressionSupportedInWherePart() { - $this->assertValidDql('FROM User WHERE NOT EXISTS (SELECT g.id FROM Groupuser g WHERE g.user_id = u.id)'); + $this->assertValidDql('FROM User WHERE NOT EXISTS (SELECT g.id FROM UserGroupuser g WHERE g.user_id = u.id)'); } public function testLiteralValueAsInOperatorOperandIsSupported() @@ -183,42 +183,42 @@ class Doctrine_Query_LanguageRecognition_TestCase extends Doctrine_UnitTestCase public function testLeftJoin() { - $this->assertValidDql('FROM User u LEFT JOIN u.Group'); + $this->assertValidDql('FROM User u LEFT JOIN u.UserGroup'); } public function testJoin() { - $this->assertValidDql('FROM User u JOIN u.Group'); + $this->assertValidDql('FROM User u JOIN u.UserGroup'); } public function testInnerJoin() { - $this->assertValidDql('FROM User u INNER JOIN u.Group'); + $this->assertValidDql('FROM User u INNER JOIN u.UserGroup'); } public function testMultipleLeftJoin() { - $this->assertValidDql('FROM User u LEFT JOIN u.Group LEFT JOIN u.Phonenumber'); + $this->assertValidDql('FROM User u LEFT JOIN u.UserGroup LEFT JOIN u.Phonenumber'); } public function testMultipleInnerJoin() { - $this->assertValidDql('SELECT u.name FROM User u INNER JOIN u.Group INNER JOIN u.Phonenumber'); + $this->assertValidDql('SELECT u.name FROM User u INNER JOIN u.UserGroup INNER JOIN u.Phonenumber'); } public function testMultipleInnerJoin2() { - $this->assertValidDql('SELECT u.name FROM User u INNER JOIN u.Group, u.Phonenumber'); + $this->assertValidDql('SELECT u.name FROM User u INNER JOIN u.UserGroup, u.Phonenumber'); } public function testMixingOfJoins() { - $this->assertValidDql('SELECT u.name, g.name, p.phonenumber FROM User u INNER JOIN u.Group g LEFT JOIN u.Phonenumber p'); + $this->assertValidDql('SELECT u.name, g.name, p.phonenumber FROM User u INNER JOIN u.UserGroup g LEFT JOIN u.Phonenumber p'); } public function testMixingOfJoins2() { - $this->assertValidDql('SELECT u.name, g.name, p.phonenumber FROM User u INNER JOIN u.Group.Phonenumber p'); + $this->assertValidDql('SELECT u.name, g.name, p.phonenumber FROM User u INNER JOIN u.UserGroup.Phonenumber p'); } public function testOrderBySingleColumn() @@ -256,14 +256,14 @@ class Doctrine_Query_LanguageRecognition_TestCase extends Doctrine_UnitTestCase $this->assertValidDql("SELECT u.name, (SELECT COUNT(p.id) FROM Phonenumber p WHERE p.entity_id = u.id) pcount FROM User u WHERE u.name = 'zYne' LIMIT 1"); } - public function testInputParameter() + public function testPositionalInputParameter() { - $this->assertValidDql('FROM User WHERE u.id = ?'); + $this->assertValidDql('FROM User u WHERE u.id = ?'); } public function testNamedInputParameter() { - $this->assertValidDql('FROM User WHERE u.id = :id'); + $this->assertValidDql('FROM User u WHERE u.id = :id'); } public function testCustomJoinsAndWithKeywordSupported() @@ -280,4 +280,39 @@ class Doctrine_Query_LanguageRecognition_TestCase extends Doctrine_UnitTestCase { $this->assertValidDql('FROM Record_City c INDEX BY c.name'); } + + public function testIndexBySupportsJoins() + { + $this->assertValidDql('FROM Record_Country c LEFT JOIN c.City c2 INDEX BY c2.name'); + } + + public function testIndexBySupportsJoins2() + { + $this->assertValidDql('FROM User u INDEX BY u.name LEFT JOIN u.Phonenumber p INDEX BY p.phonenumber'); + } + + public function testBetweenExpressionSupported() + { + $this->assertValidDql("FROM User u WHERE u.name BETWEEN 'jepso' AND 'zYne'"); + } + + public function testNotBetweenExpressionSupported() + { + $this->assertValidDql("FROM User u WHERE u.name NOT BETWEEN 'jepso' AND 'zYne'"); + } + + public function testAllExpression() + { + $this->assertValidDql('FROM Employee e WHERE e.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); + } + + public function testAnyExpression() + { + $this->assertValidDql('FROM Employee e WHERE e.salary > ANY (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); + } + + public function testSomeExpression() + { + $this->assertValidDql('FROM Employee e WHERE e.salary > SOME (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); + } } diff --git a/draft/tests/Query/ScannerTestCase.php b/draft/tests/Query/ScannerTestCase.php index ce7b808d6..6a8450ada 100644 --- a/draft/tests/Query/ScannerTestCase.php +++ b/draft/tests/Query/ScannerTestCase.php @@ -42,7 +42,7 @@ class Doctrine_Query_Scanner_TestCase extends Doctrine_UnitTestCase $scanner = new Doctrine_Query_Scanner('1234'); $token = $scanner->next(); - $this->assertEqual(Doctrine_Query_Token::T_NUMERIC, $token['type']); + $this->assertEqual(Doctrine_Query_Token::T_INTEGER, $token['type']); $this->assertEqual(1234, $token['value']); } @@ -51,7 +51,7 @@ class Doctrine_Query_Scanner_TestCase extends Doctrine_UnitTestCase $scanner = new Doctrine_Query_Scanner('1.234'); $token = $scanner->next(); - $this->assertEqual(Doctrine_Query_Token::T_NUMERIC, $token['type']); + $this->assertEqual(Doctrine_Query_Token::T_FLOAT, $token['type']); $this->assertEqual(1.234, $token['value']); } @@ -60,7 +60,7 @@ class Doctrine_Query_Scanner_TestCase extends Doctrine_UnitTestCase $scanner = new Doctrine_Query_Scanner('1.2e3'); $token = $scanner->next(); - $this->assertEqual(Doctrine_Query_Token::T_NUMERIC, $token['type']); + $this->assertEqual(Doctrine_Query_Token::T_FLOAT, $token['type']); $this->assertEqual(1.2e3, $token['value']); } @@ -69,7 +69,7 @@ class Doctrine_Query_Scanner_TestCase extends Doctrine_UnitTestCase $scanner = new Doctrine_Query_Scanner('7E-10'); $token = $scanner->next(); - $this->assertEqual(Doctrine_Query_Token::T_NUMERIC, $token['type']); + $this->assertEqual(Doctrine_Query_Token::T_FLOAT, $token['type']); $this->assertEqual(7E-10, $token['value']); }