1
0
mirror of synced 2025-01-29 19:41:45 +03:00

Merge branch 'master' of github.com:doctrine/doctrine2

This commit is contained in:
Benjamin Eberlei 2010-08-15 20:16:28 +02:00
commit 2f00db08e1
7 changed files with 307 additions and 88 deletions

View File

@ -393,7 +393,7 @@ class ClassMetadataFactory
if ( ! $definition) {
$sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq';
$definition['sequenceName'] = $this->_targetPlatform->fixSchemaElementName($sequenceName);
$definition['allocationSize'] = 10;
$definition['allocationSize'] = 1;
$definition['initialValue'] = 1;
$class->setSequenceGeneratorDefinition($definition);
}

View File

@ -61,48 +61,52 @@ class Lexer extends \Doctrine\Common\Lexer
const T_BETWEEN = 107;
const T_BOTH = 108;
const T_BY = 109;
const T_COUNT = 110;
const T_DELETE = 111;
const T_DESC = 112;
const T_DISTINCT = 113;
const T_EMPTY = 114;
const T_ESCAPE = 115;
const T_EXISTS = 116;
const T_FALSE = 117;
const T_FROM = 118;
const T_GROUP = 119;
const T_HAVING = 120;
const T_IN = 121;
const T_INDEX = 122;
const T_INNER = 123;
const T_INSTANCE = 124;
const T_IS = 125;
const T_JOIN = 126;
const T_LEADING = 127;
const T_LEFT = 128;
const T_LIKE = 129;
const T_MAX = 130;
const T_MEMBER = 131;
const T_MIN = 132;
const T_NOT = 133;
const T_NULL = 134;
const T_OF = 135;
const T_OR = 136;
const T_ORDER = 137;
const T_OUTER = 138;
const T_SELECT = 139;
const T_SET = 140;
const T_SIZE = 141;
const T_SOME = 142;
const T_SUM = 143;
const T_TRAILING = 144;
const T_TRUE = 145;
const T_UPDATE = 146;
const T_WHERE = 147;
const T_WITH = 148;
const T_PARTIAL = 149;
const T_MOD = 150;
const T_CASE = 110;
const T_COALESCE = 111;
const T_COUNT = 112;
const T_DELETE = 113;
const T_DESC = 114;
const T_DISTINCT = 115;
const T_EMPTY = 116;
const T_ESCAPE = 117;
const T_EXISTS = 118;
const T_FALSE = 119;
const T_FROM = 120;
const T_GROUP = 121;
const T_HAVING = 122;
const T_IN = 123;
const T_INDEX = 124;
const T_INNER = 125;
const T_INSTANCE = 126;
const T_IS = 127;
const T_JOIN = 128;
const T_LEADING = 129;
const T_LEFT = 130;
const T_LIKE = 131;
const T_MAX = 132;
const T_MEMBER = 133;
const T_MIN = 134;
const T_NOT = 135;
const T_NULL = 136;
const T_NULLIF = 137;
const T_OF = 138;
const T_OR = 139;
const T_ORDER = 140;
const T_OUTER = 141;
const T_SELECT = 142;
const T_SET = 143;
const T_SIZE = 144;
const T_SOME = 145;
const T_SUM = 146;
const T_TRAILING = 147;
const T_TRUE = 148;
const T_UPDATE = 149;
const T_WHEN = 150;
const T_WHERE = 151;
const T_WITH = 153;
const T_PARTIAL = 154;
const T_MOD = 155;
/**
* Creates a new query scanner object.
*

View File

@ -397,6 +397,45 @@ class Parser
return $peek;
}
/**
* Peek beyond the matched closing parenthesis and return the first token after that one.
*
* @return array
*/
private function _peekBeyondClosingParenthesis()
{
$token = $this->_lexer->peek();
$numUnmatched = 1;
while ($numUnmatched > 0 && $token !== null) {
if ($token['value'] == ')') {
--$numUnmatched;
} else if ($token['value'] == '(') {
++$numUnmatched;
}
$token = $this->_lexer->peek();
}
$this->_lexer->resetPeek();
return $token;
}
/**
* Checks if the given token indicates a mathematical operator.
*
* @return boolean TRUE is the token is a mathematical operator, FALSE otherwise.
*/
private function _isMathOperator($token)
{
if (in_array($token['value'], array("+", "-", "/", "*"))) {
return true;
}
return false;
}
/**
* Checks if the next-next (after lookahead) token starts a function.
*
@ -451,7 +490,7 @@ class Parser
}
/**
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
* Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
* It must exist in query components list.
*
* @return void
@ -486,6 +525,12 @@ class Parser
}
}
/**
* Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
* It must exist in query components list.
*
* @return void
*/
private function _processDeferredPartialObjectExpressions()
{
foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
@ -511,7 +556,7 @@ class Parser
}
/**
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
* Validates that the given <tt>ResultVariable</tt> is semantically correct.
* It must exist in query components list.
*
* @return void
@ -547,7 +592,7 @@ class Parser
}
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct for grammar rules:
* Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
*
* AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
* SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
@ -1537,7 +1582,7 @@ class Parser
$peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
$this->_lexer->resetPeek();
if ($peek['value'] == '+' || $peek['value'] == '-' || $peek['value'] == '/' || $peek['value'] == '*') {
if ($this->_isMathOperator($peek)) {
return $this->SimpleArithmeticExpression();
}
@ -1545,6 +1590,14 @@ class Parser
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
return $this->SimpleArithmeticExpression();
} else if ($this->_isFunction()) {
// We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
$this->_lexer->peek(); // "("
$peek = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($peek)) {
return $this->SimpleArithmeticExpression();
}
return $this->FunctionDeclaration();
} else if ($lookahead == Lexer::T_STRING) {
return $this->StringPrimary();
@ -1603,7 +1656,12 @@ class Parser
$expression = $this->SimpleArithmeticExpression();
}
} else if ($this->_isFunction()) {
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$this->_lexer->peek(); // "("
$beyond = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($beyond)) {
$expression = $this->ScalarExpression();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else {
// Shortcut: ScalarExpression => Function
@ -1644,26 +1702,51 @@ class Parser
}
/**
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] AliasResultVariable])
* SimpleSelectExpression ::=
* StateFieldPathExpression | IdentificationVariable |
* ((AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable])
*
* @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
*/
public function SimpleSelectExpression()
{
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
// SingleValuedPathExpression | IdentificationVariable
$glimpse = $this->_lexer->glimpse();
$peek = $this->_lexer->glimpse();
if ($glimpse['type'] == Lexer::T_DOT) {
return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
// SingleValuedPathExpression | IdentificationVariable
if ($peek['value'] == '.') {
$expression = $this->StateFieldPathExpression();
} else {
$expression = $this->IdentificationVariable();
}
$this->match(Lexer::T_IDENTIFIER);
return new AST\SimpleSelectExpression($expression);
} else if ($this->_lexer->lookahead['value'] == '(') {
if ($peek['type'] == Lexer::T_SELECT) {
// Subselect
$this->match(Lexer::T_OPEN_PARENTHESIS);
$expression = $this->Subselect();
$this->match(Lexer::T_CLOSE_PARENTHESIS);
} else {
// Shortcut: ScalarExpression => SimpleArithmeticExpression
$expression = $this->SimpleArithmeticExpression();
}
return new AST\SimpleSelectExpression($this->_lexer->token['value']);
return new AST\SimpleSelectExpression($expression);
}
$expr = new AST\SimpleSelectExpression($this->AggregateExpression());
$this->_lexer->peek();
$beyond = $this->_peekBeyondClosingParenthesis();
if ($this->_isMathOperator($beyond)) {
$expression = $this->ScalarExpression();
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else {
$expression = $this->FunctionDeclaration();
}
$expr = new AST\SimpleSelectExpression($expression);
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
@ -1869,23 +1952,6 @@ class Parser
}
}
private function _peekBeyondClosingParenthesis()
{
$numUnmatched = 1;
$token = $this->_lexer->peek();
while ($numUnmatched > 0 && $token !== null) {
if ($token['value'] == ')') {
--$numUnmatched;
} else if ($token['value'] == '(') {
++$numUnmatched;
}
$token = $this->_lexer->peek();
}
$this->_lexer->resetPeek();
return $token;
}
/**
* EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
*
@ -2355,13 +2421,13 @@ class Parser
}
/**
* InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
* InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
*
* @return \Doctrine\ORM\Query\AST\InExpression
*/
public function InExpression()
{
$inExpression = new AST\InExpression($this->StateFieldPathExpression());
$inExpression = new AST\InExpression($this->SingleValuedPathExpression());
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);

