[FEATURE] Relations to the same table in Extbase 56/33956/12
authorAlexander Stehlik <alexander.stehlik@gmail.com>
Mon, 24 Nov 2014 16:15:17 +0000 (17:15 +0100)
committerAlexander Opitz <opitz.alexander@googlemail.com>
Wed, 30 Sep 2015 12:56:11 +0000 (14:56 +0200)
This patch introduces a mapping for properties to table aliases in
the Extbase Typo3DbQueryParser. This allows relations between
identical classes / tables.

Additionally the configured match fields of a table are now
respected in UNION statement (MM_match_fields,
foreign_match_fields or foreign_table_field).

Releases: master
Resolves: #27057
Change-Id: If1e88c994b8a1179f6babf2bdfc1da3e61b2c658
Reviewed-on: http://review.typo3.org/33956
Reviewed-by: Stephan Großberndt <stephan@grossberndt.de>
Tested-by: Stephan Großberndt <stephan@grossberndt.de>
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Tested-by: Alexander Opitz <opitz.alexander@googlemail.com>
19 files changed:
typo3/sysext/core/Documentation/Changelog/master/Feature-27057-RelationsToTheSameTableInExtbase.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/Person.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Repository/PostRepository.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/TCA/Person.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Resources/Private/Language/locallang_db.xml
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_tables.sql
typo3/sysext/extbase/Tests/Functional/Persistence/CountTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-post-mm.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml
typo3/sysext/extbase/Tests/Functional/Persistence/InTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/QueryParserTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-27057-RelationsToTheSameTableInExtbase.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-27057-RelationsToTheSameTableInExtbase.rst
new file mode 100644 (file)
index 0000000..0fdc835
--- /dev/null
@@ -0,0 +1,64 @@
+========================================================
+Feature: #27057 - Relations to the same table in Extbase
+========================================================
+
+Description
+===========
+
+It is now possible to use a domain model where an object is connected to another object of the same class directly
+
+.. code-block:: php
+
+       namespace \Vendor\Extension\Domain\Model;
+       class A {
+               /**
+               * @var \Vendor\Extension\Domain\Model\A
+               */
+               protected $parent;
+
+as well as using a domain model where an object has multiple relations to objects of the same class
+
+.. code-block:: php
+
+       namespace \Vendor\Extension\Domain\Model;
+       class A {
+               /**
+               * @var \Vendor\Extension\Domain\Model\B
+               */
+               protected $x;
+
+               /**
+               * @var \Vendor\Extension\Domain\Model\B
+               */
+               protected $y;
+
+as well as indirectly
+
+.. code-block:: php
+
+       namespace \Vendor\Extension\Domain\Model;
+       class A {
+               /**
+               * @var \Vendor\Extension\Domain\Model\B
+               */
+               protected $b;
+
+               /**
+               * @var \Vendor\Extension\Domain\Model\C
+               */
+               protected $c;
+
+       namespace \Vendor\Extension\Domain\Model;
+       class B {
+               /**
+               * @var \Vendor\Extension\Domain\Model\C
+               */
+               protected $c;
+
+Using this kind of relations was possible before only by overriding the Extbase query builder and doing manual queries because the Extbase query builder created wrong SQL statements. Now Extbase properly supports these cases.
+
+
+Impact
+======
+
+Extbase now correctly handles relations to objects of the same class.
\ No newline at end of file
index b1a6058..0f51fe8 100644 (file)
@@ -81,6 +81,16 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Maps domain model properties to their corresponding table aliases that are used in the query, e.g.:
+        *
+        * 'property1' => 'tableName',
+        * 'property1.property2' => 'tableName1',
+        *
+        * @var array
+        */
+       protected $tablePropertyMap = array();
+
+       /**
         * Constructor. takes the database handle from $GLOBALS['TYPO3_DB']
         */
        public function __construct() {
@@ -202,6 +212,7 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
         * @return array The SQL statement parts
         */
        public function parseQuery(QueryInterface $query) {
+               $this->tablePropertyMap = array();
                $sql = array();
                $sql['keywords'] = array();
                $sql['tables'] = array();
@@ -212,15 +223,17 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                $sql['orderings'] = array();
                $sql['limit'] = ((int)$query->getLimit() ?: NULL);
                $sql['offset'] = ((int)$query->getOffset() ?: NULL);
+               $sql['tableAliasMap'] = array();
                $source = $query->getSource();
                $this->parseSource($source, $sql);
                $this->parseConstraint($query->getConstraint(), $source, $sql);
                $this->parseOrderings($query->getOrderings(), $source, $sql);
 
-               $tableNames = array_unique(array_keys($sql['tables'] + $sql['unions']));
-               foreach ($tableNames as $tableName) {
-                       if (is_string($tableName) && !empty($tableName)) {
-                               $this->addAdditionalWhereClause($query->getQuerySettings(), $tableName, $sql);
+               foreach ($sql['tableAliasMap'] as $tableAlias => $tableName) {
+                       $additionalWhereClause = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
+                       if ($additionalWhereClause !== '') {
+                               $additionalWhereClause = $this->addNullConditionToStatementIfRequired($sql, $additionalWhereClause, $tableAlias);
+                               $sql['additionalWhereClause'][] = $additionalWhereClause;
                        }
                }
 
@@ -240,15 +253,36 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                if (!isset($sql['additionalWhereClause'])) {
                        throw new \InvalidArgumentException('Invalid statement given.', 1399512421);
                }
-               $tableNames = array_unique(array_keys($sql['tables'] + $sql['unions']));
-               foreach ($tableNames as $tableName) {
-                       if (is_string($tableName) && !empty($tableName)) {
-                               $this->addVisibilityConstraintStatement($querySettings, $tableName, $sql);
+               foreach ($sql['tableAliasMap'] as $tableAlias => $tableName) {
+                       $statement = $this->getVisibilityConstraintStatement($querySettings, $tableName, $tableAlias);
+                       if ($statement !== '') {
+                               $statement = $this->addNullConditionToStatementIfRequired($sql, $statement, $tableAlias);
+                               $sql['additionalWhereClause'][] = $statement;
                        }
                }
        }
 
        /**
+        * If the given table alias is used in a UNION statement it is required to
+        * add an additional condition that allows the fields of the joined table
+        * to be NULL. Otherwise the condition would be too strict and filter out
+        * records that are actually valid.
+        *
+        * @param array $sql The current SQL query parts.
+        * @param string $statement The SQL statement to which the NULL condition should be added.
+        * @param string $tableAlias The table alias used in the SQL statement.
+        * @return string The statement including the NULL condition or the original statement.
+        */
+       protected function addNullConditionToStatementIfRequired(array $sql, $statement, $tableAlias) {
+
+               if (isset($sql['unions'][$tableAlias])) {
+                       $statement = '((' . $statement . ') OR ' . $tableAlias . '.uid' . ' IS NULL)';
+               }
+
+               return $statement;
+       }
+
+       /**
         * Transforms a Query Source into SQL and parameter arrays
         *
         * @param Qom\SourceInterface $source The source
@@ -260,6 +294,7 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                        $className = $source->getNodeTypeName();
                        $tableName = $this->dataMapper->getDataMap($className)->getTableName();
                        $this->addRecordTypeConstraint($className, $sql);
+                       $tableName = $this->getUniqueAlias($sql, $tableName);
                        $sql['fields'][$tableName] = $tableName . '.*';
                        $sql['tables'][$tableName] = $tableName;
                } elseif ($source instanceof Qom\JoinInterface) {
@@ -323,8 +358,9 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                        if ($source instanceof Qom\SelectorInterface) {
                                $className = $source->getNodeTypeName();
                                $tableName = $this->dataMapper->convertClassNameToTableName($className);
+                               $fullPropertyPath = '';
                                while (strpos($propertyName, '.') !== FALSE) {
-                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql);
+                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
                                }
                        } elseif ($source instanceof Qom\JoinInterface) {
                                $tableName = $source->getLeft()->getSelectorName();
@@ -378,8 +414,9 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                $tableName = $this->dataMapper->convertClassNameToTableName($className);
                                $operand1 = $comparison->getOperand1();
                                $propertyName = $operand1->getPropertyName();
+                               $fullPropertyPath = '';
                                while (strpos($propertyName, '.') !== FALSE) {
-                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql);
+                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
                                }
                                $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
                                $dataMap = $this->dataMapper->getDataMap($className);
@@ -387,16 +424,7 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                $typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : NULL;
                                if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
                                        $relationTableName = $columnMap->getRelationTableName();
-                                       $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
-                                       if (is_array($relationTableMatchFields)) {
-                                               $additionalWhere = array();
-                                               foreach ($relationTableMatchFields as $fieldName => $value) {
-                                                       $additionalWhere[] = $fieldName . ' = ' . $this->databaseHandle->fullQuoteStr($value, $relationTableName);
-                                               }
-                                               $additionalWhereForMatchFields = ' AND ' . implode(' AND ', $additionalWhere);
-                                       } else {
-                                               $additionalWhereForMatchFields = '';
-                                       }
+                                       $additionalWhereForMatchFields = $this->getAdditionalMatchFieldsStatement($columnMap, $relationTableName, $relationTableName);
                                        $sql['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=' . $parameterIdentifier . $additionalWhereForMatchFields . ')';
                                } elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
                                        $parentKeyFieldName = $columnMap->getParentKeyFieldName();
@@ -457,8 +485,9 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                // @todo Only necessary to differ from  Join
                                $className = $source->getNodeTypeName();
                                $tableName = $this->dataMapper->convertClassNameToTableName($className);
+                               $fullPropertyPath = '';
                                while (strpos($propertyName, '.') !== FALSE) {
-                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql);
+                                       $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
                                }
                        } elseif ($source instanceof Qom\JoinInterface) {
                                $tableName = $source->getJoinCondition()->getSelector1Name();
@@ -505,20 +534,67 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Builds a condition for filtering records by the configured match field,
+        * e.g. MM_match_fields, foreign_match_fields or foreign_table_field.
+        *
+        * @param ColumnMap $columnMap The column man for which the condition should be build.
+        * @param string $childTableName The real name of the child record table.
+        * @param string $childTableAlias The alias of the child record table used in the query.
+        * @param string $parentTable The real name of the parent table (used for building the foreign_table_field condition).
+        * @return string The match field conditions or an empty string.
+        */
+       protected function getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $parentTable = NULL) {
+
+               $additionalWhereForMatchFields = '';
+
+               $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
+               if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
+                       $additionalWhere = array();
+                       foreach ($relationTableMatchFields as $fieldName => $value) {
+                               $additionalWhere[] = $childTableAlias . '.' . $fieldName . ' = ' . $this->databaseHandle->fullQuoteStr($value, $childTableName);
+                       }
+                       $additionalWhereForMatchFields .= ' AND ' . implode(' AND ', $additionalWhere);
+               }
+
+               if (isset($parentTable)) {
+                       $parentTableFieldName = $columnMap->getParentTableFieldName();
+                       if (isset($parentTableFieldName) && $parentTableFieldName !== '') {
+                               $additionalWhereForMatchFields .= ' AND ' . $childTableAlias . '.' . $parentTableFieldName . ' = ' . $this->databaseHandle->fullQuoteStr($parentTable, $childTableAlias);
+                       }
+               }
+
+               return $additionalWhereForMatchFields;
+       }
+
+       /**
         * Adds additional WHERE statements according to the query settings.
         *
         * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
         * @param string $tableName The table name to add the additional where clause for
-        * @param string &$sql
-        * @return void
+        * @param string $tableAlias The table alias used in the query.
+        * @return string
         */
-       protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, &$sql) {
+       protected function getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias = NULL) {
+
+               $sysLanguageStatement = '';
                if ($querySettings->getRespectSysLanguage()) {
-                       $this->addSysLanguageStatement($tableName, $sql, $querySettings);
+                       $sysLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
                }
+
+               $pageIdStatement = '';
                if ($querySettings->getRespectStoragePage()) {
-                       $this->addPageIdStatement($tableName, $sql, $querySettings->getStoragePageIds());
+                       $pageIdStatement = $this->getPageIdStatement($tableName, $tableAlias, $querySettings->getStoragePageIds());
+               }
+
+               if ($sysLanguageStatement !== '' && $pageIdStatement !== '') {
+                       $whereClause = $sysLanguageStatement . ' AND ' . $pageIdStatement;
+               } elseif ($sysLanguageStatement !== '') {
+                       $whereClause = $sysLanguageStatement;
+               } else {
+                       $whereClause = $pageIdStatement;
                }
+
+               return $whereClause;
        }
 
        /**
@@ -526,10 +602,10 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
         *
         * @param QuerySettingsInterface $querySettings
         * @param string $tableName The database table name
-        * @param array &$sql The query parts
-        * @return void
+        * @param string $tableAlias
+        * @return string
         */
-       protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, array &$sql) {
+       protected function getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias) {
                $statement = '';
                if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
                        $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
@@ -542,10 +618,11 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
                        }
                        if (!empty($statement)) {
+                               $statement = $this->replaceTableNameWithAlias($statement, $tableName, $tableAlias);
                                $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
-                               $sql['additionalWhereClause'][] = $statement;
                        }
                }
+               return $statement;
        }
 
        /**
@@ -598,15 +675,16 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
         * Builds the language field statement
         *
         * @param string $tableName The database table name
-        * @param array &$sql The query parts
+        * @param string $tableAlias The table alias used in the query.
         * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
-        * @return void
+        * @return string
         */
-       protected function addSysLanguageStatement($tableName, array &$sql, $querySettings) {
+       protected function getSysLanguageStatement($tableName, $tableAlias, $querySettings) {
+               $sysLanguageStatement = '';
                if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
                        if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
                                // Select all entries for the current language
-                               $additionalWhereClause = $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . (int)$querySettings->getLanguageUid() . ',-1)';
+                               $additionalWhereClause = $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . (int)$querySettings->getLanguageUid() . ',-1)';
                                // If any language is set -> get those entries which are not translated yet
                                // They will be removed by \TYPO3\CMS\Frontend\Page\PageRepository::getRecordOverlay if not matching overlay mode
                                if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
@@ -615,17 +693,17 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
 
                                        $mode = $querySettings->getLanguageMode();
                                        if ($mode === 'strict') {
-                                               $additionalWhereClause = $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=-1' .
-                                                       ' OR (' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = ' . (int)$querySettings->getLanguageUid() .
-                                                       ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '=0' .
-                                                       ') OR (' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
-                                                       ' AND ' . $tableName . '.uid IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
+                                               $additionalWhereClause = $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=-1' .
+                                                       ' OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = ' . (int)$querySettings->getLanguageUid() .
+                                                       ' AND ' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '=0' .
+                                                       ') OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
+                                                       ' AND ' . $tableAlias . '.uid IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
                                                        ' FROM ' . $tableName .
                                                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
                                                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
                                        } else {
-                                               $additionalWhereClause .= ' OR (' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
-                                                       ' AND ' . $tableName . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
+                                               $additionalWhereClause .= ' OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
+                                                       ' AND ' . $tableAlias . '.uid NOT IN (SELECT ' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
                                                        ' FROM ' . $tableName .
                                                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
                                                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
@@ -637,21 +715,23 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                        }
                                        $additionalWhereClause .= '))';
                                }
-                               $sql['additionalWhereClause'][] = '(' . $additionalWhereClause . ')';
+                               $sysLanguageStatement = '(' . $additionalWhereClause . ')';
                        }
                }
+               return $sysLanguageStatement;
        }
 
        /**
         * Builds the page ID checking statement
         *
         * @param string $tableName The database table name
-        * @param array &$sql The query parts
+        * @param string $tableAlias The table alias used in the query.
         * @param array $storagePageIds list of storage page ids
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException
-        * @return void
+        * @return string
         */
-       protected function addPageIdStatement($tableName, array &$sql, array $storagePageIds) {
+       protected function getPageIdStatement($tableName, $tableAlias, array $storagePageIds) {
+               $pageIdStatement = '';
                $tableColumns = $this->tableColumnCache->get($tableName);
                if ($tableColumns === FALSE) {
                        $tableColumns = $this->databaseHandle->admin_get_fields($tableName);
@@ -661,15 +741,16 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                        $rootLevel = (int)$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'];
                        if ($rootLevel) {
                                if ($rootLevel === 1) {
-                                       $sql['additionalWhereClause'][] = $tableName . '.pid = 0';
+                                       $pageIdStatement = $tableAlias . '.pid = 0';
                                }
                        } else {
                                if (empty($storagePageIds)) {
                                        throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException('Missing storage page ids.', 1365779762);
                                }
-                               $sql['additionalWhereClause'][] = $tableName . '.pid IN (' . implode(', ', $storagePageIds) . ')';
+                               $pageIdStatement = $tableAlias . '.pid IN (' . implode(', ', $storagePageIds) . ')';
                        }
                }
+               return $pageIdStatement;
        }
 
        /**
@@ -695,13 +776,15 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                        $sql['fields'][$leftTableName] = $rightTableName . '.*';
                }
                $this->addRecordTypeConstraint($rightClassName, $sql);
+               $leftTableName = $this->getUniqueAlias($sql, $leftTableName);
                $sql['tables'][$leftTableName] = $leftTableName;
+               $rightTableName = $this->getUniqueAlias($sql, $rightTableName);
                $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
                $joinCondition = $join->getJoinCondition();
                if ($joinCondition instanceof Qom\EquiJoinCondition) {
                        $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
                        $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
-                       $sql['unions'][$rightTableName] .= ' ON ' . $joinCondition->getSelector1Name() . '.' . $column1Name . ' = ' . $joinCondition->getSelector2Name() . '.' . $column2Name;
+                       $sql['unions'][$rightTableName] .= ' ON ' . $leftTableName . '.' . $column1Name . ' = ' . $rightTableName . '.' . $column2Name;
                }
                if ($rightSource instanceof Qom\JoinInterface) {
                        $this->parseJoin($rightSource, $sql);
@@ -709,21 +792,56 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Generates a unique alias for the given table and the given property path.
+        * The property path will be mapped to the generated alias in the tablePropertyMap.
+        *
+        * @param array $sql The SQL satement parts, will be filled with the tableAliasMap.
+        * @param string $tableName The name of the table for which the alias should be generated.
+        * @param string $fullPropertyPath The full property path that is related to the given table.
+        * @return string The generated table alias.
+        */
+       protected function getUniqueAlias(array &$sql, $tableName, $fullPropertyPath = NULL) {
+
+               if (isset($fullPropertyPath) && isset($this->tablePropertyMap[$fullPropertyPath])) {
+                       return $this->tablePropertyMap[$fullPropertyPath];
+               }
+
+               $alias = $tableName;
+               $i = 0;
+               while (isset($sql['tableAliasMap'][$alias])) {
+                       $alias = $tableName . $i;
+                       $i++;
+               }
+
+               $sql['tableAliasMap'][$alias] = $tableName;
+
+               if (isset($fullPropertyPath)) {
+                       $this->tablePropertyMap[$fullPropertyPath] = $alias;
+               }
+
+               return $alias;
+       }
+
+       /**
         * adds a union statement to the query, mostly for tables referenced in the where condition.
+        * The property for which the union statement is generated will be appended.
         *
-        * @param string &$className
-        * @param string &$tableName
-        * @param array &$propertyPath
-        * @param array &$sql
+        * @param string &$className The name of the parent class, will be set to the child class after processing.
+        * @param string &$tableName The name of the parent table, will be set to the table alias that is used in the union statement.
+        * @param array &$propertyPath The remaining property path, will be cut of by one part during the process.
+        * @param array &$sql The SQL statement parts, will be filled with the union statements.
+        * @param string $fullPropertyPath The full path the the current property, will be used to make table names unique.
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException
         */
-       protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql) {
+       protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql, &$fullPropertyPath) {
+
                $explodedPropertyPath = explode('.', $propertyPath, 2);
                $propertyName = $explodedPropertyPath[0];
                $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
-               $tableName = $this->dataMapper->convertClassNameToTableName($className);
+               $realTableName = $this->dataMapper->convertClassNameToTableName($className);
+               $tableName = isset($this->tablePropertyMap[$fullPropertyPath]) ? $this->tablePropertyMap[$fullPropertyPath] : $realTableName;
                $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
 
                if ($columnMap === NULL) {
@@ -737,33 +855,64 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                        throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
                }
 
+               $fullPropertyPath .= ($fullPropertyPath === '') ? $propertyName : '.' . $propertyName;
+               $childTableAlias = $this->getUniqueAlias($sql, $childTableName, $fullPropertyPath);
+
+               // If there is already exists a union with the current identifier we do not need to build it again and exit early.
+               if (isset($sql['unions'][$childTableAlias])) {
+                       $propertyPath = $explodedPropertyPath[1];
+                       $tableName = $childTableAlias;
+                       $className = $this->dataMapper->getType($className, $propertyName);
+                       return;
+               }
+
                if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
                        if (isset($parentKeyFieldName)) {
-                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
+                               $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.uid=' . $childTableAlias . '.' . $parentKeyFieldName;
                        } else {
-                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableName . '.uid';
+                               $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableAlias . '.uid';
                        }
-                       $className = $this->dataMapper->getType($className, $propertyName);
+                       $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $realTableName);
                } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
                        if (isset($parentKeyFieldName)) {
-                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
+                               $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.uid=' . $childTableAlias . '.' . $parentKeyFieldName;
                        } else {
-                               $onStatement = '(FIND_IN_SET(' . $childTableName . '.uid, ' . $tableName . '.' . $columnName . '))';
-                               $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $onStatement;
+                               $onStatement = '(FIND_IN_SET(' . $childTableAlias . '.uid, ' . $tableName . '.' . $columnName . '))';
+                               $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $onStatement;
                        }
