Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDC-551] Add SQLFilter functionality #210

Merged
merged 34 commits into from
Dec 18, 2011
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a85902b
[DDC-551] Initial code for filter functionality
asm89 Jul 22, 2011
b867744
[DDC-551] Added tests for SQLFilter functionality + small fixes
asm89 Jul 22, 2011
277fc75
[DDC-551] Added tests for SQLFilter
asm89 Jul 22, 2011
d1908f7
[DDC-551] Keep filter parameters and enabled filters sorted for hashing
asm89 Jul 22, 2011
4cf63a4
[DDC-551] Fixed the escaping of filter parameters
asm89 Jul 22, 2011
4266ab7
[DDC-551] Added __toString() method to SQLFilter
asm89 Jul 22, 2011
afd7a54
[DDC-551] Removed 'use ..DBAL\..\Type', causing full testsuite to fail
asm89 Jul 22, 2011
6163d9d
[DDC-551] Added enabled filters to Query hash
asm89 Jul 22, 2011
e3dcfa8
[DDC-551] Added filters to query hash + tests for hash
asm89 Jul 22, 2011
6cf7bdc
Merge branch 'master' into DDC-551
asm89 Jul 26, 2011
3b1ddb0
[DDC-551] Added filters to SQLWalker
asm89 Aug 16, 2011
2653d73
[DDC-551] Added state of the complete filter collection to the EM
asm89 Aug 16, 2011
3800581
[DDC-551] Altered persisters to make filters work with EXTRA_LAZY ass…
asm89 Aug 16, 2011
ed0fb4e
Merge branch 'master' into DDC-551
asm89 Aug 16, 2011
63a3fb5
[DDC-551] Moved SQLFilter logic to a separate FilterCollection class
asm89 Sep 15, 2011
097d573
Merge branch 'master' into DDC-551
asm89 Oct 14, 2011
58b381b
[DDC-551] use isClean to check the filterCollection state..
asm89 Oct 15, 2011
07ce409
Merge branch 'master' into DDC-551
asm89 Oct 21, 2011
9ccce8e
[DDC-551] Add filters to eagerly joined entities in the persisters
asm89 Oct 26, 2011
53055f1
[DDC-551] Fixed a bug in the sql generation for filters
asm89 Nov 1, 2011
68027e1
Merge remote-tracking branch 'upstream/master'
asm89 Nov 23, 2011
be48821
Merge remote-tracking branch 'origin/master' into DDC-551
asm89 Nov 23, 2011
4c94a7c
[DDC-551] Various minor fixes after merge and cleanup
asm89 Nov 30, 2011
f6d5f04
[DDC-551] Initial support for filters in the JoinedSubclassPersister
asm89 Nov 30, 2011
bf1cc29
[DDC-551] Fixed some comments
asm89 Dec 1, 2011
e98c775
Revert "[DDC-551] Initial support for filters in the JoinedSubclassPe…
asm89 Dec 5, 2011
752b502
[DDC-551] Add filters only on root entities in JoinedSubclassPersister
asm89 Dec 5, 2011
4c84297
[DDC-551] Add filters only on root entities in SingleTablePersister
asm89 Dec 5, 2011
3b7d16c
[DDC-551] General cleanup of the code.
asm89 Dec 5, 2011
04635ad
Merge remote-tracking branch 'upstream/master' into DDC-551
asm89 Dec 5, 2011
e8d3006
[DDC-551] Various refactorings
asm89 Dec 5, 2011
f4663f4
[DDC-551] Another batch of small refactorings
asm89 Dec 5, 2011
efe7a01
[DDC-551] Fixed CS, comments by @stof
asm89 Dec 5, 2011
5e91f0c
[DDC-551] Update SQLWalker to reflect filter requirements for inherit…
asm89 Dec 7, 2011
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions lib/Doctrine/ORM/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,33 @@ public function getClassMetadataFactoryName()
}
return $this->_attributes['classMetadataFactoryName'];
}


/**
* Add a filter to the list of possible filters.
*
* @param string $name The name of the filter.
* @param string $className The class name of the filter.
*/
public function addFilter($name, $className)
{
$this->_attributes['filters'][strtolower($name)] = $className;
}

