[2.0][DDC-335][DDC-347][DDC-317] Fixed. Also prepared DQL for CASE/COALESCE/NULLIF support.
This commit is contained in:
parent
6f6628c22a
commit
639718e95c
@ -124,10 +124,6 @@ class DoctrineException extends \Exception
|
|||||||
if ( ! self::$_messages) {
|
if ( ! self::$_messages) {
|
||||||
// Lazy-init messages
|
// Lazy-init messages
|
||||||
self::$_messages = array(
|
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' =>
|
'QueryException#nonUniqueResult' =>
|
||||||
"The query contains more than one result."
|
"The query contains more than one result."
|
||||||
);
|
);
|
||||||
|
@ -79,6 +79,9 @@ class ArrayHydrator extends AbstractHydrator
|
|||||||
if (isset($rowData['scalars'])) {
|
if (isset($rowData['scalars'])) {
|
||||||
$scalars = $rowData['scalars'];
|
$scalars = $rowData['scalars'];
|
||||||
unset($rowData['scalars']);
|
unset($rowData['scalars']);
|
||||||
|
if (empty($rowData)) {
|
||||||
|
++$this->_resultCounter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Now hydrate the data found in the current row.
|
// 2) Now hydrate the data found in the current row.
|
||||||
@ -129,7 +132,7 @@ class ArrayHydrator extends AbstractHydrator
|
|||||||
}
|
}
|
||||||
end($baseElement[$relationAlias]);
|
end($baseElement[$relationAlias]);
|
||||||
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
|
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] =
|
||||||
key($baseElement[$relationAlias]);
|
key($baseElement[$relationAlias]);
|
||||||
}
|
}
|
||||||
} else if ( ! isset($baseElement[$relationAlias])) {
|
} else if ( ! isset($baseElement[$relationAlias])) {
|
||||||
$baseElement[$relationAlias] = array();
|
$baseElement[$relationAlias] = array();
|
||||||
@ -177,6 +180,10 @@ class ArrayHydrator extends AbstractHydrator
|
|||||||
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
|
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
|
||||||
} else {
|
} else {
|
||||||
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
|
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
|
||||||
|
/*if ($this->_rsm->isMixed) {
|
||||||
|
$result[] =& $result[$index];
|
||||||
|
++$this->_resultCounter;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
$this->updateResultPointer($result, $index, $dqlAlias, false);
|
$this->updateResultPointer($result, $index, $dqlAlias, false);
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,9 @@ class ObjectHydrator extends AbstractHydrator
|
|||||||
if (isset($rowData['scalars'])) {
|
if (isset($rowData['scalars'])) {
|
||||||
$scalars = $rowData['scalars'];
|
$scalars = $rowData['scalars'];
|
||||||
unset($rowData['scalars']);
|
unset($rowData['scalars']);
|
||||||
|
if (empty($rowData)) {
|
||||||
|
++$this->_resultCounter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hydrate the data chunks
|
// Hydrate the data chunks
|
||||||
@ -417,6 +420,10 @@ class ObjectHydrator extends AbstractHydrator
|
|||||||
// Update result pointer
|
// Update result pointer
|
||||||
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
|
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
|
||||||
$this->_resultPointers[$dqlAlias] = $result[$index];
|
$this->_resultPointers[$dqlAlias] = $result[$index];
|
||||||
|
/*if ($this->_rsm->isMixed) {
|
||||||
|
$result[] = $result[$index];
|
||||||
|
++$this->_resultCounter;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ class OneToOneMapping extends AssociationMapping
|
|||||||
/**
|
/**
|
||||||
* @internal Experimental. For MetaModel API, Doctrine 2.1 or later.
|
* @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 = new self(array());
|
||||||
$assoc->isOptional = $state['isOptional'];
|
$assoc->isOptional = $state['isOptional'];
|
||||||
@ -302,5 +302,5 @@ class OneToOneMapping extends AssociationMapping
|
|||||||
$assoc->sourceFieldName = $state['sourceFieldName'];
|
$assoc->sourceFieldName = $state['sourceFieldName'];
|
||||||
|
|
||||||
return $assoc;
|
return $assoc;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
13
lib/Doctrine/ORM/Query/AST/ASTException.php
Normal file
13
lib/Doctrine/ORM/Query/AST/ASTException.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Query\AST;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\QueryException;
|
||||||
|
|
||||||
|
class ASTException extends QueryException
|
||||||
|
{
|
||||||
|
public static function noDispatchForNode($node)
|
||||||
|
{
|
||||||
|
return new self("Double-dispatch for node " . get_class($node) . " is not supported.");
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ class SizeFunction extends FunctionNode
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
|
* @todo If the collection being counted is already joined, the SQL can be simpler (more efficient).
|
||||||
*/
|
*/
|
||||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||||
{
|
{
|
||||||
|
@ -38,13 +38,10 @@ class Join extends Node
|
|||||||
const JOIN_TYPE_LEFT = 1;
|
const JOIN_TYPE_LEFT = 1;
|
||||||
const JOIN_TYPE_LEFTOUTER = 2;
|
const JOIN_TYPE_LEFTOUTER = 2;
|
||||||
const JOIN_TYPE_INNER = 3;
|
const JOIN_TYPE_INNER = 3;
|
||||||
const JOIN_WHERE_ON = 1;
|
|
||||||
const JOIN_WHERE_WITH = 2;
|
|
||||||
|
|
||||||
public $joinType = self::JOIN_TYPE_INNER;
|
public $joinType = self::JOIN_TYPE_INNER;
|
||||||
public $joinAssociationPathExpression = null;
|
public $joinAssociationPathExpression = null;
|
||||||
public $aliasIdentificationVariable = null;
|
public $aliasIdentificationVariable = null;
|
||||||
public $whereType = self::JOIN_WHERE_WITH;
|
|
||||||
public $conditionalExpression = null;
|
public $conditionalExpression = null;
|
||||||
|
|
||||||
public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar)
|
public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar)
|
||||||
|
@ -34,7 +34,17 @@ namespace Doctrine\ORM\Query\AST;
|
|||||||
*/
|
*/
|
||||||
abstract class Node
|
abstract class Node
|
||||||
{
|
{
|
||||||
abstract public function dispatch($walker);
|
/**
|
||||||
|
* Double-dispatch method, supposed to dispatch back to the walker.
|
||||||
|
*
|
||||||
|
* Implementation is not mandatory for all nodes.
|
||||||
|
*
|
||||||
|
* @param $walker
|
||||||
|
*/
|
||||||
|
public function dispatch($walker)
|
||||||
|
{
|
||||||
|
throw ASTException::noDispatchForNode($this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps the AST Node into a string representation for information purpose only
|
* Dumps the AST Node into a string representation for information purpose only
|
||||||
|
15
lib/Doctrine/ORM/Query/AST/PartialObjectExpression.php
Normal file
15
lib/Doctrine/ORM/Query/AST/PartialObjectExpression.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Query\AST;
|
||||||
|
|
||||||
|
class PartialObjectExpression extends Node
|
||||||
|
{
|
||||||
|
public $identificationVariable;
|
||||||
|
public $partialFieldSet;
|
||||||
|
|
||||||
|
public function __construct($identificationVariable, array $partialFieldSet)
|
||||||
|
{
|
||||||
|
$this->identificationVariable = $identificationVariable;
|
||||||
|
$this->partialFieldSet = $partialFieldSet;
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,6 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
const T_NULL = 145;
|
const T_NULL = 145;
|
||||||
const T_OF = 146;
|
const T_OF = 146;
|
||||||
const T_OFFSET = 147;
|
const T_OFFSET = 147;
|
||||||
const T_ON = 148;
|
|
||||||
const T_OPEN_PARENTHESIS = 149;
|
const T_OPEN_PARENTHESIS = 149;
|
||||||
const T_OR = 150;
|
const T_OR = 150;
|
||||||
const T_ORDER = 151;
|
const T_ORDER = 151;
|
||||||
@ -104,8 +103,9 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
const T_UPDATE = 161;
|
const T_UPDATE = 161;
|
||||||
const T_WHERE = 162;
|
const T_WHERE = 162;
|
||||||
const T_WITH = 163;
|
const T_WITH = 163;
|
||||||
|
const T_PARTIAL = 164;
|
||||||
private $_keywordsTable;
|
const T_OPEN_CURLY_BRACE = 165;
|
||||||
|
const T_CLOSE_CURLY_BRACE = 166;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new query scanner object.
|
* Creates a new query scanner object.
|
||||||
@ -151,7 +151,7 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
$value = $newVal;
|
$value = $newVal;
|
||||||
|
|
||||||
return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
|
return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
|
||||||
? self::T_FLOAT : self::T_INTEGER;
|
? self::T_FLOAT : self::T_INTEGER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value[0] === "'") {
|
if ($value[0] === "'") {
|
||||||
@ -176,6 +176,8 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
case '*': return self::T_MULTIPLY;
|
case '*': return self::T_MULTIPLY;
|
||||||
case '/': return self::T_DIVIDE;
|
case '/': return self::T_DIVIDE;
|
||||||
case '!': return self::T_NEGATE;
|
case '!': return self::T_NEGATE;
|
||||||
|
case '{': return self::T_OPEN_CURLY_BRACE;
|
||||||
|
case '}': return self::T_CLOSE_CURLY_BRACE;
|
||||||
default:
|
default:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
break;
|
break;
|
||||||
@ -186,7 +188,7 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Doc
|
* @todo Inline this method.
|
||||||
*/
|
*/
|
||||||
private function _getNumeric($value)
|
private function _getNumeric($value)
|
||||||
{
|
{
|
||||||
@ -206,6 +208,7 @@ class Lexer extends \Doctrine\Common\Lexer
|
|||||||
*
|
*
|
||||||
* @param string $identifier identifier name
|
* @param string $identifier identifier name
|
||||||
* @return int token type
|
* @return int token type
|
||||||
|
* @todo Inline this method.
|
||||||
*/
|
*/
|
||||||
private function _checkLiteral($identifier)
|
private function _checkLiteral($identifier)
|
||||||
{
|
{
|
||||||
|
@ -21,11 +21,11 @@
|
|||||||
|
|
||||||
namespace Doctrine\ORM\Query;
|
namespace Doctrine\ORM\Query;
|
||||||
|
|
||||||
use Doctrine\Common\DoctrineException,
|
use Doctrine\Common\DoctrineException, //TODO: Remove
|
||||||
Doctrine\ORM\Query;
|
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.
|
* Parses a DQL query, reports any errors in it, and generates an AST.
|
||||||
*
|
*
|
||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
* @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
|
* Expressions that were encountered during parsing of identifiers and expressions
|
||||||
* and still need to be validated.
|
* 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.
|
* The lexer.
|
||||||
@ -233,7 +234,8 @@ class Parser
|
|||||||
* error.
|
* error.
|
||||||
*
|
*
|
||||||
* @param int|string token type or value
|
* @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)
|
public function match($token)
|
||||||
{
|
{
|
||||||
@ -274,8 +276,18 @@ class Parser
|
|||||||
// Parse & build AST
|
// Parse & build AST
|
||||||
$AST = $this->QueryLanguage();
|
$AST = $this->QueryLanguage();
|
||||||
|
|
||||||
// Activate semantical checks after this point. Process all deferred checks in pipeline
|
// Process any deferred validations of some nodes in the AST.
|
||||||
$this->_processDeferredExpressionsStack($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)) {
|
if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) {
|
||||||
$this->_customTreeWalkers = $customWalkers;
|
$this->_customTreeWalkers = $customWalkers;
|
||||||
@ -317,8 +329,8 @@ class Parser
|
|||||||
/**
|
/**
|
||||||
* Generates a new syntax error.
|
* Generates a new syntax error.
|
||||||
*
|
*
|
||||||
* @param string $expected Optional expected string.
|
* @param string $expected Expected string.
|
||||||
* @param array $token Optional token.
|
* @param array $token Got token.
|
||||||
*
|
*
|
||||||
* @throws \Doctrine\ORM\Query\QueryException
|
* @throws \Doctrine\ORM\Query\QueryException
|
||||||
*/
|
*/
|
||||||
@ -484,143 +496,98 @@ class Parser
|
|||||||
return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
|
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 <tt>IdentificationVariable</tt> is a semantically correct.
|
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
|
||||||
* It must exist in query components list.
|
* It must exist in query components list.
|
||||||
*
|
*
|
||||||
* @param array $deferredItem
|
* @return void
|
||||||
* @param mixed $AST
|
|
||||||
*
|
|
||||||
* @return array Query Component
|
|
||||||
*/
|
*/
|
||||||
private function _validateIdentificationVariable($deferredItem, $AST)
|
private function _processDeferredIdentificationVariables()
|
||||||
{
|
{
|
||||||
$identVariable = $deferredItem['expression'];
|
foreach ($this->_deferredIdentificationVariables as $deferredItem) {
|
||||||
|
$identVariable = $deferredItem['expression'];
|
||||||
|
|
||||||
// Check if IdentificationVariable exists in queryComponents
|
// Check if IdentificationVariable exists in queryComponents
|
||||||
if ( ! isset($this->_queryComponents[$identVariable])) {
|
if ( ! isset($this->_queryComponents[$identVariable])) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$identVariable' is not defined.", $deferredItem['token']
|
"'$identVariable' is not defined.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$qComp = $this->_queryComponents[$identVariable];
|
$qComp = $this->_queryComponents[$identVariable];
|
||||||
|
|
||||||
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
|
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
|
||||||
if ( ! isset($qComp['metadata'])) {
|
if ( ! isset($qComp['metadata'])) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$identVariable' does not point to a Class.", $deferredItem['token']
|
"'$identVariable' does not point to a Class.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if identification variable nesting level is lower or equal than the current one
|
// Validate if identification variable nesting level is lower or equal than the current one
|
||||||
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
|
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
|
"'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $qComp;
|
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']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
|
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
|
||||||
* It must exist in query components list.
|
* It must exist in query components list.
|
||||||
*
|
*
|
||||||
* @param array $deferredItem
|
* @return void
|
||||||
* @param mixed $AST
|
|
||||||
*
|
|
||||||
* @return array Query Component
|
|
||||||
*/
|
*/
|
||||||
private function _validateResultVariable($deferredItem, $AST)
|
private function _processDeferredResultVariables()
|
||||||
{
|
{
|
||||||
$resultVariable = $deferredItem['expression'];
|
foreach ($this->_deferredResultVariables as $deferredItem) {
|
||||||
|
$resultVariable = $deferredItem['expression'];
|
||||||
|
|
||||||
// Check if ResultVariable exists in queryComponents
|
// Check if ResultVariable exists in queryComponents
|
||||||
if ( ! isset($this->_queryComponents[$resultVariable])) {
|
if ( ! isset($this->_queryComponents[$resultVariable])) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$resultVariable' is not defined.", $deferredItem['token']
|
"'$resultVariable' is not defined.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$qComp = $this->_queryComponents[$resultVariable];
|
$qComp = $this->_queryComponents[$resultVariable];
|
||||||
|
|
||||||
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
|
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
|
||||||
if ( ! isset($qComp['resultVariable'])) {
|
if ( ! isset($qComp['resultVariable'])) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
|
"'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if identification variable nesting level is lower or equal than the current one
|
// Validate if identification variable nesting level is lower or equal than the current one
|
||||||
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
|
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
"'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
|
"'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $qComp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that the given <tt>JoinAssociationPathExpression</tt> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -634,134 +601,133 @@ class Parser
|
|||||||
*
|
*
|
||||||
* @param array $deferredItem
|
* @param array $deferredItem
|
||||||
* @param mixed $AST
|
* @param mixed $AST
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
private function _validatePathExpression($deferredItem, $AST)
|
private function _processDeferredPathExpressions($AST)
|
||||||
{
|
{
|
||||||
$pathExpression = $deferredItem['expression'];
|
foreach ($this->_deferredPathExpressions as $deferredItem) {
|
||||||
$parts = $pathExpression->parts;
|
$pathExpression = $deferredItem['expression'];
|
||||||
$numParts = count($parts);
|
$parts = $pathExpression->parts;
|
||||||
|
$numParts = count($parts);
|
||||||
|
|
||||||
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
|
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
|
||||||
|
|
||||||
$aliasIdentificationVariable = $pathExpression->identificationVariable;
|
$aliasIdentificationVariable = $pathExpression->identificationVariable;
|
||||||
$parentField = $pathExpression->identificationVariable;
|
$parentField = $pathExpression->identificationVariable;
|
||||||
$class = $qComp['metadata'];
|
$class = $qComp['metadata'];
|
||||||
$fieldType = null;
|
$fieldType = null;
|
||||||
$curIndex = 0;
|
$curIndex = 0;
|
||||||
|
|
||||||
foreach ($parts as $field) {
|
foreach ($parts as $field) {
|
||||||
// Check if it is not in a state field
|
// Check if it is not in a state field
|
||||||
if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
|
if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
|
'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
|
||||||
$deferredItem['token']
|
$deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it is not a collection field
|
// Check if it is not a collection field
|
||||||
if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
|
if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
|
'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
|
||||||
$deferredItem['token']
|
$deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if field or association exists
|
// Check if field or association exists
|
||||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||||
$this->semanticalError(
|
$this->semanticalError(
|
||||||
'Class ' . $class->name . ' has no field or association named ' . $field,
|
'Class ' . $class->name . ' has no field or association named ' . $field,
|
||||||
$deferredItem['token']
|
$deferredItem['token']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$parentField = $field;
|
$parentField = $field;
|
||||||
|
|
||||||
if (isset($class->fieldMappings[$field])) {
|
if (isset($class->fieldMappings[$field])) {
|
||||||
$fieldType = AST\PathExpression::TYPE_STATE_FIELD;
|
$fieldType = AST\PathExpression::TYPE_STATE_FIELD;
|
||||||
} else {
|
} else {
|
||||||
$assoc = $class->associationMappings[$field];
|
$assoc = $class->associationMappings[$field];
|
||||||
$class = $this->_em->getClassMetadata($assoc->targetEntityName);
|
$class = $this->_em->getClassMetadata($assoc->targetEntityName);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
($curIndex != $numParts - 1) &&
|
($curIndex != $numParts - 1) &&
|
||||||
! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
|
! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
|
||||||
) {
|
) {
|
||||||
// Building queryComponent
|
// Building queryComponent
|
||||||
$joinQueryComponent = array(
|
$joinQueryComponent = array(
|
||||||
'metadata' => $class,
|
'metadata' => $class,
|
||||||
'parent' => $aliasIdentificationVariable,
|
'parent' => $aliasIdentificationVariable,
|
||||||
'relation' => $assoc,
|
'relation' => $assoc,
|
||||||
'map' => null,
|
'map' => null,
|
||||||
'nestingLevel' => $this->_nestingLevel,
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
'token' => $deferredItem['token'],
|
'token' => $deferredItem['token'],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create AST node
|
// Create AST node
|
||||||
$joinVariableDeclaration = new AST\JoinVariableDeclaration(
|
$joinVariableDeclaration = new AST\JoinVariableDeclaration(
|
||||||
new AST\Join(
|
new AST\Join(
|
||||||
AST\Join::JOIN_TYPE_INNER,
|
AST\Join::JOIN_TYPE_INNER,
|
||||||
new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
|
new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
|
||||||
$aliasIdentificationVariable . '.' . $field
|
$aliasIdentificationVariable . '.' . $field,
|
||||||
|
false
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
$AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
|
$AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
|
||||||
|
|
||||||
$this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
|
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$aliasIdentificationVariable .= '.' . $field;
|
++$curIndex;
|
||||||
|
}
|
||||||
|
|
||||||
if ($assoc->isOneToOne()) {
|
// Validate if PathExpression is one of the expected types
|
||||||
$fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
|
$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 {
|
} else {
|
||||||
$fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
|
$semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->semanticalError($semanticalError, $deferredItem['token']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$curIndex++;
|
// We need to force the type in PathExpression
|
||||||
|
$pathExpression->type = $fieldType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to force the type in PathExpression
|
|
||||||
$pathExpression->type = $fieldType;
|
|
||||||
|
|
||||||
return $fieldType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -777,14 +743,14 @@ class Parser
|
|||||||
|
|
||||||
switch ($this->_lexer->lookahead['type']) {
|
switch ($this->_lexer->lookahead['type']) {
|
||||||
case Lexer::T_SELECT:
|
case Lexer::T_SELECT:
|
||||||
return $this->SelectStatement();
|
$statement = $this->SelectStatement();
|
||||||
|
break;
|
||||||
case Lexer::T_UPDATE:
|
case Lexer::T_UPDATE:
|
||||||
return $this->UpdateStatement();
|
$statement = $this->UpdateStatement();
|
||||||
|
break;
|
||||||
case Lexer::T_DELETE:
|
case Lexer::T_DELETE:
|
||||||
return $this->DeleteStatement();
|
$statement = $this->DeleteStatement();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$this->syntaxError('SELECT, UPDATE or DELETE');
|
$this->syntaxError('SELECT, UPDATE or DELETE');
|
||||||
break;
|
break;
|
||||||
@ -794,8 +760,9 @@ class Parser
|
|||||||
if ($this->_lexer->lookahead !== null) {
|
if ($this->_lexer->lookahead !== null) {
|
||||||
$this->syntaxError('end of string');
|
$this->syntaxError('end of string');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return $statement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
|
* SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
|
||||||
@ -830,7 +797,7 @@ class Parser
|
|||||||
{
|
{
|
||||||
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
|
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
|
||||||
$updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
$updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||||
? $this->WhereClause() : null;
|
? $this->WhereClause() : null;
|
||||||
|
|
||||||
return $updateStatement;
|
return $updateStatement;
|
||||||
}
|
}
|
||||||
@ -844,12 +811,11 @@ class Parser
|
|||||||
{
|
{
|
||||||
$deleteStatement = new AST\DeleteStatement($this->DeleteClause());
|
$deleteStatement = new AST\DeleteStatement($this->DeleteClause());
|
||||||
$deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
$deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||||
? $this->WhereClause() : null;
|
? $this->WhereClause() : null;
|
||||||
|
|
||||||
return $deleteStatement;
|
return $deleteStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IdentificationVariable ::= identifier
|
* IdentificationVariable ::= identifier
|
||||||
*
|
*
|
||||||
@ -861,16 +827,12 @@ class Parser
|
|||||||
|
|
||||||
$identVariable = $this->_lexer->token['value'];
|
$identVariable = $this->_lexer->token['value'];
|
||||||
|
|
||||||
// Defer IdentificationVariable validation
|
$this->_deferredIdentificationVariables[] = array(
|
||||||
$exprStack = array(
|
|
||||||
'method' => 'IdentificationVariable',
|
|
||||||
'expression' => $identVariable,
|
'expression' => $identVariable,
|
||||||
'nestingLevel' => $this->_nestingLevel,
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
'token' => $this->_lexer->token,
|
'token' => $this->_lexer->token,
|
||||||
);
|
);
|
||||||
|
|
||||||
array_push($this->_deferredExpressionsStack, $exprStack);
|
|
||||||
|
|
||||||
return $identVariable;
|
return $identVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,15 +909,15 @@ class Parser
|
|||||||
$resultVariable = $this->_lexer->token['value'];
|
$resultVariable = $this->_lexer->token['value'];
|
||||||
|
|
||||||
// Defer ResultVariable validation
|
// Defer ResultVariable validation
|
||||||
$this->_subscribeExpression(
|
$this->_deferredResultVariables[] = array(
|
||||||
$resultVariable, 'ResultVariable',
|
'expression' => $resultVariable,
|
||||||
$this->_lexer->token, $this->_nestingLevel
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
|
'token' => $this->_lexer->token,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $resultVariable;
|
return $resultVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
|
* JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
|
||||||
*
|
*
|
||||||
@ -967,18 +929,19 @@ class Parser
|
|||||||
|
|
||||||
$identVariable = $this->IdentificationVariable();
|
$identVariable = $this->IdentificationVariable();
|
||||||
$this->match(Lexer::T_DOT);
|
$this->match(Lexer::T_DOT);
|
||||||
|
//TODO: $this->match($this->_lexer->lookahead['value']);
|
||||||
$this->match(Lexer::T_IDENTIFIER);
|
$this->match(Lexer::T_IDENTIFIER);
|
||||||
$field = $this->_lexer->token['value'];
|
$field = $this->_lexer->token['value'];
|
||||||
|
|
||||||
$pathExpr = new AST\JoinAssociationPathExpression($identVariable, $field);
|
// Validate association field
|
||||||
|
$qComp = $this->_queryComponents[$identVariable];
|
||||||
|
$class = $qComp['metadata'];
|
||||||
|
|
||||||
// Defer JoinAssociationPathExpression validation
|
if ( ! isset($class->associationMappings[$field])) {
|
||||||
$this->_subscribeExpression(
|
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
|
||||||
$pathExpr, 'JoinAssociationPathExpression',
|
}
|
||||||
$token, $this->_nestingLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
return $pathExpr;
|
return new AST\JoinAssociationPathExpression($identVariable, $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1007,9 +970,10 @@ class Parser
|
|||||||
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
|
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
|
||||||
|
|
||||||
// Defer PathExpression validation if requested to be defered
|
// Defer PathExpression validation if requested to be defered
|
||||||
$this->_subscribeExpression(
|
$this->_deferredPathExpressions[] = array(
|
||||||
$pathExpr, 'PathExpression',
|
'expression' => $pathExpr,
|
||||||
$token, $this->_nestingLevel
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
|
'token' => $this->_lexer->token,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $pathExpr;
|
return $pathExpr;
|
||||||
@ -1091,7 +1055,6 @@ class Parser
|
|||||||
return $pathExpression;
|
return $pathExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
|
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
|
||||||
*
|
*
|
||||||
@ -1356,7 +1319,6 @@ class Parser
|
|||||||
return $subselect;
|
return $subselect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
|
* UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
|
||||||
*
|
*
|
||||||
@ -1404,9 +1366,6 @@ class Parser
|
|||||||
$token = $this->_lexer->lookahead;
|
$token = $this->_lexer->lookahead;
|
||||||
$identVariable = $this->IdentificationVariable();
|
$identVariable = $this->IdentificationVariable();
|
||||||
|
|
||||||
// Validate if IdentificationVariable is defined
|
|
||||||
$this->_validateIdentificationVariable($identVariable, null, $token);
|
|
||||||
|
|
||||||
return $identVariable;
|
return $identVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1472,7 +1431,6 @@ class Parser
|
|||||||
return $this->SimpleArithmeticExpression();
|
return $this->SimpleArithmeticExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
|
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
|
||||||
*
|
*
|
||||||
@ -1528,7 +1486,7 @@ class Parser
|
|||||||
{
|
{
|
||||||
$join = $this->Join();
|
$join = $this->Join();
|
||||||
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
|
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
|
||||||
? $this->IndexBy() : null;
|
? $this->IndexBy() : null;
|
||||||
|
|
||||||
return new AST\JoinVariableDeclaration($join, $indexBy);
|
return new AST\JoinVariableDeclaration($join, $indexBy);
|
||||||
}
|
}
|
||||||
@ -1536,7 +1494,7 @@ class Parser
|
|||||||
/**
|
/**
|
||||||
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||||
*
|
*
|
||||||
* @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
|
* @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
|
||||||
*/
|
*/
|
||||||
public function RangeVariableDeclaration()
|
public function RangeVariableDeclaration()
|
||||||
{
|
{
|
||||||
@ -1557,7 +1515,7 @@ class Parser
|
|||||||
'relation' => null,
|
'relation' => null,
|
||||||
'map' => null,
|
'map' => null,
|
||||||
'nestingLevel' => $this->_nestingLevel,
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
'token' => $token,
|
'token' => $token
|
||||||
);
|
);
|
||||||
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
|
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
|
||||||
|
|
||||||
@ -1565,10 +1523,47 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
|
* PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||||
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
|
* PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||||
*
|
*
|
||||||
* @return \Doctrine\ORM\Query\AST\Join
|
* @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 ["WITH" ConditionalExpression]
|
||||||
|
*
|
||||||
|
* @return Doctrine\ORM\Query\AST\Join
|
||||||
*/
|
*/
|
||||||
public function Join()
|
public function Join()
|
||||||
{
|
{
|
||||||
@ -1619,7 +1614,7 @@ class Parser
|
|||||||
'relation' => $parentClass->getAssociationMapping($assocField),
|
'relation' => $parentClass->getAssociationMapping($assocField),
|
||||||
'map' => null,
|
'map' => null,
|
||||||
'nestingLevel' => $this->_nestingLevel,
|
'nestingLevel' => $this->_nestingLevel,
|
||||||
'token' => $token,
|
'token' => $token
|
||||||
);
|
);
|
||||||
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
|
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
|
||||||
|
|
||||||
@ -1627,14 +1622,8 @@ class Parser
|
|||||||
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
|
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
|
||||||
|
|
||||||
// Check for ad-hoc Join conditions
|
// 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_WITH)) {
|
||||||
if ($this->_lexer->isNextToken(Lexer::T_ON)) {
|
$this->match(Lexer::T_WITH);
|
||||||
$this->match(Lexer::T_ON);
|
|
||||||
$join->whereType = AST\Join::JOIN_WHERE_ON;
|
|
||||||
} else {
|
|
||||||
$this->match(Lexer::T_WITH);
|
|
||||||
}
|
|
||||||
|
|
||||||
$join->conditionalExpression = $this->ConditionalExpression();
|
$join->conditionalExpression = $this->ConditionalExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1644,7 +1633,7 @@ class Parser
|
|||||||
/**
|
/**
|
||||||
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
|
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
|
||||||
*
|
*
|
||||||
* @return \Doctrine\ORM\Query\AST\IndexBy
|
* @return Doctrine\ORM\Query\AST\IndexBy
|
||||||
*/
|
*/
|
||||||
public function IndexBy()
|
public function IndexBy()
|
||||||
{
|
{
|
||||||
@ -1659,13 +1648,60 @@ class Parser
|
|||||||
return $pathExp;
|
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 ::=
|
* SelectExpression ::=
|
||||||
* IdentificationVariable | StateFieldPathExpression |
|
* 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()
|
public function SelectExpression()
|
||||||
{
|
{
|
||||||
@ -1673,22 +1709,46 @@ class Parser
|
|||||||
$fieldAliasIdentificationVariable = null;
|
$fieldAliasIdentificationVariable = null;
|
||||||
$peek = $this->_lexer->glimpse();
|
$peek = $this->_lexer->glimpse();
|
||||||
|
|
||||||
// First we recognize for an IdentificationVariable (DQL class alias)
|
$supportsAlias = true;
|
||||||
if ($peek['value'] != '.' && $peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
|
if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
|
||||||
$expression = $this->IdentificationVariable();
|
if ($peek['value'] == '.') {
|
||||||
} else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) {
|
// ScalarExpression
|
||||||
if ($isFunction) {
|
$expression = $this->ScalarExpression();
|
||||||
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
|
||||||
$expression = $this->AggregateExpression();
|
|
||||||
} else {
|
|
||||||
$expression = $this->FunctionDeclaration();
|
|
||||||
}
|
|
||||||
} else {
|
} 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);
|
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||||
$expression = $this->Subselect();
|
$expression = $this->Subselect();
|
||||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
$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)) {
|
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
||||||
$this->match(Lexer::T_AS);
|
$this->match(Lexer::T_AS);
|
||||||
}
|
}
|
||||||
@ -1704,16 +1764,6 @@ class Parser
|
|||||||
'token' => $token,
|
'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);
|
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
|
||||||
@ -1761,7 +1811,6 @@ class Parser
|
|||||||
return $expr;
|
return $expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
|
* ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
|
||||||
*
|
*
|
||||||
@ -1945,7 +1994,6 @@ class Parser
|
|||||||
return $this->ComparisonExpression();
|
return $this->ComparisonExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
|
* EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
|
||||||
*
|
*
|
||||||
@ -2001,7 +2049,6 @@ class Parser
|
|||||||
return $collMemberExpr;
|
return $collMemberExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Literal ::= string | char | integer | float | boolean
|
* Literal ::= string | char | integer | float | boolean
|
||||||
*
|
*
|
||||||
@ -2047,7 +2094,6 @@ class Parser
|
|||||||
return $this->Literal();
|
return $this->Literal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InputParameter ::= PositionalParameter | NamedParameter
|
* InputParameter ::= PositionalParameter | NamedParameter
|
||||||
*
|
*
|
||||||
@ -2060,7 +2106,6 @@ class Parser
|
|||||||
return new AST\InputParameter($this->_lexer->token['value']);
|
return new AST\InputParameter($this->_lexer->token['value']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
|
* ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
|
||||||
*
|
*
|
||||||
@ -2274,7 +2319,6 @@ class Parser
|
|||||||
return $this->IdentificationVariable();
|
return $this->IdentificationVariable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AggregateExpression ::=
|
* AggregateExpression ::=
|
||||||
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
|
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
|
||||||
@ -2321,7 +2365,6 @@ class Parser
|
|||||||
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
|
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
|
* QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
|
||||||
*
|
*
|
||||||
@ -2573,7 +2616,6 @@ class Parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
|
* FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
|
||||||
*/
|
*/
|
||||||
|
@ -86,6 +86,15 @@ class QueryException extends \Doctrine\Common\DoctrineException
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
public static function overwritingJoinConditionsNotYetSupported($assoc)
|
||||||
{
|
{
|
||||||
return new self(
|
return new self(
|
||||||
@ -94,4 +103,21 @@ class QueryException extends \Doctrine\Common\DoctrineException
|
|||||||
"Use WITH to append additional join conditions to the association."
|
"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."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -32,6 +32,7 @@ use Doctrine\ORM\Query,
|
|||||||
* @author Roman Borschel <roman@code-factory.org>
|
* @author Roman Borschel <roman@code-factory.org>
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
|
* @todo Rename: SQLWalker
|
||||||
*/
|
*/
|
||||||
class SqlWalker implements TreeWalker
|
class SqlWalker implements TreeWalker
|
||||||
{
|
{
|
||||||
@ -40,14 +41,10 @@ class SqlWalker implements TreeWalker
|
|||||||
*/
|
*/
|
||||||
private $_rsm;
|
private $_rsm;
|
||||||
|
|
||||||
/** Counter for generating unique column aliases. */
|
/** Counters for generating unique column aliases, table aliases and parameter indexes. */
|
||||||
private $_aliasCounter = 0;
|
private $_aliasCounter = 0;
|
||||||
|
|
||||||
/** Counter for generating unique table aliases. */
|
|
||||||
private $_tableAliasCounter = 0;
|
private $_tableAliasCounter = 0;
|
||||||
private $_scalarResultCounter = 1;
|
private $_scalarResultCounter = 1;
|
||||||
|
|
||||||
/** Counter for SQL parameter positions. */
|
|
||||||
private $_sqlParamIndex = 1;
|
private $_sqlParamIndex = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,7 +67,7 @@ class SqlWalker implements TreeWalker
|
|||||||
*/
|
*/
|
||||||
private $_query;
|
private $_query;
|
||||||
|
|
||||||
private $_dqlToSqlAliasMap = array();
|
private $_tableAliasMap = array();
|
||||||
|
|
||||||
/** Map from result variable names to their SQL column alias names. */
|
/** Map from result variable names to their SQL column alias names. */
|
||||||
private $_scalarResultAliasMap = array();
|
private $_scalarResultAliasMap = array();
|
||||||
@ -89,7 +86,7 @@ class SqlWalker implements TreeWalker
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag that indicates whether to generate SQL table aliases in the SQL.
|
* 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;
|
private $_useSqlTableAliases = true;
|
||||||
|
|
||||||
@ -101,17 +98,17 @@ class SqlWalker implements TreeWalker
|
|||||||
private $_platform;
|
private $_platform;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __construct($query, $parserResult, array $queryComponents)
|
public function __construct($query, $parserResult, array $queryComponents)
|
||||||
{
|
{
|
||||||
$this->_rsm = $parserResult->getResultSetMapping();
|
|
||||||
$this->_query = $query;
|
$this->_query = $query;
|
||||||
|
$this->_parserResult = $parserResult;
|
||||||
|
$this->_queryComponents = $queryComponents;
|
||||||
|
$this->_rsm = $parserResult->getResultSetMapping();
|
||||||
$this->_em = $query->getEntityManager();
|
$this->_em = $query->getEntityManager();
|
||||||
$this->_conn = $this->_em->getConnection();
|
$this->_conn = $this->_em->getConnection();
|
||||||
$this->_platform = $this->_conn->getDatabasePlatform();
|
$this->_platform = $this->_conn->getDatabasePlatform();
|
||||||
$this->_parserResult = $parserResult;
|
|
||||||
$this->_queryComponents = $queryComponents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,11 +197,11 @@ class SqlWalker implements TreeWalker
|
|||||||
{
|
{
|
||||||
$tableName .= $dqlAlias;
|
$tableName .= $dqlAlias;
|
||||||
|
|
||||||
if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) {
|
if ( ! isset($this->_tableAliasMap[$tableName])) {
|
||||||
$this->_dqlToSqlAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
|
$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)
|
public function setSqlTableAlias($tableName, $alias)
|
||||||
{
|
{
|
||||||
$this->_dqlToSqlAliasMap[$tableName] = $alias;
|
$this->_tableAliasMap[$tableName] = $alias;
|
||||||
|
|
||||||
return $alias;
|
return $alias;
|
||||||
}
|
}
|
||||||
@ -270,16 +267,16 @@ class SqlWalker implements TreeWalker
|
|||||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||||
$tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
|
$tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
|
||||||
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
|
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
|
||||||
. ' ' . $tableAlias . ' ON ';
|
. ' ' . $tableAlias . ' ON ';
|
||||||
$first = true;
|
|
||||||
|
|
||||||
|
$first = true;
|
||||||
foreach ($class->identifier as $idField) {
|
foreach ($class->identifier as $idField) {
|
||||||
if ($first) $first = false; else $sql .= ' AND ';
|
if ($first) $first = false; else $sql .= ' AND ';
|
||||||
|
|
||||||
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
|
$columnName = $class->getQuotedColumnName($idField, $this->_platform);
|
||||||
$sql .= $baseTableAlias . '.' . $columnName
|
$sql .= $baseTableAlias . '.' . $columnName
|
||||||
. ' = '
|
. ' = '
|
||||||
. $tableAlias . '.' . $columnName;
|
. $tableAlias . '.' . $columnName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,9 +336,8 @@ class SqlWalker implements TreeWalker
|
|||||||
$sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
|
$sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
|
||||||
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
|
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
|
||||||
|
|
||||||
$q = $this->getQuery();
|
|
||||||
$sql = $this->_platform->modifyLimitQuery(
|
$sql = $this->_platform->modifyLimitQuery(
|
||||||
$sql, $q->getMaxResults(), $q->getFirstResult()
|
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $sql;
|
return $sql;
|
||||||
@ -408,7 +404,6 @@ class SqlWalker implements TreeWalker
|
|||||||
return $this->getSqlTableAlias($class->primaryTable['name'], $identificationVariable);
|
return $this->getSqlTableAlias($class->primaryTable['name'], $identificationVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks down a PathExpression AST node, thereby generating the appropriate SQL.
|
* Walks down a PathExpression AST node, thereby generating the appropriate SQL.
|
||||||
*
|
*
|
||||||
@ -418,15 +413,13 @@ class SqlWalker implements TreeWalker
|
|||||||
public function walkPathExpression($pathExpr)
|
public function walkPathExpression($pathExpr)
|
||||||
{
|
{
|
||||||
$sql = '';
|
$sql = '';
|
||||||
$pathExprType = $pathExpr->type;
|
|
||||||
|
|
||||||
switch ($pathExpr->type) {
|
switch ($pathExpr->type) {
|
||||||
case AST\PathExpression::TYPE_STATE_FIELD:
|
case AST\PathExpression::TYPE_STATE_FIELD:
|
||||||
$parts = $pathExpr->parts;
|
$parts = $pathExpr->parts;
|
||||||
$fieldName = array_pop($parts);
|
$fieldName = array_pop($parts);
|
||||||
$dqlAlias = $pathExpr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
|
$dqlAlias = $pathExpr->identificationVariable . ( ! empty($parts) ? '.' . implode('.', $parts) : '');
|
||||||
$qComp = $this->_queryComponents[$dqlAlias];
|
$class = $this->_queryComponents[$dqlAlias]['metadata'];
|
||||||
$class = $qComp['metadata'];
|
|
||||||
|
|
||||||
if ($this->_useSqlTableAliases) {
|
if ($this->_useSqlTableAliases) {
|
||||||
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
|
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
|
||||||
@ -434,33 +427,27 @@ class SqlWalker implements TreeWalker
|
|||||||
|
|
||||||
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
|
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
|
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
|
||||||
// "u.Group" should be converted to:
|
// 1- the owning side:
|
||||||
// 1- IdentificationVariable is the owning side:
|
// Just use the foreign key, i.e. u.group_id
|
||||||
// Just append the condition: u.group_id = ?
|
$parts = $pathExpr->parts;
|
||||||
/*$parts = $pathExpr->parts;
|
$fieldName = array_pop($parts);
|
||||||
$numParts = count($parts);
|
|
||||||
$dqlAlias = $pathExpr->identificationVariable;
|
$dqlAlias = $pathExpr->identificationVariable;
|
||||||
$fieldName = $parts[$numParts - 1];
|
$class = $this->_queryComponents[$dqlAlias]['metadata'];
|
||||||
$qComp = $this->_queryComponents[$dqlAlias];
|
|
||||||
$class = $qComp['metadata'];
|
|
||||||
$assoc = $class->associationMappings[$fieldName];
|
$assoc = $class->associationMappings[$fieldName];
|
||||||
|
|
||||||
if ($assoc->isOwningSide) {
|
if ($assoc->isOwningSide) {
|
||||||
foreach ($assoc->)
|
// COMPOSITE KEYS NOT (YET?) SUPPORTED
|
||||||
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
|
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();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// 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();
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw QueryException::invalidPathExpression($pathExpr);
|
throw QueryException::invalidPathExpression($pathExpr);
|
||||||
}
|
}
|
||||||
@ -468,7 +455,6 @@ class SqlWalker implements TreeWalker
|
|||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
|
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
|
||||||
*
|
*
|
||||||
@ -639,10 +625,8 @@ class SqlWalker implements TreeWalker
|
|||||||
*/
|
*/
|
||||||
public function walkHavingClause($havingClause)
|
public function walkHavingClause($havingClause)
|
||||||
{
|
{
|
||||||
$condExpr = $havingClause->conditionalExpression;
|
|
||||||
|
|
||||||
return ' HAVING ' . implode(
|
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->conditionalExpression !== null) {
|
||||||
if ($join->whereType == AST\Join::JOIN_WHERE_ON) {
|
$sql .= ' AND (' . implode(' OR ',
|
||||||
throw QueryException::overwritingJoinConditionsNotYetSupported($assoc);
|
array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms)
|
||||||
} else {
|
). ')';
|
||||||
$sql .= ' AND (' . implode(' OR ',
|
|
||||||
array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms)
|
|
||||||
). ')';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias);
|
$discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias);
|
||||||
@ -793,21 +773,24 @@ class SqlWalker implements TreeWalker
|
|||||||
$qComp = $this->_queryComponents[$dqlAlias];
|
$qComp = $this->_queryComponents[$dqlAlias];
|
||||||
$class = $qComp['metadata'];
|
$class = $qComp['metadata'];
|
||||||
|
|
||||||
if ( ! isset($this->_selectedClasses[$dqlAlias])) {
|
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||||
$this->_selectedClasses[$dqlAlias] = $class;
|
$resultAlias = $fieldName;
|
||||||
|
} else {
|
||||||
|
$resultAlias = $selectExpression->fieldIdentificationVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
|
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
|
||||||
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
|
$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);
|
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
|
||||||
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
|
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||||
} else {
|
} 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) {
|
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||||
$resultAlias = $this->_scalarResultCounter++;
|
$resultAlias = $this->_scalarResultCounter++;
|
||||||
} else {
|
} else {
|
||||||
@ -820,9 +803,11 @@ class SqlWalker implements TreeWalker
|
|||||||
|
|
||||||
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
|
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
|
||||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||||
} else if ($expr instanceof AST\Subselect) {
|
}
|
||||||
|
else if ($expr instanceof AST\Subselect) {
|
||||||
$sql .= $this->walkSubselect($expr);
|
$sql .= $this->walkSubselect($expr);
|
||||||
} else if ($expr instanceof AST\Functions\FunctionNode) {
|
}
|
||||||
|
else if ($expr instanceof AST\Functions\FunctionNode) {
|
||||||
if ( ! $selectExpression->fieldIdentificationVariable) {
|
if ( ! $selectExpression->fieldIdentificationVariable) {
|
||||||
$resultAlias = $this->_scalarResultCounter++;
|
$resultAlias = $this->_scalarResultCounter++;
|
||||||
} else {
|
} else {
|
||||||
@ -833,11 +818,32 @@ class SqlWalker implements TreeWalker
|
|||||||
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
|
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
|
||||||
$this->_scalarResultAliasMap[$resultAlias] = $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);
|
$columnAlias = $this->_platform->getSqlResultCasing($columnAlias);
|
||||||
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
|
||||||
} else {
|
} else {
|
||||||
// $expr == IdentificationVariable
|
// IdentificationVariable or PartialObjectExpression
|
||||||
$dqlAlias = $expr;
|
if ($expr instanceof AST\PartialObjectExpression) {
|
||||||
|
$dqlAlias = $expr->identificationVariable;
|
||||||
|
$partialFieldSet = $expr->partialFieldSet;
|
||||||
|
} else {
|
||||||
|
$dqlAlias = $expr;
|
||||||
|
$partialFieldSet = array();
|
||||||
|
}
|
||||||
|
|
||||||
$queryComp = $this->_queryComponents[$dqlAlias];
|
$queryComp = $this->_queryComponents[$dqlAlias];
|
||||||
$class = $queryComp['metadata'];
|
$class = $queryComp['metadata'];
|
||||||
|
|
||||||
@ -848,6 +854,10 @@ class SqlWalker implements TreeWalker
|
|||||||
$beginning = true;
|
$beginning = true;
|
||||||
// Select all fields from the queried class
|
// Select all fields from the queried class
|
||||||
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||||
|
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($mapping['inherited'])) {
|
if (isset($mapping['inherited'])) {
|
||||||
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->primaryTable['name'];
|
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->primaryTable['name'];
|
||||||
} else {
|
} else {
|
||||||
@ -874,7 +884,7 @@ class SqlWalker implements TreeWalker
|
|||||||
$subClass = $this->_em->getClassMetadata($subClassName);
|
$subClass = $this->_em->getClassMetadata($subClassName);
|
||||||
$sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
|
$sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias);
|
||||||
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
|
foreach ($subClass->fieldMappings as $fieldName => $mapping) {
|
||||||
if (isset($mapping['inherited'])) {
|
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$conn->setRollbackOnly();
|
$conn->setRollbackOnly();
|
||||||
$conn->rollback();
|
$conn->rollback();
|
||||||
$this->clear();
|
$this->_em->close();
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,5 +116,4 @@ class CmsUser
|
|||||||
$address->setUser($this);
|
$address->setUser($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -286,6 +286,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertEquals('gblanco', $users[0]->username);
|
$this->assertEquals('gblanco', $users[0]->username);
|
||||||
$this->assertEquals('developer', $users[0]->status);
|
$this->assertEquals('developer', $users[0]->status);
|
||||||
$this->assertTrue($users[0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
$this->assertTrue($users[0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection);
|
||||||
|
$this->assertTrue($users[0]->phonenumbers->isInitialized());
|
||||||
$this->assertEquals(0, $users[0]->phonenumbers->count());
|
$this->assertEquals(0, $users[0]->phonenumbers->count());
|
||||||
//$this->assertNull($users[0]->articles);
|
//$this->assertNull($users[0]->articles);
|
||||||
}
|
}
|
||||||
@ -332,7 +333,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->_em->flush();
|
$this->_em->flush();
|
||||||
$this->_em->clear();
|
$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()));
|
$this->assertEquals(0, count($query->getResult()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional;
|
|||||||
|
|
||||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||||
use Doctrine\ORM\UnitOfWork;
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
@ -84,6 +85,5 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertTrue($this->_em->contains($phonenumbers[0]));
|
$this->assertTrue($this->_em->contains($phonenumbers[0]));
|
||||||
$this->assertTrue($this->_em->contains($phonenumbers[1]));
|
$this->assertTrue($this->_em->contains($phonenumbers[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,8 +189,7 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
" TRIM(LEADING '.' FROM m.name) AS str2, TRIM(CONCAT(' ', CONCAT(m.name, ' '))) AS str3 ".
|
" TRIM(LEADING '.' FROM m.name) AS str2, TRIM(CONCAT(' ', CONCAT(m.name, ' '))) AS str3 ".
|
||||||
"FROM Doctrine\Tests\Models\Company\CompanyManager m";
|
"FROM Doctrine\Tests\Models\Company\CompanyManager m";
|
||||||
|
|
||||||
$result = $this->_em->createQuery($dql)
|
$result = $this->_em->createQuery($dql)->getArrayResult();
|
||||||
->getArrayResult();
|
|
||||||
|
|
||||||
$this->assertEquals(4, count($result));
|
$this->assertEquals(4, count($result));
|
||||||
$this->assertEquals('Roman B', $result[0]['str1']);
|
$this->assertEquals('Roman B', $result[0]['str1']);
|
||||||
@ -207,34 +206,34 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertEquals('Jonathan W.', $result[3]['str3']);
|
$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')
|
$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(4, count($result));
|
||||||
$this->assertEquals(102500, $result[0]['op']);
|
$this->assertEquals(102500, $result[0]['add']);
|
||||||
$this->assertEquals(202500, $result[1]['op']);
|
$this->assertEquals(202500, $result[1]['add']);
|
||||||
$this->assertEquals(402500, $result[2]['op']);
|
$this->assertEquals(402500, $result[2]['add']);
|
||||||
$this->assertEquals(802500, $result[3]['op']);
|
$this->assertEquals(802500, $result[3]['add']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOperatorSub()
|
public function testOperatorSub()
|
||||||
{
|
{
|
||||||
$result = $this->_em->createQuery('SELECT m, m.salary-2500 AS add FROM Doctrine\Tests\Models\Company\CompanyManager m')
|
$result = $this->_em->createQuery('SELECT m, m.salary-2500 AS sub FROM Doctrine\Tests\Models\Company\CompanyManager m')
|
||||||
->getResult();
|
->getResult();
|
||||||
|
|
||||||
$this->assertEquals(4, count($result));
|
$this->assertEquals(4, count($result));
|
||||||
$this->assertEquals(102500, $result[0]['op']);
|
$this->assertEquals(97500, $result[0]['sub']);
|
||||||
$this->assertEquals(202500, $result[1]['op']);
|
$this->assertEquals(197500, $result[1]['sub']);
|
||||||
$this->assertEquals(402500, $result[2]['op']);
|
$this->assertEquals(397500, $result[2]['sub']);
|
||||||
$this->assertEquals(802500, $result[3]['op']);
|
$this->assertEquals(797500, $result[3]['sub']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOperatorMultiply()
|
public function testOperatorMultiply()
|
||||||
{
|
{
|
||||||
$result = $this->_em->createQuery('SELECT m, m.salary*2 AS op FROM Doctrine\Tests\Models\Company\CompanyManager m')
|
$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(4, count($result));
|
||||||
$this->assertEquals(200000, $result[0]['op']);
|
$this->assertEquals(200000, $result[0]['op']);
|
||||||
@ -243,17 +242,33 @@ class QueryDqlFunctionTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertEquals(1600000, $result[3]['op']);
|
$this->assertEquals(1600000, $result[3]['op']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group test
|
||||||
|
*/
|
||||||
public function testOperatorDiv()
|
public function testOperatorDiv()
|
||||||
{
|
{
|
||||||
$result = $this->_em->createQuery('SELECT m, (m.salary/0.5) AS op FROM Doctrine\Tests\Models\Company\CompanyManager m')
|
$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(4, count($result));
|
||||||
$this->assertEquals(200000, $result[0]['op']);
|
$this->assertEquals(200000, $result[0]['op']);
|
||||||
$this->assertEquals(400000, $result[1]['op']);
|
$this->assertEquals(400000, $result[1]['op']);
|
||||||
$this->assertEquals(800000, $result[2]['op']);
|
$this->assertEquals(800000, $result[2]['op']);
|
||||||
$this->assertEquals(1600000, $result[3]['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()
|
protected function generateFixture()
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ class DDC163Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->_em->flush();
|
$this->_em->flush();
|
||||||
$this->_em->clear();
|
$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
|
FROM Doctrine\Tests\Models\Company\CompanyPerson person
|
||||||
LEFT JOIN person.spouse spouse
|
LEFT JOIN person.spouse spouse
|
||||||
LEFT JOIN person.friends friend
|
LEFT JOIN person.friends friend
|
||||||
|
@ -627,6 +627,76 @@ class ArrayHydratorTest extends HydrationTestCase
|
|||||||
$this->assertEquals(1, count($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()
|
public function testResultIteration()
|
||||||
{
|
{
|
||||||
$rsm = new ResultSetMapping;
|
$rsm = new ResultSetMapping;
|
||||||
|
@ -744,6 +744,81 @@ class ObjectHydratorTest extends HydrationTestCase
|
|||||||
$this->assertEquals(0, $result[1]->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()
|
public function testResultIteration()
|
||||||
{
|
{
|
||||||
$rsm = new ResultSetMapping;
|
$rsm = new ResultSetMapping;
|
||||||
|
@ -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');
|
$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()
|
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");
|
$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.
|
// 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 = ?
|
// 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()
|
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
|
* 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.
|
* 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->assertInvalidDql('SELECT u FROM UnknownClassName u');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testCorrectPartialObjectLoad()
|
||||||
* This checks for invalid attempt to hydrate a proxy. It should throw an exception
|
|
||||||
*
|
|
||||||
* @expectedException \Doctrine\Common\DoctrineException
|
|
||||||
*/
|
|
||||||
public function testPartialObjectLoad()
|
|
||||||
{
|
{
|
||||||
$this->parseDql('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u', array(
|
$this->assertValidDql('SELECT PARTIAL u.{id,name} FROM Doctrine\Tests\Models\CMS\CmsUser u');
|
||||||
\Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD => false
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
|
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
|
||||||
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
|
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
|
||||||
$query->free();
|
$query->free();
|
||||||
} catch (DoctrineException $e) {
|
} catch (\Exception $e) {
|
||||||
$this->fail($e->getMessage());
|
$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 = ?)"
|
"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"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user