-                       $className = $this->dataMapper->getType($className, $propertyName);
+                       $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $realTableName);
                } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
                        $relationTableName = $columnMap->getRelationTableName();
-                       $sql['unions'][$relationTableName] = 'LEFT JOIN ' . $relationTableName . ' ON ' . $tableName . '.uid=' . $relationTableName . '.' . $columnMap->getParentKeyFieldName();
-                       $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $relationTableName . '.' . $columnMap->getChildKeyFieldName() . '=' . $childTableName . '.uid';
-                       $className = $this->dataMapper->getType($className, $propertyName);
+                       $relationTableAlias = $relationTableAlias = $this->getUniqueAlias($sql, $relationTableName, $fullPropertyPath . '_mm');
+                       $sql['unions'][$relationTableAlias] = 'LEFT JOIN ' . $relationTableName . ' AS ' . $relationTableAlias . ' ON ' . $tableName . '.uid=' . $relationTableAlias . '.' . $columnMap->getParentKeyFieldName();
+                       $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $relationTableAlias . '.' . $columnMap->getChildKeyFieldName() . '=' . $childTableAlias . '.uid';
+                       $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $relationTableName, $relationTableAlias, $realTableName);
                } else {
                        throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Could not determine type of relation.', 1252502725);
                }
                // @todo check if there is another solution for this
                $sql['keywords']['distinct'] = 'DISTINCT';
                $propertyPath = $explodedPropertyPath[1];