/**
* Gets the class name for a given filter name.
*
* @param string $name The name of the filter.
*
* @return string The class name of the filter, or null of it is not
* defined.
*/
public function getFilterClassName($name)
{
$name = strtolower($name);
return isset($this->_attributes['filters'][$name]) ?
$this->_attributes['filters'][$name] : null;
}

/**
* Set default repository class.
*
Expand Down Expand Up @@ -523,4 +549,4 @@ public function getDefaultRepositoryClassName()
return isset($this->_attributes['defaultRepositoryClassName']) ?
$this->_attributes['defaultRepositoryClassName'] : 'Doctrine\ORM\EntityRepository';
}
}
}
45 changes: 44 additions & 1 deletion lib/Doctrine/ORM/EntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Query\ResultSetMapping,
Doctrine\ORM\Proxy\ProxyFactory;
Doctrine\ORM\Proxy\ProxyFactory,
Doctrine\ORM\Query\FilterCollection;

/**
* The EntityManager is the central access point to ORM functionality.
Expand Down Expand Up @@ -110,6 +111,13 @@ class EntityManager implements ObjectManager
*/
private $closed = false;

/**
* Collection of query filters.
*
* @var Doctrine\ORM\Query\FilterCollection
*/
private $filterCollection;

/**
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
Expand Down Expand Up @@ -788,4 +796,39 @@ public static function create($conn, Configuration $config, EventManager $eventM

return new EntityManager($conn, $config, $conn->getEventManager());
}

/**
* Gets the enabled filters.
*
* @return FilterCollection The active filter collection.
*/
public function getFilters()
{
if(null === $this->filterCollection) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should add a space after the if

$this->filterCollection = new FilterCollection($this);
}

return $this->filterCollection;
}

/**
* Checks whether the state of the filter collection is clean.
*
* @return boolean True, iff the filter collection is clean.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo here

*/
public function isFiltersStateClean()
{
return null === $this->filterCollection
|| $this->filterCollection->isClean();
}

/**
* Checks whether the Entity Manager has filters.
*
* @return True, iff the EM has a filter collection.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

*/
public function hasFilters()
{
return null !== $this->filterCollection;
}
}
51 changes: 46 additions & 5 deletions lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
* @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
*/
class BasicEntityPersister
Expand Down Expand Up @@ -900,9 +901,16 @@ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMo
$lockSql = ' ' . $this->_platform->getWriteLockSql();
}

$alias = $this->_getSQLTableAlias($this->_class->name);

if('' !== $filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space needed here and next line

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are missing in many other places

if($conditionSql) $conditionSql .= ' AND ';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should always use braces for if statements, even when there is only one line

$conditionSql .= $filterSql;
}

return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL()
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name), $lockMode)
. $alias, $lockMode)
. $this->_selectJoinSql . $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql, $limit, $offset)
Expand Down Expand Up @@ -1018,10 +1026,16 @@ protected function _getSelectColumnListSQL()
if ( ! $first) {
$this->_selectJoinSql .= ' AND ';
}
$tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias);
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
. $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol;
. $tableAlias . '.' . $targetCol;
$first = false;
}

// Add filter SQL
if('' !== $filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) {
$this->_selectJoinSql .= ' AND ' . $filterSql;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is $this->_selectJoinSql never empty here ? otherwise AND should be omitted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->_selectJoinSql is never empty at that place

}
} else {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
Expand Down Expand Up @@ -1267,10 +1281,10 @@ public function lock(array $criteria, $lockMode)
*
* @return string
*/
protected function getLockTablesSql()
protected function getLockTablesSql($alias = null)
{
return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name);
. (null !== $alias ? $alias : $this->_getSQLTableAlias($this->_class->name));
}

/**
Expand Down Expand Up @@ -1521,10 +1535,16 @@ public function exists($entity, array $extraConditions = array())
$criteria = array_merge($criteria, $extraConditions);
}

$alias = $this->_getSQLTableAlias($this->_class->name);

$sql = 'SELECT 1 '
. $this->getLockTablesSql()
. $this->getLockTablesSql($alias)
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);

if('' !== $filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
$sql .= ' AND ' . $filterSql;
}

list($params, $types) = $this->expandParameters($criteria);

return (bool) $this->_conn->fetchColumn($sql, $params);
Expand Down Expand Up @@ -1562,4 +1582,25 @@ public function getSQLColumnAlias($columnName)
substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength())
);
}

/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
$filterClauses = array();

foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}

return implode(' AND ', $filterClauses);
}
}
21 changes: 19 additions & 2 deletions lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
*/
Expand Down Expand Up @@ -327,6 +328,13 @@ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMo
if ($first) $first = false; else $joinSql .= ' AND ';