View File

@ -1106,7 +1106,7 @@ class SqlWalker implements TreeWalker
$expr = $simpleSelectExpression->expression;
if ($expr instanceof AST\PathExpression) {
$sql .= ' ' . $this->walkPathExpression($expr);
$sql .= $this->walkPathExpression($expr);
} else if ($expr instanceof AST\AggregateExpression) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
@ -1114,17 +1114,55 @@ class SqlWalker implements TreeWalker
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$sql .= ' ' . $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
$sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
} else if ($expr instanceof AST\Subselect) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = 'sclr' . $this->_aliasCounter++;
$sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias;
} else if ($expr instanceof AST\Functions\FunctionNode) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = 'sclr' . $this->_aliasCounter++;
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias;
} else if (
$expr instanceof AST\SimpleArithmeticExpression ||
$expr instanceof AST\ArithmeticTerm ||
$expr instanceof AST\ArithmeticFactor ||
$expr instanceof AST\ArithmeticPrimary
) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarResultCounter++;
} else {
$alias = $simpleSelectExpression->fieldIdentificationVariable;
}
$columnAlias = 'sclr' . $this->_aliasCounter++;
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$alias] = $columnAlias;
} else {
// IdentificationVariable
// FIXME: Composite key support, or select all columns? Does that make sense
// in a subquery?
$class = $this->_queryComponents[$expr]['metadata'];
$sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.'
. $class->getQuotedColumnName($class->identifier[0], $this->_platform);
$tableAlias = $this->getSqlTableAlias($class->getTableName(), $expr);
$first = true;
foreach ($class->identifier as $identifier) {
if ($first) $first = false; else $sql .= ', ';
$sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform);
}
}
return $sql;
return ' ' . $sql;
}
/**

View File

@ -513,7 +513,7 @@ public function <methodName>()
$methods[] = $code;
}
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
if ($associationMapping->isOwningSide) {
if ($associationMapping['isOwningSide']) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}

View File

@ -53,6 +53,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
}
$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');
@ -134,6 +135,16 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (1)');
}
public function testInExpressionWithSingleValuedAssociationPathExpression()
{
$this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar IN (?1, ?2)");
}
public function testInvalidInExpressionWithCollectionValuedAssociationPathExpression()
{
$this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.phonenumbers IN (?1, ?2)");
}
public function testInstanceOfExpressionSupportedInWherePart()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee');
@ -225,15 +236,25 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql("SELECT u.name, (SELECT COUNT(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234) pcount FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
}
/*public function testSubselectInSelectPart2()
public function testArithmeticExpressionInSelectPart()
{
$this->assertValidDql("SELECT SUM(u.id) / COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u");
}*/
}
/*public function testSubselectInSelectPart3()
public function testArithmeticExpressionInSubselectPart()
{
$this->assertValidDql("SELECT (SELECT SUM(u.id) / COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) value FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
}*/
}
public function testArithmeticExpressionWithParenthesisInSubselectPart()
{
$this->assertValidDql("SELECT (SELECT (SUM(u.id) / COUNT(u.id)) FROM Doctrine\Tests\Models\CMS\CmsUser u2) value FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
}
public function testDuplicateAliasInSubselectPart()
{
$this->assertInvalidDql("SELECT (SELECT SUM(u.id) / COUNT(u.id) AS foo FROM Doctrine\Tests\Models\CMS\CmsUser u2) foo FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
}
public function testPositionalInputParameter()
{
@ -377,6 +398,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id > SOME (SELECT u2.id FROM Doctrine\Tests\Models\CMS\CmsUser u2 WHERE u2.name = u.name)');
}
public function testArithmeticExpressionWithoutParenthesisInWhereClause()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.phonenumbers) + 1 > 10');
}
public function testMemberOfExpression()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers');