-               $tableName = $childTableName;
+               $tableName = $childTableAlias;
+               $className = $this->dataMapper->getType($className, $propertyName);
+       }
+
+       /**
+        * If the table name does not match the table alias all occurrences of
+        * "tableName." are replaced with "tableAlias." in the given SQL statement.
+        *
+        * @param string $statement The SQL statement in which the values are replaced.
+        * @param string $tableName The table name that is replaced.
+        * @param string $tableAlias The table alias that replaced the table name.
+        * @return string The modified SQL statement.
+        */
+       protected function replaceTableNameWithAlias($statement, $tableName, $tableAlias) {
+
+               if ($tableAlias !== $tableName) {
+                       $statement = str_replace($tableName . '.', $tableAlias . '.', $statement);
+               }
+
+               return $statement;
        }
 
        /**
index 71f39ec..cfd68fe 100644 (file)
@@ -35,6 +35,16 @@ class Person extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
        protected $email = '';
 
        /**
+        * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ExtbaseTeam\BlogExample\Domain\Model\Tag>
+        */
+       protected $tags = NULL;
+
+       /**
+        * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ExtbaseTeam\BlogExample\Domain\Model\Tag>
+        */
+       protected $tagsSpecial = NULL;
+
+       /**
         * Constructs a new Person
         *
         */
index 17ae40f..d43f58a 100644 (file)
@@ -16,6 +16,8 @@ namespace ExtbaseTeam\BlogExample\Domain\Repository;
 
 /**
  * A repository for blog posts
+ *
+ * @method \ExtbaseTeam\BlogExample\Domain\Model\Post findByUid($uid)
  */
 class PostRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {
 
index 0911a0f..16f79fb 100644 (file)
@@ -43,10 +43,50 @@ $TCA['tx_blogexample_domain_model_person'] = array(
                                'eval' => 'trim, required',
                                'max' => 256
                        )
-               )
+               ),
+               'tags' => array(
+                       'exclude' => 1,
+                       'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xml:tx_blogexample_domain_model_person.tags',
+                       'config' => array(
+                               'type' => 'inline',
+                               'foreign_table' => 'tx_blogexample_domain_model_tag',
+                               'MM' => 'tx_blogexample_domain_model_tag_mm',
+                               'foreign_table_field' => 'tablenames',
+                               'foreign_match_fields' => array(
+                                       'fieldname' => 'tags'
+                               ),
+                               'maxitems' => 9999,
+                               'appearance' => array(
+                                       'useCombination' => 1,
+                                       'useSortable' => 1,
+                                       'collapseAll' => 1,
+                                       'expandSingle' => 1,
+                               )
+                       )
+               ),
+               'tags_special' => array(
+                       'exclude' => 1,
+                       'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xml:tx_blogexample_domain_model_person.tags_special',
+                       'config' => array(
+                               'type' => 'inline',
+                               'foreign_table' => 'tx_blogexample_domain_model_tag',
+                               'MM' => 'tx_blogexample_domain_model_tag_mm',
+                               'foreign_table_field' => 'tablenames',
+                               'foreign_match_fields' => array(
+                                       'fieldname' => 'tags_special'
+                               ),
+                               'maxitems' => 9999,
+                               'appearance' => array(
+                                       'useCombination' => 1,
+                                       'useSortable' => 1,
+                                       'collapseAll' => 1,
+                                       'expandSingle' => 1,
+                               )
+                       )
+               ),
        ),
        'types' => array(
-               '1' => array('showitem' => 'firstname, lastname, email, avatar')
+               '1' => array('showitem' => 'firstname, lastname, email, avatar, tags, tags_special')
        ),
        'palettes' => array(
                '1' => array('showitem' => '')
index 01899a0..d3d400a 100644 (file)
@@ -28,6 +28,8 @@
                        <label index="tx_blogexample_domain_model_person.lastname">Lastname</label>
                        <label index="tx_blogexample_domain_model_person.email">E-Mail</label>
                        <label index="tx_blogexample_domain_model_person.avatar">Avatar</label>
+                       <label index="tx_blogexample_domain_model_person.tags">Tags</label>
+                       <label index="tx_blogexample_domain_model_person.tags_special">Special tags</label>
                        <label index="tx_blogexample_domain_model_comment">Comment</label>
                        <label index="tx_blogexample_domain_model_comment.date">Date</label>
                        <label index="tx_blogexample_domain_model_comment.author">Author</label>
index 10aa5bf..67ef7cf 100644 (file)
@@ -35,7 +35,7 @@ CREATE TABLE tx_blogexample_domain_model_blog (
 
        PRIMARY KEY (uid),
        KEY parent (pid),
-       KEY t3ver_oid (t3ver_oid,t3ver_wsid),
+       KEY t3ver_oid (t3ver_oid,t3ver_wsid)
 );
 
 #
@@ -78,7 +78,7 @@ CREATE TABLE tx_blogexample_domain_model_post (
 
        PRIMARY KEY (uid),
        KEY parent (pid),
-       KEY t3ver_oid (t3ver_oid,t3ver_wsid),
+       KEY t3ver_oid (t3ver_oid,t3ver_wsid)
 );
 
 #
@@ -101,7 +101,7 @@ CREATE TABLE tx_blogexample_domain_model_comment (
        hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
 
        PRIMARY KEY (uid),
-       KEY parent (pid),
+       KEY parent (pid)
 );
 
 #
@@ -114,6 +114,8 @@ CREATE TABLE tx_blogexample_domain_model_person (
        firstname varchar(255) DEFAULT '' NOT NULL,
        lastname varchar(255) DEFAULT '' NOT NULL,
        email varchar(255) DEFAULT '' NOT NULL,
+       tags int(11) unsigned DEFAULT '0' NOT NULL,
+       tags_special int(11) unsigned DEFAULT '0' NOT NULL,
 
        tstamp int(11) unsigned DEFAULT '0' NOT NULL,
        crdate int(11) unsigned DEFAULT '0' NOT NULL,
@@ -133,7 +135,7 @@ CREATE TABLE tx_blogexample_domain_model_person (
 
        PRIMARY KEY (uid),
        KEY parent (pid),
-       KEY t3ver_oid (t3ver_oid,t3ver_wsid),
+       KEY t3ver_oid (t3ver_oid,t3ver_wsid)
 );
 
 #
@@ -154,7 +156,22 @@ CREATE TABLE tx_blogexample_domain_model_tag (
        sys_language_uid int(11) DEFAULT '0' NOT NULL,
 
        PRIMARY KEY (uid),
-       KEY parent (pid),
+       KEY parent (pid)
+);
+
+#
+# Table structure for table 'tx_blogexample_domain_model_tag_mm'
+#
+CREATE TABLE tx_blogexample_domain_model_tag_mm (
+       uid_local int(11) unsigned DEFAULT '0' NOT NULL,
+       uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
+       tablenames varchar(255) DEFAULT '' NOT NULL,
+       fieldname varchar(255) DEFAULT '' NOT NULL,
+       sorting int(11) unsigned DEFAULT '0' NOT NULL,
+       sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
+
+       KEY uid_local (uid_local),
+       KEY uid_foreign (uid_foreign)
 );
 
 #
index a359a83..325d873 100644 (file)
@@ -21,7 +21,7 @@ class CountTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
        /**
         * @var int number of all records
         */
-       protected $numberOfRecordsInFixture = 11;
+       protected $numberOfRecordsInFixture = 14;
 
        /**
         * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
@@ -49,6 +49,11 @@ class CountTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
        protected $blogRepository;
 
        /**
+        * @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository
+        */
+       protected $postRepository;
+
+       /**
         * Sets up this test suite.
         */
        protected function setUp() {
@@ -57,8 +62,11 @@ class CountTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/pages.xml');
                $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml');
                $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-post-mm.xml');
                $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml');
                $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml');
 
                $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
                $this->persistentManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class);
@@ -148,6 +156,21 @@ class CountTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
        }
 
        /**
+        * Test if count works with subproperties in subselects that use the same table as the repository.
+        *
+        * @test
+        */
+       public function subpropertyJoinSameTableCountTest() {
+               $query = $this->postRepository->createQuery();
+
+               $query->matching(
+                       $query->equals('relatedPosts.title', 'Post2')
+               );
+
+               $this->assertSame(1, $query->count());
+       }
+
+       /**
         * Test if count works with subproperties in multiple left join.
         *
         * @test
@@ -165,4 +188,36 @@ class CountTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->assertSame(10, $query->count());
        }
 
+       /**
+        * @test
+        */
+       public function queryWithAndConditionsToTheSameTableReturnExpectedCount() {
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository $personRepository */
+               $personRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository::class);
+               $query = $personRepository->createQuery();
+               $query->matching(
+                       $query->logicalAnd(
+                               $query->equals('tags.name', 'TagForAuthor1'),
+                               $query->equals('tagsSpecial.name', 'SpecialTagForAuthor1')
+                       )
+               );
+               $this->assertSame(1, $query->count());
+       }
+
+       /**
+        * @test
+        */
+       public function queryWithOrConditionsToTheSameTableReturnExpectedCount() {
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository $personRepository */
+               $personRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository::class);
+               $query = $personRepository->createQuery();
+               $query->matching(
+                       $query->logicalOr(
+                               $query->equals('tags.name', 'TagForAuthor1'),
+                               $query->equals('tagsSpecial.name', 'SpecialTagForAuthor1')
+                       )
+               );
+               $this->assertSame(3, $query->count());
+       }
+
 }
index 1da71af..f002eca 100644 (file)
                <deleted>0</deleted>
                <posts>1</posts>
        </tx_blogexample_domain_model_blog>
+       <tx_blogexample_domain_model_blog>
+               <uid>3</uid>
+               <pid>0</pid>
+               <title>Blog3</title>
+               <description>Blog3 Description</description>
+               <logo></logo>
+               <l18n_diffsource></l18n_diffsource>
+               <deleted>0</deleted>
+               <posts>1</posts>
+       </tx_blogexample_domain_model_blog>
+       <tx_blogexample_domain_model_blog>
+               <uid>4</uid>
+               <pid>0</pid>
+               <title>Blog4Hidden</title>
+               <description>Blog4 Description</description>
+               <logo></logo>
+               <l18n_diffsource></l18n_diffsource>
+               <hidden>1</hidden>
+               <deleted>0</deleted>
+               <posts>1</posts>
+       </tx_blogexample_domain_model_blog>
+       <tx_blogexample_domain_model_blog>
+               <uid>5</uid>
+               <pid>0</pid>
+               <title>Blog5Deleted</title>
+               <description>Blog5 Description</description>
+               <logo></logo>
+               <l18n_diffsource></l18n_diffsource>
+               <deleted>1</deleted>
+               <posts>1</posts>
+       </tx_blogexample_domain_model_blog>
 </dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml
new file mode 100644 (file)
index 0000000..f140363
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <tx_blogexample_domain_model_person>
+               <uid>1</uid>
+               <pid>0</pid>
+               <firstname>Author</firstname>
+               <lastname>With tag and special tag</lastname>
+               <tags>1</tags>
+               <tags_special>1</tags_special>
+       </tx_blogexample_domain_model_person>
+       <tx_blogexample_domain_model_person>
+               <uid>2</uid>
+               <pid>0</pid>
+               <firstname>Author</firstname>
+               <lastname>With tag</lastname>
+               <tags>1</tags>
+               <tags_special>0</tags_special>
+       </tx_blogexample_domain_model_person>
+       <tx_blogexample_domain_model_person>
+               <uid>3</uid>
+               <pid>0</pid>
+               <firstname>Author</firstname>
+               <lastname>With special tag</lastname>
+               <tags>0</tags>
+               <tags_special>1</tags_special>
+       </tx_blogexample_domain_model_person>
+</dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-post-mm.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-post-mm.xml
new file mode 100644 (file)
index 0000000..7eeccfb
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <!-- Blog post 2 is related to blog post 1 -->
+       <tx_blogexample_post_post_mm>
+               <uid_local>2</uid_local>
+               <uid_foreign>1</uid_foreign>
+               <sorting>1</sorting>
+               <sorting_foreign>1</sorting_foreign>
+       </tx_blogexample_post_post_mm>
+</dataset>
\ No newline at end of file
index 128edfe..222a590 100644 (file)
                <sorting>1</sorting>
                <sorting_foreign>10</sorting_foreign>
        </tx_blogexample_post_tag_mm>
+       <tx_blogexample_post_tag_mm>
+               <uid_local>12</uid_local>
+               <uid_foreign>12</uid_foreign>
+               <sorting>1</sorting>
+               <sorting_foreign>10</sorting_foreign>
+       </tx_blogexample_post_tag_mm>
+       <tx_blogexample_post_tag_mm>
+               <uid_local>13</uid_local>
+               <uid_foreign>12</uid_foreign>
+               <sorting>1</sorting>
+               <sorting_foreign>10</sorting_foreign>
+       </tx_blogexample_post_tag_mm>
 </dataset>
\ No newline at end of file
index 598f423..4676719 100644 (file)
@@ -12,6 +12,7 @@
                <l18n_diffsource></l18n_diffsource>
                <sorting>1</sorting>
                <deleted>0</deleted>
+               <related_posts>1</related_posts>
        </tx_blogexample_domain_model_post>
        <tx_blogexample_domain_model_post>
                <uid>2</uid>
                <sorting>11</sorting>
                <deleted>0</deleted>
        </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>12</uid>
+               <pid>0</pid>
+               <blog>3</blog>
+               <author>0</author>
+               <tags>1</tags>
+               <categories>0</categories>
+               <title>post with tag</title>
+               <content>Lorem ipsum...</content>
+               <l18n_diffsource></l18n_diffsource>
+               <sorting>1</sorting>
+               <deleted>0</deleted>
+               <hidden>0</hidden>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>13</uid>
+               <pid>0</pid>
+               <blog>3</blog>
+               <author>1</author>
+               <tags>0</tags>
+               <categories>0</categories>
+               <title>post with tagged author</title>
+               <content>Lorem ipsum...</content>
+               <l18n_diffsource></l18n_diffsource>
+               <sorting>2</sorting>
+               <deleted>0</deleted>
+               <hidden>0</hidden>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>14</uid>
+               <pid>0</pid>
+               <blog>3</blog>
+               <author>1</author>
+               <tags>1</tags>
+               <categories>0</categories>
+               <title>post with tag and tagged author</title>
+               <content>Lorem ipsum...</content>
+               <l18n_diffsource></l18n_diffsource>
+               <sorting>2</sorting>
+               <deleted>0</deleted>
+               <hidden>0</hidden>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>20</uid>
+               <pid>0</pid>
+               <blog>3</blog>
+               <tags>0</tags>
+               <categories>0</categories>
+               <title>post20 hidden</title>
+               <content>Lorem ipsum...</content>
+               <l18n_diffsource></l18n_diffsource>
+               <sorting>11</sorting>
+               <deleted>0</deleted>
+               <hidden>1</hidden>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>30</uid>
+               <pid>0</pid>
+               <blog>3</blog>
+               <tags>0</tags>
+               <categories>0</categories>
+               <title>post30 deleted</title>
+               <content>Lorem ipsum...</content>
+               <l18n_diffsource></l18n_diffsource>
+               <sorting>11</sorting>
+               <deleted>1</deleted>
+               <hidden>0</hidden>
+       </tx_blogexample_domain_model_post>
 </dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml
new file mode 100644 (file)
index 0000000..560562c
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <tx_blogexample_domain_model_tag_mm>
+               <uid_local>1</uid_local>
+               <uid_foreign>11</uid_foreign>
+               <tablenames>tx_blogexample_domain_model_person</tablenames>
+               <fieldname>tags</fieldname>
+               <sorting>1</sorting>
+               <sorting_foreign>1</sorting_foreign>
+       </tx_blogexample_domain_model_tag_mm>
+       <tx_blogexample_domain_model_tag_mm>
+               <uid_local>1</uid_local>
+               <uid_foreign>13</uid_foreign>
+               <tablenames>tx_blogexample_domain_model_person</tablenames>
+               <fieldname>tags_special</fieldname>
+               <sorting>1</sorting>
+               <sorting_foreign>1</sorting_foreign>
+       </tx_blogexample_domain_model_tag_mm>
+       <tx_blogexample_domain_model_tag_mm>
+               <uid_local>2</uid_local>
+               <uid_foreign>11</uid_foreign>
+               <tablenames>tx_blogexample_domain_model_person</tablenames>
+               <fieldname>tags</fieldname>
+               <sorting>1</sorting>
+               <sorting_foreign>1</sorting_foreign>
+       </tx_blogexample_domain_model_tag_mm>
+       <tx_blogexample_domain_model_tag_mm>
+               <uid_local>3</uid_local>
+               <uid_foreign>13</uid_foreign>
+               <tablenames>tx_blogexample_domain_model_person</tablenames>
+               <fieldname>tags_special</fieldname>
+               <sorting>1</sorting>
+               <sorting_foreign>1</sorting_foreign>
+       </tx_blogexample_domain_model_tag_mm>
+</dataset>
\ No newline at end of file
index 71ef969..e1a2b8c 100644 (file)
                <name>Tag10</name>
                <deleted>0</deleted>
        </tx_blogexample_domain_model_tag>
+       <tx_blogexample_domain_model_tag>
+               <uid>11</uid>
+               <pid>0</pid>
+               <posts>0</posts>
+               <name>TagForAuthor1</name>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_tag>
+       <tx_blogexample_domain_model_tag>
+               <uid>12</uid>
+               <pid>0</pid>
+               <posts>0</posts>
+               <name>Tag12</name>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_tag>
+       <tx_blogexample_domain_model_tag>
+               <uid>13</uid>
+               <pid>0</pid>
+               <posts>0</posts>
+               <name>SpecialTagForAuthor1</name>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_tag>
 </dataset>
index bc5eb4a..5ba238c 100644 (file)
@@ -156,7 +156,9 @@ class InTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function inConditionWorksWithQueryResult() {
-               $queryResult = $this->blogRepository->findAll();
+               $query = $this->blogRepository->createQuery();
+               $query->matching($query->in('uid', array(1,2)));
+               $queryResult = $query->execute();
 
                $inQuery = $this->postRepository->createQuery();
 
@@ -172,7 +174,9 @@ class InTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function inConditionWorksWithQueryResultOnSecondCall() {
-               $queryResult = $this->blogRepository->findAll();
+               $query = $this->blogRepository->createQuery();
+               $query->matching($query->in('uid', array(1,2)));
+               $queryResult = $query->execute();
 
                $inQuery = $this->postRepository->createQuery();
 
index b2c2f68..f6ccb93 100644 (file)
@@ -39,11 +39,22 @@ class QueryParserTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
        protected $objectManager;
 
        /**
+        * @var \ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository
+        */
+       protected $blogRepository;
+
+       /**
         * Sets up this test suite.
         */
        protected function setUp() {
                parent::setUp();
 
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml');
+
                $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
                $this->queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
                $this->blogRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository::class);
@@ -119,4 +130,42 @@ class QueryParserTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->assertNotSame($hashWithCaseSensitiveFalse, $hashWithCaseSensitiveTrue);
        }
 
+       /**
+        * @test
+        */
+       public function queryWithMultipleRelationsToIdenticalTablesReturnsExpectedResultForOrQuery() {
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
+               $postRepository = $this->objectManager->get('ExtbaseTeam\\BlogExample\\Domain\\Repository\\PostRepository');
+               $query = $postRepository->createQuery();
+               $query->matching(
+                       $query->logicalAnd(
+                               $query->equals('blog', 3),
+                               $query->logicalOr(
+                                       $query->equals('tags.name', 'Tag12'),
+                                       $query->equals('author.tags.name', 'TagForAuthor1')
+                               )
+                       )
+               );
+               $result = $query->execute()->toArray();
+               $this->assertEquals(3, count($result));
+       }
+
+       /**
+        * @test
+        */
+       public function queryWithMultipleRelationsToIdenticalTablesReturnsExpectedResultForAndQuery() {
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
+               $postRepository = $this->objectManager->get('ExtbaseTeam\\BlogExample\\Domain\\Repository\\PostRepository');
+               $query = $postRepository->createQuery();
+               $query->matching(
+                       $query->logicalAnd(
+                               $query->equals('blog', 3),
+                               $query->equals('tags.name', 'Tag12'),
+                               $query->equals('author.tags.name', 'TagForAuthor1')
+                       )
+               );
+               $result = $query->execute()->toArray();
+               $this->assertEquals(1, count($result));
+       }
+
 }
index a23e5ca..5982ef8 100644 (file)
@@ -20,11 +20,6 @@ use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
 class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
 
        /**
-        * @var int number of all records
-        */
-       protected $numberOfRecordsInFixture = 11;
-
-       /**
         * @var \ExtbaseTeam\BlogExample\Domain\Model\Blog
         */
        protected $blog;
@@ -70,10 +65,10 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function attachPostToBlogAtTheEnd() {
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $countPostsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid());
 
                $newPostTitle = 'sdufhisdhuf';
+               /** @var \ExtbaseTeam\BlogExample\Domain\Model\Post $newPost */
                $newPost = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Post::class);
                $newPost->setBlog($this->blog);
                $newPost->setTitle($newPostTitle);
@@ -82,12 +77,12 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->blog->addPost($newPost);
                $this->updateAndPersistBlog();
 
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame(($this->numberOfRecordsInFixture + 1), $countPosts);
+               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid());
+               $this->assertSame(($countPostsOriginal + 1), $countPosts);
 
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid(), '', 'sorting DESC');
                $this->assertSame($newPostTitle, $post['title']);
-               $this->assertSame((string)($this->numberOfRecordsInFixture), $post['sorting']);
+               $this->assertSame((string)($countPostsOriginal + 1), $post['sorting']);
        }
 
        /**
@@ -96,8 +91,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function removeLastPostFromBlog() {
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $countPostsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
 
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('sorting', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid(), '', 'sorting DESC');
                $this->assertEquals(10, $post['sorting']);
@@ -112,7 +106,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->updateAndPersistBlog();
 
                $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
-               $this->assertEquals(($this->numberOfRecordsInFixture - 1), $countPosts);
+               $this->assertEquals(($countPostsOriginal - 1), $countPosts);
 
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', 'tx_blogexample_domain_model_post', 'uid =' . $latestPost->getUid() . ' AND deleted=0');
                $this->assertSame(NULL, $post['uid']);
@@ -128,8 +122,10 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function addPostToBlogInTheMiddle() {
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $countPostsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
+
+               /** @var \ExtbaseTeam\BlogExample\Domain\Model\Post $newPost */
+               $newPost = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Post::class);
 
                $posts = clone $this->blog->getPosts();
                $this->blog->getPosts()->removeAll($posts);
@@ -138,7 +134,6 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                foreach ($posts as $post) {
                        $this->blog->addPost($post);
                        if ($counter == 5) {
-                               $newPost = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Post::class);
                                $newPost->setBlog($this->blog);
                                $newPost->setTitle($newPostTitle);
                                $newPost->setContent('Bla Bla Bla');
@@ -148,8 +143,8 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                }
                $this->updateAndPersistBlog();
 
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post',  'deleted=0');
-               $this->assertSame(($this->numberOfRecordsInFixture + 1), $countPosts);
+               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
+               $this->assertSame(($countPostsOriginal + 1), $countPosts);
 
                //last post
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid(), '', 'sorting DESC');
@@ -157,7 +152,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->assertSame('11', $post['sorting']);
 
                // check sorting of the post added in the middle
-               $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'uid=' . ($this->numberOfRecordsInFixture + 1));
+               $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'uid=' . $newPost->getUid());
                $this->assertSame($newPostTitle, $post['title']);
                $this->assertSame('6', $post['sorting']);
        }
@@ -168,8 +163,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function removeMiddlePostFromBlog() {
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $countPostsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
 
                $posts = clone $this->blog->getPosts();
                $counter = 1;
@@ -182,7 +176,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->updateAndPersistBlog();
 
                $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
-               $this->assertSame(($this->numberOfRecordsInFixture - 1), $countPosts);
+               $this->assertSame(($countPostsOriginal - 1), $countPosts);
 
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid(), '', 'sorting DESC');
                $this->assertSame('Post10', $post['title']);
@@ -195,8 +189,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function movePostFromEndToTheMiddle() {
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $countPostsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
 
                $posts = clone $this->blog->getPosts();
                $postsArray = $posts->toArray();
@@ -218,7 +211,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->updateAndPersistBlog();
 
                $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_post', 'deleted=0');
-               $this->assertSame($this->numberOfRecordsInFixture, $countPosts);
+               $this->assertSame($countPostsOriginal, $countPosts);
 
                $post = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('title,sorting', 'tx_blogexample_domain_model_post', 'blog =' . $this->blog->getUid(), '', 'sorting DESC');
                $this->assertSame('Post9', $post['title']);
@@ -235,12 +228,14 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function attachTagToPostAtTheEnd() {
-               $count = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag');
-               $this->assertSame(10, $count);
+               $countOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag');
 
                $newTagTitle = 'sdufhisdhuf';
-               $newTag = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Tag::class, $newTagTitle);
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Model\Tag $newTag */
+               $newTag = $this->objectManager->get('ExtbaseTeam\\BlogExample\\Domain\\Model\\Tag', $newTagTitle);
+
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $post->addTag($newTag);
@@ -249,22 +244,21 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->persistentManager->persistAll();
 
                $count = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag');
-               $this->assertSame(11, $count);
+               $this->assertSame(($countOriginal + 1), $count);
 
                $tag = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid_foreign', 'tx_blogexample_post_tag_mm', 'uid_local =' . $post->getUid(), '', 'sorting DESC');
-               $this->assertSame('11', $tag['uid_foreign']);
+               $this->assertSame($newTag->getUid(), (int)$tag['uid_foreign']);
        }
 
-
        /**
         * Tests removing object from the end of sorted M:M relation (Post:Tag)
         *
         * @test
         */
-       public  function removeLastTagFromPost() {
-               $count = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag');
-               $this->assertSame(10, $count);
+       public function removeLastTagFromPost() {
+               $countOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag', 'deleted=0');
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $tags = $post->getTags();
@@ -278,8 +272,8 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $postRepository->update($post);
                $this->persistentManager->persistAll();
 
-               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag', 'deleted=0' );
-               $this->assertEquals(10, $countPosts);
+               $countPosts = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_domain_model_tag', 'deleted=0');
+               $this->assertEquals($countOriginal, $countPosts);
 
                $tag = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid_foreign', 'tx_blogexample_post_tag_mm', 'uid_local =' . $post->getUid(), '', 'sorting DESC');
                $this->assertSame('9', $tag['uid_foreign']);
@@ -293,20 +287,22 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         *
         * @test
         */
-       public  function addTagToPostInTheMiddle() {
-               $countTags = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_post_tag_mm', 'uid_local=1');
-               $this->assertSame(10, $countTags);
+       public function addTagToPostInTheMiddle() {
+               $countTagsOriginal = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_post_tag_mm', 'uid_local=1');
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $tags = clone $post->getTags();
                $post->setTags(new ObjectStorage());
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Model\Tag $newTag */
+               $newTag = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Tag::class, 'INSERTED TAG at position 6 : ' . strftime(''));
+
                $counter = 1;
                foreach ($tags as $tag) {
                        $post->addTag($tag);
                        if ($counter == 5) {
-                               $newTag = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Model\Tag::class, 'INSERTED TAG at position 6 : ' . strftime(''));
                                $post->addTag($newTag);
                        }
                        $counter++;
@@ -316,16 +312,15 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->persistentManager->persistAll();
 
                $countTags = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_post_tag_mm', 'uid_local=1');
-               $this->assertSame(11, $countTags);
+               $this->assertSame(($countTagsOriginal + 1), $countTags);
 
                $tag = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid_foreign', 'tx_blogexample_post_tag_mm', 'uid_local =' . $post->getUid(), '', 'sorting DESC');
                $this->assertSame('10', $tag['uid_foreign']);
 
                $tag = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid_foreign', 'tx_blogexample_post_tag_mm', 'uid_local =' . $post->getUid() . ' AND sorting=6');
-               $this->assertSame('11', $tag['uid_foreign']);
+               $this->assertSame($newTag->getUid(), (int)$tag['uid_foreign']);
        }
 
-
        /**
         * Tests removing object from the middle of the sorted M:M relation (Post:Tag)
         *
@@ -335,6 +330,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $countTags = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_post_tag_mm', 'uid_local=1');
                $this->assertSame(10, $countTags);
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $tags = clone $post->getTags();
@@ -369,6 +365,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $countTags = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'tx_blogexample_post_tag_mm', 'uid_local=1');
                $this->assertSame(10, $countTags);
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $tags = clone $post->getTags();
@@ -413,6 +410,7 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
        public function timestampFieldIsUpdatedOnPostSave() {
                $rawPost = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'tx_blogexample_domain_model_post', 'uid=1');
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
                $post->setTitle("newTitle");
@@ -471,9 +469,11 @@ class RelationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $countCategories = $this->getDatabaseConnection()->exec_SELECTcountRows('*', 'sys_category_record_mm', 'uid_foreign=1 AND tablenames="tx_blogexample_domain_model_post" AND fieldname="categories"');
                $this->assertSame(3, $countCategories);
 
+               /** @var \ExtbaseTeam\BlogExample\Domain\Repository\PostRepository $postRepository */
                $postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
                $post = $postRepository->findByUid(1);
 
+               /** @var \TYPO3\CMS\Extbase\Domain\Model\Category $newCategory */
                $newCategory = $this->objectManager->get(\TYPO3\CMS\Extbase\Domain\Model\Category::class);
                $newCategory->setTitle('New Category');
 
index 635f4f3..cccd303 100644 (file)
@@ -25,10 +25,9 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                );
                /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings|\PHPUnit_Framework_MockObject_MockObject $querySettings */
                $querySettings = $this->getMock(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class);
-               $sql = array();
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (0,-1))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (0,-1))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -40,13 +39,12 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $GLOBALS['TCA'][$table]['ctrl'] = array(
                        'languageField' => 'sys_language_uid'
                );
-               $sql = array();
                /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings|\PHPUnit_Framework_MockObject_MockObject $querySettings */
                $querySettings = $this->getMock(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class, array('dummy'));
                $querySettings->setLanguageUid('1');
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $result = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (1,-1))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $result = '(' . $table . '.sys_language_uid IN (1,-1))';
                $this->assertSame($result, $sql);
        }
 
@@ -58,11 +56,10 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $GLOBALS['TCA'][$table]['ctrl'] = array(
                        'languageField' => 'sys_language_uid'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (0,-1))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (0,-1))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -75,12 +72,11 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                        'languageField' => 'sys_language_uid',
                        'delete' => 'deleted'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $querySettings->setLanguageUid(0);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (0,-1))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (0,-1))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -92,12 +88,11 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $GLOBALS['TCA'][$table]['ctrl'] = array(
                        'languageField' => 'sys_language_uid'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $querySettings->setLanguageUid(2);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (2,-1))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (2,-1))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -110,12 +105,11 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                        'languageField' => 'sys_language_uid',
                        'transOrigPointerField' => 'l10n_parent'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $querySettings->setLanguageUid(2);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (2,-1) OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (SELECT ' . $table . '.l10n_parent FROM ' . $table . ' WHERE ' . $table . '.l10n_parent>0 AND ' . $table . '.sys_language_uid=2)))'));
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (2,-1) OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (SELECT ' . $table . '.l10n_parent FROM ' . $table . ' WHERE ' . $table . '.l10n_parent>0 AND ' . $table . '.sys_language_uid=2)))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -129,19 +123,16 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                        'transOrigPointerField' => 'l10n_parent',
                        'delete' => 'deleted'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $querySettings->setLanguageUid(2);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array(
-                       '(' . $table . '.sys_language_uid IN (2,-1)' .
+               $sql= $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (2,-1)' .
                                ' OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (' .
                                'SELECT ' . $table . '.l10n_parent FROM ' . $table .
                                ' WHERE ' . $table . '.l10n_parent>0 AND ' .
                                $table . '.sys_language_uid=2 AND ' .
-                               $table . '.deleted=0)))')
-               );
+                               $table . '.deleted=0)))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -149,26 +140,22 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function addSysLanguageStatementWorksInBackendContextWithSubselectionTakesDeleteStatementIntoAccountIfNecessary() {
-               $table = $this->getUniqueId('tx_coretest_table');
                $table = 'tt_content';
                $GLOBALS['TCA'][$table]['ctrl'] = array(
                        'languageField' => 'sys_language_uid',
                        'transOrigPointerField' => 'l10n_parent',
                        'delete' => 'deleted'
                );