$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;

if($parentClass->name === $this->_class->rootEntityName) {
// Add filters on the root class
if('' !== $filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
$joinSql .= ' AND ' . $filterSql;
}
}
}
}

Expand Down Expand Up @@ -374,6 +382,14 @@ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMo

$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);

// If the current class in the root entity, add the filters
if($this->_class->name === $this->_class->rootEntityName) {
if('' !== $filterSql = $this->generateFilterConditionSQL($this->_class, $baseTableAlias)) {
if($conditionSql) $conditionSql .= ' AND ';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curly braces are needed here too

$conditionSql .= $filterSql;
}
}

$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';

Expand Down Expand Up @@ -401,10 +417,10 @@ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMo
*
* @return string
*/
public function getLockTablesSql()
public function getLockTablesSql($alias = null)
{
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
$baseTableAlias = null !== $alias ? $alias : $this->_getSQLTableAlias($this->_class->name);

// INNER JOIN parent tables
$joinSql = '';
Expand Down Expand Up @@ -473,4 +489,5 @@ protected function assignDefaultVersionValue($entity, $id)
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
}

}
83 changes: 75 additions & 8 deletions lib/Doctrine/ORM/Persisters/ManyToManyPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@

namespace Doctrine\ORM\Persisters;

use Doctrine\ORM\PersistentCollection,
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\UnitOfWork;

/**
* Persister for many-to-many collections.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
*/
class ManyToManyPersister extends AbstractCollectionPersister
Expand Down Expand Up @@ -215,10 +217,16 @@ public function count(PersistentCollection $coll)
? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
}


list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
if('' !== $filterSql) {
$whereClauses[] = $filterSql;
}

$sql = 'SELECT COUNT(*)'
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . implode(' AND ', $whereClauses);
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t'
. $joinTargetEntitySQL
. ' WHERE ' . implode(' AND ', $whereClauses);

return $this->_conn->fetchColumn($sql, $params);
}
Expand Down Expand Up @@ -250,7 +258,7 @@ public function contains(PersistentCollection $coll, $element)
return false;
}

list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true);

$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);

Expand All @@ -271,7 +279,7 @@ public function removeElement(PersistentCollection $coll, $element)
return false;
}

list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false);

$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);

Expand All @@ -281,9 +289,10 @@ public function removeElement(PersistentCollection $coll, $element)
/**
* @param Doctrine\ORM\PersistentCollection $coll
* @param object $element
* @param boolean $addFilters Whether the filter SQL should be included or not.
* @return array
*/
private function getJoinTableRestrictions(PersistentCollection $coll, $element)
private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters)
{
$uow = $this->_em->getUnitOfWork();
$mapping = $coll->getMapping();
Expand Down Expand Up @@ -321,7 +330,65 @@ private function getJoinTableRestrictions(PersistentCollection $coll, $element)
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}

if($addFilters) {
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
if('' !== $filterSql) {
$quotedJoinTable .= ' t ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}

return array($quotedJoinTable, $whereClauses, $params);
}
}

/**
* Generates the filter SQL for a given mapping.
*
* @param array $targetEntity Array containing mapping information.
*
* @return string The SQL query part to add to a query.
*/
public function getFilterSql($mapping)
{
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);

// A join is needed if there is filtering on the target entity
$joinTargetEntitySQL = '';
if('' !== $filterSql = $this->generateFilterConditionSQL($targetClass, 'te')) {
$joinTargetEntitySQL = ' JOIN '
. $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' te'
. ' ON';

$joinTargetEntitySQLClauses = array();
foreach($mapping['relationToTargetKeyColumns'] as $joinTableColumn => $targetTableColumn) {
$joinTargetEntitySQLClauses[] = ' t.' . $joinTableColumn . ' = ' . 'te.' . $targetTableColumn;
}

$joinTargetEntitySQL .= implode(' AND ', $joinTargetEntitySQLClauses);
}

return array($joinTargetEntitySQL, $filterSql);
}

/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
$filterClauses = array();

foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}

return implode(' AND ', $filterClauses);
}
}
Loading