View File

@ -15,6 +15,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$this->_em = $this->_getTestEntityManager();
}
/**
* Assert a valid SQL generation.
*
* @param string $dqlToBeTested
* @param string $sqlToBeConfirmed
* @param array $queryHints
* @param array $queryParams
*/
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array(), array $queryParams = array())
{
try {
@ -38,6 +46,39 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
}
}
/**
* Asser an invalid SQL generation.
*
* @param string $dqlToBeTested
* @param string $expectedException
* @param array $queryHints
* @param array $queryParams
*/
public function assertInvalidSqlGeneration($dqlToBeTested, $expectedException, array $queryHints = array(), array $queryParams = array())
{
$this->setExpectedException($expectedException);
$query = $this->_em->createQuery($dqlToBeTested);
foreach ($queryParams AS $name => $value) {
$query->setParameter($name, $value);
}
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false);
foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value);
}
$sql = $query->getSql();
$query->free();
// If we reached here, test failed
$this->fail($sql);
}
public function testSupportsSelectForAllFields()
{
$this->assertSqlGeneration(
@ -102,6 +143,33 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
public function testSelectCorrelatedSubqueryComplexMathematicalExpression()
{
$this->assertSqlGeneration(
'SELECT (SELECT (count(p.phonenumber)+5)*10 FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p JOIN p.user ui WHERE ui.id = u.id) AS c FROM Doctrine\Tests\Models\CMS\CmsUser u',
'SELECT (SELECT (count(c0_.phonenumber) + 5) * 10 AS sclr1 FROM cms_phonenumbers c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id WHERE c1_.id = c2_.id) AS sclr0 FROM cms_users c2_'
);
}
public function testSelectComplexMathematicalExpression()
{
$this->assertSqlGeneration(
'SELECT (count(p.phonenumber)+5)*10 FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p JOIN p.user ui WHERE ui.id = ?1',
'SELECT (count(c0_.phonenumber) + 5) * 10 AS sclr0 FROM cms_phonenumbers c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id WHERE c1_.id = ?'
);
}
/* NOT (YET?) SUPPORTED.
Can be supported if SimpleSelectExpresion supports SingleValuedPathExpression instead of StateFieldPathExpression.
public function testSingleAssociationPathExpressionInSubselect()
{
$this->assertSqlGeneration(
'SELECT (SELECT p.user FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user = u) user_id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1',
'SELECT (SELECT c0_.user_id FROM cms_phonenumbers c0_ WHERE c0_.user_id = c1_.id) AS sclr0 FROM cms_users c1_ WHERE c1_.id = ?'
);
}*/
public function testSupportsOrderByWithAscAsDefault()
{
$this->assertSqlGeneration(
@ -347,6 +415,23 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
public function testInExpressionWithSingleValuedAssociationPathExpressionInWherePart()
{
$this->assertSqlGeneration(
'SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar IN (?1, ?2)',
'SELECT f0_.id AS id0, f0_.username AS username1 FROM forum_users f0_ WHERE f0_.avatar_id IN (?, ?)'
);
}
public function testInvalidInExpressionWithSingleValuedAssociationPathExpressionOnInverseSide()
{
// We do not support SingleValuedAssociationPathExpression on inverse side
$this->assertInvalidSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address IN (?1, ?2)",
"Doctrine\ORM\Query\QueryException"
);
}
public function testSupportsConcatFunctionForMysqlAndPostgresql()
{
$connMock = $this->_em->getConnection();