Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
3928ba9d
Commit
3928ba9d
authored
Dec 31, 2009
by
guilhermeblanco
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[2.0] Added support to complex PathExpression in DQL queries
parent
20c84166
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
254 additions
and
192 deletions
+254
-192
Parser.php
lib/Doctrine/ORM/Query/Parser.php
+189
-157
SqlWalker.php
lib/Doctrine/ORM/Query/SqlWalker.php
+17
-33
LanguageRecognitionTest.php
tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
+29
-0
SelectSqlGenerationTest.php
tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+19
-2
No files found.
lib/Doctrine/ORM/Query/Parser.php
View file @
3928ba9d
...
...
@@ -94,6 +94,13 @@ class Parser
*/
private
$_em
;
/**
* The Abtract Syntax Tree of processed DQL query.
*
* @var SelectStatement | UpdateStatement | DeleteStatement
*/
private
$_ast
;
/**
* The Query to parse.
*
...
...
@@ -140,6 +147,7 @@ class Parser
$this
->
_em
=
$query
->
getEntityManager
();
$this
->
_lexer
=
new
Lexer
(
$query
->
getDql
());
$this
->
_parserResult
=
new
ParserResult
();
$this
->
_ast
=
null
;
}
/**
...
...
@@ -193,6 +201,16 @@ class Parser
return
$this
->
_em
;
}
/**
* Gets the Abstract Syntax Tree generated by the parser.
*
* @return SelectStatement | UpdateStatement | DeleteStatement
*/
public
function
getAST
()
{
return
$this
->
_ast
;
}
/**
* Registers a custom function that returns strings.
*
...
...
@@ -274,10 +292,8 @@ class Parser
// Parse & build AST
$AST
=
$this
->
QueryLanguage
();
// Check for end of string
if
(
$this
->
_lexer
->
lookahead
!==
null
)
{
$this
->
syntaxError
(
'end of string'
);
}
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this
->
_processDeferredExpressionsStack
(
$AST
);
if
(
$customWalkers
=
$this
->
_query
->
getHint
(
Query
::
HINT_CUSTOM_TREE_WALKERS
))
{
$this
->
_customTreeWalkers
=
$customWalkers
;
...
...
@@ -286,9 +302,11 @@ class Parser
// Run any custom tree walkers over the AST
if
(
$this
->
_customTreeWalkers
)
{
$treeWalkerChain
=
new
TreeWalkerChain
(
$this
->
_query
,
$this
->
_parserResult
,
$this
->
_queryComponents
);
foreach
(
$this
->
_customTreeWalkers
as
$walker
)
{
$treeWalkerChain
->
addTreeWalker
(
$walker
);
}
if
(
$AST
instanceof
AST\SelectStatement
)
{
$treeWalkerChain
->
walkSelectStatement
(
$AST
);
}
else
if
(
$AST
instanceof
AST\UpdateStatement
)
{
...
...
@@ -492,11 +510,11 @@ class Parser
* @param array $token
* @param integer $nestingLevel
*/
private
function
_subscribeExpression
(
$expression
,
$method
,
$token
,
$ne
x
tingLevel
=
null
)
private
function
_subscribeExpression
(
$expression
,
$method
,
$token
,
$ne
s
tingLevel
=
null
)
{
$nestingLevel
=
(
$nestingLevel
!==
null
)
?:
$this
->
_nestingLevel
;
$exprStack
[]
=
array
(
$exprStack
=
array
(
'method'
=>
$method
,
'expression'
=>
$expression
,
'nestingLevel'
=>
$nestingLevel
,
...
...
@@ -508,13 +526,17 @@ class Parser
/**
* Processes the topmost stack of deferred path expressions.
*
* @param mixed $AST
*/
private
function
_processDeferredExpressionsStack
()
private
function
_processDeferredExpressionsStack
(
$AST
)
{
foreach
(
$this
->
_deferredExpressionsStack
as
$item
)
{
if
(
!
isset
(
$item
[
'method'
]))
var_dump
(
$item
);
$method
=
'_validate'
.
$item
[
'method'
];
$this
->
$method
(
$item
[
'expression'
],
$item
[
'token'
],
$item
[
'nestingLevel'
]
);
$this
->
$method
(
$item
,
$AST
);
}
}
...
...
@@ -523,30 +545,35 @@ class Parser
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $identVariable
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private
function
_validateIdentificationVariable
(
$
identVariable
,
$token
,
$nestingLevel
)
private
function
_validateIdentificationVariable
(
$
deferredItem
,
$AST
)
{
$identVariable
=
$deferredItem
[
'expression'
];
// Check if IdentificationVariable exists in queryComponents
if
(
!
isset
(
$this
->
_queryComponents
[
$identVariable
]))
{
$this
->
semanticalError
(
"'
$identVariable
' is not defined."
,
$token
);
$this
->
semanticalError
(
"'
$identVariable
' is not defined."
,
$deferredItem
[
'token'
]
);
}
$qComp
=
$this
->
_queryComponents
[
$identVariable
];
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
if
(
!
isset
(
$qComp
[
'metadata'
]))
{
$this
->
semanticalError
(
"'
$identVariable
' does not point to a Class."
,
$token
);
$this
->
semanticalError
(
"'
$identVariable
' does not point to a Class."
,
$deferredItem
[
'token'
]
);
}
// Validate if identification variable nesting level is lower or equal than the current one
if
(
$qComp
[
'nestingLevel'
]
>
$
nestingLevel
)
{
if
(
$qComp
[
'nestingLevel'
]
>
$
deferredItem
[
'nestingLevel'
]
)
{
$this
->
semanticalError
(
"'
$identVariable
' is used outside the scope of its declaration."
,
$
token
"'
$identVariable
' is used outside the scope of its declaration."
,
$
deferredItem
[
'token'
]
);
}
...
...
@@ -554,75 +581,38 @@ class Parser
}
/**
* Validates that the given <tt>AliasIdentificationVariable</tt> is a semantically correct.
* It must not exist in query components list.
*
* @param string $aliasIdentVariable
* @param array $token
* @param integer $nestingLevel
*
* @return boolean
*/
private
function
_validateAliasIdentificationVariable
(
$aliasIdentVariable
,
$token
,
$nestingLevel
)
{
$exists
=
isset
(
$this
->
_queryComponents
[
$aliasIdentVariable
]);
if
(
$exists
)
{
$this
->
semanticalError
(
"'
$aliasIdentVariable
' is already defined."
,
$token
);
}
return
$exists
;
}
/**
* Validates that the given <tt>AbstractSchemaName</tt> is a semantically correct.
* It must be defined in the scope of Application.
*
* @param string $schemaName
* @param array $token
* @param integer $nestingLevel
*
* @return boolean
*/
private
function
_validateAbstractSchemaName
(
$schemaName
,
$token
,
$nestingLevel
)
{
$exists
=
class_exists
(
$schemaName
,
true
);
if
(
!
$exists
)
{
$this
->
semanticalError
(
"Class '
$schemaName
' is not defined."
,
$token
);
}
return
$exists
;
}
/**
* Validates that the given <tt>AliasIdentificationVariable</tt> is a semantically correct.
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $resultVariable
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private
function
_validateResultVariable
(
$
resultVariable
,
$token
,
$nestingLevel
)
private
function
_validateResultVariable
(
$
deferredItem
,
$AST
)
{
$resultVariable
=
$deferredItem
[
'expression'
];
// Check if ResultVariable exists in queryComponents
if
(
!
isset
(
$this
->
_queryComponents
[
$resultVariable
]))
{
$this
->
semanticalError
(
"'
$resultVariable
' is not defined."
,
$token
);
$this
->
semanticalError
(
"'
$resultVariable
' is not defined."
,
$deferredItem
[
'token'
]
);
}
$qComp
=
$this
->
_queryComponents
[
$resultVariable
];
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
if
(
!
isset
(
$qComp
[
'resultVariable'
]))
{
$this
->
semanticalError
(
"'
$identVariable
' does not point to a ResultVariable."
,
$token
);
$this
->
semanticalError
(
"'
$identVariable
' does not point to a ResultVariable."
,
$deferredItem
[
'token'
]
);
}
// Validate if identification variable nesting level is lower or equal than the current one
if
(
$qComp
[
'nestingLevel'
]
>
$
nestingLevel
)
{
if
(
$qComp
[
'nestingLevel'
]
>
$
deferredItem
[
'nestingLevel'
]
)
{
$this
->
semanticalError
(
"'
$resultVariable
' is used outside the scope of its declaration."
,
$
token
"'
$resultVariable
' is used outside the scope of its declaration."
,
$
deferredItem
[
'token'
]
);
}
...
...
@@ -632,14 +622,14 @@ class Parser
/**
* Validates that the given <tt>JoinAssociationPathExpression</tt> is a semantically correct.
*
* @param JoinAssociationPathExpression $pathExpression
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private
function
_validateJoinAssociationPathExpression
(
AST\JoinAssociationPathExpression
$pathExpression
,
$token
,
$nestingLevel
)
private
function
_validateJoinAssociationPathExpression
(
$deferredItem
,
$AST
)
{
$pathExpression
=
$deferredItem
[
'expression'
];
$qComp
=
$this
->
_queryComponents
[
$pathExpression
->
identificationVariable
];;
// Validating association field (*-to-one or *-to-many)
...
...
@@ -662,70 +652,106 @@ class Parser
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
*
* @param PathExpression $pathExpression
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return integer
*/
private
function
_validatePathExpression
(
AST\PathExpression
$pathExpression
,
$token
,
$nestingLevel
)
private
function
_validatePathExpression
(
$deferredItem
,
$AST
)
{
$pathExpression
=
$deferredItem
[
'expression'
];
$parts
=
$pathExpression
->
parts
;
$numParts
=
count
(
$parts
);
$qComp
=
$this
->
_queryComponents
[
$pathExpression
->
identificationVariable
];
$aliasIdentificationVariable
=
$pathExpression
->
identificationVariable
;
$parentField
=
$pathExpression
->
identificationVariable
;
$class
=
$qComp
[
'metadata'
];
$stateField
=
$collectionField
=
null
;
$fieldType
=
null
;
$curIndex
=
0
;
foreach
(
$pa
thExpression
->
pa
rts
as
$field
)
{
foreach
(
$parts
as
$field
)
{
// Check if it is not in a state field
if
(
$
stateField
!==
null
)
{
if
(
$
fieldType
&
AST\PathExpression
::
TYPE_STATE_FIELD
)
{
$this
->
semanticalError
(
'Cannot navigate through state field named '
.
$stateField
,
$token
'Cannot navigate through state field named '
.
$field
.
' on '
.
$parentField
,
$deferredItem
[
'token'
]
);
}
// Check if it is not a collection field
if
(
$
collectionField
!==
null
)
{
if
(
$
fieldType
&
AST\PathExpression
::
TYPE_COLLECTION_VALUED_ASSOCIATION
)
{
$this
->
semanticalError
(
'Cannot navigate through collection
-valued field named '
.
$collection
Field
,
$
token
'Cannot navigate through collection
field named '
.
$field
.
' on '
.
$parent
Field
,
$
deferredItem
[
'token'
]
);
}
// Check if field exists
// Check if field
or association
exists
if
(
!
isset
(
$class
->
associationMappings
[
$field
])
&&
!
isset
(
$class
->
fieldMappings
[
$field
]))
{
$this
->
semanticalError
(
'Class '
.
$class
->
name
.
' has no field or association named '
.
$field
,
$token
'Class '
.
$class
->
name
.
' has no field or association named '
.
$field
,
$deferredItem
[
'token'
]
);
}
$parentField
=
$field
;
if
(
isset
(
$class
->
fieldMappings
[
$field
]))
{
$stateField
=
$field
;
}
else
if
(
$class
->
associationMappings
[
$field
]
->
isOneToOne
())
{
$class
=
$this
->
_em
->
getClassMetadata
(
$class
->
associationMappings
[
$field
]
->
targetEntityName
);
$fieldType
=
AST\PathExpression
::
TYPE_STATE_FIELD
;
}
else
{
$collectionField
=
$field
;
}
$assoc
=
$class
->
associationMappings
[
$field
];
$class
=
$this
->
_em
->
getClassMetadata
(
$assoc
->
targetEntityName
);
if
(
(
$curIndex
!=
$numParts
-
1
)
&&
!
isset
(
$this
->
_queryComponents
[
$aliasIdentificationVariable
.
'.'
.
$field
])
)
{
// Building queryComponent
$joinQueryComponent
=
array
(
'metadata'
=>
$class
,
'parent'
=>
$aliasIdentificationVariable
,
'relation'
=>
$assoc
,
'map'
=>
null
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
'token'
=>
$deferredItem
[
'token'
],
);
// Create AST node
$joinVariableDeclaration
=
new
AST\JoinVariableDeclaration
(
new
AST\Join
(
AST\Join
::
JOIN_TYPE_INNER
,
new
AST\JoinAssociationPathExpression
(
$aliasIdentificationVariable
,
$field
),
$aliasIdentificationVariable
.
'.'
.
$field
),
null
);
$AST
->
fromClause
->
identificationVariableDeclarations
[
0
]
->
joinVariableDeclarations
[]
=
$joinVariableDeclaration
;
$this
->
_queryComponents
[
$aliasIdentificationVariable
.
'.'
.
$field
]
=
$joinQueryComponent
;
}
// Recognize correct expression type
$expressionType
=
null
;
$aliasIdentificationVariable
.=
'.'
.
$field
;
if
(
$stateField
!==
null
)
{
$expressionType
=
AST\PathExpression
::
TYPE_STATE_FIELD
;
}
else
if
(
$collectionField
!==
null
)
{
$expressionType
=
AST\PathExpression
::
TYPE_COLLECTION_VALUED_ASSOCIATION
;
if
(
$assoc
->
isOneToOne
())
{
$fieldType
=
AST\PathExpression
::
TYPE_SINGLE_VALUED_ASSOCIATION
;
}
else
{
$expressionType
=
AST\PathExpression
::
TYPE_SINGLE_VALUED_ASSOCIATION
;
$fieldType
=
AST\PathExpression
::
TYPE_COLLECTION_VALUED_ASSOCIATION
;
}
}
$curIndex
++
;
}
// Validate if PathExpression is one of the expected types
$expectedType
=
$pathExpression
->
expectedType
;
if
(
!
(
$expectedType
&
$
expression
Type
))
{
if
(
!
(
$expectedType
&
$
field
Type
))
{
// We need to recognize which was expected type(s)
$expectedStringTypes
=
array
();
// Validate state field type
(field/column)
// Validate state field type
if
(
$expectedType
&
AST\PathExpression
::
TYPE_STATE_FIELD
)
{
$expectedStringTypes
[]
=
'StateFieldPathExpression'
;
}
...
...
@@ -749,16 +775,15 @@ class Parser
$semanticalError
.=
implode
(
' or '
,
$expectedStringTypes
)
.
' expected.'
;
}
$this
->
semanticalError
(
$semanticalError
,
$
token
);
$this
->
semanticalError
(
$semanticalError
,
$
deferredItem
[
'token'
]
);
}
// We need to force the type in PathExpression
$pathExpression
->
type
=
$
expression
Type
;
$pathExpression
->
type
=
$
field
Type
;
return
$
expression
Type
;
return
$
field
Type
;
}
/**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
*
...
...
@@ -784,6 +809,11 @@ class Parser
$this
->
syntaxError
(
'SELECT, UPDATE or DELETE'
);
break
;
}
// Check for end of string
if
(
$this
->
_lexer
->
lookahead
!==
null
)
{
$this
->
syntaxError
(
'end of string'
);
}
}
...
...
@@ -808,9 +838,6 @@ class Parser
$selectStatement
->
orderByClause
=
$this
->
_lexer
->
isNextToken
(
Lexer
::
T_ORDER
)
?
$this
->
OrderByClause
()
:
null
;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this
->
_processDeferredExpressionsStack
();
return
$selectStatement
;
}
...
...
@@ -825,9 +852,6 @@ class Parser
$updateStatement
->
whereClause
=
$this
->
_lexer
->
isNextToken
(
Lexer
::
T_WHERE
)
?
$this
->
WhereClause
()
:
null
;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this
->
_processDeferredExpressionsStack
();
return
$updateStatement
;
}
...
...
@@ -842,9 +866,6 @@ class Parser
$deleteStatement
->
whereClause
=
$this
->
_lexer
->
isNextToken
(
Lexer
::
T_WHERE
)
?
$this
->
WhereClause
()
:
null
;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this
->
_processDeferredExpressionsStack
();
return
$deleteStatement
;
}
...
...
@@ -883,11 +904,13 @@ class Parser
$this
->
match
(
Lexer
::
T_IDENTIFIER
);
$aliasIdentVariable
=
$this
->
_lexer
->
token
[
'value'
];
$exists
=
isset
(
$this
->
_queryComponents
[
$aliasIdentVariable
]);
// Apply AliasIdentificationVariable validation
$this
->
_validateAliasIdentificationVariable
(
$aliasIdentVariable
,
$this
->
_lexer
->
token
,
$this
->
_nestingLevel
if
(
$exists
)
{
$this
->
semanticalError
(
"'
$aliasIdentVariable
' is already defined."
,
$this
->
_lexer
->
token
);
}
return
$aliasIdentVariable
;
}
...
...
@@ -902,15 +925,36 @@ class Parser
$this
->
match
(
Lexer
::
T_IDENTIFIER
);
$schemaName
=
$this
->
_lexer
->
token
[
'value'
];
$exists
=
class_exists
(
$schemaName
,
true
);
// Apply AbstractSchemaName validation
$this
->
_validateAbstractSchemaName
(
$schemaName
,
$this
->
_lexer
->
token
,
$this
->
_nestingLevel
);
if
(
!
$exists
)
{
$this
->
semanticalError
(
"Class '
$schemaName
' is not defined."
,
$this
->
_lexer
->
token
);
}
return
$schemaName
;
}
/**
* AliasResultVariable ::= identifier
*
* @return string
*/
public
function
AliasResultVariable
()
{
$this
->
match
(
Lexer
::
T_IDENTIFIER
);
$resultVariable
=
$this
->
_lexer
->
token
[
'value'
];
$exists
=
isset
(
$this
->
_queryComponents
[
$resultVariable
]);
if
(
$exists
)
{
$this
->
semanticalError
(
"'
$resultVariable
' is already defined."
,
$this
->
_lexer
->
token
);
}
return
$resultVariable
;
}
/**
* ResultVariable ::= identifier
*
...
...
@@ -923,15 +967,11 @@ class Parser
$resultVariable
=
$this
->
_lexer
->
token
[
'value'
];
// Defer ResultVariable validation
$exprStack
=
array
(
'method'
=>
'ResultVariable'
,
'expression'
=>
$resultVariable
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
'token'
=>
$this
->
_lexer
->
token
,
$this
->
_subscribeExpression
(
$resultVariable
,
'ResultVariable'
,
$this
->
_lexer
->
token
,
$this
->
_nestingLevel
);
array_push
(
$this
->
_deferredExpressionsStack
,
$exprStack
);
return
$resultVariable
;
}
...
...
@@ -953,15 +993,11 @@ class Parser
$pathExpr
=
new
AST\JoinAssociationPathExpression
(
$identVariable
,
$field
);
// Defer JoinAssociationPathExpression validation
$exprStack
=
array
(
'method'
=>
'JoinAssociationPathExpression'
,
'expression'
=>
$pathExpr
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
'token'
=>
$token
,
$this
->
_subscribeExpression
(
$pathExpr
,
'JoinAssociationPathExpression'
,
$token
,
$this
->
_nestingLevel
);
array_push
(
$this
->
_deferredExpressionsStack
,
$exprStack
);
return
$pathExpr
;
}
...
...
@@ -969,7 +1005,7 @@ class Parser
* Parses an arbitrary path expression and defers semantical validation
* based on expected types.
*
* PathExpression ::= IdentificationVariable
"." {identifier "."}*
identifier
* PathExpression ::= IdentificationVariable
{"." identifier}* "."
identifier
*
* @param integer $expectedTypes
* @return \Doctrine\ORM\Query\AST\PathExpression
...
...
@@ -991,15 +1027,11 @@ class Parser
$pathExpr
=
new
AST\PathExpression
(
$expectedTypes
,
$identVariable
,
$parts
);
// Defer PathExpression validation if requested to be defered
$exprStack
=
array
(
'method'
=>
'PathExpression'
,
'expression'
=>
$pathExpr
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
'token'
=>
$token
,
$this
->
_subscribeExpression
(
$pathExpr
,
'PathExpression'
,
$token
,
$this
->
_nestingLevel
);
array_push
(
$this
->
_deferredExpressionsStack
,
$exprStack
);
return
$pathExpr
;
}
...
...
@@ -1598,7 +1630,7 @@ class Parser
);
}
$targetClassName
=
$parentClass
->
getAssociationMapping
(
$assocField
)
->
getTargetEntityName
()
;
$targetClassName
=
$parentClass
->
getAssociationMapping
(
$assocField
)
->
targetEntityName
;
// Building queryComponent
$joinQueryComponent
=
array
(
...
...
@@ -1651,7 +1683,7 @@ class Parser
/**
* SelectExpression ::=
* IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"] ResultVariable]
* (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"]
Alias
ResultVariable]
*
* @return \Doctrine\ORM\Query\AST\SelectExpression
*/
...
...
@@ -1683,9 +1715,9 @@ class Parser
if
(
$this
->
_lexer
->
isNextToken
(
Lexer
::
T_IDENTIFIER
))
{
$token
=
$this
->
_lexer
->
lookahead
;
$fieldAliasIdentificationVariable
=
$this
->
ResultVariable
();
$fieldAliasIdentificationVariable
=
$this
->
Alias
ResultVariable
();
// Include ResultVariable in query components.
// Include
Alias
ResultVariable in query components.
$this
->
_queryComponents
[
$fieldAliasIdentificationVariable
]
=
array
(
'resultVariable'
=>
$expression
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
...
...
@@ -1708,7 +1740,7 @@ class Parser
}
/**
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] ResultVariable])
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"]
Alias
ResultVariable])
*
* @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
*/
...
...
@@ -1735,10 +1767,10 @@ class Parser
if
(
$this
->
_lexer
->
isNextToken
(
Lexer
::
T_IDENTIFIER
))
{
$token
=
$this
->
_lexer
->
lookahead
;
$resultVariable
=
$this
->
ResultVariable
();
$resultVariable
=
$this
->
Alias
ResultVariable
();
$expr
->
fieldIdentificationVariable
=
$resultVariable
;
// Include ResultVariable in query components.
// Include
Alias
ResultVariable in query components.
$this
->
_queryComponents
[
$resultVariable
]
=
array
(
'resultvariable'
=>
$expr
,
'nestingLevel'
=>
$this
->
_nestingLevel
,
...
...
lib/Doctrine/ORM/Query/SqlWalker.php
View file @
3928ba9d
...
...
@@ -414,9 +414,8 @@ class SqlWalker implements TreeWalker
switch
(
$pathExpr
->
type
)
{
case
AST\PathExpression
::
TYPE_STATE_FIELD
:
$parts
=
$pathExpr
->
parts
;
$numParts
=
count
(
$parts
);
$dqlAlias
=
$pathExpr
->
identificationVariable
;
$fieldName
=
$parts
[
$numParts
-
1
];
$fieldName
=
array_pop
(
$parts
);
$dqlAlias
=
$pathExpr
->
identificationVariable
.
((
!
empty
(
$parts
))
?
'.'
.
implode
(
'.'
,
$parts
)
:
''
);
$qComp
=
$this
->
_queryComponents
[
$dqlAlias
];
$class
=
$qComp
[
'metadata'
];
...
...
@@ -609,20 +608,18 @@ class SqlWalker implements TreeWalker
*/
public
function
walkOrderByItem
(
$orderByItem
)
{
$sql
=
''
;
$expr
=
$orderByItem
->
expression
;
if
(
$expr
instanceof
AST\PathExpression
)
{
$parts
=
$expr
->
parts
;
$dqlAlias
=
$expr
->
identificationVariable
;
$class
=
$this
->
_queryComponents
[
$dqlAlias
][
'metadata'
];
$columnName
=
$class
->
getQuotedColumnName
(
$parts
[
0
],
$this
->
_platform
);
return
$this
->
getSqlTableAlias
(
$class
->
getTableName
(),
$dqlAlias
)
.
'.'
.
$columnName
.
' '
.
strtoupper
(
$orderByItem
->
type
);
if
(
$expr
instanceof
AST\PathExpression
)
{
$sql
=
$this
->
walkPathExpression
(
$expr
);
}
else
{
$columnName
=
$this
->
_queryComponents
[
$expr
][
'token'
][
'value'
];
return
$this
->
_scalarResultAliasMap
[
$columnName
]
.
' '
.
strtoupper
(
$orderByItem
->
type
)
;
$sql
=
$this
->
_scalarResultAliasMap
[
$columnName
]
;
}
return
$sql
.
' '
.
strtoupper
(
$orderByItem
->
type
);;
}
/**
...
...
@@ -771,9 +768,8 @@ class SqlWalker implements TreeWalker
if
(
$expr
instanceof
AST\PathExpression
)
{
if
(
$expr
->
type
==
AST\PathExpression
::
TYPE_STATE_FIELD
)
{
$parts
=
$expr
->
parts
;
$numParts
=
count
(
$parts
);
$dqlAlias
=
$expr
->
identificationVariable
;
$fieldName
=
$parts
[
$numParts
-
1
];
$fieldName
=
array_pop
(
$parts
);
$dqlAlias
=
$expr
->
identificationVariable
.
((
!
empty
(
$parts
))
?
'.'
.
implode
(
'.'
,
$parts
)
:
''
);
$qComp
=
$this
->
_queryComponents
[
$dqlAlias
];
$class
=
$qComp
[
'metadata'
];
...
...
@@ -1004,17 +1000,8 @@ class SqlWalker implements TreeWalker
*/
public
function
walkAggregateExpression
(
$aggExpression
)
{
$sql
=
''
;
$parts
=
$aggExpression
->
pathExpression
->
parts
;
$dqlAlias
=
$aggExpression
->
pathExpression
->
identificationVariable
;
$fieldName
=
$parts
[
0
];
$qComp
=
$this
->
_queryComponents
[
$dqlAlias
];
$columnName
=
$qComp
[
'metadata'
]
->
getQuotedColumnName
(
$fieldName
,
$this
->
_platform
);
return
$aggExpression
->
functionName
.
'('
.
(
$aggExpression
->
isDistinct
?
'DISTINCT '
:
''
)
.
$this
->
getSqlTableAlias
(
$qComp
[
'metadata'
]
->
getTableName
(),
$dqlAlias
)
.
'.'
.
$columnName
.
')'
;
.
$this
->
walkPathExpression
(
$aggExpression
->
pathExpression
)
.
')'
;
}
/**
...
...
@@ -1038,12 +1025,7 @@ class SqlWalker implements TreeWalker
*/
public
function
walkGroupByItem
(
AST\PathExpression
$pathExpr
)
{
$parts
=
$pathExpr
->
parts
;
$dqlAlias
=
$pathExpr
->
identificationVariable
;
$qComp
=
$this
->
_queryComponents
[
$dqlAlias
];
$columnName
=
$qComp
[
'metadata'
]
->
getQuotedColumnName
(
$parts
[
0
],
$this
->
_platform
);
return
$this
->
getSqlTableAlias
(
$qComp
[
'metadata'
]
->
getTableName
(),
$dqlAlias
)
.
'.'
.
$columnName
;
return
$this
->
walkPathExpression
(
$pathExpr
);
}
/**
...
...
@@ -1219,8 +1201,10 @@ class SqlWalker implements TreeWalker
$sql
.=
'EXISTS (SELECT 1 FROM '
;
$entityExpr
=
$collMemberExpr
->
entityExpression
;
$collPathExpr
=
$collMemberExpr
->
collectionValuedPathExpression
;
$parts
=
$collPathExpr
->
parts
;
$dqlAlias
=
$collPathExpr
->
identificationVariable
;
$fieldName
=
array_pop
(
$parts
);
$dqlAlias
=
$collPathExpr
->
identificationVariable
.
((
!
empty
(
$parts
))
?
'.'
.
implode
(
'.'
,
$parts
)
:
''
);
$class
=
$this
->
_queryComponents
[
$dqlAlias
][
'metadata'
];
...
...
@@ -1232,7 +1216,7 @@ class SqlWalker implements TreeWalker
throw
DoctrineException
::
notImplemented
();
}
$assoc
=
$class
->
associationMappings
[
$
parts
[
0
]
];
$assoc
=
$class
->
associationMappings
[
$
fieldName
];
if
(
$assoc
->
isOneToMany
())
{
$targetClass
=
$this
->
_em
->
getClassMetadata
(
$assoc
->
targetEntityName
);
...
...
tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
View file @
3928ba9d
...
...
@@ -59,6 +59,25 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
return
$parser
->
parse
();
}
public
function
parseDqlForAST
(
$dql
,
$hints
=
array
())
{
$query
=
$this
->
_em
->
createQuery
(
$dql
);
$query
->
setHint
(
Query
::
HINT_FORCE_PARTIAL_LOAD
,
true
);
$query
->
setDql
(
$dql
);
foreach
(
$hints
as
$key
=>
$value
)
{
$query
->
setHint
(
$key
,
$value
);
}
$parser
=
new
\Doctrine\ORM\Query\Parser
(
$query
);
// We do NOT test SQL output here. That only unnecessarily slows down the tests!
$parser
->
setCustomOutputTreeWalker
(
'Doctrine\Tests\Mocks\MockTreeWalker'
);
$parser
->
parse
();
return
$parser
->
getAST
();
}
public
function
testEmptyQueryString
()
{
$this
->
assertInvalidDql
(
''
);
...
...
@@ -84,6 +103,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this
->
assertValidDql
(
'SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p'
);
}
public
function
testSelectMultipleComponentsWithAsterisk2
()
{
$this
->
assertValidDql
(
'SELECT a.user.name FROM Doctrine\Tests\Models\CMS\CmsArticle a'
);
}
public
function
testSelectDistinctIsSupported
()
{
$this
->
assertValidDql
(
'SELECT DISTINCT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u'
);
...
...
@@ -94,6 +118,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this
->
assertValidDql
(
'SELECT COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u'
);
}
public
function
testDuplicatedAliasInAggregateFunction
()
{
$this
->
assertInvalidDql
(
'SELECT COUNT(u.id) AS num, SUM(u.id) AS num FROM Doctrine\Tests\Models\CMS\CmsUser u'
);
}
public
function
testAggregateFunctionWithDistinctInSelect
()
{
$this
->
assertValidDql
(
'SELECT COUNT(DISTINCT u.name) FROM Doctrine\Tests\Models\CMS\CmsUser u'
);
...
...
tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
View file @
3928ba9d
...
...
@@ -2,7 +2,8 @@
namespace
Doctrine\Tests\ORM\Query
;
use
Doctrine\ORM\Query
;
use
Doctrine\ORM\Query
,
Doctrine\Common\DoctrineException
;
require_once
__DIR__
.
'/../../TestInit.php'
;
...
...
@@ -22,7 +23,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$query
->
setHint
(
Query
::
HINT_FORCE_PARTIAL_LOAD
,
true
);
parent
::
assertEquals
(
$sqlToBeConfirmed
,
$query
->
getSql
());
$query
->
free
();
}
catch
(
Doctrine
_
Exception
$e
)
{
}
catch
(
DoctrineException
$e
)
{
$this
->
fail
(
$e
->
getMessage
());
}
}
...
...
@@ -43,6 +44,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
public
function
testSupportsSelectForOneNestedField
()
{
$this
->
assertSqlGeneration
(
'SELECT a.user.id FROM Doctrine\Tests\Models\CMS\CmsArticle a'
,
'SELECT c0_.id AS id0 FROM cms_articles c1_ INNER JOIN cms_users c0_ ON c1_.user_id = c0_.id'
);
}
public
function
testSupportsSelectForAllNestedField
()
{
$this
->
assertSqlGeneration
(
'SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a ORDER BY a.user.name ASC'
,
'SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2 FROM cms_articles c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id ORDER BY c1_.name ASC'
);
}
public
function
testSupportsSelectForMultipleColumnsOfASingleComponent
()
{
$this
->
assertSqlGeneration
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment