From 602c6d973eb2371022d527ba4f4ec56bda5decc8 Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 19 Jan 2009 18:40:12 +0000 Subject: [PATCH] DQL Parser work. Getting some first tests back running. Reorganizing all parser rules into the Parser itself. --- lib/Doctrine/ORM/Mapping/OneToManyMapping.php | 1 - lib/Doctrine/ORM/Query/AST.php | 7 - .../ORM/Query/AST/AggregateExpression.php | 40 + .../ORM/Query/AST/ComparisonExpression.php | 16 + .../ORM/Query/AST/ConditionalExpression.php | 26 + .../ORM/Query/AST/ConditionalFactor.php | 33 + .../ORM/Query/AST/ConditionalPrimary.php | 37 + .../ORM/Query/AST/ConditionalTerm.php | 26 + lib/Doctrine/ORM/Query/AST/FromClause.php | 17 +- lib/Doctrine/ORM/Query/AST/GroupByClause.php | 26 + .../AST/IdentificationVariableDeclaration.php | 27 +- lib/Doctrine/ORM/Query/AST/IndexBy.php | 9 +- lib/Doctrine/ORM/Query/AST/Join.php | 18 +- .../ORM/Query/AST/JoinPathExpression.php | 33 + .../ORM/Query/AST/JoinVariableDeclaration.php | 10 +- lib/Doctrine/ORM/Query/AST/PathExpression.php | 86 +++ .../Query/AST/RangeVariableDeclaration.php | 29 +- lib/Doctrine/ORM/Query/AST/SelectClause.php | 22 +- .../ORM/Query/AST/SelectExpression.php | 11 +- .../ORM/Query/AST/SelectStatement.php | 43 +- .../Query/AST/SimpleConditionalExpression.php | 16 + .../AST/SimpleStateFieldPathExpression.php | 14 +- lib/Doctrine/ORM/Query/AST/WhereClause.php | 26 + lib/Doctrine/ORM/Query/AbstractResult.php | 5 +- lib/Doctrine/ORM/Query/Parser.php | 715 +++++++++++++++++- .../ORM/Query/Parser/SelectExpression.php | 9 +- .../ORM/Query/Parser/SelectStatement.php | 4 +- lib/Doctrine/ORM/Query/ParserResult.php | 37 - lib/Doctrine/ORM/Query/ParserRule.php | 42 +- lib/Doctrine/ORM/Query/SqlBuilder.php | 65 -- lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php | 36 - lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php | 36 - .../ORM/Query/SqlExecutor/Abstract.php | 11 +- .../ORM/Query/SqlExecutor/SingleSelect.php | 6 +- lib/Doctrine/ORM/Query/SqlWalker.php | 254 +++++++ query-language.txt | 84 +- tests/Orm/Query/AllTests.php | 7 +- tests/Orm/Query/IdentifierRecognitionTest.php | 26 +- tests/Orm/Query/SelectSqlGenerationTest.php | 74 +- 39 files changed, 1460 insertions(+), 524 deletions(-) create mode 100644 lib/Doctrine/ORM/Query/AST/AggregateExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/ComparisonExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/ConditionalExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/ConditionalFactor.php create mode 100644 lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php create mode 100644 lib/Doctrine/ORM/Query/AST/ConditionalTerm.php create mode 100644 lib/Doctrine/ORM/Query/AST/GroupByClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/JoinPathExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/PathExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/SimpleConditionalExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/WhereClause.php delete mode 100644 lib/Doctrine/ORM/Query/SqlBuilder.php delete mode 100644 lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php delete mode 100644 lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php create mode 100644 lib/Doctrine/ORM/Query/SqlWalker.php diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index ec457453e..5283f919d 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -118,4 +118,3 @@ class Doctrine_ORM_Mapping_OneToManyMapping extends Doctrine_ORM_Mapping_Associa } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST.php b/lib/Doctrine/ORM/Query/AST.php index 3616f1be2..43d292d01 100644 --- a/lib/Doctrine/ORM/Query/AST.php +++ b/lib/Doctrine/ORM/Query/AST.php @@ -31,11 +31,4 @@ */ abstract class Doctrine_ORM_Query_AST { - protected $_parserResult = null; - - - public function __construct(Doctrine_ORM_Query_ParserResult $parserResult) - { - $this->_parserResult = $parserResult; - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/AggregateExpression.php b/lib/Doctrine/ORM/Query/AST/AggregateExpression.php new file mode 100644 index 000000000..741b49c26 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/AggregateExpression.php @@ -0,0 +1,40 @@ +_functionName = $functionName; + $this->_pathExpression = $pathExpression; + $this->_isDistinct = $isDistinct; + } + + public function getPathExpression() + { + return $this->_pathExpression; + } + + public function isDistinct() + { + return $this->_isDistinct; + } + + public function getFunctionName() + { + return $this->_functionName; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/ComparisonExpression.php b/lib/Doctrine/ORM/Query/AST/ComparisonExpression.php new file mode 100644 index 000000000..c46d736f8 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/ComparisonExpression.php @@ -0,0 +1,16 @@ +_conditionalTerms = $conditionalTerms; + } + + public function getConditionalTerms() + { + return $this->_conditionalTerm; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/ConditionalFactor.php b/lib/Doctrine/ORM/Query/AST/ConditionalFactor.php new file mode 100644 index 000000000..980ed892b --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/ConditionalFactor.php @@ -0,0 +1,33 @@ +_conditionalPrimary = $conditionalPrimary; + $this->_not = $not; + } + + public function isNot() + { + return $this->_not; + } + + public function getConditionalPrimary() + { + return $this->_conditionalPrimary; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php b/lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php new file mode 100644 index 000000000..9ff301d6e --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php @@ -0,0 +1,37 @@ +_simpleConditionalExpression = $simpleConditionalExpr; + } + + public function setConditionalExpression($conditionalExpr) + { + $this->_conditionalExpression = $conditionalExpr; + } + + public function getSimpleConditionalExpression() + { + return $this->_simpleConditionalExpression; + } + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/ConditionalTerm.php b/lib/Doctrine/ORM/Query/AST/ConditionalTerm.php new file mode 100644 index 000000000..bc5d30324 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/ConditionalTerm.php @@ -0,0 +1,26 @@ +_conditionalFactors = $conditionalFactors; + } + + public function getConditionalFactors() + { + return $this->_conditionalFactors; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/FromClause.php b/lib/Doctrine/ORM/Query/AST/FromClause.php index 42effd6dd..7fb3b8949 100644 --- a/lib/Doctrine/ORM/Query/AST/FromClause.php +++ b/lib/Doctrine/ORM/Query/AST/FromClause.php @@ -31,22 +31,11 @@ class Doctrine_ORM_Query_AST_FromClause extends Doctrine_ORM_Query_AST { protected $_identificationVariableDeclarations = array(); - - /* Setters */ - public function addIdentificationVariableDeclaration($identificationVariableDeclaration) + public function __construct(array $identificationVariableDeclarations) { - $this->_identificationVariableDeclarations[] = $identificationVariableDeclaration; - } - - - public function setIdentificationVariableDeclarations($identificationVariableDeclarations, $append = false) - { - $this->_selectExpressions = ($append === true) - ? array_merge($this->_identificationVariableDeclarations, $identificationVariableDeclarations) - : $identificationVariableDeclarations; - } - + $this->_identificationVariableDeclarations = $identificationVariableDeclarations; + } /* Getters */ public function getIdentificationVariableDeclarations() diff --git a/lib/Doctrine/ORM/Query/AST/GroupByClause.php b/lib/Doctrine/ORM/Query/AST/GroupByClause.php new file mode 100644 index 000000000..1ac4417a2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/GroupByClause.php @@ -0,0 +1,26 @@ +_groupByItems = $groupByItems; + } + + public function getGroupByItems() + { + return $this->_groupByItems; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php index c53f5b012..c3adc7caa 100644 --- a/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php +++ b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php @@ -35,34 +35,13 @@ class Doctrine_ORM_Query_AST_IdentificationVariableDeclaration extends Doctrine_ protected $_indexBy = null; protected $_joinVariableDeclarations = array(); - - /* Setters */ - public function setRangeVariableDeclaration($rangeVariableDeclaration) - { - $this->_rangeVariableDeclaration = $rangeVariableDeclaration; - } - - - public function setIndexBy($indexBy) + public function __construct($rangeVariableDecl, $indexBy, array $joinVariableDecls) { + $this->_rangeVariableDeclaration = $rangeVariableDecl; $this->_indexBy = $indexBy; + $this->_joinVariableDeclarations = $joinVariableDecls; } - - - public function addJoinVariableDeclaration($joinVariableDeclaration) - { - $this->_joinVariableDeclarations[] = $joinVariableDeclaration; - } - - - public function setJoinVariableDeclarations($joinVariableDeclarations, $append = false) - { - $this->_joinVariableDeclarations = ($append === true) - ? array_merge($this->_joinVariableDeclarations, $joinVariableDeclarations) - : $joinVariableDeclarations; - } - /* Getters */ public function getRangeVariableDeclaration() diff --git a/lib/Doctrine/ORM/Query/AST/IndexBy.php b/lib/Doctrine/ORM/Query/AST/IndexBy.php index 738dd8182..8600c0270 100644 --- a/lib/Doctrine/ORM/Query/AST/IndexBy.php +++ b/lib/Doctrine/ORM/Query/AST/IndexBy.php @@ -31,14 +31,11 @@ class Doctrine_ORM_Query_AST_IndexBy extends Doctrine_ORM_Query_AST { protected $_simpleStateFieldPathExpression = null; - - - /* Setters */ - public function setSimpleStateFieldPathExpression($simpleStateFieldPathExpression) + + public function __construct($simpleStateFieldPathExpression) { $this->_simpleStateFieldPathExpression = $simpleStateFieldPathExpression; - } - + } /* Getters */ public function getSimpleStateFieldPathExpression() diff --git a/lib/Doctrine/ORM/Query/AST/Join.php b/lib/Doctrine/ORM/Query/AST/Join.php index 8a8a70653..cb806ffb0 100644 --- a/lib/Doctrine/ORM/Query/AST/Join.php +++ b/lib/Doctrine/ORM/Query/AST/Join.php @@ -52,23 +52,15 @@ class Doctrine_ORM_Query_AST_Join extends Doctrine_ORM_Query_AST protected $_whereType = self::JOIN_WHERE_WITH; protected $_conditionalExpression = null; - - - /* Setters */ - public function setJoinType($joinType) + + public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar) { $this->_joinType = $joinType; + $this->_joinAssociationPathExpression = $joinAssocPathExpr; + $this->_aliasIdentificationVariable = $aliasIdentVar; } - public function setJoinAssociationPathExpression($joinAssociationPathExpression) - { - $this->_joinAssociationPathExpression = $joinAssociationPathExpression; - } - - public function setAliasIdentificationVariable($aliasIdentificationVariable) - { - $this->_aliasIdentificationVariable = $aliasIdentificationVariable; - } + /* Setters */ public function setWhereType($whereType) { diff --git a/lib/Doctrine/ORM/Query/AST/JoinPathExpression.php b/lib/Doctrine/ORM/Query/AST/JoinPathExpression.php new file mode 100644 index 000000000..34b400ae0 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/JoinPathExpression.php @@ -0,0 +1,33 @@ +_identificationVariable = $identificationVariable; + $this->_assocField = $assocField; + } + + public function getIdentificationVariable() + { + return $this->_identificationVariable; + } + + public function getAssociationField() + { + return $this->_assocField; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php index e3a5f14ef..ac560ab5e 100644 --- a/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php +++ b/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php @@ -34,19 +34,11 @@ class Doctrine_ORM_Query_AST_JoinVariableDeclaration extends Doctrine_ORM_Query_ protected $_indexBy = null; - - /* Setters */ - public function setJoin($join) + public function __construct($join, $indexBy) { $this->_join = $join; - } - - - public function setIndexBy($indexBy) - { $this->_indexBy = $indexBy; } - /* Getters */ public function getJoin() diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php new file mode 100644 index 000000000..20683ca8a --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -0,0 +1,86 @@ +_parts = $parts; + } + + public function getParts() { + return $this->_parts; + } + + /** + * Gets whether the path expression represents a state field that is reached + * either directly (u.name) or by navigating over optionally many embedded class instances + * (u.address.zip). + * + * @return boolean + */ + public function isSimpleStateFieldPathExpression() + { + return $this->_isSimpleStateFieldPathExpression; + } + + /** + * Gets whether the path expression represents a state field that is reached + * by navigating over at least one single-valued association and optionally + * many embedded class instances. (u.Group.address.zip, u.Group.address, ...) + * + * @return boolean + */ + public function isSimpleStateFieldAssociationPathExpression() + { + return $this->_isSimpleStateFieldAssociationPathExpression; + } + + public function isPartEmbeddedClassField($part) + { + return isset($this->_embeddedClassFields[$part]); + } + + public function isPartSingleValuedAssociationField($part) + { + return isset($this->_singleValuedAssociationFields[$part]); + } + + /* Setters to attach semantical information during semantical analysis. */ + + public function setIsSimpleStateFieldPathExpression($bool) + { + $this->_isSimpleStateFieldPathExpression = $bool; + } + + public function setIsSimpleStateFieldAssociationPathExpression($bool) + { + $this->_isSimpleStateFieldAssociationPathExpression = $bool; + } + + public function setIsEmbeddedClassPart($part) + { + $this->_embeddedClassFields[$part] = true; + } + + public function setIsSingleValuedAssociationPart($part) + { + $this->_singleValuedAssociationFields[$part] = true; + } +} + diff --git a/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php index 1379f7b26..28e9403e2 100644 --- a/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php +++ b/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php @@ -30,23 +30,16 @@ */ class Doctrine_ORM_Query_AST_RangeVariableDeclaration extends Doctrine_ORM_Query_AST { - protected $_abstractSchemaName = null; - - protected $_aliasIdentificationVariable = null; - - - /* Setters */ - public function setAbstractSchemaName($abstractSchemaName) - { - $this->_abstractSchemaName = $abstractSchemaName; - } + private $_classMetadata; + private $_abstractSchemaName; + private $_aliasIdentificationVariable; - - public function setAliasIdentificationVariable($aliasIdentificationVariable) + public function __construct($classMetadata, $aliasIdentificationVar) { - $this->_aliasIdentificationVariable = $aliasIdentificationVariable; - } - + $this->_classMetadata = $classMetadata; + $this->_abstractSchemaName = $classMetadata->getClassName(); + $this->_aliasIdentificationVariable = $aliasIdentificationVar; + } /* Getters */ public function getAbstractSchemaName() @@ -54,11 +47,15 @@ class Doctrine_ORM_Query_AST_RangeVariableDeclaration extends Doctrine_ORM_Query return $this->_abstractSchemaName; } - public function getAliasIdentificationVariable() { return $this->_aliasIdentificationVariable; } + + public function getClassMetadata() + { + return $this->_classMetadata; + } /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ diff --git a/lib/Doctrine/ORM/Query/AST/SelectClause.php b/lib/Doctrine/ORM/Query/AST/SelectClause.php index ad5eca7dc..254a7b585 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectClause.php +++ b/lib/Doctrine/ORM/Query/AST/SelectClause.php @@ -33,28 +33,12 @@ class Doctrine_ORM_Query_AST_SelectClause extends Doctrine_ORM_Query_AST protected $_isDistinct; protected $_selectExpressions = array(); - - - /* Setters */ - public function setIsDistinct($value) - { - $this->_isDistinct = $value; - } - - - public function addSelectExpression($expression) - { - $this->_selectExpressions[] = $expression; - } - - public function setSelectExpressions($expressions, $append = false) + public function __construct(array $selectExpressions, $isDistinct) { - $this->_selectExpressions = ($append === true) - ? array_merge($this->_selectExpressions, $expressions) - : $expressions; + $this->_isDistinct = $isDistinct; + $this->_selectExpressions = $selectExpressions; } - /* Getters */ public function isDistinct() diff --git a/lib/Doctrine/ORM/Query/AST/SelectExpression.php b/lib/Doctrine/ORM/Query/AST/SelectExpression.php index 29bb79989..df7231194 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectExpression.php +++ b/lib/Doctrine/ORM/Query/AST/SelectExpression.php @@ -34,19 +34,12 @@ class Doctrine_ORM_Query_AST_SelectExpression extends Doctrine_ORM_Query_AST protected $_expression; protected $_fieldIdentificationVariable; - - /* Setters */ - public function setExpression($expression) + public function __construct($expression, $fieldIdentificationVariable) { $this->_expression = $expression; - } - - public function setFieldIdentificationVariable($fieldIdentificationVariable) - { $this->_fieldIdentificationVariable = $fieldIdentificationVariable; - } - + } /* Getters */ public function getExpression() diff --git a/lib/Doctrine/ORM/Query/AST/SelectStatement.php b/lib/Doctrine/ORM/Query/AST/SelectStatement.php index 3c9026f19..7ce807148 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectStatement.php +++ b/lib/Doctrine/ORM/Query/AST/SelectStatement.php @@ -31,54 +31,21 @@ class Doctrine_ORM_Query_AST_SelectStatement extends Doctrine_ORM_Query_AST { protected $_selectClause; - protected $_fromClause; - protected $_whereClause; - protected $_groupByClause; - protected $_havingClause; - protected $_orderByClause; - - - /* Setters */ - public function setSelectClause($selectClause) - { + + public function __construct($selectClause, $fromClause, $whereClause, $groupByClause, + $havingClause, $orderByClause) { $this->_selectClause = $selectClause; - } - - - public function setFromClause($fromClause) - { $this->_fromClause = $fromClause; - } - - - public function setWhereClause($whereClause) - { $this->_whereClause = $whereClause; - } - - - public function setGroupByClause($groupByClause) - { $this->_groupByClause = $groupByClause; - } - - - public function setHavingClause($havingClause) - { $this->_havingClause = $havingClause; - } - - - public function setOrderByClause($orderByClause) - { $this->_orderByClause = $orderByClause; - } - + } /* Getters */ public function getSelectClause() @@ -122,7 +89,7 @@ class Doctrine_ORM_Query_AST_SelectStatement extends Doctrine_ORM_Query_AST public function buildSql() { return $this->_selectClause->buildSql() . ' ' . $this->_fromClause->buildSql() - . (($this->_whereClause !== null) ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1') + . (($this->_whereClause !== null) ? ' ' . $this->_whereClause->buildSql() : '') . (($this->_groupByClause !== null) ? ' ' . $this->_groupByClause->buildSql() : '') . (($this->_havingClause !== null) ? ' ' . $this->_havingClause->buildSql() : '') . (($this->_orderByClause !== null) ? ' ' . $this->_orderByClause->buildSql() : ''); diff --git a/lib/Doctrine/ORM/Query/AST/SimpleConditionalExpression.php b/lib/Doctrine/ORM/Query/AST/SimpleConditionalExpression.php new file mode 100644 index 000000000..c3b3ef199 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SimpleConditionalExpression.php @@ -0,0 +1,16 @@ +_identificationVariable = $identificationVariable; - } - - - public function setSimpleStateField($simpleStateField) - { $this->_simpleStateField = $simpleStateField; - } - + } /* Getters */ public function getIdentificationVariable() diff --git a/lib/Doctrine/ORM/Query/AST/WhereClause.php b/lib/Doctrine/ORM/Query/AST/WhereClause.php new file mode 100644 index 000000000..9442b243b --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/WhereClause.php @@ -0,0 +1,26 @@ +_conditionalExpression = $conditionalExpression; + } + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} + diff --git a/lib/Doctrine/ORM/Query/AbstractResult.php b/lib/Doctrine/ORM/Query/AbstractResult.php index dff5da695..03d21ea02 100644 --- a/lib/Doctrine/ORM/Query/AbstractResult.php +++ b/lib/Doctrine/ORM/Query/AbstractResult.php @@ -121,7 +121,7 @@ abstract class Doctrine_ORM_Query_AbstractResult */ public function getQueryComponent($componentAlias) { - if ( ! array_key_exists($componentAlias, $this->_queryComponents)) { + if ( ! isset($this->_queryComponents[$componentAlias])) { throw new Doctrine_ORM_Query_Exception('Unknown query component ' . $componentAlias); } @@ -188,7 +188,7 @@ abstract class Doctrine_ORM_Query_AbstractResult /** - * Get component alias associated with given table alias. + * Get DQL alias associated with given SQL table alias. * * @param string $tableAlias SQL table alias that identifies the component alias * @return string Component alias @@ -272,5 +272,4 @@ abstract class Doctrine_ORM_Query_AbstractResult $this->getEnumParams() )); } - } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index e844b882f..007477480 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -28,7 +28,7 @@ * @author Guilherme Blanco * @author Janne Vanhala * @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$ */ @@ -42,13 +42,13 @@ class Doctrine_ORM_Query_Parser */ const MIN_ERROR_DISTANCE = 2; - /** - * The Sql Builder object. + * Path expressions that were encountered during parsing of SelectExpressions + * and still need to be validated. * - * @var Doctrine_ORM_Query_SqlBuilder + * @var array */ - protected $_sqlbuilder; + private $_pendingPathExpressionsInSelect = array(); /** * DQL string. @@ -128,7 +128,6 @@ class Doctrine_ORM_Query_Parser $this->_em = $query->getEntityManager(); $this->_input = $query->getDql(); $this->_scanner = new Doctrine_ORM_Query_Scanner($this->_input); - $this->_sqlBuilder = new Doctrine_ORM_Query_SqlBuilder($this->_em); $this->_keywordTable = new Doctrine_ORM_Query_Token(); $defaultQueryComponent = Doctrine_ORM_Query_ParserRule::DEFAULT_QUERYCOMPONENT; @@ -231,12 +230,8 @@ class Doctrine_ORM_Query_Parser */ public function parse() { - $this->lookahead = $this->_scanner->next(); - - // Building the Abstract Syntax Tree - // We have to double the call of QueryLanguage to allow it to work correctly... =\ - $DQL = new Doctrine_ORM_Query_Parser_QueryLanguage($this); - $AST = $DQL->parse('QueryLanguage'); + // Parse & build AST + $AST = $this->_QueryLanguage(); // Check for end of string if ($this->lookahead !== null) { @@ -245,27 +240,18 @@ class Doctrine_ORM_Query_Parser // Check for semantical errors if (count($this->_errors) > 0) { - throw new Doctrine_ORM_Query_Parser_Exception(implode("\r\n", $this->_errors)); + throw new Doctrine_ORM_Query_Exception(implode("\r\n", $this->_errors)); } + // Create SqlWalker who creates the SQL from the AST + $sqlWalker = new Doctrine_ORM_Query_SqlWalker($this->_em, $this->_parserResult); + // Assign the executor in parser result - $this->_parserResult->setSqlExecutor(Doctrine_ORM_Query_SqlExecutor_Abstract::create($AST)); + $this->_parserResult->setSqlExecutor(Doctrine_ORM_Query_SqlExecutor_Abstract::create($AST, $sqlWalker)); return $this->_parserResult; } - - /** - * Retrieves the assocated Doctrine_ORM_Query_SqlBuilder to this object. - * - * @return Doctrine_ORM_Query_SqlBuilder - */ - public function getSqlBuilder() - { - return $this->_sqlBuilder; - } - - /** * Returns the scanner object associated with this object. * @@ -276,7 +262,6 @@ class Doctrine_ORM_Query_Parser return $this->_scanner; } - /** * Returns the parser result associated with this object. * @@ -287,7 +272,6 @@ class Doctrine_ORM_Query_Parser return $this->_parserResult; } - /** * Generates a new syntax error. * @@ -315,10 +299,9 @@ class Doctrine_ORM_Query_Parser $message .= "'{$this->lookahead['value']}'"; } - throw new Doctrine_ORM_Query_Parser_Exception($message); + throw new Doctrine_ORM_Query_Exception($message); } - /** * Generates a new semantical error. * @@ -336,7 +319,6 @@ class Doctrine_ORM_Query_Parser $this->_logError('Warning: ' . $message, $token); } - /** * Logs new error entry. * @@ -361,8 +343,7 @@ class Doctrine_ORM_Query_Parser public function getEntityManager() { return $this->_em; - } - + } /** * Retrieve the piece of DQL string given the token position @@ -370,11 +351,677 @@ class Doctrine_ORM_Query_Parser * @param array $token Token that it was processing. * @return string Piece of DQL string. */ - public function getQueryPiece($token, $previousChars = 10, $nextChars = 10) + /*public function getQueryPiece($token, $previousChars = 10, $nextChars = 10) { $start = max(0, $token['position'] - $previousChars); $end = max($token['position'] + $nextChars, strlen($this->_input)); return substr($this->_input, $start, $end); + }*/ + + private function _isNextToken($token) + { + $la = $this->lookahead; + return ($la['type'] === $token || $la['value'] === $token); + } + + /** + * Checks if the next-next (after lookahead) token start a function. + * + * @return boolean + */ + private function _isFunction() + { + $next = $this->_scanner->peek(); + $this->_scanner->resetPeek(); + return ($next['value'] === '('); + } + + /** + * Checks whether the next 2 tokens start a subselect. + * + * @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise. + */ + private function _isSubselect() + { + $la = $this->lookahead; + $next = $this->_scanner->peek(); + $this->_scanner->resetPeek(); + return ($la['value'] === '(' && $next['type'] === Doctrine_ORM_Query_Token::T_SELECT); + } + + /* Parse methods */ + + /** + * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + * + * @return + */ + private function _QueryLanguage() + { + $this->lookahead = $this->_scanner->next(); + switch ($this->lookahead['type']) { + case Doctrine_ORM_Query_Token::T_SELECT: + return $this->_SelectStatement(); + break; + + case Doctrine_ORM_Query_Token::T_UPDATE: + return $this->_UpdateStatement(); + break; + + case Doctrine_ORM_Query_Token::T_DELETE: + return $this->_DeleteStatement(); + break; + + default: + $this->syntaxError('SELECT, UPDATE or DELETE'); + break; + } + } + + /** + * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + * + * @return + */ + private function _SelectStatement() + { + $selectClause = $this->_SelectClause(); + $fromClause = $this->_FromClause(); + $this->_processPendingPathExpressionsInSelect(); + + $whereClause = $this->_isNextToken(Doctrine_ORM_Query_Token::T_WHERE) ? + $this->_WhereClause() : null; + + $groupByClause = $this->_isNextToken(Doctrine_ORM_Query_Token::T_GROUP) ? + $this->_GroupByClause() : null; + + $havingClause = $this->_isNextToken(Doctrine_ORM_Query_Token::T_HAVING) ? + $this->_HavingClause() : null; + + $orderByClause = $this->_isNextToken(Doctrine_ORM_Query_Token::T_ORDER) ? + $this->_OrderByClause() : null; + + return new Doctrine_ORM_Query_AST_SelectStatement( + $selectClause, $fromClause, $whereClause, $groupByClause, $havingClause, $orderByClause + ); + } + + /** + * Processes pending path expressions that were encountered while parsing + * select expressions. These will be validated to make sure they are indeed + * valid StateFieldPathExpressions and additional information + * is attached to their AST nodes. + */ + private function _processPendingPathExpressionsInSelect() + { + $qComps = $this->_parserResult->getQueryComponents(); + foreach ($this->_pendingPathExpressionsInSelect as $expr) { + $parts = $expr->getParts(); + $numParts = count($parts); + $dqlAlias = $parts[0]; + if (count($parts) == 2) { + $expr->setIsSimpleStateFieldPathExpression(true); + if ( ! $qComps[$dqlAlias]['metadata']->hasField($parts[1])) { + $this->syntaxError(); + } + } else { + $embeddedClassFieldSeen = false; + $assocSeen = false; + for ($i = 1; $i < $numParts - 1; ++$i) { + if ($qComps[$dqlAlias]['metadata']->hasAssociation($parts[$i])) { + if ($embeddedClassFieldSeen) { + $this->semanticalError('Invalid navigation path.'); + } + // Indirect join + $assoc = $qComps[$dqlAlias]['metadata']->getAssociationMapping($parts[$i]); + if ( ! $assoc->isOneToOne()) { + $this->semanticalError('Single-valued association expected.'); + } + $expr->setIsSingleValuedAssociationPart($parts[$i]); + //TODO... + $assocSeen = true; + } else if ($qComps[$dqlAlias]['metadata']->hasEmbeddedClassField($parts[$i])) { + //TODO... + $expr->setIsEmbeddedClassPart($parts[$i]); + $this->syntaxError(); + } else { + $this->syntaxError(); + } + } + if ( ! $assocSeen) { + $expr->setIsSimpleStateFieldPathExpression(true); + } else { + $expr->setIsSimpleStateFieldAssociationPathExpression(true); + } + // Last part MUST be a simple state field + if ( ! $qComps[$dqlAlias]['metadata']->hasField($parts[$numParts-1])) { + $this->syntaxError(); + } + } + } + } + + private function _UpdateStatement() + { + //TODO + } + + private function _DeleteStatement() + { + //TODO + } + + /** + * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + */ + private function _SelectClause() + { + $isDistinct = false; + $this->match(Doctrine_ORM_Query_Token::T_SELECT); + + // Inspecting if we are in a DISTINCT query + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_DISTINCT)) { + $this->match(Doctrine_ORM_Query_Token::T_DISTINCT); + $isDistinct = true; + } + + // Process SelectExpressions (1..N) + $selectExpressions = array(); + $selectExpressions[] = $this->_SelectExpression(); + while ($this->_isNextToken(',')) { + $this->match(','); + $selectExpressions[] = $this->_SelectExpression(); + } + + return new Doctrine_ORM_Query_AST_SelectClause($selectExpressions, $isDistinct); + } + + /** + * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + */ + private function _FromClause() + { + $this->match(Doctrine_ORM_Query_Token::T_FROM); + $identificationVariableDeclarations = array(); + $identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration(); + while ($this->_isNextToken(',')) { + $this->match(','); + $identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration(); + } + + return new Doctrine_ORM_Query_AST_FromClause($identificationVariableDeclarations); + } + + /** + * SelectExpression ::= + * IdentificationVariable | StateFieldPathExpression | + * (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] + */ + private function _SelectExpression() + { + $expression = null; + $fieldIdentificationVariable = null; + $peek = $this->_scanner->peek(); + $this->_scanner->resetPeek(); + // First we recognize for an IdentificationVariable (DQL class alias) + if ($peek['value'] != '.' && $this->lookahead['type'] === Doctrine_ORM_Query_Token::T_IDENTIFIER) { + $expression = $this->_IdentificationVariable(); + } else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) { + $expression = $isFunction ? $this->_AggregateExpression() : $this->_Subselect(); + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AS)) { + $this->match(Doctrine_ORM_Query_Token::T_AS); + $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); + } elseif ($this->_isNextToken(Doctrine_ORM_Query_Token::T_IDENTIFIER)) { + $fieldIdentificationVariable = $this->_FieldAliasIdentificationVariable(); + } + } else { + $expression = $this->_PathExpressionInSelect(); + } + + return new Doctrine_ORM_Query_AST_SelectExpression($expression, $fieldIdentificationVariable); + } + + /** + * IdentificationVariable ::= identifier + */ + private function _IdentificationVariable() + { + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + return $this->token['value']; + } + + /** + * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + */ + private function _IdentificationVariableDeclaration() + { + $rangeVariableDeclaration = $this->_RangeVariableDeclaration(); + $indexBy = $this->_isNextToken(Doctrine_ORM_Query_Token::T_INDEX) ? + $this->_IndexBy() : null; + $joinVariableDeclarations = array(); + while ( + $this->_isNextToken(Doctrine_ORM_Query_Token::T_LEFT) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_INNER) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_JOIN) + ) { + $joinVariableDeclarations[] = $this->_JoinVariableDeclaration(); + } + + return new Doctrine_ORM_Query_AST_IdentificationVariableDeclaration( + $rangeVariableDeclaration, $indexBy, $joinVariableDeclarations + ); + } + + /** + * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + */ + private function _RangeVariableDeclaration() + { + $abstractSchemaName = $this->_AbstractSchemaName(); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AS)) { + $this->match(Doctrine_ORM_Query_Token::T_AS); + } + $aliasIdentificationVariable = $this->_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); + + return new Doctrine_ORM_Query_AST_RangeVariableDeclaration( + $classMetadata, $aliasIdentificationVariable + ); + } + + /** + * AbstractSchemaName ::= identifier + */ + private function _AbstractSchemaName() + { + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + return $this->token['value']; + } + + /** + * AliasIdentificationVariable = identifier + */ + private function _AliasIdentificationVariable() + { + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + return $this->token['value']; + } + + /** + * Special rule that acceps all kinds of path expressions. + */ + private function _PathExpression() + { + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $parts = array($this->token['value']); + while ($this->_isNextToken('.')) { + $this->match('.'); + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $parts[] = $this->token['value']; + } + $pathExpression = new Doctrine_ORM_Query_AST_PathExpression($parts); + + return $pathExpression; + } + + /** + * Special rule that acceps all kinds of path expressions. and defers their + * semantical checking until the FROM part has been parsed completely (joins inclusive). + * Mainly used for path expressions in the SelectExpressions. + */ + private function _PathExpressionInSelect() + { + $expr = $this->_PathExpression(); + $this->_pendingPathExpressionsInSelect[] = $expr; + return $expr; + } + + /** + * JoinVariableDeclaration ::= Join [IndexBy] + */ + private function _JoinVariableDeclaration() + { + $join = $this->_Join(); + $indexBy = $this->_isNextToken(Doctrine_ORM_Query_Token::T_INDEX) ? + $this->_IndexBy() : null; + return new Doctrine_ORM_Query_AST_JoinVariableDeclaration($join, $indexBy); + } + + /** + * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] + */ + private function _Join() + { + // Check Join type + $joinType = Doctrine_ORM_Query_AST_Join::JOIN_TYPE_INNER; + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_LEFT)) { + $this->match(Doctrine_ORM_Query_Token::T_LEFT); + // Possible LEFT OUTER join + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_OUTER)) { + $this->match(Doctrine_ORM_Query_Token::T_OUTER); + $joinType = Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFTOUTER; + } else { + $joinType = Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFT; + } + } else if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_INNER)) { + $this->match(Doctrine_ORM_Query_Token::T_INNER); + } + + $this->match(Doctrine_ORM_Query_Token::T_JOIN); + + $joinPathExpression = $this->_JoinPathExpression(); + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AS)) { + $this->match(Doctrine_ORM_Query_Token::T_AS); + } + + $aliasIdentificationVariable = $this->_AliasIdentificationVariable(); + + // Verify that the association exists, if yes update the ParserResult + // with the new component. + $parentComp = $this->_parserResult->getQueryComponent($joinPathExpression->getIdentificationVariable()); + $parentClass = $parentComp['metadata']; + $assocField = $joinPathExpression->getAssociationField(); + if ( ! $parentClass->hasAssociation($assocField)) { + $this->semanticalError("Class " . $parentClass->getClassName() . + " has no association named '$assocField'."); + } + $targetClassName = $parentClass->getAssociationMapping($assocField)->getTargetEntityName(); + + // Building queryComponent + $joinQueryComponent = array( + 'metadata' => $this->_em->getClassMetadata($targetClassName), + 'parent' => $joinPathExpression->getIdentificationVariable(), + 'relation' => $parentClass->getAssociationMapping($assocField), + 'map' => null, + 'scalar' => null, + ); + $this->_parserResult->setQueryComponent($aliasIdentificationVariable, $joinQueryComponent); + + // Create AST node + $join = new Doctrine_ORM_Query_AST_Join($joinType, $joinPathExpression, $aliasIdentificationVariable); + + // Check Join where type + if ( + $this->_isNextToken(Doctrine_ORM_Query_Token::T_ON) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_WITH) + ) { + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_ON)) { + $this->match(Doctrine_ORM_Query_Token::T_ON); + $join->setWhereType(Doctrine_ORM_Query_AST_Join::JOIN_WHERE_ON); + } else { + $this->match(Doctrine_ORM_Query_Token::T_WITH); + } + $join->setConditionalExpression($this->_ConditionalExpression()); + } + + return $join; + } + + /*private function _JoinAssociationPathExpression() { + if ($this->_isSingleValuedPathExpression()) { + return $this->_JoinSingleValuedAssociationPathExpression(); + } else { + return $this->_JoinCollectionValuedPathExpression(); + } + }*/ + + /*private function _isSingleValuedPathExpression() + { + $parserResult = $this->_parserResult; + + // Trying to recoginize this grammar: + // IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + $token = $this->lookahead; + $this->_scanner->resetPeek(); + if ($parserResult->hasQueryComponent($token['value'])) { + $queryComponent = $parserResult->getQueryComponent($token['value']); + $peek = $this->_scanner->peek(); + if ($peek['value'] === '.') { + $peek2 = $this->_scanner->peek(); + if ($queryComponent['metadata']->hasAssociation($peek2['value']) && + $queryComponent['metadata']->getAssociationMapping($peek2['value'])->isOneToOne()) { + return true; + } + } + } + return false; + }*/ + + /** + * JoinPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + */ + private function _JoinPathExpression() + { + $identificationVariable = $this->_IdentificationVariable(); + $this->match('.'); + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $assocField = $this->token['value']; + return new Doctrine_ORM_Query_AST_JoinPathExpression( + $identificationVariable, $assocField + ); + } + + /** + * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + */ + private function _IndexBy() + { + $this->match(Doctrine_ORM_Query_Token::T_INDEX); + $this->match(Doctrine_ORM_Query_Token::T_BY); + $pathExp = $this->_SimpleStateFieldPathExpression(); + // Add the INDEX BY info to the query component + $qComp = $this->_parserResult->getQueryComponent($pathExp->getIdentificationVariable()); + $qComp['map'] = $pathExp->getSimpleStateField(); + $this->_parserResult->setQueryComponent($pathExp->getIdentificationVariable(), $qComp); + return $pathExp; + } + + /** + * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + */ + private function _SimpleStateFieldPathExpression() + { + $identificationVariable = $this->_IdentificationVariable(); + $this->match('.'); + $this->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $simpleStateField = $this->token['value']; + return new Doctrine_ORM_Query_AST_SimpleStateFieldPathExpression($identificationVariable, $simpleStateField); + } + + /** + * AggregateExpression ::= + * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedAssociationPathExpression | StateFieldPathExpression) ")" + */ + private function _AggregateExpression() + { + $isDistinct = false; + $functionName = ''; + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_COUNT)) { + $this->match(Doctrine_ORM_Query_Token::T_COUNT); + $functionName = $this->token['value']; + $this->match('('); + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_DISTINCT)) { + $this->match(Doctrine_ORM_Query_Token::T_DISTINCT); + $isDistinct = true; + } + // For now we only support a PathExpression here... + $pathExp = $this->_PathExpression(); + $this->match(')'); + } else if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AVG)) { + $this->match(Doctrine_ORM_Query_Token::T_AVG); + $functionName = $this->token['value']; + $this->match('('); + //... + } else { + $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); + } + return new Doctrine_ORM_Query_AST_AggregateExpression($functionName, $pathExp, $isDistinct); + } + + /** + * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + * GroupByItem ::= SingleValuedPathExpression + */ + private function _GroupByClause() + { + $this->match(Doctrine_ORM_Query_Token::T_GROUP); + $this->match(Doctrine_ORM_Query_Token::T_BY); + $groupByItems = array(); + $groupByItems[] = $this->_PathExpression(); + while ($this->_isNextToken(',')) { + $this->match(','); + $groupByItems[] = $this->_PathExpression(); + } + return new Doctrine_ORM_Query_AST_GroupByClause($groupByItems); + } + + /** + * WhereClause ::= "WHERE" ConditionalExpression + */ + private function _WhereClause() + { + $this->match(Doctrine_ORM_Query_Token::T_WHERE); + return new Doctrine_ORM_Query_AST_WhereClause($this->_ConditionalExpression()); + } + + /** + * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + */ + private function _ConditionalExpression() + { + $conditionalTerms = array(); + $conditionalTerms[] = $this->_ConditionalTerm(); + while ($this->_isNextToken(Doctrine_ORM_Query_Token::T_OR)) { + $this->match(Doctrine_ORM_Query_Token::T_OR); + $conditionalTerms[] = $this->_ConditionalTerm(); + } + return new Doctrine_ORM_Query_AST_ConditionalExpression($conditionalTerms); + } + + /** + * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + */ + private function _ConditionalTerm() + { + $conditionalFactors = array(); + $conditionalFactors[] = $this->_ConditionalFactor(); + while ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AND)) { + $this->match(Doctrine_ORM_Query_Token::T_AND); + $conditionalFactors[] = $this->_ConditionalFactor(); + } + return new Doctrine_ORM_Query_AST_ConditionalTerm($conditionalFactors); + } + + /** + * ConditionalFactor ::= ["NOT"] ConditionalPrimary + */ + private function _ConditionalFactor() + { + $not = false; + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_NOT)) { + $this->match(Doctrine_ORM_Query_Token::T_NOT); + $not = true; + } + return new Doctrine_ORM_Query_AST_ConditionalFactor($this->_ConditionalPrimary(), $not); + } + + /** + * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + */ + private function _ConditionalPrimary() + { + $condPrimary = new Doctrine_ORM_Query_AST_ConditionalPrimary; + if ($this->_isNextToken('(')) { + $this->match('('); + $conditionalExpression = $this->_ConditionalExpression(); + $this->match(')'); + $condPrimary->setConditionalExpression($conditionalExpression); + } else { + $condPrimary->setSimpleConditionalExpression($this->_SimpleConditionalExpression()); + } + return $condPrimary; + } + + /** + * SimpleConditionalExpression ::= ExistsExpression | + * (SimpleStateFieldPathExpression (ComparisonExpression | BetweenExpression | LikeExpression | + * InExpression | NullComparisonExpression)) | + * (CollectionValuedPathExpression EmptyCollectionComparisonExpression) | + * (EntityExpression CollectionMemberExpression) + */ + private function _SimpleConditionalExpression() + { + if ($this->_getExpressionType() === Doctrine_ORM_Query_Token::T_EXISTS) { + return $this->_ExistsExpression(); + } + + // For now... just SimpleStateFieldPathExpression + $leftExpression = $this->_SimpleStateFieldPathExpression(); + + switch ($this->_getExpressionType()) { + case Doctrine_ORM_Query_Token::T_NONE: + // [TODO] Check out ticket #935 to understand what will be done with enumParams + $rightExpression = $this->_ComparisonExpression(); + break; + case Doctrine_ORM_Query_Token::T_BETWEEN: + $rightExpression = $this->_BetweenExpression(); + break; + + case Doctrine_ORM_Query_Token::T_LIKE: + $rightExpression = $this->_LikeExpression(); + break; + + case Doctrine_ORM_Query_Token::T_IN: + $rightExpression = $this->_InExpression(); + break; + + case Doctrine_ORM_Query_Token::T_IS: + $rightExpression = $this->_NullComparisonExpression(); + break; + + case Doctrine_ORM_Query_Token::T_ALL: + case Doctrine_ORM_Query_Token::T_ANY: + case Doctrine_ORM_Query_Token::T_SOME: + $rightExpression = $this->_QuantifiedExpression(); + break; + + default: + $message = "BETWEEN, LIKE, IN, IS, quantified (ALL, ANY or SOME) " + . "or comparison (=, <, <=, <>, >, >=, !=)"; + $this->syntaxError($message); + break; + } + + + } + + private function _getExpressionType() + { + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_NOT)) { + $token = $this->_scanner->peek(); + $this->_scanner->resetPeek(); + } else { + $token = $this->lookahead; + } + return $token['type']; + } + + private function _ComparisonExpression() + { + var_dump($this->lookahead); } } diff --git a/lib/Doctrine/ORM/Query/Parser/SelectExpression.php b/lib/Doctrine/ORM/Query/Parser/SelectExpression.php index dd53576f3..ae944d006 100644 --- a/lib/Doctrine/ORM/Query/Parser/SelectExpression.php +++ b/lib/Doctrine/ORM/Query/Parser/SelectExpression.php @@ -91,14 +91,7 @@ class Doctrine_ORM_Query_Parser_SelectExpression extends Doctrine_ORM_Query_Pars // We have an identifier here if ($token['type'] === Doctrine_ORM_Query_Token::T_IDENTIFIER) { - $token = $this->_parser->getScanner()->peek(); - - // If we have a dot ".", then next char must be the "*" - if ($token['type'] === Doctrine_ORM_Query_Token::T_DOT) { - $token = $this->_parser->getScanner()->peek(); - - return $token['value'] === '*'; - } + return true; } return false; diff --git a/lib/Doctrine/ORM/Query/Parser/SelectStatement.php b/lib/Doctrine/ORM/Query/Parser/SelectStatement.php index b3ffd084c..2b1dff3fb 100644 --- a/lib/Doctrine/ORM/Query/Parser/SelectStatement.php +++ b/lib/Doctrine/ORM/Query/Parser/SelectStatement.php @@ -44,9 +44,9 @@ class Doctrine_ORM_Query_Parser_SelectStatement extends Doctrine_ORM_Query_Parse // Disable the semantical check for SelectClause now. This is needed // since we dont know the query components yet (will be known only // when the FROM and WHERE clause are processed). - $this->_dataHolder->set('semanticalCheck', false); + //$this->_dataHolder->set('semanticalCheck', false); $this->_selectClause = $this->parse('SelectClause'); - $this->_dataHolder->remove('semanticalCheck'); + //$this->_dataHolder->remove('semanticalCheck'); $this->_AST->setFromClause($this->parse('FromClause')); diff --git a/lib/Doctrine/ORM/Query/ParserResult.php b/lib/Doctrine/ORM/Query/ParserResult.php index 88a74dfec..487cf44ff 100644 --- a/lib/Doctrine/ORM/Query/ParserResult.php +++ b/lib/Doctrine/ORM/Query/ParserResult.php @@ -38,14 +38,6 @@ class Doctrine_ORM_Query_ParserResult extends Doctrine_ORM_Query_AbstractResult */ protected $_em; - /** - * A simple array keys representing table aliases and values table alias - * seeds. The seeds are used for generating short table aliases. - * - * @var array $_tableAliasSeeds - */ - protected $_tableAliasSeeds = array(); - /** * Simple array of keys representing the fields used in query. * @@ -154,33 +146,4 @@ class Doctrine_ORM_Query_ParserResult extends Doctrine_ORM_Query_AbstractResult { return isset($this->_queryFields[$fieldAlias]); } - - - /** - * Generates a table alias from given table name and associates - * it with given component alias - * - * @param string $componentName Component name to be associated with generated table alias - * @return string Generated table alias - */ - public function generateTableAlias($componentName) - { - $baseAlias = strtolower(preg_replace('/[^A-Z]/', '\\1', $componentName)); - - // We may have a situation where we have all chars are lowercased - if ( $baseAlias == '' ) { - // We simply grab the first 2 chars of component name - $baseAlias = substr($componentNam, 0, 2); - } - - $alias = $baseAlias; - - if ( ! isset($this->_tableAliasSeeds[$baseAlias])) { - $this->_tableAliasSeeds[$baseAlias] = 1; - } else { - $alias .= $this->_tableAliasSeeds[$baseAlias]++; - } - - return $alias; - } } diff --git a/lib/Doctrine/ORM/Query/ParserRule.php b/lib/Doctrine/ORM/Query/ParserRule.php index 70960fea6..04e3b8d77 100644 --- a/lib/Doctrine/ORM/Query/ParserRule.php +++ b/lib/Doctrine/ORM/Query/ParserRule.php @@ -107,40 +107,35 @@ abstract class Doctrine_ORM_Query_ParserRule * * @param string $RuleName BNF Grammar Rule name * @param array $paramHolder Production parameter holder - * @return Doctrine_ORM_Query_ParserRule + * @return Doctrine_ORM_Query_AST The constructed subtree during parsing. */ - public function parse($RuleName) + public function parse($ruleName) { - $BNFGrammarRule = $this->_getGrammarRule($RuleName); - - //echo "Processing class: " . get_class($BNFGrammarRule) . "...\n"; - //echo "Params: " . var_export($paramHolder, true) . "\n"; + echo $ruleName . PHP_EOL; + return $this->_getGrammarRule($ruleName)->syntax(); // Syntax check - if ( ! $this->_dataHolder->has('syntaxCheck') || $this->_dataHolder->get('syntaxCheck') === true) { + /*if ( ! $this->_dataHolder->has('syntaxCheck') || $this->_dataHolder->get('syntaxCheck') === true) { //echo "Processing syntax checks of " . $RuleName . "...\n"; - - $return = $BNFGrammarRule->syntax(); - - if ($return !== null) { - //echo "Returning Gramma Rule class: " . (is_object($return) ? get_class($return) : $return) . "...\n"; - - return $return; + $ASTNode = $BNFGrammarRule->syntax(); + if ($ASTNode !== null) { + //echo "Returning Grammar Rule class: " . (is_object($ASTNode) ? get_class($ASTNode) : $ASTNode) . "...\n"; + return $ASTNode; } - } + }*/ // Semantical check - if ( ! $this->_dataHolder->has('semanticalCheck') || $this->_dataHolder->get('semanticalCheck') === true) { - //echo "Processing semantical checks of " . $RuleName . "...\n"; + /*if ( ! $this->_dataHolder->has('semanticalCheck') || $this->_dataHolder->get('semanticalCheck') === true) { + echo "Processing semantical checks of " . $RuleName . "...\n"; $return = $BNFGrammarRule->semantical(); if ($return !== null) { - //echo "Returning Gramma Rule class: " . (is_object($return) ? get_class($return) : $return) . "...\n"; + echo "Returning Grammar Rule class: " . (is_object($return) ? get_class($return) : $return) . "...\n"; return $return; } - } + }*/ return $BNFGrammarRule; } @@ -178,15 +173,6 @@ abstract class Doctrine_ORM_Query_ParserRule public function AST($AstName) { $class = 'Doctrine_ORM_Query_AST_' . $AstName; - - //echo $class . "\r\n"; - - if ( ! class_exists($class)) { - throw new Doctrine_ORM_Query_Parser_Exception( - "Unknown AST node '" . $AstName . "'. Could not find related compiler class." - ); - } - return new $class($this->_parser->getParserResult()); } diff --git a/lib/Doctrine/ORM/Query/SqlBuilder.php b/lib/Doctrine/ORM/Query/SqlBuilder.php deleted file mode 100644 index 943a3d0e8..000000000 --- a/lib/Doctrine/ORM/Query/SqlBuilder.php +++ /dev/null @@ -1,65 +0,0 @@ -. - */ - -/** - * The SqlBuilder. Creates SQL out of an AST. - * - * INTERNAL: For platform-specific SQL, the platform of the connection should be used. - * ($this->_connection->getDatabasePlatform()) - * - * @author Guilherme Blanco - * @author Janne Vanhala - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link http://www.phpdoctrine.org - * @since 2.0 - * @version $Revision$ - */ -class Doctrine_ORM_Query_SqlBuilder -{ - protected $_em; - protected $_conn; - - public function __construct(Doctrine_ORM_EntityManager $em) - { - $this->_em = $em; - $this->_conn = $this->_em->getConnection(); - } - - /** - * Retrieves the assocated Doctrine_Connection to this object. - * - * @return Doctrine_Connection - */ - public function getConnection() - { - return $this->_connection; - } - - /** - * @nodoc - */ - public function quoteIdentifier($identifier) - { - return $this->_conn->quoteIdentifier($identifier); - } - -} diff --git a/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php b/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php deleted file mode 100644 index b3aae8366..000000000 --- a/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php +++ /dev/null @@ -1,36 +0,0 @@ -. - */ - -/** - * MySql class of Sql Builder object - * - * @author Guilherme Blanco - * @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_ORM_Query_SqlBuilder_Mysql extends Doctrine_ORM_Query_SqlBuilder -{ - -} diff --git a/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php b/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php deleted file mode 100644 index 060539750..000000000 --- a/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php +++ /dev/null @@ -1,36 +0,0 @@ -. - */ - -/** - * Sqlite class of Sql Builder object - * - * @author Guilherme Blanco - * @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_ORM_Query_SqlBuilder_Sqlite extends Doctrine_ORM_Query_SqlBuilder -{ - -} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php b/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php index 402df2b03..cbed42684 100644 --- a/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php +++ b/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php @@ -30,15 +30,10 @@ */ abstract class Doctrine_ORM_Query_SqlExecutor_Abstract implements Serializable { - // [TODO] Remove me later! - //public $AST; - protected $_sqlStatements; - public function __construct(Doctrine_ORM_Query_AST $AST) + public function __construct(Doctrine_ORM_Query_AST $AST, $sqlWalker) { - // [TODO] Remove me later! - //$this->AST = $AST; } /** @@ -66,7 +61,7 @@ abstract class Doctrine_ORM_Query_SqlExecutor_Abstract implements Serializable * @param Doctrine_ORM_Query_AST $AST The root node of the AST. * @return Doctrine_ORM_Query_SqlExecutor_Abstract The executor that is suitable for the given AST. */ - public static function create(Doctrine_ORM_Query_AST $AST) + public static function create(Doctrine_ORM_Query_AST $AST, $sqlWalker) { $isDeleteStatement = $AST instanceof Doctrine_ORM_Query_AST_DeleteStatement; $isUpdateStatement = $AST instanceof Doctrine_ORM_Query_AST_UpdateStatement; @@ -84,7 +79,7 @@ abstract class Doctrine_ORM_Query_SqlExecutor_Abstract implements Serializable */ return new Doctrine_ORM_Query_SqlExecutor_SingleTableDeleteUpdate($AST); } else { - return new Doctrine_ORM_Query_SqlExecutor_SingleSelect($AST); + return new Doctrine_ORM_Query_SqlExecutor_SingleSelect($AST, $sqlWalker); } } diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php b/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php index abd6242d6..4a63631fb 100644 --- a/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php +++ b/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php @@ -30,10 +30,10 @@ */ class Doctrine_ORM_Query_SqlExecutor_SingleSelect extends Doctrine_ORM_Query_SqlExecutor_Abstract { - public function __construct(Doctrine_ORM_Query_AST $AST) + public function __construct(Doctrine_ORM_Query_AST_SelectStatement $AST, $sqlWalker) { - parent::__construct($AST); - $this->_sqlStatements = $AST->buildSql(); + parent::__construct($AST, $sqlWalker); + $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } public function execute(Doctrine_DBAL_Connection $conn, array $params) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php new file mode 100644 index 000000000..a210229f5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -0,0 +1,254 @@ +_em = $em; + $this->_parserResult = $parserResult; + $sqlToDqlAliasMap = array(); + foreach ($parserResult->getQueryComponents() as $dqlAlias => $qComp) { + if ($dqlAlias != 'dctrn') { + $sqlAlias = $this->generateTableAlias($qComp['metadata']->getClassName()); + $sqlToDqlAliasMap[$sqlAlias] = $dqlAlias; + } + } + // SQL => DQL alias stored in ParserResult, needed for hydration. + $parserResult->setTableAliasMap($sqlToDqlAliasMap); + // DQL => SQL alias stored only locally, needed for SQL construction. + $this->_dqlToSqlAliasMap = array_flip($sqlToDqlAliasMap); + } + + public function walkSelectStatement(Doctrine_ORM_Query_AST_SelectStatement $AST) + { + $sql = $this->walkSelectClause($AST->getSelectClause()); + $sql .= $this->walkFromClause($AST->getFromClause()); + $sql .= $AST->getGroupByClause() ? $this->walkGroupByClause($AST->getGroupByClause()) : ''; + //... more clauses + return $sql; + } + + public function walkSelectClause($selectClause) + { + return 'SELECT ' . (($selectClause->isDistinct()) ? 'DISTINCT ' : '') + . implode(', ', array_map(array(&$this, 'walkSelectExpression'), + $selectClause->getSelectExpressions())); + } + + public function walkFromClause($fromClause) + { + $sql = ' FROM '; + $identificationVarDecls = $fromClause->getIdentificationVariableDeclarations(); + $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; + } + + /** + * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. + * + * @param JoinVariableDeclaration $joinVarDecl + * @return string + */ + public function walkJoinVariableDeclaration($joinVarDecl) + { + $join = $joinVarDecl->getJoin(); + $joinType = $join->getJoinType(); + if ($joinType == Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFT || + $joinType == Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFTOUTER) { + $sql = ' LEFT JOIN '; + } else { + $sql = ' INNER JOIN '; + } + + $joinAssocPathExpr = $join->getJoinAssociationPathExpression(); + $sourceQComp = $this->_parserResult->getQueryComponent($joinAssocPathExpr->getIdentificationVariable()); + $targetQComp = $this->_parserResult->getQueryComponent($join->getAliasIdentificationVariable()); + $targetTableName = $targetQComp['metadata']->getTableName(); + $targetTableAlias = $this->_dqlToSqlAliasMap[$join->getAliasIdentificationVariable()]; + $sourceTableAlias = $this->_dqlToSqlAliasMap[$joinAssocPathExpr->getIdentificationVariable()]; + + $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; + + if ( ! $targetQComp['relation']->isOwningSide()) { + $assoc = $targetQComp['metadata']->getAssociationMapping($targetQComp['relation']->getMappedByFieldName()); + } else { + $assoc = $targetQComp['relation']; + } + + if ($targetQComp['relation']->isOneToOne() || $targetQComp['relation']->isOneToMany()) { + $joinColumns = $assoc->getSourceToTargetKeyColumns(); + $first = true; + foreach ($joinColumns as $sourceColumn => $targetColumn) { + if ( ! $first) $sql .= ' AND '; + if ($targetQComp['relation']->isOwningSide()) { + $sql .= "$sourceTableAlias.$sourceColumn = $targetTableAlias.$targetColumn"; + } else { + $sql .= "$sourceTableAlias.$targetColumn = $targetTableAlias.$sourceColumn"; + } + } + } else { // ManyToMany + //TODO + } + + return $sql; + } + + /** + * Walks down a SelectExpression AST node and generates the corresponding SQL. + * + * @param $selectExpression + * @return string + */ + public function walkSelectExpression($selectExpression) + { + $sql = ''; + if ($selectExpression->getExpression() instanceof Doctrine_ORM_Query_AST_PathExpression) { + $pathExpression = $selectExpression->getExpression(); + if ($pathExpression->isSimpleStateFieldPathExpression()) { + $parts = $pathExpression->getParts(); + $numParts = count($parts); + $dqlAlias = $parts[0]; + $fieldName = $parts[$numParts-1]; + $qComp = $this->_parserResult->getQueryComponent($dqlAlias); + $class = $qComp['metadata']; + + if ($numParts > 2) { + for ($i = 1; $i < $numParts-1; ++$i) { + //TODO + } + } + + $sqlTableAlias = $this->_dqlToSqlAliasMap[$dqlAlias]; + $sql .= $sqlTableAlias . '.' . $class->getColumnName($fieldName) . + ' AS ' . $sqlTableAlias . '__' . $class->getColumnName($fieldName); + } else if ($pathExpression->isSimpleStateFieldAssociationPathExpression()) { + echo "HERE!!"; + } else { + throw new Doctrine_ORM_Query_Exception("Encountered invalid PathExpression during SQL construction."); + } + } + else if ($selectExpression->getExpression() instanceof Doctrine_ORM_Query_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() . '('; + $sql .= $this->_dqlToSqlAliasMap[$dqlAlias] . '.' . $columnName; + $sql .= ') AS dctrn__' . $alias; + } + //TODO: else if Subselect + else { + $dqlAlias = $selectExpression->getExpression(); + $queryComp = $this->_parserResult->getQueryComponent($dqlAlias); + $class = $queryComp['metadata']; + + $sqlTableAlias = $this->_dqlToSqlAliasMap[$dqlAlias]; + $beginning = true; + foreach ($class->getFieldMappings() as $fieldName => $fieldMapping) { + if ($beginning) { + $beginning = false; + } else { + $sql .= ', '; + } + $sql .= $sqlTableAlias . '.' . $fieldMapping['columnName'] . + ' AS ' . $sqlTableAlias . '__' . $fieldMapping['columnName']; + } + } + return $sql; + } + + public function walkGroupByClause($groupByClause) + { + return ' GROUP BY ' + . implode(', ', array_map(array(&$this, 'walkGroupByItem'), + $groupByClause->getGroupByItems())); + } + + public function walkGroupByItem($pathExpr) + { + //TODO: support general SingleValuedPathExpression, not just state field + $parts = $pathExpr->getParts(); + $qComp = $this->_parserResult->getQueryComponent($parts[0]); + $columnName = $qComp['metadata']->getColumnName($parts[1]); + return $this->_dqlToSqlAliasMap[$parts[0]] . '.' . $columnName; + } + + public function walkUpdateStatement(Doctrine_ORM_Query_AST_UpdateStatement $AST) + { + + } + + public function walkDeleteStatement(Doctrine_ORM_Query_AST_DeleteStatement $AST) + { + + } + + /** + * Generates an SQL table alias from given table name and associates + * it with given component alias + * + * @param string $componentName Component name to be associated with generated table alias + * @return string Generated table alias + */ + public function generateTableAlias($componentName) + { + $baseAlias = strtolower(preg_replace('/[^A-Z]/', '\\1', $componentName)); + + // We may have a situation where we have all chars are lowercased + if ($baseAlias == '') { + // We simply grab the first 2 chars of component name + $baseAlias = substr($componentNam, 0, 2); + } + + $alias = $baseAlias; + + if ( ! isset($this->_tableAliasSeeds[$baseAlias])) { + $this->_tableAliasSeeds[$baseAlias] = 1; + } else { + $alias .= $this->_tableAliasSeeds[$baseAlias]++; + } + + return $alias; + } +} + diff --git a/query-language.txt b/query-language.txt index d5ff9507d..21bf1b41b 100644 --- a/query-language.txt +++ b/query-language.txt @@ -54,7 +54,7 @@ AliasIdentificationVariable :: = identifier /* identifier that must be a class name (the "User" of "FROM User u") */ AbstractSchemaName ::= identifier -/* identifier that must be a field (the "name" of "u.name") */ +/* identifier that must be a field (the "name" of "u.name") */ /* This is responsable to know if the field exists in Object, no matter if it's a relation or a simple field */ FieldIdentificationVariable ::= identifier @@ -67,49 +67,49 @@ SingleValuedAssociationField ::= FieldIdentificationVariable /* identifier that must be an embedded class state field (for the future) */ EmbeddedClassStateField ::= FieldIdentificationVariable -/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ +/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ SimpleStateField ::= FieldIdentificationVariable - -/* identifier that must be unique among other mapped field aliases (the "total" of "COUNT(*) AS total")*/ + +/* identifier that must be unique among other mapped field aliases (the "total" of "COUNT(*) AS total")*/ FieldAliasIdentificationVariable = identifier - + /* * PATH EXPRESSIONS - */ - + */ + /* "u.Group" or "u.Phonenumbers" declarations */ JoinAssociationPathExpression ::= JoinCollectionValuedPathExpression | JoinSingleValuedAssociationPathExpression - -/* "u.Phonenumbers" */ + +/* "u.Phonenumbers" */ JoinCollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField - -/* "u.Group" */ -JoinSingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField - -/* "u.Group" or "u.Phonenumbers" usages */ -AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression - -/* "u.name" or "u.Group" */ -SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression - -/* "u.name" or "u.Group.name" */ -StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression - + /* "u.Group" */ -SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField - +JoinSingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField + +/* "u.Group" or "u.Phonenumbers" usages */ +AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + +/* "u.name" or "u.Group" */ +SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + +/* "u.name" or "u.Group.name" */ +StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression + +/* "u.Group" */ +SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField + /* "u.Group.Permissions" */ -CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField - +CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField + /* "name" */ StateField ::= {EmbeddedClassStateField "."}* SimpleStateField - -/* "u.name" */ -SimpleStateFieldPathExpression ::= IdentificationVariable "." SimpleStateField - -/* "u.Group.name" */ + +/* "u.name" or "u.address.zip" (address = EmbeddedClassStateField) */ +SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField + +/* "u.Group.name" */ SimpleStateFieldAssociationPathExpression ::= SingleValuedAssociationPathExpression "." StateField @@ -156,7 +156,7 @@ IndexBy ::= "INDEX" "BY" SimpleStateFieldPath /* * SELECT EXPRESSION */ -SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | +SelectExpression ::= IdentificationVariable | StateFieldPathExpression | (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] SimpleSelectExpression ::= SingleValuedPathExpression | IdentificationVariable | AggregateExpression @@ -164,21 +164,23 @@ SimpleSelectExpression ::= SingleValuedPathExpression | IdentificationVariable | /* * CONDITIONAL EXPRESSIONS */ -ConditionalExpression ::= ConditionalTerm | ConditionalExpression "OR" ConditionalTerm -ConditionalTerm ::= ConditionalFactor | ConditionalTerm "AND" ConditionalFactor +ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* +ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* ConditionalFactor ::= ["NOT"] ConditionalPrimary ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" -SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | - InExpression | NullComparisonExpression | ExistsExpression | - EmptyCollectionComparisonExpression | CollectionMemberExpression +SimpleConditionalExpression ::= ExistsExpression | + (SimpleStateFieldPathExpression (ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression)) | + (CollectionValuedPathExpression EmptyCollectionComparisonExpression) | + (EntityExpression CollectionMemberExpression) /* EmptyCollectionComparisonExpression and CollectionMemberExpression are for the future */ /* * COLLECTION EXPRESSIONS (FOR THE FUTURE) */ -EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" -CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression +EmptyCollectionComparisonExpression ::= "IS" ["NOT"] "EMPTY" +CollectionMemberExpression ::= ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression /* @@ -199,8 +201,8 @@ NamedParameter ::= ":" string * ARITHMETIC EXPRESSIONS */ ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" -SimpleArithmeticExpression ::= ArithmeticTerm | SimpleArithmeticExpression ("+"|"-") ArithmeticTerm -ArithmeticTerm ::= ArithmeticFactor | ArithmeticTerm ("*" |"/") ArithmeticFactor +SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* +ArithmeticTerm ::= ArithmeticFactor {("*" |"/") ArithmeticFactor}* ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary ArithmeticPrimary ::= StateFieldPathExpression | Literal | "(" SimpleArithmeticExpression ")" | Function | AggregateExpression diff --git a/tests/Orm/Query/AllTests.php b/tests/Orm/Query/AllTests.php index 2a4b45713..ea6034886 100755 --- a/tests/Orm/Query/AllTests.php +++ b/tests/Orm/Query/AllTests.php @@ -25,12 +25,13 @@ class Orm_Query_AllTests $suite = new Doctrine_TestSuite('Doctrine Orm Query'); $suite->addTestSuite('Orm_Query_IdentifierRecognitionTest'); - /*$suite->addTestSuite('Orm_Query_LanguageRecognitionTest'); + $suite->addTestSuite('Orm_Query_SelectSqlGenerationTest'); + /* + $suite->addTestSuite('Orm_Query_LanguageRecognitionTest'); $suite->addTestSuite('Orm_Query_ScannerTest'); $suite->addTestSuite('Orm_Query_DqlGenerationTest'); $suite->addTestSuite('Orm_Query_DeleteSqlGenerationTest'); - $suite->addTestSuite('Orm_Query_UpdateSqlGenerationTest'); - $suite->addTestSuite('Orm_Query_SelectSqlGenerationTest');*/ + $suite->addTestSuite('Orm_Query_UpdateSqlGenerationTest');*/ return $suite; } diff --git a/tests/Orm/Query/IdentifierRecognitionTest.php b/tests/Orm/Query/IdentifierRecognitionTest.php index 535ca456f..47166bc68 100755 --- a/tests/Orm/Query/IdentifierRecognitionTest.php +++ b/tests/Orm/Query/IdentifierRecognitionTest.php @@ -24,8 +24,6 @@ require_once 'lib/DoctrineTestInit.php'; /** * Test case for testing the saving and referencing of query identifiers. * - * @package Doctrine - * @subpackage Query * @author Guilherme Blanco * @author Janne Vanhala * @author Konsta Vesterinen @@ -46,7 +44,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase public function testSingleAliasDeclarationIsSupported() { $entityManager = $this->_em; - $query = $entityManager->createQuery('SELECT u.* FROM CmsUser u'); + $query = $entityManager->createQuery('SELECT u FROM CmsUser u'); $parserResult = $query->parse(); $decl = $parserResult->getQueryComponent('u'); @@ -61,7 +59,7 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase public function testSingleAliasDeclarationWithIndexByIsSupported() { $entityManager = $this->_em; - $query = $entityManager->createQuery('SELECT u.* FROM CmsUser u INDEX BY u.id'); + $query = $entityManager->createQuery('SELECT u FROM CmsUser u INDEX BY u.id'); $parserResult = $query->parse(); $decl = $parserResult->getQueryComponent('u'); @@ -76,12 +74,12 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase public function testQueryParserSupportsMultipleAliasDeclarations() { $entityManager = $this->_em; - $query = $entityManager->createQuery('SELECT u.* FROM CmsUser u INDEX BY u.id LEFT JOIN u.phonenumbers p'); + $query = $entityManager->createQuery('SELECT u FROM CmsUser u INDEX BY u.id LEFT JOIN u.phonenumbers p'); $parserResult = $query->parse(); $decl = $parserResult->getQueryComponent('u'); - $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); + $this->assertTrue($decl['metadata'] instanceof Doctrine_ORM_Mapping_ClassMetadata); $this->assertEquals(null, $decl['relation']); $this->assertEquals(null, $decl['parent']); $this->assertEquals(null, $decl['scalar']); @@ -89,8 +87,8 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase $decl = $parserResult->getQueryComponent('p'); - $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Association); + $this->assertTrue($decl['metadata'] instanceof Doctrine_ORM_Mapping_ClassMetadata); + $this->assertTrue($decl['relation'] instanceof Doctrine_ORM_Mapping_AssociationMapping); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals(null, $decl['map']); @@ -100,12 +98,12 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase public function testQueryParserSupportsMultipleAliasDeclarationsWithIndexBy() { $entityManager = $this->_em; - $query = $entityManager->createQuery('SELECT u.* FROM CmsUser u INDEX BY u.id LEFT JOIN u.articles a INNER JOIN u.phonenumbers pn INDEX BY pn.phonenumber'); + $query = $entityManager->createQuery('SELECT u FROM CmsUser u INDEX BY u.id LEFT JOIN u.articles a INNER JOIN u.phonenumbers pn INDEX BY pn.phonenumber'); $parserResult = $query->parse(); $decl = $parserResult->getQueryComponent('u'); - $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); + $this->assertTrue($decl['metadata'] instanceof Doctrine_ORM_Mapping_ClassMetadata); $this->assertEquals(null, $decl['relation']); $this->assertEquals(null, $decl['parent']); $this->assertEquals(null, $decl['scalar']); @@ -113,16 +111,16 @@ class Orm_Query_IdentifierRecognitionTest extends Doctrine_OrmTestCase $decl = $parserResult->getQueryComponent('a'); - $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Association); + $this->assertTrue($decl['metadata'] instanceof Doctrine_ORM_Mapping_ClassMetadata); + $this->assertTrue($decl['relation'] instanceof Doctrine_ORM_Mapping_AssociationMapping); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals(null, $decl['map']); $decl = $parserResult->getQueryComponent('pn'); - $this->assertTrue($decl['metadata'] instanceof Doctrine_ClassMetadata); - $this->assertTrue($decl['relation'] instanceof Doctrine_Association); + $this->assertTrue($decl['metadata'] instanceof Doctrine_ORM_Mapping_ClassMetadata); + $this->assertTrue($decl['relation'] instanceof Doctrine_ORM_Mapping_AssociationMapping); $this->assertEquals('u', $decl['parent']); $this->assertEquals(null, $decl['scalar']); $this->assertEquals('phonenumber', $decl['map']); diff --git a/tests/Orm/Query/SelectSqlGenerationTest.php b/tests/Orm/Query/SelectSqlGenerationTest.php index 25bfad84b..0f81ac658 100755 --- a/tests/Orm/Query/SelectSqlGenerationTest.php +++ b/tests/Orm/Query/SelectSqlGenerationTest.php @@ -37,18 +37,20 @@ require_once 'lib/DoctrineTestInit.php'; */ class Orm_Query_SelectSqlGenerationTest extends Doctrine_OrmTestCase { + private $_em; + + protected function setUp() { + $this->_em = $this->_getTestEntityManager(); + } + public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) { try { - $entityManager = $this->_em; - $query = $entityManager->createQuery($dqlToBeTested); - //echo print_r($query->parse()->getQueryFields(), true) . "\n"; - + $query = $this->_em->createQuery($dqlToBeTested); parent::assertEquals($sqlToBeConfirmed, $query->getSql()); - //echo $query->getSql() . "\n"; - $query->free(); } catch (Doctrine_Exception $e) { + echo $e->getTraceAsString(); die(); $this->fail($e->getMessage()); } } @@ -57,71 +59,65 @@ class Orm_Query_SelectSqlGenerationTest extends Doctrine_OrmTestCase public function testPlainFromClauseWithoutAlias() { $this->assertSqlGeneration( - 'SELECT * FROM CmsUser', - 'SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name FROM cms_user cu WHERE 1 = 1' + 'SELECT u FROM CmsUser u', + 'SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name FROM CmsUser cu' ); $this->assertSqlGeneration( - 'SELECT id FROM CmsUser', - 'SELECT cu.id AS cu__id FROM cms_user cu WHERE 1 = 1' + 'SELECT u.id FROM CmsUser u', + 'SELECT cu.id AS cu__id FROM CmsUser cu' ); } - - public function testPlainFromClauseWithAlias() - { - $this->assertSqlGeneration( - 'SELECT u.id FROM CmsUser u', - 'SELECT cu.id AS cu__id FROM cms_user cu WHERE 1 = 1' - ); - } - - - public function testSelectSingleComponentWithAsterisk() - { - $this->assertSqlGeneration( - 'SELECT u.* FROM CmsUser u', - 'SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name FROM cms_user cu WHERE 1 = 1' - ); - } - - public function testSelectSingleComponentWithMultipleColumns() { $this->assertSqlGeneration( 'SELECT u.username, u.name FROM CmsUser u', - 'SELECT cu.username AS cu__username, cu.name AS cu__name FROM cms_user cu WHERE 1 = 1' + 'SELECT cu.username AS cu__username, cu.name AS cu__name FROM CmsUser cu' ); } - - public function testSelectMultipleComponentsWithAsterisk() + public function testSelectWithCollectionAssociationJoin() { $this->assertSqlGeneration( - 'SELECT u.*, p.* FROM CmsUser u, u.phonenumbers p', - 'SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name, cp.user_id AS cp__user_id, cp.phonenumber AS cp__phonenumber FROM cms_user cu, cms_phonenumber cp WHERE 1 = 1' + 'SELECT u, p FROM CmsUser u JOIN u.phonenumbers p', + 'SELECT cu.id AS cu__id, cu.status AS cu__status, cu.username AS cu__username, cu.name AS cu__name, cp.phonenumber AS cp__phonenumber FROM CmsUser cu INNER JOIN CmsPhonenumber cp ON cu.id = cp.user_id' ); } + public function testSelectWithSingleValuedAssociationJoin() + { + $this->assertSqlGeneration( + 'SELECT u, a FROM ForumUser u JOIN u.avatar a', + 'SELECT fu.id AS fu__id, fu.username AS fu__username, fa.id AS fa__id FROM ForumUser fu INNER JOIN ForumAvatar fa ON fu.avatar_id = fa.id' + ); + } public function testSelectDistinctIsSupported() { $this->assertSqlGeneration( 'SELECT DISTINCT u.name FROM CmsUser u', - 'SELECT DISTINCT cu.name AS cu__name FROM cms_user cu WHERE 1 = 1' + 'SELECT DISTINCT cu.name AS cu__name FROM CmsUser cu' ); } - public function testAggregateFunctionInSelect() { $this->assertSqlGeneration( 'SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id', - 'SELECT COUNT(cu.id) AS dctrn__0 FROM cms_user cu WHERE 1 = 1 GROUP BY cu.id' + 'SELECT COUNT(cu.id) AS dctrn__0 FROM CmsUser cu GROUP BY cu.id' ); } - public function testAggregateFunctionWithDistinctInSelect() + /* public function testWhereClauseInSelect() + { + $this->assertSqlGeneration( + 'select u from ForumUser u where u.id = ?', + 'SELECT fu.id AS fu__id, fu.username AS fu__username FROM ForumUser fu WHERE fu.id = ?' + ); + } +*/ +/* public function testAggregateFunctionWithDistinctInSelect() { $this->assertSqlGeneration( 'SELECT COUNT(DISTINCT u.name) FROM CmsUser u', @@ -209,5 +205,5 @@ class Orm_Query_SelectSqlGenerationTest extends Doctrine_OrmTestCase 'SELECT cu.id AS cu__id, ca.id AS ca__id FROM cms_user cu INNER JOIN cms_article ca ON cu.id = ca.user_id WHERE 1 = 1' ); } - +*/ } \ No newline at end of file