From 227667c95db065d8c72f5055ef1bb4bc63374648 Mon Sep 17 00:00:00 2001 From: guilhermeblanco Date: Sat, 18 Jul 2009 14:53:21 +0000 Subject: [PATCH] [2.0] Changes in DQL grammar for optimization purposes. Implemented new DQL grammar rules and did a couple of TODOs --- lib/Doctrine/ORM/AbstractQuery.php | 4 +- lib/Doctrine/ORM/Query/AST/PathExpression.php | 12 ++- lib/Doctrine/ORM/Query/Parser.php | 97 +++++++++++++------ lib/Doctrine/ORM/Query/SqlWalker.php | 4 +- .../ORM/Query/LanguageRecognitionTest.php | 13 ++- 5 files changed, 88 insertions(+), 42 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index a9dc16ae3..fa5789c5d 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -392,7 +392,7 @@ abstract class AbstractQuery * FALSE is returned. * * @param string $name The name of the hint. - * @return mixed The value of the hint or FALSe, if the hint name is not recognized. + * @return mixed The value of the hint or FALSE, if the hint name is not recognized. */ public function getHint($name) { @@ -404,7 +404,7 @@ abstract class AbstractQuery * iterated over the result. * * @param array $params The query parameters. - * @param integer $hydrationMode The hydratio mode to use. + * @param integer $hydrationMode The hydration mode to use. * @return IterableResult */ public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index d5ef759ef..a8833f6f6 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -26,9 +26,10 @@ namespace Doctrine\ORM\Query\AST; */ class PathExpression extends Node { - const TYPE_COLLECTION_VALUED_ASSOCIATION = 1; - const TYPE_SINGLE_VALUED_ASSOCIATION = 2; - const TYPE_STATE_FIELD = 4; + const TYPE_SINGLE_VALUED_PATH_EXPRESSION = 1; + const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; + const TYPE_SINGLE_VALUED_ASSOCIATION = 4; + const TYPE_STATE_FIELD = 8; private $_type; private $_identificationVariable; @@ -51,6 +52,11 @@ class PathExpression extends Node return $this->_parts; } + public function setType($type) + { + $this->_type = $type; + } + public function getType() { return $this->_type; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 6d578b077..378ffbfc4 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -415,6 +415,10 @@ class Parser case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION: $this->_validateCollectionValuedAssociationPathExpression($expr); break; + + case AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION: + $this->_validateSingleValuedPathExpression($expr); + break; default: $this->semanticalError('Encountered invalid PathExpression.'); @@ -660,7 +664,8 @@ class Parser // Deny hydration of partial objects if doctrine.forcePartialLoad query hint not defined if ( $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT && - ! $this->_em->getConfiguration()->getAllowPartialObjects() + ! $this->_em->getConfiguration()->getAllowPartialObjects() && + ! $this->_query->getHint('doctrine.forcePartialLoad') ) { throw DoctrineException::partialObjectsAreDangerous(); } @@ -891,9 +896,10 @@ class Parser { $pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); - if (count($pathExpr->getParts()) > 1) { - $this->syntaxError('SimpleStateFieldPathExpression'); - } + // @TODO It seems PathExpression already checks for the minimum amount of parts + // if (count($pathExpr->getParts()) > 1) { + // $this->syntaxError('SimpleStateFieldPathExpression'); + // } if ( ! empty($this->_deferredPathExpressionStacks)) { $exprStack = array_pop($this->_deferredPathExpressionStacks); @@ -947,6 +953,26 @@ class Parser return $pathExpr; } + + /** + * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + */ + public function SingleValuedPathExpression() + { + $pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION); + + if ( ! empty($this->_deferredPathExpressionStacks)) { + $exprStack = array_pop($this->_deferredPathExpressionStacks); + $exprStack[] = $pathExpr; + array_push($this->_deferredPathExpressionStacks, $exprStack); + + return $pathExpr; + } + + $this->_validateSingleValuedPathExpression($pathExpr); + + return $pathExpr; + } /** * Validates that the given PathExpression is a semantically correct @@ -982,7 +1008,7 @@ class Parser /** * Validates that the given PathExpression is a semantically correct - * SingleValuedPathExpression as specified in the grammar. + * SingleValuedAssociationPathExpression as specified in the grammar. * * @param PathExpression $pathExpression */ @@ -1010,6 +1036,22 @@ class Parser * @param PathExpression $pathExpression */ private function _validateStateFieldPathExpression(AST\PathExpression $pathExpression) + { + $stateFieldSeen = $this->_validateSingleValuedPathExpression($pathExpression); + + if ( ! $stateFieldSeen) { + $this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.'); + } + } + + /** + * Validates that the given PathExpression is a semantically correct + * SingleValuedPathExpression as specified in the grammar. + * + * @param PathExpression $pathExpression + * @return boolean + */ + private function _validateSingleValuedPathExpression(AST\PathExpression $pathExpression) { $class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata']; $stateFieldSeen = false; @@ -1027,10 +1069,16 @@ class Parser $this->semanticalError('SingleValuedAssociationField or StateField expected.'); } } + + // We need to force the type in PathExpression since SingleValuedPathExpression is in a + // state of recognition of what's is the dealed type (StateField or SingleValuedAssociation) + $pathExpression->setType( + ($stateFieldSeen) + ? AST\PathExpression::TYPE_STATE_FIELD + : AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION + ); - if ( ! $stateFieldSeen) { - $this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.'); - } + return $stateFieldSeen; } /** @@ -1042,8 +1090,7 @@ class Parser */ public function PathExpression($type) { - $this->match(Lexer::T_IDENTIFIER); - $identificationVariable = $this->_lexer->token['value']; + $identificationVariable = $this->IdentificationVariable(); $parts = array(); @@ -1081,15 +1128,11 @@ class Parser return new AST\InputParameter($this->_lexer->token['value']); } - $this->match(Lexer::T_IDENTIFIER); - - return $this->_lexer->token['value']; + return $this->IdentificationVariable(); } /** * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" - * - * @todo Implementation incomplete for SingleValuedPathExpression. */ public function NullComparisonExpression() { @@ -1097,8 +1140,7 @@ class Parser $this->match(Lexer::T_INPUT_PARAMETER); $expr = new AST\InputParameter($this->_lexer->token['value']); } else { - //TODO: Support SingleValuedAssociationPathExpression - $expr = $this->StateFieldPathExpression(); + $expr = $this->SingleValuedPathExpression(); } $nullCompExpr = new AST\NullComparisonExpression($expr); @@ -1117,9 +1159,7 @@ class Parser /** * AggregateExpression ::= * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | - * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedAssociationPathExpression | StateFieldPathExpression) ")" - * - * @todo Implementation incomplete. Support for SingleValuedAssociationPathExpression. + * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" */ public function AggregateExpression() { @@ -1136,8 +1176,7 @@ class Parser $isDistinct = true; } - //TODO: Support SingleValuedAssociationPathExpression - $pathExp = $this->StateFieldPathExpression(); + $pathExp = $this->SingleValuedPathExpression(); $this->match(')'); } else { if ($this->_lexer->isNextToken(Lexer::T_AVG)) { @@ -1878,11 +1917,9 @@ class Parser } /** - * ArithmeticPrimary ::= StateFieldPathExpression | Literal | "(" SimpleArithmeticExpression ")" + * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings - * | FunctionsReturningDatetime | IdentificationVariable | SingleValuedAssociationPathExpression - * - * @todo IdentificationVariable | SingleValuedAssociationPathExpression + * | FunctionsReturningDatetime | IdentificationVariable */ public function ArithmeticPrimary() { @@ -1903,14 +1940,10 @@ class Parser } if ($peek['value'] == '.') { - //TODO: SingleValuedAssociationPathExpression - return $this->StateFieldPathExpression(); + return $this->SingleValuedPathExpression(); } - $identificationVariable = $this->_lexer->lookahead['value']; - $this->match($identificationVariable); - - return $identificationVariable; + return $this->IdentificationVariable(); case Lexer::T_INPUT_PARAMETER: $this->match($this->_lexer->lookahead['value']); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 86264f689..c00376467 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1065,7 +1065,7 @@ class SqlWalker implements TreeWalker if ($leftExpr instanceof AST\Node) { $sql .= $leftExpr->dispatch($this); } else { - $sql .= $leftExpr; //TODO: quote() + $sql .= $this->_conn->quote($leftExpr); } $sql .= ' ' . $compExpr->getOperator() . ' '; @@ -1073,7 +1073,7 @@ class SqlWalker implements TreeWalker if ($rightExpr instanceof AST\Node) { $sql .= $rightExpr->dispatch($this); } else { - $sql .= $rightExpr; //TODO: quote() + $sql .= $this->_conn->quote($rightExpr); } return $sql; diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index a59ce1b25..986af97fe 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -37,10 +37,15 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase } } - public function parseDql($dql) + public function parseDql($dql, $hints = array()) { $query = $this->_em->createQuery($dql); $query->setDql($dql); + + foreach ($hints as $key => $value) { + $query->setHint($key, $value); + } + $parser = new \Doctrine\ORM\Query\Parser($query); $parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker); @@ -339,8 +344,10 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase { $oldValue = $this->_em->getConfiguration()->getAllowPartialObjects(); $this->_em->getConfiguration()->setAllowPartialObjects(false); - - $this->parseDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u'); + + $this->parseDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u', array( + 'doctrine.forcePartialLoad' => false + )); $this->_em->getConfiguration()->setAllowPartialObjects($oldValue); }