From 889094709e2df7d5a40dd09a7ef24211f97bb958 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 00:04:44 -0300 Subject: [PATCH 01/12] [2.0] Added support to IdentificationVariable that was missing in ArithmeticPrimary (it was not correctly handling it). Uncommented a unit test that added coverage to it. --- .../Hydration/SingleScalarHydrator.php | 1 + lib/Doctrine/ORM/Query/AST/PathExpression.php | 1 + lib/Doctrine/ORM/Query/Parser.php | 32 +++++++++++-------- lib/Doctrine/ORM/Query/SqlWalker.php | 13 ++++++++ .../ORM/Query/SelectSqlGenerationTest.php | 5 +-- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php index a095c2a7d..21846b6dc 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php @@ -43,6 +43,7 @@ class SingleScalarHydrator extends AbstractHydrator } else if ($num > 1 || count($result[key($result)]) > 1) { throw new \Doctrine\ORM\NonUniqueResultException; } + $result = $this->_gatherScalarRowData($result[key($result)], $cache); return array_shift($result); diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 4f8d0e277..996d35c27 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -41,6 +41,7 @@ namespace Doctrine\ORM\Query\AST; */ class PathExpression extends Node { + const TYPE_IDENTIFICATION_VARIABLE = 1; const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index d3c7e3808..6f9e73c26 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -565,7 +565,8 @@ class Parser $aliasIdentificationVariable = $pathExpression->identificationVariable; $parentField = $pathExpression->identificationVariable; $class = $qComp['metadata']; - $fieldType = null; + $fieldType = ($pathExpression->expectedType == AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) + ? AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE : null; $curIndex = 0; foreach ($parts as $field) { @@ -602,8 +603,8 @@ class Parser $class = $this->_em->getClassMetadata($assoc->targetEntityName); if ( - ($curIndex != $numParts - 1) && - ! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field]) + ($curIndex != $numParts - 1) && + ! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field]) ) { // Building queryComponent $joinQueryComponent = array( @@ -617,13 +618,13 @@ class Parser // Create AST node $joinVariableDeclaration = new AST\JoinVariableDeclaration( - new AST\Join( - AST\Join::JOIN_TYPE_INNER, - new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field), - $aliasIdentificationVariable . '.' . $field, - false - ), - null + new AST\Join( + AST\Join::JOIN_TYPE_INNER, + new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field), + $aliasIdentificationVariable . '.' . $field, + false + ), + null ); $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration; @@ -649,6 +650,11 @@ class Parser // We need to recognize which was expected type(s) $expectedStringTypes = array(); + // Validate state field type + if ($expectedType & AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) { + $expectedStringTypes[] = 'IdentificationVariable'; + } + // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; @@ -915,12 +921,12 @@ class Parser $identVariable = $this->IdentificationVariable(); $parts = array(); - do { + while ($this->_lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $parts[] = $this->_lexer->token['value']; - } while ($this->_lexer->isNextToken(Lexer::T_DOT)); + } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts); @@ -2168,7 +2174,7 @@ class Parser return $this->SingleValuedPathExpression(); } - return $this->IdentificationVariable(); + return $this->PathExpression(AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index be583c625..7a0b362c1 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -446,6 +446,17 @@ class SqlWalker implements TreeWalker $sql = ''; switch ($pathExpr->type) { + case AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE: + $dqlAlias = $pathExpr->identificationVariable; + $class = $this->_queryComponents[$dqlAlias]['metadata']; + + if ($this->_useSqlTableAliases) { + $sql .= $this->walkIdentificationVariable($dqlAlias) . '.'; + } + + $sql .= $class->getQuotedColumnName($class->identifier[0], $this->_platform); + break; + case AST\PathExpression::TYPE_STATE_FIELD: $parts = $pathExpr->parts; $fieldName = array_pop($parts); @@ -458,6 +469,7 @@ class SqlWalker implements TreeWalker $sql .= $class->getQuotedColumnName($fieldName, $this->_platform); break; + case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // 1- the owning side: // Just use the foreign key, i.e. u.group_id @@ -484,6 +496,7 @@ class SqlWalker implements TreeWalker throw QueryException::associationPathInverseSideNotSupported(); } break; + default: throw QueryException::invalidPathExpression($pathExpr); } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 163bc594e..7ed9c6693 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -21,6 +21,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query = $this->_em->createQuery($dqlToBeTested); $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) ->useQueryCache(false); + parent::assertEquals($sqlToBeConfirmed, $query->getSql()); $query->free(); } catch (\Exception $e) { @@ -385,7 +386,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('SELECT d0_.id AS id0 FROM date_time_model d0_ WHERE d0_.col_datetime > CURRENT_TIMESTAMP', $q->getSql()); } - /*public function testExistsExpressionInWhereCorrelatedSubqueryAssocCondition() + public function testExistsExpressionInWhereCorrelatedSubqueryAssocCondition() { $this->assertSqlGeneration( // DQL @@ -402,7 +403,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase . ')' ); - }*/ + } public function testLimitFromQueryClass() { From c122953a7e928c1b8859ce45334f3c4a08b609e7 Mon Sep 17 00:00:00 2001 From: Steven Surowiec Date: Wed, 14 Apr 2010 14:20:44 -0400 Subject: [PATCH 02/12] Changed privates to protected and updated setUp to use late static binding. This allows projects using Doctrine2 to use its test cases for testing their own entities. --- .../Doctrine/Tests/OrmFunctionalTestCase.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 9e02cb285..9502f2b7a 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -15,7 +15,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase private static $_queryCacheImpl = null; /* Shared connection when a TestCase is run alone (outside of it's functional suite) */ - private static $_sharedConn; + protected static $_sharedConn; /** * @var \Doctrine\ORM\EntityManager @@ -33,13 +33,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase protected $_sqlLoggerStack; /** The names of the model sets used in this testcase. */ - private $_usedModelSets = array(); + protected $_usedModelSets = array(); /** Whether the database schema has already been created. */ - private static $_tablesCreated = array(); + protected static $_tablesCreated = array(); /** List of model sets and their classes. */ - private static $_modelSets = array( + protected static $_modelSets = array( 'cms' => array( 'Doctrine\Tests\Models\CMS\CmsUser', 'Doctrine\Tests\Models\CMS\CmsPhonenumber', @@ -170,11 +170,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $forceCreateTables = false; if ( ! isset($this->sharedFixture['conn'])) { - if ( ! isset(self::$_sharedConn)) { - self::$_sharedConn = TestUtil::getConnection(); + if ( ! isset(static::$_sharedConn)) { + static::$_sharedConn = TestUtil::getConnection(); } - $this->sharedFixture['conn'] = self::$_sharedConn; + $this->sharedFixture['conn'] = static::$_sharedConn; if ($this->sharedFixture['conn']->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) { $forceCreateTables = true; @@ -189,12 +189,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $classes = array(); foreach ($this->_usedModelSets as $setName => $bool) { - if ( ! isset(self::$_tablesCreated[$setName])/* || $forceCreateTables*/) { - foreach (self::$_modelSets[$setName] as $className) { + if ( ! isset(static::$_tablesCreated[$setName])/* || $forceCreateTables*/) { + foreach (static::$_modelSets[$setName] as $className) { $classes[] = $this->_em->getClassMetadata($className); } - self::$_tablesCreated[$setName] = true; + static::$_tablesCreated[$setName] = true; } } From 955dc09cb92a5e31e21dc780fa71c38396113832 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 22:03:29 -0300 Subject: [PATCH 03/12] [2.0] Optimized support to IdentificationVariable in ArithmeticPrimary --- lib/Doctrine/ORM/Query/AST/PathExpression.php | 1 - lib/Doctrine/ORM/Query/Parser.php | 31 +++++++++---------- lib/Doctrine/ORM/Query/SqlWalker.php | 11 ------- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 996d35c27..4f8d0e277 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -41,7 +41,6 @@ namespace Doctrine\ORM\Query\AST; */ class PathExpression extends Node { - const TYPE_IDENTIFICATION_VARIABLE = 1; const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 6f9e73c26..5d9766262 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -557,40 +557,44 @@ class Parser { foreach ($this->_deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; - $parts = $pathExpression->parts; - $numParts = count($parts); $qComp = $this->_queryComponents[$pathExpression->identificationVariable]; + $numParts = count($pathExpression->parts); + if ($numParts == 0) { + $pathExpression->parts = array($qComp['metadata']->identifier[0]); + $numParts++; + } + + $parts = $pathExpression->parts; $aliasIdentificationVariable = $pathExpression->identificationVariable; $parentField = $pathExpression->identificationVariable; $class = $qComp['metadata']; - $fieldType = ($pathExpression->expectedType == AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) - ? AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE : null; + $fieldType = null; $curIndex = 0; foreach ($parts as $field) { // Check if it is not in a state field if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) { $this->semanticalError( - 'Cannot navigate through state field named ' . $field . ' on ' . $parentField, - $deferredItem['token'] + 'Cannot navigate through state field named ' . $field . ' on ' . $parentField, + $deferredItem['token'] ); } // Check if it is not a collection field if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { $this->semanticalError( - 'Cannot navigate through collection field named ' . $field . ' on ' . $parentField, - $deferredItem['token'] + 'Cannot navigate through collection field named ' . $field . ' on ' . $parentField, + $deferredItem['token'] ); } // Check if field or association exists if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( - 'Class ' . $class->name . ' has no field or association named ' . $field, - $deferredItem['token'] + 'Class ' . $class->name . ' has no field or association named ' . $field, + $deferredItem['token'] ); } @@ -650,11 +654,6 @@ class Parser // We need to recognize which was expected type(s) $expectedStringTypes = array(); - // Validate state field type - if ($expectedType & AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) { - $expectedStringTypes[] = 'IdentificationVariable'; - } - // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; @@ -2174,7 +2173,7 @@ class Parser return $this->SingleValuedPathExpression(); } - return $this->PathExpression(AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE); + return $this->SimpleStateFieldPathExpression(); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 25263dac5..f624798fd 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -446,17 +446,6 @@ class SqlWalker implements TreeWalker $sql = ''; switch ($pathExpr->type) { - case AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE: - $dqlAlias = $pathExpr->identificationVariable; - $class = $this->_queryComponents[$dqlAlias]['metadata']; - - if ($this->_useSqlTableAliases) { - $sql .= $this->walkIdentificationVariable($dqlAlias) . '.'; - } - - $sql .= $class->getQuotedColumnName($class->identifier[0], $this->_platform); - break; - case AST\PathExpression::TYPE_STATE_FIELD: $parts = $pathExpr->parts; $fieldName = array_pop($parts); From c4ffd04da094a474fe5607332cf7decd6331cd67 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 22:49:53 -0300 Subject: [PATCH 04/12] [2.0][DDC-430] Added coverage, fixing the ticket. --- .../Tests/ORM/Query/SelectSqlGenerationTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 7ed9c6693..dda612152 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -585,4 +585,15 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT c0_.name AS name0, (SELECT COUNT(c1_.phonenumber) AS dctrn__1 FROM cms_phonenumbers c1_ WHERE c1_.phonenumber = 1234) AS sclr1 FROM cms_users c0_ WHERE c0_.name = 'jon'" ); } + + /** + * DDC-430 + */ + public function testSupportSelectWithMoreThan10InputParameters() + { + $this->assertSqlGeneration( + "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1 OR u.id = ?2 OR u.id = ?3 OR u.id = ?4 OR u.id = ?5 OR u.id = ?6 OR u.id = ?7 OR u.id = ?8 OR u.id = ?9 OR u.id = ?10 OR u.id = ?11", + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ?" + ); + } } From 26ff265652bc4824ff585b9915c3a5bcb2d82b30 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 14 Apr 2010 23:27:33 -0300 Subject: [PATCH 05/12] [2.0][DDC-431] Added coverage, fixing the ticket. --- lib/Doctrine/ORM/Configuration.php | 42 ++++++++++++++++- .../ORM/Query/SelectSqlGenerationTest.php | 47 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 609bbfcd1..229126635 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -50,7 +50,14 @@ class Configuration extends \Doctrine\DBAL\Configuration 'proxyDir' => null, 'useCExtension' => false, 'autoGenerateProxyClasses' => true, - 'proxyNamespace' => null + 'proxyNamespace' => null, + 'entityNamespaces' => array(), + 'namedNativeQueries' => array(), + 'namedQueries' => array(), + // Custom DQL Functions + 'customDatetimeFunctions' => array(), + 'customNumericFunctions' => array(), + 'customStringFunctions' => array() )); } @@ -366,10 +373,21 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } + /** + * Clean custom DQL functions that produces string values. + * + */ + public function clearCustomStringFunctions() + { + $this->_attributes['customStringFunctions'] = array(); + } + /** * Registers a custom DQL function that produces a numeric value. * Such a function can then be used in any DQL statement in any place where numeric @@ -391,10 +409,21 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } + /** + * Clean custom DQL functions that produces numeric values. + * + */ + public function clearCustomNumericFunctions() + { + $this->_attributes['customNumericFunctions'] = array(); + } + /** * Registers a custom DQL function that produces a date/time value. * Such a function can then be used in any DQL statement in any place where date/time @@ -416,7 +445,18 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { + $name = strtolower($name); + return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } + + /** + * Clean custom DQL functions that produces date/time values. + * + */ + public function clearCustomDatetimeFunctions() + { + $this->_attributes['customDatetimeFunctions'] = array(); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index dda612152..1ef832571 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -596,4 +596,51 @@ 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 c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ? OR c0_.id = ?" ); } + + /** + * DDC-431 + */ + public function testSupportToCustomDQLFunctions() + { + $config = $this->_em->getConfiguration(); + $config->addCustomNumericFunction('MYABS', 'Doctrine\Tests\ORM\Query\MyAbsFunction'); + + $this->assertSqlGeneration( + 'SELECT MYABS(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p', + 'SELECT ABS(c0_.phonenumber) AS sclr0 FROM cms_phonenumbers c0_' + ); + + $config->clearCustomNumericFunctions(); + } } + + +class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode +{ + public $simpleArithmeticExpression; + + /** + * @override + */ + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + /** + * @override + */ + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(\Doctrine\ORM\Query\Lexer::T_IDENTIFIER); + $parser->match(\Doctrine\ORM\Query\Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(\Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS); + } +} \ No newline at end of file From e83bfeede34351e44eb18f43dbf1865918eb3b8c Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 11:55:03 +0200 Subject: [PATCH 06/12] Simplified and streamlined configuration classes. --- lib/Doctrine/DBAL/Configuration.php | 19 +--- lib/Doctrine/ORM/Configuration.php | 96 +++++++++---------- .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 3 files changed, 50 insertions(+), 67 deletions(-) diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 3ad9e36a0..dfda73240 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -1,7 +1,5 @@ _attributes = array( - 'sqlLogger' => null - ); - } - /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @param SQLLogger $logger */ - public function setSQLLogger($logger) + public function setSQLLogger(SqlLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } @@ -73,6 +61,7 @@ class Configuration */ public function getSQLLogger() { - return $this->_attributes['sqlLogger']; + return isset($this->_attributes['sqlLogger']) ? + $this->_attributes['sqlLogger'] : null; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 229126635..abb8a15d8 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -27,8 +27,7 @@ use Doctrine\Common\Cache\Cache, * It combines all configuration options from DBAL & ORM. * * @since 2.0 - * @internal When adding a new configuration option just write a getter/setter - * pair and add the option to the _attributes array with a proper default value. + * @internal When adding a new configuration option just write a getter/setter pair. * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage @@ -36,31 +35,6 @@ use Doctrine\Common\Cache\Cache, */ class Configuration extends \Doctrine\DBAL\Configuration { - /** - * Creates a new configuration that can be used for Doctrine. - */ - public function __construct() - { - parent::__construct(); - $this->_attributes = array_merge($this->_attributes, array( - 'resultCacheImpl' => null, - 'queryCacheImpl' => null, - 'metadataCacheImpl' => null, - 'metadataDriverImpl' => null, - 'proxyDir' => null, - 'useCExtension' => false, - 'autoGenerateProxyClasses' => true, - 'proxyNamespace' => null, - 'entityNamespaces' => array(), - 'namedNativeQueries' => array(), - 'namedQueries' => array(), - // Custom DQL Functions - 'customDatetimeFunctions' => array(), - 'customNumericFunctions' => array(), - 'customStringFunctions' => array() - )); - } - /** * Sets the directory where Doctrine generates any necessary proxy class files. * @@ -78,7 +52,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyDir() { - return $this->_attributes['proxyDir']; + return isset($this->_attributes['proxyDir']) ? + $this->_attributes['proxyDir'] : null; } /** @@ -89,7 +64,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getAutoGenerateProxyClasses() { - return $this->_attributes['autoGenerateProxyClasses']; + return isset($this->_attributes['autoGenerateProxyClasses']) ? + $this->_attributes['autoGenerateProxyClasses'] : true; } /** @@ -110,7 +86,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyNamespace() { - return $this->_attributes['proxyNamespace']; + return isset($this->_attributes['proxyNamespace']) ? + $this->_attributes['proxyNamespace'] : null; } /** @@ -195,7 +172,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataDriverImpl() { - return $this->_attributes['metadataDriverImpl']; + return isset($this->_attributes['metadataDriverImpl']) ? + $this->_attributes['metadataDriverImpl'] : null; } /** @@ -205,7 +183,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getResultCacheImpl() { - return $this->_attributes['resultCacheImpl']; + return isset($this->_attributes['resultCacheImpl']) ? + $this->_attributes['resultCacheImpl'] : null; } /** @@ -225,7 +204,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getQueryCacheImpl() { - return $this->_attributes['queryCacheImpl']; + return isset($this->_attributes['queryCacheImpl']) ? + $this->_attributes['queryCacheImpl'] : null; } /** @@ -245,7 +225,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataCacheImpl() { - return $this->_attributes['metadataCacheImpl']; + return isset($this->_attributes['metadataCacheImpl']) ? + $this->_attributes['metadataCacheImpl'] : null; } /** @@ -266,7 +247,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getUseCExtension() { - return $this->_attributes['useCExtension']; + return isset($this->_attributes['useCExtension']) ? + $this->_attributes['useCExtension'] : false; } /** @@ -373,19 +355,23 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces string values. + * Sets a map of custom DQL string functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added string functions are discarded. + * + * @param array $functions The map of custom DQL string functions. */ - public function clearCustomStringFunctions() + public function setCustomStringFunctions(array $functions) { - $this->_attributes['customStringFunctions'] = array(); + $this->_attributes['customStringFunctions'] = $functions; } /** @@ -409,19 +395,23 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces numeric values. + * Sets a map of custom DQL numeric functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added numeric functions are discarded. + * + * @param array $functions The map of custom DQL numeric functions. */ - public function clearCustomNumericFunctions() + public function setCustomNumericFunctions(array $functions) { - $this->_attributes['customNumericFunctions'] = array(); + $this->_attributes['customNumericFunctions'] = $functions; } /** @@ -445,18 +435,22 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { - $name = strtolower($name); - return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces date/time values. - * + * Sets a map of custom DQL date/time functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added date/time functions are discarded. + * + * @param array $functions The map of custom DQL date/time functions. */ - public function clearCustomDatetimeFunctions() + public function setCustomDatetimeFunctions(array $functions) { - $this->_attributes['customDatetimeFunctions'] = array(); + $this->_attributes['customDatetimeFunctions'] = $functions; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 1ef832571..a85873a92 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -610,7 +610,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT ABS(c0_.phonenumber) AS sclr0 FROM cms_phonenumbers c0_' ); - $config->clearCustomNumericFunctions(); + $config->setCustomNumericFunctions(array()); } } From 29e0863ffa13b5a6fe122b900ebebaecc9e67a92 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 12:41:34 +0200 Subject: [PATCH 07/12] Fixed casing. --- lib/Doctrine/DBAL/Configuration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index dfda73240..1ef539b90 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -19,7 +19,7 @@ namespace Doctrine\DBAL; -use Doctrine\DBAL\Logging\SqlLogger; +use Doctrine\DBAL\Logging\SQLLogger; /** * Configuration container for the Doctrine DBAL. @@ -49,7 +49,7 @@ class Configuration * * @param SQLLogger $logger */ - public function setSQLLogger(SqlLogger $logger) + public function setSQLLogger(SQLLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } From 01c2c06bbf529d89c9741ea97702359509ea230a Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 18:36:17 +0200 Subject: [PATCH 08/12] [DDC-512] Fixed. --- lib/Doctrine/DBAL/Configuration.php | 3 - .../ORM/Persisters/SingleTablePersister.php | 1 - lib/Doctrine/ORM/Query/AST/PathExpression.php | 5 - lib/Doctrine/ORM/Query/Parser.php | 5 - lib/Doctrine/ORM/Query/SqlWalker.php | 16 ++-- .../Tests/Models/Company/CompanyEvent.php | 4 +- .../Functional/ClassTableInheritanceTest.php | 1 - .../ORM/Functional/Ticket/DDC512Test.php | 94 +++++++++++++++++++ 8 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 1ef539b90..76ce1c4f0 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -24,10 +24,7 @@ use Doctrine\DBAL\Logging\SQLLogger; /** * Configuration container for the Doctrine DBAL. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index a0adbea67..5f5a8a4d7 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -26,7 +26,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; * SINGLE_TABLE strategy. * * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @since 2.0 * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 4f8d0e277..ed856f679 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 5d9766262..645cb5ae0 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index f624798fd..0b71b82bf 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1,7 +1,5 @@ getSqlTableAlias($class->table['name'], $dqlAlias); + $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->getSqlTableAlias($parentClass->table['name'], $dqlAlias); - $sql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) + $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); + // If this is a joined association we must use left joins to preserve the correct result. + $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; + $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; @@ -260,14 +259,13 @@ class SqlWalker implements TreeWalker } } - // LEFT JOIN subclass tables, if partial objects disallowed + // LEFT JOIN subclass tables, if partial objects disallowed. if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias); + $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $first = true; foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; diff --git a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php index f0bb730cf..2db4986ae 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php @@ -6,9 +6,9 @@ namespace Doctrine\Tests\Models\Company; * @Entity @Table(name="company_events") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="event_type", type="string") - * @DiscriminatorMap({"auction" = "CompanyAuction", "raffle" = "CompanyRaffle"}) + * @DiscriminatorMap({"auction"="CompanyAuction", "raffle"="CompanyRaffle"}) */ -class CompanyEvent { +abstract class CompanyEvent { /** * @Id @Column(type="integer") * @GeneratedValue diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index 206a567b6..ea1ad15d8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -294,5 +294,4 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase ->getResult()) > 0); } - } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php new file mode 100644 index 000000000..72b11aaab --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php @@ -0,0 +1,94 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Customer'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512OfferItem'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Item'), + )); + } + + public function testIssue() + { + $customer1 = new DDC512Customer(); + $item = new DDC512OfferItem(); + $customer1->item = $item; + $this->_em->persist($customer1); + + $customer2 = new DDC512Customer(); + $this->_em->persist($customer2); + + $this->_em->flush(); + $this->_em->clear(); + + $q = $this->_em->createQuery("select u,i from ".__NAMESPACE__."\\DDC512Customer u left join u.item i"); + $result = $q->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertTrue($result[0] instanceof DDC512Customer); + $this->assertTrue($result[1] instanceof DDC512Customer); + if ($result[0]->id == $customer1->id) { + $this->assertTrue($result[0]->item instanceof DDC512OfferItem); + $this->assertEquals($item->id, $result[0]->item->id); + $this->assertNull($result[1]->item); + } else { + $this->assertTrue($result[1]->item instanceof DDC512OfferItem); + $this->assertNull($result[0]->item); + } + } +} + +/** + * @Entity + */ +class DDC512Customer { + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * NOTE that we can currently not name the join column the same as the field + * (item = item), this currently confuses Doctrine. + * + * @OneToOne(targetEntity="DDC512OfferItem", cascade={"remove","persist"}) + * @JoinColumn(name="item_id", referencedColumnName="id") + */ + public $item; +} + +/** + * @Entity + */ +class DDC512OfferItem extends DDC512Item +{ +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"item" = "DDC512Item", "offerItem" = "DDC512OfferItem"}) + */ +class DDC512Item +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; +} + + + + From 4b39705cd460f8db7f5ad2ecc08eb6b38fda57ab Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Thu, 15 Apr 2010 20:14:03 +0200 Subject: [PATCH 09/12] Fixed case-sensitivity of custom DQL functions. --- lib/Doctrine/ORM/Configuration.php | 15 ++++++++++++--- lib/Doctrine/ORM/Query/Parser.php | 21 ++++++++++++--------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index abb8a15d8..b6fa3a99e 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -339,6 +339,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where string * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -355,6 +357,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomStringFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } @@ -371,7 +374,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomStringFunctions(array $functions) { - $this->_attributes['customStringFunctions'] = $functions; + $this->_attributes['customStringFunctions'] = array_change_key_case($functions); } /** @@ -379,6 +382,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where numeric * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -395,6 +400,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomNumericFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } @@ -411,7 +417,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomNumericFunctions(array $functions) { - $this->_attributes['customNumericFunctions'] = $functions; + $this->_attributes['customNumericFunctions'] = array_change_key_case($functions); } /** @@ -419,6 +425,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where date/time * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -435,6 +443,7 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getCustomDatetimeFunction($name) { + $name = strtolower($name); return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } @@ -451,6 +460,6 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function setCustomDatetimeFunctions(array $functions) { - $this->_attributes['customDatetimeFunctions'] = $functions; + $this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 645cb5ae0..960ae426d 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2619,9 +2619,10 @@ class Parser public function CustomFunctionsReturningNumerics() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = strtolower($this->_lexer->lookahead['value']); + // getCustomNumericFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2642,9 +2643,10 @@ class Parser public function CustomFunctionsReturningDatetime() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomDatetimeFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2670,9 +2672,10 @@ class Parser public function CustomFunctionsReturningStrings() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomStringFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; From 5ecca4f5e0817d0bc5769e20c8588529249cfae5 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 22 Apr 2010 11:32:01 -0300 Subject: [PATCH 10/12] [2.0][DDC-524] Fixed issue with UPDATE/DELETE statements generating wrong SQL when using Association Paths. --- lib/Doctrine/ORM/Query/SqlWalker.php | 8 ++++++-- .../Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index f624798fd..ece61bd50 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -478,8 +478,12 @@ class SqlWalker implements TreeWalker if (count($assoc->sourceToTargetKeyColumns) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); } - $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.' - . reset($assoc->targetToSourceKeyColumns); + + if ($this->_useSqlTableAliases) { + $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.'; + } + + $sql .= reset($assoc->targetToSourceKeyColumns); } else { // 2- Inverse side: NOT (YET?) SUPPORTED throw QueryException::associationPathInverseSideNotSupported(); diff --git a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php index 969ee35ca..506dacb8b 100644 --- a/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/UpdateSqlGenerationTest.php @@ -159,4 +159,12 @@ class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'UPDATE cms_users SET status = ? WHERE id BETWEEN ? AND ?' ); } + + public function testSingleValuedAssociationFieldInWhere() + { + $this->assertSqlGeneration( + "UPDATE Doctrine\Tests\Models\CMS\CmsPhonenumber p SET p.phonenumber = 1234 WHERE p.user = ?1", + "UPDATE cms_phonenumbers SET phonenumber = 1234 WHERE user_id = ?" + ); + } } From 825cd7f47822a55784cdcbc7747210439088e5b9 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 22 Apr 2010 12:17:58 -0300 Subject: [PATCH 11/12] [2.0][DDC-529] Fixed undeclared in dbal:run-sql command. Thanks for provided patch, Hannes. --- lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php index 0e6c05c82..a4fdaccd0 100644 --- a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php +++ b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php @@ -78,7 +78,7 @@ EOT if (preg_match('/^select/i', $sql)) { $resultSet = $conn->fetchAll($sql); } else { - $resultSet = $em->getConnection()->executeUpdate($sql); + $resultSet = $conn->executeUpdate($sql); } \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth); From 841008c46107e7ff68e2d49d9aff1a5b5af04e03 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 23 Apr 2010 00:51:32 -0300 Subject: [PATCH 12/12] [2.0] Coding Standards fixes, added missing docblocks, removed some dependencies from Common package (in Annotations component), etc. --- .../Common/Annotations/Annotation.php | 24 +++++++++-- .../Annotations/AnnotationException.php | 20 +++++++-- .../Common/Annotations/AnnotationReader.php | 18 ++++---- lib/Doctrine/Common/Annotations/Lexer.php | 42 ++++++++++++++----- lib/Doctrine/Common/Annotations/Parser.php | 10 +++-- lib/Doctrine/ORM/EntityManager.php | 3 +- lib/Doctrine/ORM/Query/Parser.php | 24 ++++++----- 7 files changed, 99 insertions(+), 42 deletions(-) diff --git a/lib/Doctrine/Common/Annotations/Annotation.php b/lib/Doctrine/Common/Annotations/Annotation.php index ba7704be0..e2bf42bb5 100644 --- a/lib/Doctrine/Common/Annotations/Annotation.php +++ b/lib/Doctrine/Common/Annotations/Annotation.php @@ -27,7 +27,8 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -52,14 +53,29 @@ class Annotation $this->$key = $value; } } - + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name + */ public function __get($name) { - throw new \BadMethodCallException("Unknown annotation property '$name' on annotation '".get_class($this)."'."); + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); } + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unkown property name + * @param mixed $value Property value + */ public function __set($name, $value) { - throw new \BadMethodCallException("Unknown annotation property '$name' on annotation '".get_class($this)."'."); + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); } } \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/AnnotationException.php b/lib/Doctrine/Common/Annotations/AnnotationException.php index 7610b16ef..bdee49420 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationException.php +++ b/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -27,20 +27,32 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ -class AnnotationException extends \Doctrine\Common\CommonException +class AnnotationException extends \Exception { + /** + * Creates a new AnnotationException describing a Syntax error. + * + * @param string $message Exception message + * @return AnnotationException + */ public static function syntaxError($message) { return new self('[Syntax Error] ' . $message); } - - public static function semanticalError($message) + /** + * Creates a new AnnotationException describing a Semantical error. + * + * @param string $message Exception message + * @return AnnotationException + */ + public static function semanticalError($message) { return new self('[Semantical Error] ' . $message); } diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index bed578e35..dbbf8dff6 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -32,7 +32,7 @@ use \ReflectionClass, * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage @@ -46,7 +46,7 @@ class AnnotationReader * @var string * @static */ - private static $CACHE_SALT = "@"; + private static $CACHE_SALT = '@'; /** * Annotations Parser @@ -56,15 +56,14 @@ class AnnotationReader private $_parser; /** - * Cache machanism to store processed Annotations + * Cache mechanism to store processed Annotations * * @var Doctrine\Common\Cache\Cache */ private $_cache; /** - * Constructor. Initializes a new AnnotationReader that uses the given - * Cache provider. + * Constructor. Initializes a new AnnotationReader that uses the given Cache provider. * * @param Cache $cache The cache provider to use. If none is provided, ArrayCache is used. */ @@ -112,7 +111,7 @@ class AnnotationReader return $data; } - $annotations = $this->_parser->parse($class->getDocComment(), "class ".$class->getName()); + $annotations = $this->_parser->parse($class->getDocComment(), 'class ' . $class->getName()); $this->_cache->save($cacheKey, $annotations, null); return $annotations; @@ -128,6 +127,7 @@ class AnnotationReader public function getClassAnnotation(ReflectionClass $class, $annotation) { $annotations = $this->getClassAnnotations($class); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } @@ -148,7 +148,7 @@ class AnnotationReader return $data; } - $context = "property ".$property->getDeclaringClass()->getName()."::\$".$property->getName(); + $context = 'property ' . $property->getDeclaringClass()->getName() . "::\$" . $property->getName(); $annotations = $this->_parser->parse($property->getDocComment(), $context); $this->_cache->save($cacheKey, $annotations, null); @@ -165,6 +165,7 @@ class AnnotationReader public function getPropertyAnnotation(ReflectionProperty $property, $annotation) { $annotations = $this->getPropertyAnnotations($property); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } @@ -185,7 +186,7 @@ class AnnotationReader return $data; } - $context = "method ".$method->getDeclaringClass()->getName()."::".$method->getName()."()"; + $context = 'method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()'; $annotations = $this->_parser->parse($method->getDocComment(), $context); $this->_cache->save($cacheKey, $annotations, null); @@ -202,6 +203,7 @@ class AnnotationReader public function getMethodAnnotation(ReflectionMethod $method, $annotation) { $annotations = $this->getMethodAnnotations($method); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } } \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/Lexer.php b/lib/Doctrine/Common/Annotations/Lexer.php index cb32f0cc2..a6b8c0675 100644 --- a/lib/Doctrine/Common/Annotations/Lexer.php +++ b/lib/Doctrine/Common/Annotations/Lexer.php @@ -27,7 +27,8 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel @@ -80,7 +81,7 @@ class Lexer extends \Doctrine\Common\Lexer $newVal = $this->_getNumeric($value); // Checking numeric value - if ($newVal !== false){ + if ($newVal !== false) { $value = $newVal; return (strpos($value, '.') !== false || stripos($value, 'e') !== false) @@ -93,16 +94,34 @@ class Lexer extends \Doctrine\Common\Lexer return self::T_STRING; } else { switch (strtolower($value)) { - case '@': return self::T_AT; - case ',': return self::T_COMMA; - case '(': return self::T_OPEN_PARENTHESIS; - case ')': return self::T_CLOSE_PARENTHESIS; - case '{': return self::T_OPEN_CURLY_BRACES; + case '@': + return self::T_AT; + + case ',': + return self::T_COMMA; + + case '(': + return self::T_OPEN_PARENTHESIS; + + case ')': + return self::T_CLOSE_PARENTHESIS; + + case '{': + return self::T_OPEN_CURLY_BRACES; + case '}': return self::T_CLOSE_CURLY_BRACES; - case '=': return self::T_EQUALS; - case '\\': return self::T_NAMESPACE_SEPARATOR; - case 'true': return self::T_TRUE; - case 'false': return self::T_FALSE; + case '=': + return self::T_EQUALS; + + case '\\': + return self::T_NAMESPACE_SEPARATOR; + + case 'true': + return self::T_TRUE; + + case 'false': + return self::T_FALSE; + default: if (ctype_alpha($value[0]) || $value[0] === '_') { return self::T_IDENTIFIER; @@ -126,6 +145,7 @@ class Lexer extends \Doctrine\Common\Lexer if ( ! is_scalar($value)) { return false; } + // Checking for valid numeric numbers: 1.234, -1.234e-2 if (is_numeric($value)) { return $value; diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index bde253d58..eba4ae42a 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -27,11 +27,11 @@ namespace Doctrine\Common\Annotations; * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 - * @version $Revision: 3938 $ + * @version $Revision$ + * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel - * @author Benjamin Eberlei */ class Parser { @@ -173,9 +173,10 @@ class Parser $message .= "'{$token['value']}' at position {$token['position']}"; } - if(strlen($this->_context)) { - $message .= ' in '.$this->_context; + if (strlen($this->_context)) { + $message .= ' in ' . $this->_context; } + $message .= '.'; throw AnnotationException::syntaxError($message); @@ -411,6 +412,7 @@ class Parser foreach ($values as $value) { list ($key, $val) = $value; + if ($key !== null) { $array[$key] = $val; } else { diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index e2db2b0d0..c4aa9bb55 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -440,7 +440,8 @@ class EntityManager * * @param object $entity The entity to copy. * @return object The new entity. - * @todo Implementation or remove. + * @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e: + * Fatal error: Maximum function nesting level of '100' reached, aborting! */ public function copy($entity, $deep = false) { diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 960ae426d..b15248689 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -203,12 +203,15 @@ class Parser // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. $this->_processDeferredIdentificationVariables(); + if ($this->_deferredPartialObjectExpressions) { $this->_processDeferredPartialObjectExpressions(); } + if ($this->_deferredPathExpressions) { $this->_processDeferredPathExpressions($AST); } + if ($this->_deferredResultVariables) { $this->_processDeferredResultVariables(); } @@ -456,7 +459,7 @@ class Parser // Check if IdentificationVariable exists in queryComponents if ( ! isset($this->_queryComponents[$identVariable])) { $this->semanticalError( - "'$identVariable' is not defined.", $deferredItem['token'] + "'$identVariable' is not defined.", $deferredItem['token'] ); } @@ -465,14 +468,14 @@ class Parser // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['metadata'])) { $this->semanticalError( - "'$identVariable' does not point to a Class.", $deferredItem['token'] + "'$identVariable' does not point to a Class.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( - "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] + "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } @@ -486,15 +489,15 @@ class Parser 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'] + "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'] + "The partial field selection of class " . $class->name . " must contain the identifier.", + $deferredItem['token'] ); } } @@ -514,7 +517,7 @@ class Parser // Check if ResultVariable exists in queryComponents if ( ! isset($this->_queryComponents[$resultVariable])) { $this->semanticalError( - "'$resultVariable' is not defined.", $deferredItem['token'] + "'$resultVariable' is not defined.", $deferredItem['token'] ); } @@ -523,14 +526,14 @@ class Parser // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['resultVariable'])) { $this->semanticalError( - "'$identVariable' does not point to a ResultVariable.", $deferredItem['token'] + "'$identVariable' does not point to a ResultVariable.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( - "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] + "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } @@ -625,6 +628,7 @@ class Parser ), null ); + $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration; $this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;