-               $sql = array();
                $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
                $querySettings->setLanguageUid(2);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
-               $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array(
-                       '(' . $table . '.sys_language_uid IN (2,-1)' .
+               $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
+               $expectedSql = '(' . $table . '.sys_language_uid IN (2,-1)' .
                                ' OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (' .
                                'SELECT ' . $table . '.l10n_parent FROM ' . $table .
                                ' WHERE ' . $table . '.l10n_parent>0 AND ' .
                                $table . '.sys_language_uid=2 AND ' .
-                               $table . '.deleted=0)))')
-               );
+                               $table . '.deleted=0)))';
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -230,14 +217,14 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
        public function providerForVisibilityConstraintStatement() {
                return array(
-                       'in be: include all' => array('BE', TRUE, array(), TRUE, NULL),
-                       'in be: ignore enable fields but do not include deleted' => array('BE', TRUE, array(), FALSE, array('tx_foo_table.deleted_column=0')),
-                       'in be: respect enable fields but include deleted' => array('BE', FALSE, array(), TRUE, array('tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789)')),
-                       'in be: respect enable fields and do not include deleted' => array('BE', FALSE, array(), FALSE, array('tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column=0')),
-                       'in fe: include all' => array('FE', TRUE, array(), TRUE, NULL),
-                       'in fe: ignore enable fields but do not include deleted' => array('FE', TRUE, array(), FALSE, array('tx_foo_table.deleted_column=0')),
-                       'in fe: ignore only starttime and do not include deleted' => array('FE', TRUE, array('starttime'), FALSE, array('tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0')),
-                       'in fe: respect enable fields and do not include deleted' => array('FE', FALSE, array(), FALSE, array('tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789'))
+                       'in be: include all' => array('BE', TRUE, array(), TRUE, ''),
+                       'in be: ignore enable fields but do not include deleted' => array('BE', TRUE, array(), FALSE, 'tx_foo_table.deleted_column=0'),
+                       'in be: respect enable fields but include deleted' => array('BE', FALSE, array(), TRUE, 'tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789)'),
+                       'in be: respect enable fields and do not include deleted' => array('BE', FALSE, array(), FALSE, 'tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column=0'),
+                       'in fe: include all' => array('FE', TRUE, array(), TRUE, ''),
+                       'in fe: ignore enable fields but do not include deleted' => array('FE', TRUE, array(), FALSE, 'tx_foo_table.deleted_column=0'),
+                       'in fe: ignore only starttime and do not include deleted' => array('FE', TRUE, array('starttime'), FALSE, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0'),
+                       'in fe: respect enable fields and do not include deleted' => array('FE', FALSE, array(), FALSE, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789')
                );
        }
 
@@ -261,7 +248,6 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue($ignoreEnableFields));
                $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue($enableFieldsToBeIgnored));
                $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue($deletedValue));
-               $sql = array();
 
                /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
                $mockEnvironmentService = $this->getMock(\TYPO3\CMS\Extbase\Service\EnvironmentService::class, array('isEnvironmentInFrontendMode'));
@@ -269,17 +255,17 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
                $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
-               $mockTypo3DbQueryParser->_callRef('addVisibilityConstraintStatement', $mockQuerySettings, $tableName, $sql);
-               $this->assertSame($expectedSql, $sql['additionalWhereClause']);
+               $resultSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
+               $this->assertSame($expectedSql, $resultSql);
                unset($GLOBALS['TCA'][$tableName]);
        }
 
        public function providerForRespectEnableFields() {
                return array(
-                       'in be: respectEnableFields=false' => array('BE', FALSE, NULL),
-                       'in be: respectEnableFields=true' => array('BE', TRUE, array('tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column=0')),
-                       'in FE: respectEnableFields=false' => array('FE', FALSE, NULL),
-                       'in FE: respectEnableFields=true' => array('FE', TRUE, array('tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789'))
+                       'in be: respectEnableFields=false' => array('BE', FALSE, ''),
+                       'in be: respectEnableFields=true' => array('BE', TRUE, 'tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column=0'),
+                       'in FE: respectEnableFields=false' => array('FE', FALSE, ''),
+                       'in FE: respectEnableFields=true' => array('FE', TRUE, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789')
                );
        }
 
@@ -303,7 +289,6 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $mockQuerySettings = $this->getMock(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class, array('dummy'), array(), '', FALSE);
                $mockQuerySettings->setIgnoreEnableFields(!$respectEnableFields);
                $mockQuerySettings->setIncludeDeleted(!$respectEnableFields);
-               $sql = array();
 
                /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
                $mockEnvironmentService = $this->getMock(\TYPO3\CMS\Extbase\Service\EnvironmentService::class, array('isEnvironmentInFrontendMode'));
@@ -311,8 +296,8 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
                $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
-               $mockTypo3DbQueryParser->_callRef('addVisibilityConstraintStatement', $mockQuerySettings, $tableName, $sql);
-               $this->assertSame($expectedSql, $sql['additionalWhereClause']);
+               $actualSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
+               $this->assertSame($expectedSql, $actualSql);
                unset($GLOBALS['TCA'][$tableName]);
        }
 
@@ -332,7 +317,6 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue(FALSE));
                $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue(array()));
                $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue(TRUE));
-               $sql = array();
 
                /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
                $mockEnvironmentService = $this->getMock(\TYPO3\CMS\Extbase\Service\EnvironmentService::class, array('isEnvironmentInFrontendMode'));
@@ -340,7 +324,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
                $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
-               $mockTypo3DbQueryParser->_callRef('addVisibilityConstraintStatement', $mockQuerySettings, $tableName, $sql);
+               $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
                unset($GLOBALS['TCA'][$tableName]);
        }
        /**
@@ -352,17 +336,17 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                        'set Pid to zero if rootLevel = 1' => array(
                                '1',
                                $table,
-                               array('additionalWhereClause' => array($table . '.pid = 0'))
+                               $table . '.pid = 0'
                        ),
                        'set Pid to given Pids if rootLevel = 0' => array(
                                '0',
                                $table,
-                               array('additionalWhereClause' => array($table . '.pid IN (42, 27)'))
+                               $table . '.pid IN (42, 27)'
                        ),
                        'set no statement if rootLevel = -1' => array(
                                '-1',
                                $table,
-                               array()
+                               ''
                        )
                );
        }
@@ -376,13 +360,12 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $GLOBALS['TCA'][$table]['ctrl'] = array(
                        'rootLevel' => $rootLevel
                );
-               $sql = array();
                $storagePageIds = array(42,27);
                $mockTypo3DbQueryParser = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class, array('dummy'), array(), '', FALSE);
                $mockFrontendVariableCache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class, array(), array(), '', FALSE);
                $mockTypo3DbQueryParser->_set('tableColumnCache', $mockFrontendVariableCache);
                $mockFrontendVariableCache->expects($this->once())->method('get')->will($this->returnValue(array('pid' => '42')));
-               $mockTypo3DbQueryParser->_callRef('addPageIdStatement', $table, $sql, $storagePageIds);
+               $sql = $mockTypo3DbQueryParser->_callRef('getPageIdStatement', $table, $table, $storagePageIds);
 
                $this->assertSame($expectedSql, $sql);
        }