diff --git a/lib/Doctrine/Common/DoctrineException.php b/lib/Doctrine/Common/DoctrineException.php
index 994d79fe9..725638650 100644
--- a/lib/Doctrine/Common/DoctrineException.php
+++ b/lib/Doctrine/Common/DoctrineException.php
@@ -124,10 +124,6 @@ class DoctrineException extends \Exception
if ( ! self::$_messages) {
// Lazy-init messages
self::$_messages = array(
- 'DoctrineException#partialObjectsAreDangerous' =>
- "Loading partial objects is dangerous. Fetch full objects or consider " .
- "using a different fetch mode. If you really want partial objects, " .
- "set the doctrine.forcePartialLoad query hint to TRUE.",
'QueryException#nonUniqueResult' =>
"The query contains more than one result."
);
diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
index 2fe8acf22..8ba3b18ac 100644
--- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
+++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php
@@ -79,6 +79,9 @@ class ArrayHydrator extends AbstractHydrator
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
+ if (empty($rowData)) {
+ ++$this->_resultCounter;
+ }
}
// 2) Now hydrate the data found in the current row.
@@ -129,7 +132,7 @@ class ArrayHydrator extends AbstractHydrator
}
end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
- key($baseElement[$relationAlias]);
+ key($baseElement[$relationAlias]);
}
} else if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = array();
@@ -177,6 +180,10 @@ class ArrayHydrator extends AbstractHydrator
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
+ /*if ($this->_rsm->isMixed) {
+ $result[] =& $result[$index];
+ ++$this->_resultCounter;
+ }*/
}
$this->updateResultPointer($result, $index, $dqlAlias, false);
}
diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
index 1eee52e6e..7a364bb25 100644
--- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
+++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
@@ -251,6 +251,9 @@ class ObjectHydrator extends AbstractHydrator
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
+ if (empty($rowData)) {
+ ++$this->_resultCounter;
+ }
}
// Hydrate the data chunks
@@ -409,14 +412,18 @@ class ObjectHydrator extends AbstractHydrator
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
++$this->_resultCounter;
}
-
+
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
-
+
} else {
// Update result pointer
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$this->_resultPointers[$dqlAlias] = $result[$index];
+ /*if ($this->_rsm->isMixed) {
+ $result[] = $result[$index];
+ ++$this->_resultCounter;
+ }*/
}
}
}
diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php
index 32f96ab4a..4a0cf3480 100644
--- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php
+++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php
@@ -278,7 +278,7 @@ class OneToOneMapping extends AssociationMapping
/**
* @internal Experimental. For MetaModel API, Doctrine 2.1 or later.
*/
- public static function __set_state(array $state)
+ /*public static function __set_state(array $state)
{
$assoc = new self(array());
$assoc->isOptional = $state['isOptional'];
@@ -302,5 +302,5 @@ class OneToOneMapping extends AssociationMapping
$assoc->sourceFieldName = $state['sourceFieldName'];
return $assoc;
- }
+ }*/
}
diff --git a/lib/Doctrine/ORM/Query/AST/ASTException.php b/lib/Doctrine/ORM/Query/AST/ASTException.php
new file mode 100644
index 000000000..7472572e4
--- /dev/null
+++ b/lib/Doctrine/ORM/Query/AST/ASTException.php
@@ -0,0 +1,13 @@
+identificationVariable = $identificationVariable;
+ $this->partialFieldSet = $partialFieldSet;
+ }
+}
\ No newline at end of file
diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php
index 3fff56fc3..b956291d5 100644
--- a/lib/Doctrine/ORM/Query/Lexer.php
+++ b/lib/Doctrine/ORM/Query/Lexer.php
@@ -88,7 +88,6 @@ class Lexer extends \Doctrine\Common\Lexer
const T_NULL = 145;
const T_OF = 146;
const T_OFFSET = 147;
- const T_ON = 148;
const T_OPEN_PARENTHESIS = 149;
const T_OR = 150;
const T_ORDER = 151;
@@ -104,8 +103,9 @@ class Lexer extends \Doctrine\Common\Lexer
const T_UPDATE = 161;
const T_WHERE = 162;
const T_WITH = 163;
-
- private $_keywordsTable;
+ const T_PARTIAL = 164;
+ const T_OPEN_CURLY_BRACE = 165;
+ const T_CLOSE_CURLY_BRACE = 166;
/**
* Creates a new query scanner object.
@@ -151,7 +151,7 @@ class Lexer extends \Doctrine\Common\Lexer
$value = $newVal;
return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
- ? self::T_FLOAT : self::T_INTEGER;
+ ? self::T_FLOAT : self::T_INTEGER;
}
if ($value[0] === "'") {
@@ -176,6 +176,8 @@ class Lexer extends \Doctrine\Common\Lexer
case '*': return self::T_MULTIPLY;
case '/': return self::T_DIVIDE;
case '!': return self::T_NEGATE;
+ case '{': return self::T_OPEN_CURLY_BRACE;
+ case '}': return self::T_CLOSE_CURLY_BRACE;
default:
// Do nothing
break;
@@ -186,7 +188,7 @@ class Lexer extends \Doctrine\Common\Lexer
}
/**
- * @todo Doc
+ * @todo Inline this method.
*/
private function _getNumeric($value)
{
@@ -206,6 +208,7 @@ class Lexer extends \Doctrine\Common\Lexer
*
* @param string $identifier identifier name
* @return int token type
+ * @todo Inline this method.
*/
private function _checkLiteral($identifier)
{
diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php
index 864848248..cc4faffc1 100644
--- a/lib/Doctrine/ORM/Query/Parser.php
+++ b/lib/Doctrine/ORM/Query/Parser.php
@@ -21,11 +21,11 @@
namespace Doctrine\ORM\Query;
-use Doctrine\Common\DoctrineException,
+use Doctrine\Common\DoctrineException, //TODO: Remove
Doctrine\ORM\Query;
/**
- * An LL(*) parser for the context-free grammar of the Doctrine Query Language.
+ * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates an AST.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
@@ -68,10 +68,11 @@ class Parser
/**
* Expressions that were encountered during parsing of identifiers and expressions
* and still need to be validated.
- *
- * @var array
*/
- private $_deferredExpressionsStack = array();
+ private $_deferredIdentificationVariables = array();
+ private $_deferredPartialObjectExpressions = array();
+ private $_deferredPathExpressions = array();
+ private $_deferredResultVariables = array();
/**
* The lexer.
@@ -233,7 +234,8 @@ class Parser
* error.
*
* @param int|string token type or value
- * @return bool True, if tokens match; false otherwise.
+ * @return void
+ * @throws QueryException If the tokens dont match.
*/
public function match($token)
{
@@ -274,9 +276,19 @@ class Parser
// Parse & build AST
$AST = $this->QueryLanguage();
- // Activate semantical checks after this point. Process all deferred checks in pipeline
- $this->_processDeferredExpressionsStack($AST);
-
+ // Process any deferred validations of some nodes in the AST.
+ // This also allows post-processing of the AST for modification purposes.
+ $this->_processDeferredIdentificationVariables();
+ if ($this->_deferredPartialObjectExpressions) {
+ $this->_processDeferredPartialObjectExpressions();
+ }
+ if ($this->_deferredPathExpressions) {
+ $this->_processDeferredPathExpressions($AST);
+ }
+ if ($this->_deferredResultVariables) {
+ $this->_processDeferredResultVariables();
+ }
+
if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) {
$this->_customTreeWalkers = $customWalkers;
}
@@ -317,8 +329,8 @@ class Parser
/**
* Generates a new syntax error.
*
- * @param string $expected Optional expected string.
- * @param array $token Optional token.
+ * @param string $expected Expected string.
+ * @param array $token Got token.
*
* @throws \Doctrine\ORM\Query\QueryException
*/
@@ -483,146 +495,101 @@ class Parser
return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
}
-
- /**
- * Subscribe expression to be validated.
- *
- * @param mixed $expression
- * @param string $method
- * @param array $token
- * @param integer $nestingLevel
- */
- private function _subscribeExpression($expression, $method, $token, $nestingLevel = null)
- {
- $nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
-
- $exprStack = array(
- 'method' => $method,
- 'expression' => $expression,
- 'nestingLevel' => $nestingLevel,
- 'token' => $token,
- );
-
- array_push($this->_deferredExpressionsStack, $exprStack);
- }
- /**
- * Processes the topmost stack of deferred path expressions.
- *
- * @param mixed $AST
- */
- private function _processDeferredExpressionsStack($AST)
- {
- foreach ($this->_deferredExpressionsStack as $item) {
- $method = '_validate' . $item['method'];
-
- $this->$method($item, $AST);
- }
- }
-
-
/**
* Validates that the given IdentificationVariable is a semantically correct.
* It must exist in query components list.
*
- * @param array $deferredItem
- * @param mixed $AST
- *
- * @return array Query Component
+ * @return void
*/
- private function _validateIdentificationVariable($deferredItem, $AST)
+ private function _processDeferredIdentificationVariables()
{
- $identVariable = $deferredItem['expression'];
-
- // Check if IdentificationVariable exists in queryComponents
- if ( ! isset($this->_queryComponents[$identVariable])) {
- $this->semanticalError(
+ foreach ($this->_deferredIdentificationVariables as $deferredItem) {
+ $identVariable = $deferredItem['expression'];
+
+ // Check if IdentificationVariable exists in queryComponents
+ if ( ! isset($this->_queryComponents[$identVariable])) {
+ $this->semanticalError(
"'$identVariable' is not defined.", $deferredItem['token']
- );
- }
-
- $qComp = $this->_queryComponents[$identVariable];
-
- // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
- if ( ! isset($qComp['metadata'])) {
- $this->semanticalError(
+ );
+ }
+
+ $qComp = $this->_queryComponents[$identVariable];
+
+ // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
+ if ( ! isset($qComp['metadata'])) {
+ $this->semanticalError(
"'$identVariable' does not point to a Class.", $deferredItem['token']
- );
- }
-
- // Validate if identification variable nesting level is lower or equal than the current one
- if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
- $this->semanticalError(
+ );
+ }
+
+ // Validate if identification variable nesting level is lower or equal than the current one
+ if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
+ $this->semanticalError(
"'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
- );
+ );
+ }
+ }
+ }
+
+ private function _processDeferredPartialObjectExpressions()
+ {
+ foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
+ $expr = $deferredItem['expression'];
+ $class = $this->_queryComponents[$expr->identificationVariable]['metadata'];
+ foreach ($expr->partialFieldSet as $field) {
+ if ( ! isset($class->fieldMappings[$field])) {
+ $this->semanticalError(
+ "There is no mapped field named '$field' on class " . $class->name . ".",
+ $deferredItem['token']
+ );
+ }
+ }
+ if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
+ $this->semanticalError(
+ "The partial field selection of class " . $class->name . " must contain the identifier.",
+ $deferredItem['token']
+ );
+ }
}
-
- return $qComp;
}
/**
* Validates that the given ResultVariable is a semantically correct.
* It must exist in query components list.
*
- * @param array $deferredItem
- * @param mixed $AST
- *
- * @return array Query Component
+ * @return void
*/
- private function _validateResultVariable($deferredItem, $AST)
+ private function _processDeferredResultVariables()
{
- $resultVariable = $deferredItem['expression'];
-
- // Check if ResultVariable exists in queryComponents
- if ( ! isset($this->_queryComponents[$resultVariable])) {
- $this->semanticalError(
+ foreach ($this->_deferredResultVariables as $deferredItem) {
+ $resultVariable = $deferredItem['expression'];
+
+ // Check if ResultVariable exists in queryComponents
+ if ( ! isset($this->_queryComponents[$resultVariable])) {
+ $this->semanticalError(
"'$resultVariable' is not defined.", $deferredItem['token']
- );
- }
-
- $qComp = $this->_queryComponents[$resultVariable];
-
- // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
- if ( ! isset($qComp['resultVariable'])) {
- $this->semanticalError(
+ );
+ }
+
+ $qComp = $this->_queryComponents[$resultVariable];
+
+ // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
+ if ( ! isset($qComp['resultVariable'])) {
+ $this->semanticalError(
"'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
- );
- }
-
- // Validate if identification variable nesting level is lower or equal than the current one
- if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
- $this->semanticalError(
+ );
+ }
+
+ // Validate if identification variable nesting level is lower or equal than the current one
+ if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
+ $this->semanticalError(
"'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
- );
+ );
+ }
}
-
- return $qComp;
}
-
- /**
- * Validates that the given JoinAssociationPathExpression is a semantically correct.
- *
- * @param array $deferredItem
- * @param mixed $AST
- *
- * @return array Query Component
- */
- private function _validateJoinAssociationPathExpression($deferredItem, $AST)
- {
- $pathExpression = $deferredItem['expression'];
- $qComp = $this->_queryComponents[$pathExpression->identificationVariable];;
-
- // Validating association field (*-to-one or *-to-many)
- $field = $pathExpression->associationField;
- $class = $qComp['metadata'];
-
- if ( ! isset($class->associationMappings[$field])) {
- $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
- }
-
- return $qComp;
- }
-
+
/**
* Validates that the given PathExpression is a semantically correct for grammar rules:
*
@@ -634,136 +601,135 @@ class Parser
*
* @param array $deferredItem
* @param mixed $AST
- *
- * @return integer
*/
- private function _validatePathExpression($deferredItem, $AST)
+ private function _processDeferredPathExpressions($AST)
{
- $pathExpression = $deferredItem['expression'];
- $parts = $pathExpression->parts;
- $numParts = count($parts);
-
- $qComp = $this->_queryComponents[$pathExpression->identificationVariable];
-
- $aliasIdentificationVariable = $pathExpression->identificationVariable;
- $parentField = $pathExpression->identificationVariable;
- $class = $qComp['metadata'];
- $fieldType = null;
- $curIndex = 0;
-
- foreach ($parts as $field) {
- // Check if it is not in a state field
- if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
- $this->semanticalError(
+ foreach ($this->_deferredPathExpressions as $deferredItem) {
+ $pathExpression = $deferredItem['expression'];
+ $parts = $pathExpression->parts;
+ $numParts = count($parts);
+
+ $qComp = $this->_queryComponents[$pathExpression->identificationVariable];
+
+ $aliasIdentificationVariable = $pathExpression->identificationVariable;
+ $parentField = $pathExpression->identificationVariable;
+ $class = $qComp['metadata'];
+ $fieldType = null;
+ $curIndex = 0;
+
+ foreach ($parts as $field) {
+ // Check if it is not in a state field
+ if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
+ $this->semanticalError(
'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
$deferredItem['token']
- );
- }
-
- // Check if it is not a collection field
- if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
- $this->semanticalError(
+ );
+ }
+
+ // Check if it is not a collection field
+ if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
+ $this->semanticalError(
'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
$deferredItem['token']
- );
- }
-
- // Check if field or association exists
- if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
- $this->semanticalError(
+ );
+ }
+
+ // Check if field or association exists
+ if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
+ $this->semanticalError(
'Class ' . $class->name . ' has no field or association named ' . $field,
$deferredItem['token']
- );
- }
-
- $parentField = $field;
-
- if (isset($class->fieldMappings[$field])) {
- $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
- } else {
- $assoc = $class->associationMappings[$field];
- $class = $this->_em->getClassMetadata($assoc->targetEntityName);
+ );
+ }
- if (
+ $parentField = $field;
+
+ if (isset($class->fieldMappings[$field])) {
+ $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
+ } else {
+ $assoc = $class->associationMappings[$field];
+ $class = $this->_em->getClassMetadata($assoc->targetEntityName);
+
+ if (
($curIndex != $numParts - 1) &&
! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
- ) {
- // Building queryComponent
- $joinQueryComponent = array(
+ ) {
+ // Building queryComponent
+ $joinQueryComponent = array(
'metadata' => $class,
'parent' => $aliasIdentificationVariable,
'relation' => $assoc,
'map' => null,
'nestingLevel' => $this->_nestingLevel,
'token' => $deferredItem['token'],
- );
+ );
- // Create AST node
- $joinVariableDeclaration = new AST\JoinVariableDeclaration(
+ // Create AST node
+ $joinVariableDeclaration = new AST\JoinVariableDeclaration(
new AST\Join(
- AST\Join::JOIN_TYPE_INNER,
- new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
- $aliasIdentificationVariable . '.' . $field
+ AST\Join::JOIN_TYPE_INNER,
+ new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
+ $aliasIdentificationVariable . '.' . $field,
+ false
),
null
- );
- $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
-
- $this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
- }
-
- $aliasIdentificationVariable .= '.' . $field;
-
- if ($assoc->isOneToOne()) {
- $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
- } else {
- $fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
- }
- }
-
- $curIndex++;
- }
-
- // Validate if PathExpression is one of the expected types
- $expectedType = $pathExpression->expectedType;
+ );
+ $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
- if ( ! ($expectedType & $fieldType)) {
- // We need to recognize which was expected type(s)
- $expectedStringTypes = array();
-
- // Validate state field type
- if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
- $expectedStringTypes[] = 'StateFieldPathExpression';
+ $this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
+ }
+
+ $aliasIdentificationVariable .= '.' . $field;
+
+ if ($assoc->isOneToOne()) {
+ $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
+ } else {
+ $fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
+ }
+ }
+
+ ++$curIndex;
}
-
- // Validate single valued association (*-to-one)
- if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
- $expectedStringTypes[] = 'SingleValuedAssociationField';
+
+ // Validate if PathExpression is one of the expected types
+ $expectedType = $pathExpression->expectedType;
+
+ if ( ! ($expectedType & $fieldType)) {
+ // We need to recognize which was expected type(s)
+ $expectedStringTypes = array();
+
+ // Validate state field type
+ if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
+ $expectedStringTypes[] = 'StateFieldPathExpression';
+ }
+
+ // Validate single valued association (*-to-one)
+ if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
+ $expectedStringTypes[] = 'SingleValuedAssociationField';
+ }
+
+ // Validate single valued association (*-to-many)
+ if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
+ $expectedStringTypes[] = 'CollectionValuedAssociationField';
+ }
+
+ // Build the error message
+ $semanticalError = 'Invalid PathExpression. ';
+
+ if (count($expectedStringTypes) == 1) {
+ $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
+ } else {
+ $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
+ }
+
+ $this->semanticalError($semanticalError, $deferredItem['token']);
}
-
- // Validate single valued association (*-to-many)
- if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
- $expectedStringTypes[] = 'CollectionValuedAssociationField';
- }
-
- // Build the error message
- $semanticalError = 'Invalid PathExpression. ';
-
- if (count($expectedStringTypes) == 1) {
- $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
- } else {
- $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
- }
-
- $this->semanticalError($semanticalError, $deferredItem['token']);
+
+ // We need to force the type in PathExpression
+ $pathExpression->type = $fieldType;
}
-
- // We need to force the type in PathExpression
- $pathExpression->type = $fieldType;
-
- return $fieldType;
}
-
+
/**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
*
@@ -777,25 +743,26 @@ class Parser
switch ($this->_lexer->lookahead['type']) {
case Lexer::T_SELECT:
- return $this->SelectStatement();
-
+ $statement = $this->SelectStatement();
+ break;
case Lexer::T_UPDATE:
- return $this->UpdateStatement();
-
+ $statement = $this->UpdateStatement();
+ break;
case Lexer::T_DELETE:
- return $this->DeleteStatement();
-
+ $statement = $this->DeleteStatement();
+ break;
default:
$this->syntaxError('SELECT, UPDATE or DELETE');
break;
}
-
+
// Check for end of string
if ($this->_lexer->lookahead !== null) {
$this->syntaxError('end of string');
}
+
+ return $statement;
}
-
/**
* SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
@@ -805,7 +772,7 @@ class Parser
public function SelectStatement()
{
$selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
-
+
$selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null;
@@ -830,11 +797,11 @@ class Parser
{
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
$updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
- ? $this->WhereClause() : null;
+ ? $this->WhereClause() : null;
return $updateStatement;
}
-
+
/**
* DeleteStatement ::= DeleteClause [WhereClause]
*
@@ -844,12 +811,11 @@ class Parser
{
$deleteStatement = new AST\DeleteStatement($this->DeleteClause());
$deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
- ? $this->WhereClause() : null;
+ ? $this->WhereClause() : null;
return $deleteStatement;
}
-
-
+
/**
* IdentificationVariable ::= identifier
*
@@ -860,20 +826,16 @@ class Parser
$this->match(Lexer::T_IDENTIFIER);
$identVariable = $this->_lexer->token['value'];
-
- // Defer IdentificationVariable validation
- $exprStack = array(
- 'method' => 'IdentificationVariable',
+
+ $this->_deferredIdentificationVariables[] = array(
'expression' => $identVariable,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->token,
);
- array_push($this->_deferredExpressionsStack, $exprStack);
-
return $identVariable;
}
-
+
/**
* AliasIdentificationVariable = identifier
*
@@ -894,7 +856,7 @@ class Parser
return $aliasIdentVariable;
}
-
+
/**
* AbstractSchemaName ::= identifier
*
@@ -913,7 +875,7 @@ class Parser
return $schemaName;
}
-
+
/**
* AliasResultVariable ::= identifier
*
@@ -934,7 +896,7 @@ class Parser
return $resultVariable;
}
-
+
/**
* ResultVariable ::= identifier
*
@@ -947,14 +909,14 @@ class Parser
$resultVariable = $this->_lexer->token['value'];
// Defer ResultVariable validation
- $this->_subscribeExpression(
- $resultVariable, 'ResultVariable',
- $this->_lexer->token, $this->_nestingLevel
+ $this->_deferredResultVariables[] = array(
+ 'expression' => $resultVariable,
+ 'nestingLevel' => $this->_nestingLevel,
+ 'token' => $this->_lexer->token,
);
return $resultVariable;
}
-
/**
* JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
@@ -967,18 +929,19 @@ class Parser
$identVariable = $this->IdentificationVariable();
$this->match(Lexer::T_DOT);
+ //TODO: $this->match($this->_lexer->lookahead['value']);
$this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value'];
- $pathExpr = new AST\JoinAssociationPathExpression($identVariable, $field);
+ // Validate association field
+ $qComp = $this->_queryComponents[$identVariable];
+ $class = $qComp['metadata'];
- // Defer JoinAssociationPathExpression validation
- $this->_subscribeExpression(
- $pathExpr, 'JoinAssociationPathExpression',
- $token, $this->_nestingLevel
- );
-
- return $pathExpr;
+ if ( ! isset($class->associationMappings[$field])) {
+ $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
+ }
+
+ return new AST\JoinAssociationPathExpression($identVariable, $field);
}
/**
@@ -1007,14 +970,15 @@ class Parser
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
// Defer PathExpression validation if requested to be defered
- $this->_subscribeExpression(
- $pathExpr, 'PathExpression',
- $token, $this->_nestingLevel
+ $this->_deferredPathExpressions[] = array(
+ 'expression' => $pathExpr,
+ 'nestingLevel' => $this->_nestingLevel,
+ 'token' => $this->_lexer->token,
);
return $pathExpr;
}
-
+
/**
* AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
*
@@ -1027,7 +991,7 @@ class Parser
AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
);
}
-
+
/**
* SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
*
@@ -1040,7 +1004,7 @@ class Parser
AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
);
}
-
+
/**
* StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
*
@@ -1050,7 +1014,7 @@ class Parser
{
return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
}
-
+
/**
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
*
@@ -1060,7 +1024,7 @@ class Parser
{
return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
}
-
+
/**
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
*
@@ -1070,7 +1034,7 @@ class Parser
{
return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
}
-
+
/**
* SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
*
@@ -1091,7 +1055,6 @@ class Parser
return $pathExpression;
}
-
/**
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
*
@@ -1304,7 +1267,7 @@ class Parser
return new AST\GroupByClause($groupByItems);
}
-
+
/**
* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
*
@@ -1356,7 +1319,6 @@ class Parser
return $subselect;
}
-
/**
* UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
*
@@ -1403,13 +1365,10 @@ class Parser
if ($glimpse['value'] != '.') {
$token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable();
-
- // Validate if IdentificationVariable is defined
- $this->_validateIdentificationVariable($identVariable, null, $token);
-
+
return $identVariable;
}
-
+
return $this->SingleValuedPathExpression();
}
@@ -1472,7 +1431,6 @@ class Parser
return $this->SimpleArithmeticExpression();
}
-
/**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
*
@@ -1528,7 +1486,7 @@ class Parser
{
$join = $this->Join();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
- ? $this->IndexBy() : null;
+ ? $this->IndexBy() : null;
return new AST\JoinVariableDeclaration($join, $indexBy);
}
@@ -1536,12 +1494,12 @@ class Parser
/**
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
*
- * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
+ * @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
*/
public function RangeVariableDeclaration()
{
$abstractSchemaName = $this->AbstractSchemaName();
-
+
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
@@ -1557,18 +1515,55 @@ class Parser
'relation' => null,
'map' => null,
'nestingLevel' => $this->_nestingLevel,
- 'token' => $token,
+ 'token' => $token
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
}
+ /**
+ * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
+ * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
+ *
+ * @return array
+ */
+ public function PartialObjectExpression()
+ {
+ $this->match(Lexer::T_PARTIAL);
+
+ $partialFieldSet = array();
+
+ $identificationVariable = $this->IdentificationVariable();
+ $this->match(Lexer::T_DOT);
+
+ $this->match(Lexer::T_OPEN_CURLY_BRACE);
+ $this->match(Lexer::T_IDENTIFIER);
+ $partialFieldSet[] = $this->_lexer->token['value'];
+ while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
+ $this->match(Lexer::T_COMMA);
+ $this->match(Lexer::T_IDENTIFIER);
+ $partialFieldSet[] = $this->_lexer->token['value'];
+ }
+ $this->match(Lexer::T_CLOSE_CURLY_BRACE);
+
+ $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
+
+ // Defer PartialObjectExpression validation
+ $this->_deferredPartialObjectExpressions[] = array(
+ 'expression' => $partialObjectExpression,
+ 'nestingLevel' => $this->_nestingLevel,
+ 'token' => $this->_lexer->token,
+ );
+
+ return $partialObjectExpression;
+ }
+
/**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
- * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
+ * ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
*
- * @return \Doctrine\ORM\Query\AST\Join
+ * @return Doctrine\ORM\Query\AST\Join
*/
public function Join()
{
@@ -1590,7 +1585,7 @@ class Parser
}
$this->match(Lexer::T_JOIN);
-
+
$joinPathExpression = $this->JoinAssociationPathExpression();
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
@@ -1619,7 +1614,7 @@ class Parser
'relation' => $parentClass->getAssociationMapping($assocField),
'map' => null,
'nestingLevel' => $this->_nestingLevel,
- 'token' => $token,
+ 'token' => $token
);
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
@@ -1627,14 +1622,8 @@ class Parser
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
// Check for ad-hoc Join conditions
- if ($this->_lexer->isNextToken(Lexer::T_ON) || $this->_lexer->isNextToken(Lexer::T_WITH)) {
- if ($this->_lexer->isNextToken(Lexer::T_ON)) {
- $this->match(Lexer::T_ON);
- $join->whereType = AST\Join::JOIN_WHERE_ON;
- } else {
- $this->match(Lexer::T_WITH);
- }
-
+ if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
+ $this->match(Lexer::T_WITH);
$join->conditionalExpression = $this->ConditionalExpression();
}
@@ -1644,7 +1633,7 @@ class Parser
/**
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
*
- * @return \Doctrine\ORM\Query\AST\IndexBy
+ * @return Doctrine\ORM\Query\AST\IndexBy
*/
public function IndexBy()
{
@@ -1659,13 +1648,60 @@ class Parser
return $pathExp;
}
-
+ /**
+ * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
+ * StateFieldPathExpression | BooleanPrimary | CaseExpression |
+ * EntityTypeExpression
+ *
+ * @return mixed One of the possible expressions or subexpressions.
+ */
+ public function ScalarExpression()
+ {
+ $lookahead = $this->_lexer->lookahead['type'];
+ if ($lookahead === Lexer::T_IDENTIFIER) {
+ $this->_lexer->peek(); // lookahead => '.'
+ $this->_lexer->peek(); // lookahead => token after '.'
+ $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
+ $this->_lexer->resetPeek();
+
+ if ($peek['value'] == '+' || $peek['value'] == '-' || $peek['value'] == '/' || $peek['value'] == '*') {
+ return $this->SimpleArithmeticExpression();
+ }
+
+ return $this->StateFieldPathExpression();
+ } else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
+ return $this->SimpleArithmeticExpression();
+ } else if ($this->_isFunction()) {
+ return $this->FunctionDeclaration();
+ } else if ($lookahead == Lexer::T_STRING) {
+ return $this->StringPrimary();
+ } else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
+ return $this->InputParameter();
+ } else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
+ $this->match($lookahead);
+ return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
+ } else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
+ return $this->CaseExpression();
+ } else {
+ $this->syntaxError();
+ }
+ }
+
+ public function CaseExpression()
+ {
+ // if "CASE" "WHEN" => GeneralCaseExpression
+ // else if "CASE" => SimpleCaseExpression
+ // else if "COALESCE" => CoalesceExpression
+ // else if "NULLIF" => NullifExpression
+ $this->semanticalError('CaseExpression not yet supported.');
+ }
+
/**
* SelectExpression ::=
* IdentificationVariable | StateFieldPathExpression |
- * (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"] AliasResultVariable]
+ * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
*
- * @return \Doctrine\ORM\Query\AST\SelectExpression
+ * @return Doctrine\ORM\Query\AST\SelectExpression
*/
public function SelectExpression()
{
@@ -1673,22 +1709,46 @@ class Parser
$fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse();
- // First we recognize for an IdentificationVariable (DQL class alias)
- if ($peek['value'] != '.' && $peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
- $expression = $this->IdentificationVariable();
- } else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) {
- if ($isFunction) {
- if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
- $expression = $this->AggregateExpression();
- } else {
- $expression = $this->FunctionDeclaration();
- }
+ $supportsAlias = true;
+ if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
+ if ($peek['value'] == '.') {
+ // ScalarExpression
+ $expression = $this->ScalarExpression();
} else {
+ $supportsAlias = false;
+ $expression = $this->IdentificationVariable();
+ }
+ } else if ($this->_lexer->lookahead['value'] == '(') {
+ if ($peek['type'] == Lexer::T_SELECT) {
+ // Subselect
$this->match(Lexer::T_OPEN_PARENTHESIS);
$expression = $this->Subselect();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
+ } else {
+ // Shortcut: ScalarExpression => SimpleArithmeticExpression
+ $expression = $this->SimpleArithmeticExpression();
}
-
+ } else if ($this->_isFunction()) {
+ if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
+ $expression = $this->AggregateExpression();
+ } else {
+ // Shortcut: ScalarExpression => Function
+ $expression = $this->FunctionDeclaration();
+ }
+ } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
+ $supportsAlias = false;
+ $expression = $this->PartialObjectExpression();
+ } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
+ $this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
+ // Shortcut: ScalarExpression => SimpleArithmeticExpression
+ $expression = $this->SimpleArithmeticExpression();
+ } else {
+ $this->syntaxError('IdentificationVariable | StateFieldPathExpression'
+ . ' | AggregateExpression | "(" Subselect ")" | ScalarExpression',
+ $this->_lexer->lookahead);
+ }
+
+ if ($supportsAlias) {
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
@@ -1704,16 +1764,6 @@ class Parser
'token' => $token,
);
}
- } else {
- // Deny hydration of partial objects if doctrine.forcePartialLoad query hint not defined
- if (
- $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT &&
- ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)
- ) {
- throw DoctrineException::partialObjectsAreDangerous();
- }
-
- $expression = $this->StateFieldPathExpression();
}
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
@@ -1761,7 +1811,6 @@ class Parser
return $expr;
}
-
/**
* ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
*
@@ -1944,8 +1993,7 @@ class Parser
return $this->ComparisonExpression();
}
-
-
+
/**
* EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
*
@@ -1967,7 +2015,7 @@ class Parser
return $emptyColletionCompExpr;
}
-
+
/**
* CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
*
@@ -2001,7 +2049,6 @@ class Parser
return $collMemberExpr;
}
-
/**
* Literal ::= string | char | integer | float | boolean
*
@@ -2032,7 +2079,7 @@ class Parser
$this->syntaxError('Literal');
}
}
-
+
/**
* InParameter ::= Literal | InputParameter
*
@@ -2046,8 +2093,7 @@ class Parser
return $this->Literal();
}
-
-
+
/**
* InputParameter ::= PositionalParameter | NamedParameter
*
@@ -2059,8 +2105,7 @@ class Parser
return new AST\InputParameter($this->_lexer->token['value']);
}
-
-
+
/**
* ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
*
@@ -2103,7 +2148,7 @@ class Parser
$terms[] = $this->_lexer->token['value'];
$terms[] = $this->ArithmeticTerm();
}
-
+
return new AST\SimpleArithmeticExpression($terms);
}
@@ -2191,7 +2236,7 @@ class Parser
}
}
}
-
+
/**
* StringExpression ::= StringPrimary | "(" Subselect ")"
*
@@ -2259,7 +2304,7 @@ class Parser
return $this->SimpleEntityExpression();
}
-
+
/**
* SimpleEntityExpression ::= IdentificationVariable | InputParameter
*
@@ -2274,7 +2319,6 @@ class Parser
return $this->IdentificationVariable();
}
-
/**
* AggregateExpression ::=
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
@@ -2321,7 +2365,6 @@ class Parser
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
}
-
/**
* QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
*
@@ -2573,7 +2616,6 @@ class Parser
}
}
-
/**
* FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
*/
@@ -2623,7 +2665,7 @@ class Parser
return $function;
}
-
+
/**
* FunctionsReturningStrings ::=
* "CONCAT" "(" StringPrimary "," StringPrimary ")" |
diff --git a/lib/Doctrine/ORM/Query/QueryException.php b/lib/Doctrine/ORM/Query/QueryException.php
index ead7acf6d..3584b9119 100644
--- a/lib/Doctrine/ORM/Query/QueryException.php
+++ b/lib/Doctrine/ORM/Query/QueryException.php
@@ -85,6 +85,15 @@ class QueryException extends \Doctrine\Common\DoctrineException
"in class ".$assoc->sourceEntityName." assocation ".$assoc->sourceFieldName
);
}
+
+ public static function partialObjectsAreDangerous()
+ {
+ return new self(
+ "Loading partial objects is dangerous. Fetch full objects or consider " .
+ "using a different fetch mode. If you really want partial objects, " .
+ "set the doctrine.forcePartialLoad query hint to TRUE."
+ );
+ }
public static function overwritingJoinConditionsNotYetSupported($assoc)
{
@@ -94,4 +103,21 @@ class QueryException extends \Doctrine\Common\DoctrineException
"Use WITH to append additional join conditions to the association."
);
}
+
+ public static function associationPathInverseSideNotSupported()
+ {
+ return new self(
+ "A single-valued association path expression to an inverse side is not supported".
+ " in DQL queries. Use an explicit join instead."
+ );
+ }
+
+ public static function associationPathCompositeKeyNotSupported()
+ {
+ return new self(
+ "A single-valued association path expression to an entity with a composite primary ".
+ "key is not supported. Explicitly name the components of the composite primary key ".
+ "in the query."
+ );
+ }
}
\ No newline at end of file
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index 80edbb51c..86f6e4dc4 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -32,6 +32,7 @@ use Doctrine\ORM\Query,
* @author Roman Borschel
* @author Benjamin Eberlei
* @since 2.0
+ * @todo Rename: SQLWalker
*/
class SqlWalker implements TreeWalker
{
@@ -40,14 +41,10 @@ class SqlWalker implements TreeWalker
*/
private $_rsm;
- /** Counter for generating unique column aliases. */
+ /** Counters for generating unique column aliases, table aliases and parameter indexes. */
private $_aliasCounter = 0;
-
- /** Counter for generating unique table aliases. */
private $_tableAliasCounter = 0;
private $_scalarResultCounter = 1;
-
- /** Counter for SQL parameter positions. */
private $_sqlParamIndex = 1;
/**
@@ -70,7 +67,7 @@ class SqlWalker implements TreeWalker
*/
private $_query;
- private $_dqlToSqlAliasMap = array();
+ private $_tableAliasMap = array();
/** Map from result variable names to their SQL column alias names. */
private $_scalarResultAliasMap = array();
@@ -89,7 +86,7 @@ class SqlWalker implements TreeWalker
/**
* Flag that indicates whether to generate SQL table aliases in the SQL.
- * These should only be generated for SELECT queries.
+ * These should only be generated for SELECT queries, not for UPDATE/DELETE.
*/
private $_useSqlTableAliases = true;
@@ -101,17 +98,17 @@ class SqlWalker implements TreeWalker
private $_platform;
/**
- * @inheritdoc
+ * {@inheritDoc}
*/
public function __construct($query, $parserResult, array $queryComponents)
{
- $this->_rsm = $parserResult->getResultSetMapping();
$this->_query = $query;
+ $this->_parserResult = $parserResult;
+ $this->_queryComponents = $queryComponents;
+ $this->_rsm = $parserResult->getResultSetMapping();
$this->_em = $query->getEntityManager();
$this->_conn = $this->_em->getConnection();
$this->_platform = $this->_conn->getDatabasePlatform();
- $this->_parserResult = $parserResult;
- $this->_queryComponents = $queryComponents;
}
/**
@@ -200,11 +197,11 @@ class SqlWalker implements TreeWalker
{
$tableName .= $dqlAlias;
- if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) {
- $this->_dqlToSqlAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
+ if ( ! isset($this->_tableAliasMap[$tableName])) {
+ $this->_tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
}
- return $this->_dqlToSqlAliasMap[$tableName];
+ return $this->_tableAliasMap[$tableName];
}
/**
@@ -216,7 +213,7 @@ class SqlWalker implements TreeWalker
*/
public function setSqlTableAlias($tableName, $alias)
{
- $this->_dqlToSqlAliasMap[$tableName] = $alias;
+ $this->_tableAliasMap[$tableName] = $alias;
return $alias;
}
@@ -270,16 +267,16 @@ class SqlWalker implements TreeWalker
$subClass = $this->_em->getClassMetadata($subClassName);
$tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
- . ' ' . $tableAlias . ' ON ';
- $first = true;
+ . ' ' . $tableAlias . ' ON ';
+ $first = true;
foreach ($class->identifier as $idField) {
if ($first) $first = false; else $sql .= ' AND ';
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
$sql .= $baseTableAlias . '.' . $columnName
- . ' = '
- . $tableAlias . '.' . $columnName;
+ . ' = '
+ . $tableAlias . '.' . $columnName;
}
}
}
@@ -339,9 +336,8 @@ class SqlWalker implements TreeWalker
$sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
- $q = $this->getQuery();
$sql = $this->_platform->modifyLimitQuery(
- $sql, $q->getMaxResults(), $q->getFirstResult()
+ $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
);
return $sql;
@@ -407,7 +403,6 @@ class SqlWalker implements TreeWalker
return $this->getSqlTableAlias($class->primaryTable['name'], $identificationVariable);
}
-
/**
* Walks down a PathExpression AST node, thereby generating the appropriate SQL.
@@ -418,15 +413,13 @@ class SqlWalker implements TreeWalker
public function walkPathExpression($pathExpr)
{
$sql = '';
- $pathExprType = $pathExpr->type;
-
+
switch ($pathExpr->type) {
case AST\PathExpression::TYPE_STATE_FIELD:
$parts = $pathExpr->parts;
$fieldName = array_pop($parts);
- $dqlAlias = $pathExpr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
- $qComp = $this->_queryComponents[$dqlAlias];
- $class = $qComp['metadata'];
+ $dqlAlias = $pathExpr->identificationVariable . ( ! empty($parts) ? '.' . implode('.', $parts) : '');
+ $class = $this->_queryComponents[$dqlAlias]['metadata'];
if ($this->_useSqlTableAliases) {
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
@@ -434,33 +427,27 @@ class SqlWalker implements TreeWalker
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
break;
-
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
- // "u.Group" should be converted to:
- // 1- IdentificationVariable is the owning side:
- // Just append the condition: u.group_id = ?
- /*$parts = $pathExpr->parts;
- $numParts = count($parts);
+ // 1- the owning side:
+ // Just use the foreign key, i.e. u.group_id
+ $parts = $pathExpr->parts;
+ $fieldName = array_pop($parts);
$dqlAlias = $pathExpr->identificationVariable;
- $fieldName = $parts[$numParts - 1];
- $qComp = $this->_queryComponents[$dqlAlias];
- $class = $qComp['metadata'];
+ $class = $this->_queryComponents[$dqlAlias]['metadata'];
$assoc = $class->associationMappings[$fieldName];
if ($assoc->isOwningSide) {
- foreach ($assoc->)
- $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
-
+ // COMPOSITE KEYS NOT (YET?) SUPPORTED
+ if (count($assoc->sourceToTargetKeyColumns) > 1) {
+ throw QueryException::associationPathCompositeKeyNotSupported();
+ }
+ $sql .= $this->walkIdentificationVariable($dqlAlias) . '.'
+ . $assoc->getQuotedJoinColumnName(key($assoc->sourceToTargetKeyColumns), $this->_platform);
+ } else {
+ // 2- Inverse side: NOT (YET?) SUPPORTED
+ throw QueryException::associationPathInverseSideNotSupported();
}
-
- // 2- IdentificationVariable is the inverse side:
- // Join required: INNER JOIN u.Group g
- // Append condition: g.id = ?
- break;*/
-
- case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
- throw DoctrineException::notImplemented();
-
+ break;
default:
throw QueryException::invalidPathExpression($pathExpr);
}
@@ -468,7 +455,6 @@ class SqlWalker implements TreeWalker
return $sql;
}
-
/**
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
*
@@ -639,10 +625,8 @@ class SqlWalker implements TreeWalker
*/
public function walkHavingClause($havingClause)
{
- $condExpr = $havingClause->conditionalExpression;
-
return ' HAVING ' . implode(
- ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)
+ ' OR ', array_map(array($this, 'walkConditionalTerm'), $havingClause->conditionalExpression->conditionalTerms)
);
}
@@ -750,15 +734,11 @@ class SqlWalker implements TreeWalker
}
}
- // Handle ON / WITH clause
+ // Handle WITH clause
if ($join->conditionalExpression !== null) {
- if ($join->whereType == AST\Join::JOIN_WHERE_ON) {
- throw QueryException::overwritingJoinConditionsNotYetSupported($assoc);
- } else {
- $sql .= ' AND (' . implode(' OR ',
- array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms)
- ). ')';
- }
+ $sql .= ' AND (' . implode(' OR ',
+ array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms)
+ ). ')';
}
$discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias);
@@ -792,22 +772,25 @@ class SqlWalker implements TreeWalker
$dqlAlias = $expr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
$qComp = $this->_queryComponents[$dqlAlias];
$class = $qComp['metadata'];
-
- if ( ! isset($this->_selectedClasses[$dqlAlias])) {
- $this->_selectedClasses[$dqlAlias] = $class;
+
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $fieldName;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
}
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
- $columnAlias = $this->getSqlColumnAlias($class->columnNames[$fieldName]);
- $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
+ $columnAlias = $this->getSqlColumnAlias($columnName);
+ $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
- $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
} else {
- throw DoctrineException::invalidPathExpression($expr->type);
+ throw QueryException::invalidPathExpression($expr->type);
}
- } else if ($expr instanceof AST\AggregateExpression) {
+ }
+ else if ($expr instanceof AST\AggregateExpression) {
if ( ! $selectExpression->fieldIdentificationVariable) {
$resultAlias = $this->_scalarResultCounter++;
} else {
@@ -820,9 +803,11 @@ class SqlWalker implements TreeWalker
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
- } else if ($expr instanceof AST\Subselect) {
+ }
+ else if ($expr instanceof AST\Subselect) {
$sql .= $this->walkSubselect($expr);
- } else if ($expr instanceof AST\Functions\FunctionNode) {
+ }
+ else if ($expr instanceof AST\Functions\FunctionNode) {
if ( ! $selectExpression->fieldIdentificationVariable) {
$resultAlias = $this->_scalarResultCounter++;
} else {
@@ -833,11 +818,32 @@ class SqlWalker implements TreeWalker
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+ $columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ }
+ else if ($expr instanceof AST\SimpleArithmeticExpression) {
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $this->_scalarResultCounter++;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
} else {
- // $expr == IdentificationVariable
- $dqlAlias = $expr;
+ // IdentificationVariable or PartialObjectExpression
+ if ($expr instanceof AST\PartialObjectExpression) {
+ $dqlAlias = $expr->identificationVariable;
+ $partialFieldSet = $expr->partialFieldSet;
+ } else {
+ $dqlAlias = $expr;
+ $partialFieldSet = array();
+ }
+
$queryComp = $this->_queryComponents[$dqlAlias];
$class = $queryComp['metadata'];
@@ -848,6 +854,10 @@ class SqlWalker implements TreeWalker
$beginning = true;
// Select all fields from the queried class
foreach ($class->fieldMappings as $fieldName => $mapping) {
+ if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
+ continue;
+ }
+
if (isset($mapping['inherited'])) {
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->primaryTable['name'];
} else {
@@ -874,7 +884,7 @@ class SqlWalker implements TreeWalker
$subClass = $this->_em->getClassMetadata($subClassName);
$sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
- if (isset($mapping['inherited'])) {
+ if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
continue;
}
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index d008d0e7e..0e68b419b 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -317,7 +317,7 @@ class UnitOfWork implements PropertyChangedListener
} catch (\Exception $e) {
$conn->setRollbackOnly();
$conn->rollback();
- $this->clear();
+ $this->_em->close();
throw $e;
}
diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php
index 9c21c7836..148fa6674 100644
--- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php
+++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php
@@ -116,5 +116,4 @@ class CmsUser
$address->setUser($this);
}
}
-
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
index 5e49f86c1..6a0c6d937 100644
--- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
@@ -286,6 +286,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('gblanco', $users[0]->username);
$this->assertEquals('developer', $users[0]->status);
$this->assertTrue($users[0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
+ $this->assertTrue($users[0]->phonenumbers->isInitialized());
$this->assertEquals(0, $users[0]->phonenumbers->count());
//$this->assertNull($users[0]->articles);
}
@@ -332,7 +333,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
- $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u inner join u.groups g");
+ $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g");
$this->assertEquals(0, count($query->getResult()));
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php
index 6a3db8936..22f1d2072 100644
--- a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php
@@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
+use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../TestInit.php';
@@ -83,7 +84,6 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$phonenumbers = $user->getPhonenumbers();
$this->assertTrue($this->_em->contains($phonenumbers[0]));
$this->assertTrue($this->_em->contains($phonenumbers[1]));
- }
-
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php
index d6ea041d0..09ddaf0a3 100644
--- a/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/QueryDqlFunctionTest.php
@@ -189,8 +189,7 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
" TRIM(LEADING '.' FROM m.name) AS str2, TRIM(CONCAT(' ', CONCAT(m.name, ' '))) AS str3 ".
"FROM Doctrine\Tests\Models\Company\CompanyManager m";
- $result = $this->_em->createQuery($dql)
- ->getArrayResult();
+ $result = $this->_em->createQuery($dql)->getArrayResult();
$this->assertEquals(4, count($result));
$this->assertEquals('Roman B', $result[0]['str1']);
@@ -207,34 +206,34 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Jonathan W.', $result[3]['str3']);
}
- /*public function testOperatorAdd()
+ public function testOperatorAdd()
{
$result = $this->_em->createQuery('SELECT m, m.salary+2500 AS add FROM Doctrine\Tests\Models\Company\CompanyManager m')
- ->getResult();
-
+ ->getResult();
+
$this->assertEquals(4, count($result));
- $this->assertEquals(102500, $result[0]['op']);
- $this->assertEquals(202500, $result[1]['op']);
- $this->assertEquals(402500, $result[2]['op']);
- $this->assertEquals(802500, $result[3]['op']);
+ $this->assertEquals(102500, $result[0]['add']);
+ $this->assertEquals(202500, $result[1]['add']);
+ $this->assertEquals(402500, $result[2]['add']);
+ $this->assertEquals(802500, $result[3]['add']);
}
public function testOperatorSub()
{
- $result = $this->_em->createQuery('SELECT m, m.salary-2500 AS add FROM Doctrine\Tests\Models\Company\CompanyManager m')
- ->getResult();
+ $result = $this->_em->createQuery('SELECT m, m.salary-2500 AS sub FROM Doctrine\Tests\Models\Company\CompanyManager m')
+ ->getResult();
$this->assertEquals(4, count($result));
- $this->assertEquals(102500, $result[0]['op']);
- $this->assertEquals(202500, $result[1]['op']);
- $this->assertEquals(402500, $result[2]['op']);
- $this->assertEquals(802500, $result[3]['op']);
+ $this->assertEquals(97500, $result[0]['sub']);
+ $this->assertEquals(197500, $result[1]['sub']);
+ $this->assertEquals(397500, $result[2]['sub']);
+ $this->assertEquals(797500, $result[3]['sub']);
}
public function testOperatorMultiply()
{
$result = $this->_em->createQuery('SELECT m, m.salary*2 AS op FROM Doctrine\Tests\Models\Company\CompanyManager m')
- ->getResult();
+ ->getResult();
$this->assertEquals(4, count($result));
$this->assertEquals(200000, $result[0]['op']);
@@ -243,17 +242,33 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(1600000, $result[3]['op']);
}
+ /**
+ * @group test
+ */
public function testOperatorDiv()
{
$result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m')
- ->getResult();
+ ->getResult();
$this->assertEquals(4, count($result));
$this->assertEquals(200000, $result[0]['op']);
$this->assertEquals(400000, $result[1]['op']);
$this->assertEquals(800000, $result[2]['op']);
$this->assertEquals(1600000, $result[3]['op']);
- }*/
+ }
+
+ public function testConcatFunction()
+ {
+ $arg = $this->_em->createQuery('SELECT CONCAT(m.name, m.department) AS namedep FROM Doctrine\Tests\Models\Company\CompanyManager m order by namedep desc')
+ ->getArrayResult();
+
+ $this->assertEquals(4, count($arg));
+ $this->assertEquals('Roman B.IT', $arg[0]['namedep']);
+ $this->assertEquals('Jonathan W.Administration', $arg[1]['namedep']);
+ $this->assertEquals('Guilherme B.Complaint Department', $arg[2]['namedep']);
+ $this->assertEquals('Benjamin E.HR', $arg[3]['namedep']);
+ }
+
protected function generateFixture()
{
diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC163Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC163Test.php
index e18346c20..3126d5491 100644
--- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC163Test.php
+++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC163Test.php
@@ -45,7 +45,7 @@ class DDC163Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
- $dql = 'SELECT person.name, spouse.name, friend.name
+ $dql = 'SELECT PARTIAL person.{id,name}, PARTIAL spouse.{id,name}, PARTIAL friend.{id,name}
FROM Doctrine\Tests\Models\Company\CompanyPerson person
LEFT JOIN person.spouse spouse
LEFT JOIN person.friends friend
diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
index 1fe79610c..d81a02ced 100644
--- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php
@@ -626,6 +626,76 @@ class ArrayHydratorTest extends HydrationTestCase
$this->assertTrue(isset($result[1]['boards']));
$this->assertEquals(1, count($result[1]['boards']));
}
+
+ /**
+ * DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c
+ *
+ */
+ /*public function testChainedJoinWithScalars()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('a__id', 'id');
+ $rsm->addScalarResult('a__topic', 'topic');
+ $rsm->addScalarResult('c__id', 'cid');
+ $rsm->addScalarResult('c__topic', 'ctopic');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '1',
+ 'a__topic' => 'The First',
+ 'c__id' => '1',
+ 'c__topic' => 'First Comment'
+ ),
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '1',
+ 'a__topic' => 'The First',
+ 'c__id' => '2',
+ 'c__topic' => 'Second Comment'
+ ),
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '42',
+ 'a__topic' => 'The Answer',
+ 'c__id' => null,
+ 'c__topic' => null
+ ),
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm);
+
+ $this->assertEquals(3, count($result));
+
+ $this->assertEquals(2, count($result[0][0])); // User array
+ $this->assertEquals(1, $result[0]['id']);
+ $this->assertEquals('The First', $result[0]['topic']);
+ $this->assertEquals(1, $result[0]['cid']);
+ $this->assertEquals('First Comment', $result[0]['ctopic']);
+
+ $this->assertEquals(2, count($result[1][0])); // User array, duplicated
+ $this->assertEquals(1, $result[1]['id']); // duplicated
+ $this->assertEquals('The First', $result[1]['topic']); // duplicated
+ $this->assertEquals(2, $result[1]['cid']);
+ $this->assertEquals('Second Comment', $result[1]['ctopic']);
+
+ $this->assertEquals(2, count($result[2][0])); // User array, duplicated
+ $this->assertEquals(42, $result[2]['id']);
+ $this->assertEquals('The Answer', $result[2]['topic']);
+ $this->assertNull($result[2]['cid']);
+ $this->assertNull($result[2]['ctopic']);
+ }*/
public function testResultIteration()
{
diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
index 8cfb8d14f..f0b6ee50e 100644
--- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php
@@ -743,6 +743,81 @@ class ObjectHydratorTest extends HydrationTestCase
$this->assertEquals(0, $result[0]->articles->count());
$this->assertEquals(0, $result[1]->articles->count());
}
+
+ /**
+ * DQL: select partial u.{id,status}, a.id, a.topic, c.id as cid, c.topic as ctopic from CmsUser u left join u.articles a left join a.comments c
+ *
+ * @group bubu
+ */
+ /*public function testChainedJoinWithScalars()
+ {
+ $rsm = new ResultSetMapping;
+ $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');
+ $rsm->addFieldResult('u', 'u__id', 'id');
+ $rsm->addFieldResult('u', 'u__status', 'status');
+ $rsm->addScalarResult('a__id', 'id');
+ $rsm->addScalarResult('a__topic', 'topic');
+ $rsm->addScalarResult('c__id', 'cid');
+ $rsm->addScalarResult('c__topic', 'ctopic');
+
+ // Faked result set
+ $resultSet = array(
+ //row1
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '1',
+ 'a__topic' => 'The First',
+ 'c__id' => '1',
+ 'c__topic' => 'First Comment'
+ ),
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '1',
+ 'a__topic' => 'The First',
+ 'c__id' => '2',
+ 'c__topic' => 'Second Comment'
+ ),
+ array(
+ 'u__id' => '1',
+ 'u__status' => 'developer',
+ 'a__id' => '42',
+ 'a__topic' => 'The Answer',
+ 'c__id' => null,
+ 'c__topic' => null
+ ),
+ );
+
+ $stmt = new HydratorMockStatement($resultSet);
+ $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em);
+
+ $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true));
+
+ $this->assertEquals(3, count($result));
+
+ $this->assertTrue($result[0][0] instanceof CmsUser); // User object
+ $this->assertEquals(1, $result[0]['id']);
+ $this->assertEquals('The First', $result[0]['topic']);
+ $this->assertEquals(1, $result[0]['cid']);
+ $this->assertEquals('First Comment', $result[0]['ctopic']);
+
+ $this->assertTrue($result[1][0] instanceof CmsUser); // Same User object
+ $this->assertEquals(1, $result[1]['id']); // duplicated
+ $this->assertEquals('The First', $result[1]['topic']); // duplicated
+ $this->assertEquals(2, $result[1]['cid']);
+ $this->assertEquals('Second Comment', $result[1]['ctopic']);
+
+ $this->assertTrue($result[2][0] instanceof CmsUser); // Same User object
+ $this->assertEquals(42, $result[2]['id']);
+ $this->assertEquals('The Answer', $result[2]['topic']);
+ $this->assertNull($result[2]['cid']);
+ $this->assertNull($result[2]['ctopic']);
+
+ $this->assertTrue($result[0][0] === $result[1][0]);
+ $this->assertTrue($result[1][0] === $result[2][0]);
+ $this->assertTrue($result[0][0] === $result[2][0]);
+ }*/
public function testResultIteration()
{
diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
index 1204e1f02..59a1ca65f 100644
--- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
@@ -215,9 +215,9 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :id');
}
- public function testJoinConditionsSupported()
+ public function testJoinConditionOverrideNotSupported()
{
- $this->assertValidDql("SELECT u.name, p FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p ON p.phonenumber = '123 123'");
+ $this->assertInvalidDql("SELECT u.name, p FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.phonenumbers p ON p.phonenumber = '123 123'");
}
public function testIndexByClauseWithOneComponent()
@@ -270,12 +270,12 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles u WHERE u.id = 1");
}
- /*public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression()
+ public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression()
{
// This should be allowed because avatar is a single-value association.
// SQL: SELECT ... FROM forum_user fu INNER JOIN forum_avatar fa ON fu.avatar_id = fa.id WHERE fa.id = ?
- $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar.id = ?");
- }*/
+ $this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar.id = ?1");
+ }
public function testImplicitJoinInWhereOnCollectionValuedPathExpression()
{
@@ -319,10 +319,6 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
}
/**
- * TODO: Hydration can't deal with this currently but it should be allowed.
- * Also, generated SQL looks like: "... FROM cms_user, cms_article ..." which
- * may not work on all dbms.
- *
* The main use case for this generalized style of join is when a join condition
* does not involve a foreign key relationship that is mapped to an entity relationship.
*/
@@ -392,15 +388,30 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertInvalidDql('SELECT u FROM UnknownClassName u');
}
- /**
- * This checks for invalid attempt to hydrate a proxy. It should throw an exception
- *
- * @expectedException \Doctrine\Common\DoctrineException
- */
- public function testPartialObjectLoad()
+ public function testCorrectPartialObjectLoad()
{
- $this->parseDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u', array(
- \Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD => false
- ));
+ $this->assertValidDql('SELECT PARTIAL u.{id,name} FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
+
+ public function testIncorrectPartialObjectLoadBecauseOfMissingIdentifier()
+ {
+ $this->assertInvalidDql('SELECT PARTIAL u.{name} FROM Doctrine\Tests\Models\CMS\CmsUser u');
+ }
+
+ public function testScalarExpressionInSelect()
+ {
+ $this->assertValidDql('SELECT u, 42 + u.id AS someNumber FROM Doctrine\Tests\Models\CMS\CmsUser u');
+ }
+
+ public function testInputParameterInSelect()
+ {
+ $this->assertValidDql('SELECT u, u.id + ?1 AS someNumber FROM Doctrine\Tests\Models\CMS\CmsUser u');
+ }
+
+ /* The exception is currently thrown in the SQLWalker, not earlier.
+ public function testInverseSideSingleValuedAssociationPathNotAllowed()
+ {
+ $this->assertInvalidDql('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address = ?1');
+ }
+ */
}
\ No newline at end of file
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
index d56e985d9..e69e7bffa 100644
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -23,7 +23,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free();
- } catch (DoctrineException $e) {
+ } catch (\Exception $e) {
$this->fail($e->getMessage());
}
}
@@ -534,4 +534,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_addresses c1_ WHERE c1_.user_id = c0_.id AND c1_.id = ?)"
);*/
}
+
+
+ public function testSingleValuedAssociationNullCheckOnOwningSide()
+ {
+ $this->assertSqlGeneration(
+ "SELECT a FROM Doctrine\Tests\Models\CMS\CmsAddress a WHERE a.user IS NULL",
+ "SELECT c0_.id AS id0, c0_.country AS country1, c0_.zip AS zip2, c0_.city AS city3 FROM cms_addresses c0_ WHERE c0_.user_id IS NULL"
+ );
+ }
+
+ // Null check on inverse side has to happen through explicit JOIN.
+ // "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address IS NULL"
+ // where the CmsUser is the inverse side is not supported.
+ public function testSingleValuedAssociationNullCheckOnInverseSide()
+ {
+ $this->assertSqlGeneration(
+ "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.address a WHERE a.id IS NULL",
+ "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ LEFT JOIN cms_addresses c1_ ON c0_.id = c1_.user_id WHERE c1_.id IS NULL"
+ );
+ }
}