From b718cd1a63741e2a234d9dc142e1b14eebb014c0 Mon Sep 17 00:00:00 2001 From: romanb Date: Thu, 19 Mar 2009 12:43:48 +0000 Subject: [PATCH] [2.0] Parser work. --- .../ORM/Query/AST/BetweenExpression.php | 53 +++ lib/Doctrine/ORM/Query/AST/DeleteClause.php | 37 +++ .../ORM/Query/AST/DeleteStatement.php | 20 +- lib/Doctrine/ORM/Query/AST/HavingClause.php | 27 ++ lib/Doctrine/ORM/Query/AST/OrderByClause.php | 27 ++ lib/Doctrine/ORM/Query/AST/OrderByItem.php | 49 +++ lib/Doctrine/ORM/Query/AST/SelectClause.php | 4 +- .../ORM/Query/AST/SimpleSelectClause.php | 2 +- .../ORM/Query/AST/SimpleSelectExpression.php | 58 ++++ .../ORM/Query/AST/SubselectFromClause.php | 2 +- lib/Doctrine/ORM/Query/AST/UpdateClause.php | 44 +++ lib/Doctrine/ORM/Query/AST/UpdateItem.php | 46 +++ ...ateStatetement.php => UpdateStatement.php} | 23 +- lib/Doctrine/ORM/Query/AbstractResult.php | 4 +- .../ORM/Query/Exec/AbstractExecutor.php | 2 +- .../Exec/SingleTableDeleteUpdateExecutor.php | 16 +- lib/Doctrine/ORM/Query/Parser.php | 282 ++++++++++++++-- lib/Doctrine/ORM/Query/SqlWalker.php | 93 +++++- .../ORM/Query/LanguageRecognitionTest.php | 304 +++++++----------- 19 files changed, 817 insertions(+), 276 deletions(-) create mode 100644 lib/Doctrine/ORM/Query/AST/BetweenExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/DeleteClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/HavingClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/OrderByClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/OrderByItem.php create mode 100644 lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/UpdateClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/UpdateItem.php rename lib/Doctrine/ORM/Query/AST/{UpdateStatetement.php => UpdateStatement.php} (68%) diff --git a/lib/Doctrine/ORM/Query/AST/BetweenExpression.php b/lib/Doctrine/ORM/Query/AST/BetweenExpression.php new file mode 100644 index 000000000..75a18c369 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/BetweenExpression.php @@ -0,0 +1,53 @@ +_baseExpression = $baseExpr; + $this->_leftBetweenExpression = $leftExpr; + $this->_rightBetweenExpression = $rightExpr; + } + + public function getBaseExpression() + { + return $this->_baseExpression; + } + + public function getLeftBetweenExpression() + { + return $this->_leftBetweenExpression; + } + + public function getRightBetweenExpression() + { + return $this->_rightBetweenExpression; + } + + public function setNot($bool) + { + $this->_not = $bool; + } + + public function getNot() + { + return $this->_not; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/DeleteClause.php b/lib/Doctrine/ORM/Query/AST/DeleteClause.php new file mode 100644 index 000000000..eded9e25b --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/DeleteClause.php @@ -0,0 +1,37 @@ +_abstractSchemaName = $abstractSchemaName; + } + + public function getAbstractSchemaName() + { + return $this->_abstractSchemaName; + } + + public function getAliasIdentificationVariable() + { + return $this->_aliasIdentificationVariable; + } + + public function setAliasIdentificationVariable($alias) + { + $this->_aliasIdentificationVariable = $alias; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/DeleteStatement.php b/lib/Doctrine/ORM/Query/AST/DeleteStatement.php index 04f8e9477..581a7500c 100644 --- a/lib/Doctrine/ORM/Query/AST/DeleteStatement.php +++ b/lib/Doctrine/ORM/Query/AST/DeleteStatement.php @@ -32,11 +32,10 @@ namespace Doctrine\ORM\Query\AST; */ class DeleteStatement extends Node { - protected $_deleteClause; - protected $_whereClause; - - /* Setters */ - public function setDeleteClause($deleteClause) + private $_deleteClause; + private $_whereClause; + + public function __construct($deleteClause) { $this->_deleteClause = $deleteClause; } @@ -46,7 +45,6 @@ class DeleteStatement extends Node $this->_whereClause = $whereClause; } - /* Getters */ public function getDeleteClause() { return $this->_deleteClause; @@ -56,14 +54,4 @@ class DeleteStatement extends Node { return $this->_whereClause; } - - /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ - - public function buildSql() - { - // The 1=1 is needed to workaround the affected_rows in MySQL. - // Simple "DELETE FROM table_name" gives 0 affected rows. - return $this->_deleteClause->buildSql() . (($this->_whereClause !== null) - ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/HavingClause.php b/lib/Doctrine/ORM/Query/AST/HavingClause.php new file mode 100644 index 000000000..887da736a --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/HavingClause.php @@ -0,0 +1,27 @@ +_conditionalExpression = $conditionalExpression; + } + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/OrderByClause.php b/lib/Doctrine/ORM/Query/AST/OrderByClause.php new file mode 100644 index 000000000..c8e3f5734 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/OrderByClause.php @@ -0,0 +1,27 @@ +_orderByItems = $orderByItems; + } + + public function getOrderByItems() + { + return $this->_orderByItems; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/OrderByItem.php b/lib/Doctrine/ORM/Query/AST/OrderByItem.php new file mode 100644 index 000000000..315e5e446 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/OrderByItem.php @@ -0,0 +1,49 @@ +_pathExpr = $pathExpr; + } + + public function getStateFieldPathExpression() + { + return $this->_pathExpr; + } + + public function setAsc($bool) + { + $this->_asc = $bool; + } + + public function isAsc() + { + return $this->_asc; + } + + public function setDesc($bool) + { + $this->_desc = $bool; + } + + public function isDesc() + { + return $this->_desc; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SelectClause.php b/lib/Doctrine/ORM/Query/AST/SelectClause.php index a129bcfb3..3c96dcc4a 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectClause.php +++ b/lib/Doctrine/ORM/Query/AST/SelectClause.php @@ -16,7 +16,7 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query\AST; @@ -26,7 +26,7 @@ namespace Doctrine\ORM\Query\AST; * * @author Guilherme Blanco * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link http://www.phpdoctrine.org + * @link http://www.doctrine-project.org * @since 2.0 * @version $Revision$ */ diff --git a/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php b/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php index 3f14b956f..c3a563a3f 100644 --- a/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php +++ b/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php @@ -22,7 +22,7 @@ namespace Doctrine\ORM\Query\AST; /** - * SimpleSelectClause ::= "SELECT" [DISTINCT"] SimpleSelectExpression + * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @author Guilherme Blanco * @license http://www.opensource.org/licenses/lgpl-license.php LGPL diff --git a/lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php b/lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php new file mode 100644 index 000000000..6ab5f9222 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php @@ -0,0 +1,58 @@ +. + */ + +namespace Doctrine\ORM\Query\AST; + +/** + * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable + * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + */ +class SimpleSelectExpression extends Node +{ + private $_expression; + private $_fieldIdentificationVariable; + + public function __construct($expression) + { + $this->_expression = $expression; + } + + public function getExpression() + { + return $this->_expression; + } + + public function getFieldIdentificationVariable() + { + return $this->_fieldIdentificationVariable; + } + + public function setFieldIdentificationVariable($fieldAlias) + { + $this->_fieldIdentificationVariable = $fieldAlias; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php b/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php index baea127d1..f19f421c1 100644 --- a/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php +++ b/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php @@ -40,7 +40,7 @@ class SubselectFromClause extends Node } /* Getters */ - public function geSubselectIdentificationVariableDeclarations() + public function getSubselectIdentificationVariableDeclarations() { return $this->_identificationVariableDeclarations; } diff --git a/lib/Doctrine/ORM/Query/AST/UpdateClause.php b/lib/Doctrine/ORM/Query/AST/UpdateClause.php new file mode 100644 index 000000000..8c5bbaf5b --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/UpdateClause.php @@ -0,0 +1,44 @@ +_abstractSchemaName = $abstractSchemaName; + $this->_updateItems = $updateItems; + } + + public function getAbstractSchemaName() + { + return $this->_abstractSchemaName; + } + + public function getAliasIdentificationVariable() + { + return $this->_aliasIdentificationVariable; + } + + public function setAliasIdentificationVariable($alias) + { + $this->_aliasIdentificationVariable = $alias; + } + + public function getUpdateItems() + { + return $this->_updateItems; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/UpdateItem.php b/lib/Doctrine/ORM/Query/AST/UpdateItem.php new file mode 100644 index 000000000..517e43ef5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/UpdateItem.php @@ -0,0 +1,46 @@ +_field = $field; + $this->_newValue = $newValue; + } + + public function setIdentificationVariable($identVar) + { + $this->_identificationVariable = $identVar; + } + + public function getIdentificationVariable() + { + return $this->_identificationVariable; + } + + public function getField() + { + return $this->_field; + } + + public function getNewValue() + { + return $this->_newValue; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/UpdateStatetement.php b/lib/Doctrine/ORM/Query/AST/UpdateStatement.php similarity index 68% rename from lib/Doctrine/ORM/Query/AST/UpdateStatetement.php rename to lib/Doctrine/ORM/Query/AST/UpdateStatement.php index c900f730d..fa9092efc 100644 --- a/lib/Doctrine/ORM/Query/AST/UpdateStatetement.php +++ b/lib/Doctrine/ORM/Query/AST/UpdateStatement.php @@ -16,7 +16,7 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query\AST; @@ -26,17 +26,16 @@ namespace Doctrine\ORM\Query\AST; * * @author Guilherme Blanco * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link http://www.phpdoctrine.org + * @link http://www.doctrine-project.org * @since 2.0 * @version $Revision$ */ class UpdateStatement extends Node { - protected $_updateClause; - protected $_whereClause; - - /* Setters */ - public function setUpdateClause($updateClause) + private $_updateClause; + private $_whereClause; + + public function __construct($updateClause) { $this->_updateClause = $updateClause; } @@ -46,7 +45,6 @@ class UpdateStatement extends Node $this->_whereClause = $whereClause; } - /* Getters */ public function getUpdateClause() { return $this->_updateClause; @@ -56,13 +54,4 @@ class UpdateStatement extends Node { return $this->_whereClause; } - - /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ - public function buildSql() - { - // The 1=1 is needed to workaround the affected_rows in MySQL. - // Simple "UPDATE table_name SET column_name = value" gives 0 affected rows. - return $this->_updateClause->buildSql() . (($this->_whereClause !== null) - ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AbstractResult.php b/lib/Doctrine/ORM/Query/AbstractResult.php index 89c9a10c3..da3bf328f 100644 --- a/lib/Doctrine/ORM/Query/AbstractResult.php +++ b/lib/Doctrine/ORM/Query/AbstractResult.php @@ -17,7 +17,7 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query; @@ -26,7 +26,7 @@ namespace Doctrine\ORM\Query; * Doctrine_ORM_Query_AbstractResult * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.com + * @link www.doctrine-project.com * @since 2.0 * @version $Revision: 1393 $ * @author Guilherme Blanco diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractExecutor.php index 5e2e21914..685bb4ffa 100644 --- a/lib/Doctrine/ORM/Query/Exec/AbstractExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/AbstractExecutor.php @@ -79,7 +79,7 @@ abstract class AbstractExecutor implements \Serializable } } else ... */ - return new SingleTableDeleteUpdateExecutor($AST); + return new SingleTableDeleteUpdateExecutor($AST, $sqlWalker); } else { return new SingleSelectExecutor($AST, $sqlWalker); } diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php index 3322e9e5f..0d9396379 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -16,11 +16,13 @@ * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see - * . + * . */ namespace Doctrine\ORM\Query\Exec; +use Doctrine\ORM\Query\AST; + /** * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes * that are mapped to a single table. @@ -28,16 +30,20 @@ namespace Doctrine\ORM\Query\Exec; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Roman Borschel * @version $Revision$ - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. */ class SingleTableDeleteUpdateExecutor extends AbstractExecutor { - public function __construct(\Doctrine\ORM\Query\AST\Node $AST) + public function __construct(AST\Node $AST, $sqlWalker) { - parent::__construct($AST); - $this->_sqlStatements = $AST->buildSql(); + parent::__construct($AST, $sqlWalker); + if ($AST instanceof AST\UpdateStatement) { + $this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST); + } else if ($AST instanceof AST\DeleteStatement) { + $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); + } } public function execute(\Doctrine\DBAL\Connection $conn, array $params) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 90cc809bd..dddc18ff1 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -326,16 +326,10 @@ class Parser switch ($this->_lexer->lookahead['type']) { case Lexer::T_SELECT: return $this->_SelectStatement(); - break; - case Lexer::T_UPDATE: return $this->_UpdateStatement(); - break; - case Lexer::T_DELETE: return $this->_DeleteStatement(); - break; - default: $this->syntaxError('SELECT, UPDATE or DELETE'); break; @@ -430,7 +424,97 @@ class Parser */ private function _UpdateStatement() { - //TODO + $updateStatement = new AST\UpdateStatement($this->_UpdateClause()); + $updateStatement->setWhereClause( + $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->_WhereClause() : null + ); + return $updateStatement; + } + + /** + * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* + */ + private function _UpdateClause() + { + $this->match(Lexer::T_UPDATE); + $abstractSchemaName = $this->_AbstractSchemaName(); + $aliasIdentificationVariable = null; + if ($this->_lexer->isNextToken(Lexer::T_AS)) { + $this->match(Lexer::T_AS); + } + if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $this->match(Lexer::T_IDENTIFIER); + $aliasIdentificationVariable = $this->_lexer->token['value']; + } + $this->match(Lexer::T_SET); + $updateItems = array(); + $updateItems[] = $this->_UpdateItem(); + while ($this->_lexer->isNextToken(',')) { + $this->match(','); + $updateItems[] = $this->_UpdateItem(); + } + + if ($aliasIdentificationVariable) { + $classMetadata = $this->_em->getClassMetadata($abstractSchemaName); + // Building queryComponent + $queryComponent = array( + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ); + $this->_parserResult->setQueryComponent($aliasIdentificationVariable, $queryComponent); + } + + $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); + $updateClause->setAliasIdentificationVariable($aliasIdentificationVariable); + + return $updateClause; + } + + /** + * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue + */ + private function _UpdateItem() + { + $peek = $this->_lexer->glimpse(); + $identVariable = null; + if ($peek['value'] == '.') { + $this->match(Lexer::T_IDENTIFIER); + $identVariable = $this->_lexer->token['value']; + $this->match('.'); + } + $this->match(Lexer::T_IDENTIFIER); + $field = $this->_lexer->token['value']; + $this->match('='); + $newValue = $this->_NewValue(); + + $updateItem = new AST\UpdateItem($field, $newValue); + $updateItem->setIdentificationVariable($identVariable); + + return $updateItem; + } + + /** + * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | + * EnumPrimary | SimpleEntityExpression | "NULL" + */ + private function _NewValue() + { + if ($this->_lexer->isNextToken(Lexer::T_NULL)) { + $this->match(Lexer::T_NULL); + return null; + } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { + $this->match(Lexer::T_INPUT_PARAMETER); + return new AST\InputParameter($this->_lexer->token['value']); + } else if ($this->_lexer->isNextToken(Lexer::T_STRING)) { + //TODO: Can be StringPrimary or EnumPrimary + return $this->_StringPrimary(); + } else { + $this->syntaxError('Not yet implemented.'); + //echo "UH OH ..."; + } } /** @@ -438,7 +522,42 @@ class Parser */ private function _DeleteStatement() { - //TODO + $deleteStatement = new AST\DeleteStatement($this->_DeleteClause()); + $deleteStatement->setWhereClause( + $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->_WhereClause() : null + ); + return $deleteStatement; + } + + /** + * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] + */ + private function _DeleteClause() + { + $this->match(Lexer::T_DELETE); + if ($this->_lexer->isNextToken(Lexer::T_FROM)) { + $this->match(Lexer::T_FROM); + } + $deleteClause = new AST\DeleteClause($this->_AbstractSchemaName()); + if ($this->_lexer->isNextToken(Lexer::T_AS)) { + $this->match(Lexer::T_AS); + } + if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $this->match(Lexer::T_IDENTIFIER); + $deleteClause->setAliasIdentificationVariable($this->_lexer->token['value']); + + $classMetadata = $this->_em->getClassMetadata($deleteClause->getAbstractSchemaName()); + // Building queryComponent + $queryComponent = array( + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ); + $this->_parserResult->setQueryComponent($this->_lexer->token['value'], $queryComponent); + } + return $deleteClause; } /** @@ -496,17 +615,23 @@ class Parser if ($peek['value'] != '.' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { $expression = $this->_IdentificationVariable(); } else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) { - $expression = $isFunction ? $this->_AggregateExpression() : $this->_Subselect(); + if ($isFunction) { + $expression = $this->_AggregateExpression(); + } else { + $this->match('('); + $expression = $this->_Subselect(); + $this->match(')'); + } if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); - $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); - } else if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { - $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); + } + if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $this->match(Lexer::T_IDENTIFIER); + $fieldIdentificationVariable = $this->_lexer->token['value']; } } else { $expression = $this->_PathExpressionInSelect(); } - return new AST\SelectExpression($expression, $fieldIdentificationVariable); } @@ -835,13 +960,22 @@ class Parser // For now we only support a PathExpression here... $pathExp = $this->_PathExpression(); $this->match(')'); - } else if ($this->_lexer->isNextToken(Lexer::T_AVG)) { - $this->match(Lexer::T_AVG); + } else { + if ($this->_lexer->isNextToken(Lexer::T_AVG)) { + $this->match(Lexer::T_AVG); + } else if ($this->_lexer->isNextToken(Lexer::T_MAX)) { + $this->match(Lexer::T_MAX); + } else if ($this->_lexer->isNextToken(Lexer::T_MIN)) { + $this->match(Lexer::T_MIN); + } else if ($this->_lexer->isNextToken(Lexer::T_SUM)) { + $this->match(Lexer::T_SUM); + } else { + $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); + } $functionName = $this->_lexer->token['value']; $this->match('('); - //... - } else { - $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); + $pathExp = $this->_StateFieldPathExpression(); + $this->match(')'); } return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); } @@ -863,6 +997,49 @@ class Parser return new AST\GroupByClause($groupByItems); } + /** + * HavingClause ::= "HAVING" ConditionalExpression + */ + private function _HavingClause() + { + $this->match(Lexer::T_HAVING); + return new AST\HavingClause($this->_ConditionalExpression()); + } + + /** + * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + */ + private function _OrderByClause() + { + $this->match(Lexer::T_ORDER); + $this->match(Lexer::T_BY); + $orderByItems = array(); + $orderByItems[] = $this->_OrderByItem(); + while ($this->_lexer->isNextToken(',')) { + $this->match(','); + $orderByItems[] = $this->_OrderByItem(); + } + return new AST\OrderByClause($orderByItems); + } + + /** + * OrderByItem ::= StateFieldPathExpression ["ASC" | "DESC"] + */ + private function _OrderByItem() + { + $item = new AST\OrderByItem($this->_StateFieldPathExpression()); + if ($this->_lexer->isNextToken(Lexer::T_ASC)) { + $this->match(Lexer::T_ASC); + $item->setAsc(true); + } else if ($this->_lexer->isNextToken(Lexer::T_DESC)) { + $this->match(Lexer::T_DESC); + $item->setDesc(true); + } else { + $item->setDesc(true); + } + return $item; + } + /** * WhereClause ::= "WHERE" ConditionalExpression */ @@ -1000,9 +1177,9 @@ class Parser default: $this->syntaxError(); } - } else if ($token['value'] == '(') { - return $this->_ComparisonExpression(); } else { + return $this->_ComparisonExpression(); + /*} else { switch ($token['type']) { case Lexer::T_INTEGER: // IF it turns out its a ComparisonExpression, then it MUST be ArithmeticExpression @@ -1012,7 +1189,7 @@ class Parser break; default: $this->syntaxError(); - } + }*/ } } @@ -1226,7 +1403,7 @@ class Parser } /** - * SimpleSelectExpression ::= SingleValuedPathExpression | IdentificationVariable | AggregateExpression + * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) */ private function _SimpleSelectExpression() { @@ -1234,13 +1411,21 @@ class Parser // SingleValuedPathExpression | IdentificationVariable $peek = $this->_lexer->glimpse(); if ($peek['value'] == '.') { - return $this->_PathExpressionInSelect(); + return new AST\SimpleSelectExpression($this->_PathExpressionInSelect()); } else { $this->match($this->_lexer->lookahead['value']); - return $this->_lexer->token['value']; + return new AST\SimpleSelectExpression($this->_lexer->token['value']); } } else { - return $this->_AggregateExpression(); + $expr = new AST\SimpleSelectExpression($this->_AggregateExpression()); + if ($this->_lexer->isNextToken(Lexer::T_AS)) { + $this->match(Lexer::T_AS); + } + if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { + $this->match(Lexer::T_IDENTIFIER); + $expr->setFieldIdentificationVariable($this->_lexer->token['value']); + } + return $expr; } } @@ -1263,6 +1448,28 @@ class Parser } } + /** + * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + */ + private function _BetweenExpression() + { + $not = false; + $arithExpr1 = $this->_ArithmeticExpression(); + if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + $this->match(Lexer::T_NOT); + $not = true; + } + $this->match(Lexer::T_BETWEEN); + $arithExpr2 = $this->_ArithmeticExpression(); + $this->match(Lexer::T_AND); + $arithExpr3 = $this->_ArithmeticExpression(); + + $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3); + $betweenExpr->setNot($not); + + return $betweenExpr; + } + /** * ArithmeticPrimary ::= StateFieldPathExpression | Literal | "(" SimpleArithmeticExpression ")" | Function | AggregateExpression */ @@ -1274,13 +1481,11 @@ class Parser $this->match(')'); return $expr; } + switch ($this->_lexer->lookahead['type']) { case Lexer::T_IDENTIFIER: $peek = $this->_lexer->glimpse(); if ($peek['value'] == '(') { - if ($this->_isAggregateFunction($peek['type'])) { - return $this->_AggregateExpression(); - } return $this->_FunctionsReturningStrings(); } return $this->_StateFieldPathExpression(); @@ -1293,7 +1498,15 @@ class Parser $this->match($this->_lexer->lookahead['value']); return $this->_lexer->token['value']; default: - $this->syntaxError(); + $peek = $this->_lexer->glimpse(); + if ($peek['value'] == '(') { + if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { + return $this->_AggregateExpression(); + } + return $this->_FunctionsReturningStrings(); + } else { + $this->syntaxError(); + } } throw \Doctrine\Common\DoctrineException::updateMe("Not yet implemented."); //TODO... @@ -1402,9 +1615,8 @@ class Parser $escapeChar = null; if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) { $this->match(Lexer::T_ESCAPE); - var_dump($this->_lexer->lookahead); - //$this->match(Lexer::T_) - //$escapeChar = + $this->match(Lexer::T_STRING); + $escapeChar = $this->_lexer->token['value']; } return new AST\LikeExpression($stringExpr, $stringPattern, $isNot, $escapeChar); } @@ -1438,9 +1650,11 @@ class Parser $this->syntaxError("'.' or '('"); } } else if ($this->_lexer->lookahead['type'] === Lexer::T_STRING) { - //TODO... + $this->match(Lexer::T_STRING); + return $this->_lexer->token['value']; } else if ($this->_lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) { - //TODO... + $this->match(Lexer::T_INPUT_PARAMETER); + return new AST\InputParameter($this->_lexer->token['value']); } else { $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'); } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 6b8aa772b..8e9b8b566 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -181,27 +181,16 @@ class SqlWalker } else if ($selectExpression->getExpression() instanceof AST\AggregateExpression) { $aggExpr = $selectExpression->getExpression(); - if ( ! $selectExpression->getFieldIdentificationVariable()) { $alias = $this->_scalarAliasCounter++; } else { $alias = $selectExpression->getFieldIdentificationVariable(); } - - $parts = $aggExpr->getPathExpression()->getParts(); - $dqlAlias = $parts[0]; - $fieldName = $parts[1]; - - $qComp = $this->_parserResult->getQueryComponent($dqlAlias); - $columnName = $qComp['metadata']->getColumnName($fieldName); - - $sql .= $aggExpr->getFunctionName() . '('; - if ($aggExpr->isDistinct()) $sql .= 'DISTINCT '; - $sql .= $this->_dqlToSqlAliasMap[$dqlAlias] . '.' . $columnName; - $sql .= ') AS dctrn__' . $alias; + $sql .= $this->walkAggregateExpression($aggExpr) . ' AS dctrn__' . $alias; } - //TODO: else if Subselect - else { + else if ($selectExpression->getExpression() instanceof AST\Subselect) { + $sql .= $this->walkSubselect($selectExpression->getExpression()); + } else { $dqlAlias = $selectExpression->getExpression(); $queryComp = $this->_parserResult->getQueryComponent($dqlAlias); $class = $queryComp['metadata']; @@ -221,6 +210,80 @@ class SqlWalker return $sql; } + public function walkSubselect($subselect) + { + $sql = $this->walkSimpleSelectClause($subselect->getSimpleSelectClause()); + $sql .= $this->walkSubselectFromClause($subselect->getSubselectFromClause()); + $sql .= $subselect->getWhereClause() ? $this->walkWhereClause($subselect->getWhereClause()) : ''; + $sql .= $subselect->getGroupByClause() ? $this->walkGroupByClause($subselect->getGroupByClause()) : ''; + + //... more clauses + return $sql; + } + + public function walkSubselectFromClause($subselectFromClause) + { + $sql = ' FROM '; + $identificationVarDecls = $subselectFromClause->getSubselectIdentificationVariableDeclarations(); + $firstIdentificationVarDecl = $identificationVarDecls[0]; + $rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration(); + $sql .= $rangeDecl->getClassMetadata()->getTableName() . ' ' + . $this->_dqlToSqlAliasMap[$rangeDecl->getAliasIdentificationVariable()]; + + foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) { + $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); + } + + return $sql; + } + + public function walkSimpleSelectClause($simpleSelectClause) + { + $sql = 'SELECT'; + if ($simpleSelectClause->isDistinct()) { + $sql .= ' DISTINCT'; + } + $sql .= $this->walkSimpleSelectExpression($simpleSelectClause->getSimpleSelectExpression()); + return $sql; + } + + public function walkSimpleSelectExpression($simpleSelectExpression) + { + $sql = ''; + $expr = $simpleSelectExpression->getExpression(); + if ($expr instanceof AST\PathExpression) { + //... + } else if ($expr instanceof AST\AggregateExpression) { + if ( ! $simpleSelectExpression->getFieldIdentificationVariable()) { + $alias = $this->_scalarAliasCounter++; + } else { + $alias = $simpleSelectExpression->getFieldIdentificationVariable(); + } + $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; + } else { + // $expr is IdentificationVariable + //... + } + return $sql; + } + + public function walkAggregateExpression($aggExpression) + { + $sql = ''; + $parts = $aggExpression->getPathExpression()->getParts(); + $dqlAlias = $parts[0]; + $fieldName = $parts[1]; + + $qComp = $this->_parserResult->getQueryComponent($dqlAlias); + $columnName = $qComp['metadata']->getColumnName($fieldName); + + $sql .= $aggExpression->getFunctionName() . '('; + if ($aggExpression->isDistinct()) $sql .= 'DISTINCT '; + $sql .= $this->_dqlToSqlAliasMap[$dqlAlias] . '.' . $columnName; + $sql .= ')'; + return $sql; + } + public function walkGroupByClause($groupByClause) { return ' GROUP BY ' diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index f275c4836..d2b412bb0 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -112,111 +112,41 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase { $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE EXISTS (SELECT p.id FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234)'); } -/* + public function testNotExistsExpressionSupportedInWherePart() { - $this->assertValidDql('SELECT u.* FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE NOT EXISTS (SELECT p.user_id FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user_id = u.id)'); - } - - public function testLiteralValueAsInOperatorOperandIsSupported() - { - $this->assertValidDql('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE 1 IN (1, 2)'); - } - - public function testUpdateWorksWithOneColumn() - { - $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone'"); - } - - public function testUpdateWorksWithMultipleColumns() - { - $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone', u.username = 'some'"); - } - - public function testUpdateSupportsConditions() - { - $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone' WHERE u.id = 5"); - } - - public function testDeleteAll() - { - $this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser'); - } - - public function testDeleteWithCondition() - { - $this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser WHERE id = 3'); - } - - public function testAdditionExpression() - { - $this->assertValidDql('SELECT u.*, (u.id + u.id) addition FROM Doctrine\Tests\Models\CMS\CmsUser u'); - } - - public function testSubtractionExpression() - { - $this->assertValidDql('SELECT u.*, (u.id - u.id) subtraction FROM Doctrine\Tests\Models\CMS\CmsUser u'); - } - - public function testDivisionExpression() - { - $this->assertValidDql('SELECT u.*, (u.id/u.id) division FROM Doctrine\Tests\Models\CMS\CmsUser u'); - } - - public function testMultiplicationExpression() - { - $this->assertValidDql('SELECT u.*, (u.id * u.id) multiplication FROM Doctrine\Tests\Models\CMS\CmsUser u'); - } - - public function testNegationExpression() - { - $this->assertValidDql('SELECT u.*, -u.id negation FROM Doctrine\Tests\Models\CMS\CmsUser u'); - } - - public function testExpressionWithPrecedingPlusSign() - { - $this->assertValidDql('SELECT u.*, +u.id FROM CmsUser u'); + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE NOT EXISTS (SELECT p.id FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234)'); } public function testAggregateFunctionInHavingClause() { $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p HAVING COUNT(p.phonenumber) > 2'); - $this->assertValidDql("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p HAVING MAX(u.name) = 'zYne'"); - } - - public function testMultipleAggregateFunctionsInHavingClause() - { - $this->assertValidDql("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p HAVING MAX(u.name) = 'zYne'"); + $this->assertValidDql("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p HAVING MAX(u.name) = 'romanb'"); } public function testLeftJoin() { - $this->assertValidDql('SELECT u.*, p.* FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p'); + $this->assertValidDql('SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p'); } public function testJoin() { - $this->assertValidDql('SELECT u.* FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers'); + $this->assertValidDql('SELECT u,p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p'); } public function testInnerJoin() { - $this->assertValidDql('SELECT u.*, u.phonenumbers.* FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.phonenumbers'); + $this->assertValidDql('SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.phonenumbers p'); } public function testMultipleLeftJoin() { - $this->assertValidDql('SELECT u.articles.*, u.phonenumbers.* FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles LEFT JOIN u.phonenumbers'); + $this->assertValidDql('SELECT u, a, p FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a LEFT JOIN u.phonenumbers p'); } public function testMultipleInnerJoin() { - $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles INNER JOIN u.phonenumbers'); - } - - public function testMultipleInnerJoin2() - { - $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles, u.phonenumbers'); + $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a INNER JOIN u.phonenumbers p'); } public function testMixingOfJoins() @@ -224,11 +154,6 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertValidDql('SELECT u.name, a.topic, p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a LEFT JOIN u.phonenumbers p'); } - public function testMixingOfJoins2() - { - $this->assertInvalidDql('SELECT u.name, u.articles.topic, c.text FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles.comments c'); - } - public function testOrderBySingleColumn() { $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY u.name'); @@ -249,31 +174,134 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertValidDql('SELECT u.name, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY u.username DESC, u.name DESC'); } - public function testOrderByWithFunctionExpression() - { - $this->assertValidDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY COALESCE(u.id, u.name) DESC'); - }*/ -/* public function testSubselectInInExpression() { - $this->assertValidDql("SELECT * FROM CmsUser u WHERE u.id NOT IN (SELECT u2.id FROM CmsUser u2 WHERE u2.name = 'zYne')"); + $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (SELECT u2.id FROM Doctrine\Tests\Models\CMS\CmsUser u2 WHERE u2.name = 'zYne')"); } public function testSubselectInSelectPart() { // Semantical error: Unknown query component u (probably in subselect) - $this->assertValidDql("SELECT u.name, (SELECT COUNT(p.phonenumber) FROM CmsPhonenumber p WHERE p.user_id = u.id) pcount FROM CmsUser u WHERE u.name = 'zYne' LIMIT 1"); + $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 testPositionalInputParameter() { - $this->assertValidDql('SELECT * FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?'); + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?'); } public function testNamedInputParameter() { - $this->assertValidDql('SELECT * FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :id'); - }*/ + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :id'); + } + + public function testJoinConditionsSupported() + { + $this->assertValidDql("SELECT u.name, p FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p ON p.phonenumber = '123 123'"); + } + + public function testIndexByClauseWithOneComponent() + { + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u INDEX BY u.id'); + } + + public function testIndexBySupportsJoins() + { + $this->assertValidDql('SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a INDEX BY a.id'); // INDEX BY is now referring to articles + } + + public function testIndexBySupportsJoins2() + { + $this->assertValidDql('SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u INDEX BY u.id LEFT JOIN u.phonenumbers p INDEX BY p.phonenumber'); + } + + public function testBetweenExpressionSupported() + { + $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name BETWEEN 'jepso' AND 'zYne'"); + } + + public function testNotBetweenExpressionSupported() + { + $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name NOT BETWEEN 'jepso' AND 'zYne'"); + } + + public function testLikeExpression() + { + $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z%'"); + } + + public function testNotLikeExpression() + { + $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name NOT LIKE 'z%'"); + } + + public function testLikeExpressionWithCustomEscapeCharacter() + { + $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'"); + } + + public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression() + { + // This should be allowed because avatar is a single-value association. + // SQL: SELECT ... FROM forum_user fu INNER JOIN forum_avatar fa ON fu.avatar_id = fa.id WHERE fa.id = ? + $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar.id = ?"); + } + + public function testImplicitJoinInWhereOnCollectionValuedPathExpression() + { + // This should be forbidden, because articles is a collection + $this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.articles.title = ?"); + } + + public function testInvalidSyntaxIsRejected() + { + $this->assertInvalidDql("FOOBAR CmsUser"); + //$this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser.articles"); + //$this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser cu WHERE cu.articles.id > ?"); + $this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.articles.comments"); + + // Currently UNDEFINED OFFSET error + $this->assertInvalidDql("SELECT c FROM CmsUser.articles.comments c"); + } + + public function testUpdateWorksWithOneField() + { + $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone'"); + } + + public function testUpdateWorksWithMultipleFields() + { + $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone', u.username = 'some'"); + } + + public function testUpdateSupportsConditions() + { + $this->assertValidDql("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = 'someone' WHERE u.id = 5"); + } + + public function testDeleteAll() + { + $this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser'); + } + + public function testDeleteWithCondition() + { + $this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = 3'); + } + + /** + * TODO: Hydration can't deal with this currently but it should be allowed. + * Also, generated SQL looks like: "... FROM cms_user, cms_article ..." which + * may not work on all dbms. + * + * The main use case for this generalized style of join is when a join condition + * does not involve a foreign key relationship that is mapped to an entity relationship. + */ + public function testImplicitJoinWithCartesianProductAndConditionInWhere() + { + $this->assertValidDql("SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u, Doctrine\Tests\Models\CMS\CmsArticle a WHERE u.name = a.topic"); + } + /* public function testCustomJoinsAndWithKeywordSupported() { @@ -281,37 +309,6 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertValidDql('SELECT c.*, c2.*, d.* FROM Record_Country c INNER JOIN c.City c2 WITH c2.id = 2 WHERE c.id = 1'); } */ -/* - public function testJoinConditionsSupported() - { - $this->assertValidDql("SELECT u.name, p.* FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p ON p.phonenumber = '123 123'"); - } - - public function testIndexByClauseWithOneComponent() - { - $this->assertValidDql('SELECT * FROM Doctrine\Tests\Models\CMS\CmsUser u INDEX BY id'); - } - - public function testIndexBySupportsJoins() - { - $this->assertValidDql('SELECT u.*, u.articles.* FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles INDEX BY id'); // INDEX BY is now referring to articles - } - - public function testIndexBySupportsJoins2() - { - $this->assertValidDql('SELECT u.*, p.* FROM Doctrine\Tests\Models\CMS\CmsUser u INDEX BY id LEFT JOIN u.phonenumbers p INDEX BY phonenumber'); - } - - public function testBetweenExpressionSupported() - { - $this->assertValidDql("SELECT * FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name BETWEEN 'jepso' AND 'zYne'"); - } - - public function testNotBetweenExpressionSupported() - { - $this->assertValidDql("SELECT * FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name NOT BETWEEN 'jepso' AND 'zYne'"); - } -*/ /* public function testAllExpressionWithCorrelatedSubquery() { // We need existant classes here, otherwise semantical will always fail @@ -329,62 +326,5 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase // We need existant classes here, otherwise semantical will always fail $this->assertValidDql('SELECT * FROM Employee e WHERE e.salary > SOME (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); } -*//* - public function testLikeExpression() - { - $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z%'"); - } - - public function testNotLikeExpression() - { - $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name NOT LIKE 'z%'"); - } - - public function testLikeExpressionWithCustomEscapeCharacter() - { - $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'"); - } */ - /** - * TODO: Hydration can't deal with this currently but it should be allowed. - * Also, generated SQL looks like: "... FROM cms_user, cms_article ..." which - * may not work on all dbms. - * - * The main use case for this generalized style of join is when a join condition - * does not involve a foreign key relationship that is mapped to an entity relationship. - */ - /* public function testImplicitJoinWithCartesianProductAndConditionInWhere() - { - $this->assertValidDql("SELECT u.*, a.* FROM Doctrine\Tests\Models\CMS\CmsUser u, CmsArticle a WHERE u.name = a.topic"); - } - - public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression() - { - // This should be allowed because avatar is a single-value association. - // SQL: SELECT ... FROM forum_user fu INNER JOIN forum_avatar fa ON fu.avatar_id = fa.id WHERE fa.id = ? - //$this->assertValidDql("SELECT u.* FROM ForumUser u WHERE u.avatar.id = ?"); - } - - public function testImplicitJoinInWhereOnCollectionValuedPathExpression() - { - // This should be forbidden, because articles is a collection - $this->assertInvalidDql("SELECT u.* FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.articles.title = ?"); - } - - public function testInvalidSyntaxIsRejected() - { - $this->assertInvalidDql("FOOBAR CmsUser"); - $this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser.articles"); - $this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser cu WHERE cu.articles.id > ?"); - $this->assertInvalidDql("SELECT user FROM Doctrine\Tests\Models\CMS\CmsUser user"); - - // Error message here is: Relation 'comments' does not exist in component 'CmsUser' - // This means it is intepreted as: - // SELECT u.* FROM CmsUser u JOIN u.articles JOIN u.comments - // This seems wrong. "JOIN u.articles.comments" should not be allowed. - $this->assertInvalidDql("SELECT u.* FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.articles.comments"); - - // Currently UNDEFINED OFFSET error - //$this->assertInvalidDql("SELECT * FROM CmsUser.articles.comments"); - }*/ } \ No newline at end of file