Project

General

Profile

Actions

Bug #102708

closed

Let queryBuilder support compositeExpression as joinCondition

Added by Harald Witt 11 months ago. Updated 11 months ago.

Status:
Closed
Priority:
Should have
Assignee:
-
Category:
-
Target version:
-
Start date:
2023-12-21
Due date:
% Done:

0%

Estimated time:
TYPO3 Version:
12
PHP Version:
Tags:
queryBuilder CompositExpression Join
Complexity:
Is Regression:
Sprint Focus:

Description

$queryBuilder->leftJoin and other joins does not support TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression as joinCondition. Doing this will result in a 503.
That is really a bug or at least a missing feature, because e. g. mm_matchFields MUST be compared in the joinCondition and NOT in the where-clause. Why? Here is the reason ...

Using a CompositeExpression with mm_matchFields as joinCondition:
A primary record with no partner record in the secondary table is joined with a bunch of NULL fields and is INCLUDED in the result of the join. So a where-clause, reflecting a condition of the primary table COULD MATCH.

Using mm_matchFields in where conditions:
A primary record with no partner record in the secondary table is EXCLUDED from the join result. So a where-clause, reflecting a condition of the primary table will NEVER MATCH.

That's the same problem already solved here for the TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser:
See https://forge.typo3.org/issues/93337#change-493419

$joinConditionExpression = $this->queryBuilder->expr()->and(
    $basicJoinCondition,
    $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $childTableAlias, $realTableName)
);

Code example follows ...

Actions #1

Updated by Harald Witt 11 months ago

Stefan Bürk purposed a simple solution: Casting the CompositeExpression to string really functions!

$queryBuilder
  ->leftJoin(
        'tt_content',
        'tt_content',
        'tt_content_orig',
        (string)$queryBuilder->and(
          $queryBuilder->expr()->eq(
            'tt_content.t3_origuid',
            $queryBuilder->quoteIdentifier('tt_content_orig.uid')
          ),
          // ... further conditions
        ) 
  )

Works perfect. But I think it would be better to generally allow ConpositeExpressions for joinConditions and do the casting inside the join-methods of the queryBuilder.

Actions #2

Updated by Harald Witt 11 months ago · Edited

Here another note of Stefan:

will look if this can be done - regarding doctrine/dbal changes. currently it would be possible, but TYPO3 QueryyBuilder does not extend doctrine querybuilder, with dbal v4 this is a must have. I will see.
Does not make sense to introduce it when it would block the upgrade for v13 then.

Mabye add the info as addition and let the issue stay open for now. Will assign myself later on it.

Actions #3

Updated by Harald Witt 11 months ago · Edited

And here the promised code example:
I have a crowd of teachers in tx_myext_domain_model_teacher and a bunch of locations in tx_myext_domain_model_location. Each teacher can be available at several locations. So there is a MM-table tx_myext_domain_model_location_mm. This MM-table also contains relations for other tables / fields. Therefore tx_myext_domain_model_location_mm also contains the fiels tablenames and fieldname (defined as mm_match_fields in TCA of tx_myext_domain_model_teacher.

In my TCA I've defined, that if a location is selected, the teacher field appears via reload. But the options should only contain teachers that are available at the selected location. So I use a itemsProcFunc for this and it looks like ... Stefans (string)-casting already included ...

    public function getTeacherOptions(&$params): void
    {
        $locationUid = isset($params['row']['location']['0']) ? $params['row']['location']['0'] : $params['row']['location'];
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
        $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_myext_domain_model_teacher');
        $queryBuilder
            ->select('uid', 'surname', 'given_name', 'token')
            ->from('tx_myext_domain_model_teacher')
            ->orderBy('surname')
            ->addOrderBy('given_name');
        $constraints = [];
        if ($locationUid > 0) {
            $queryBuilder->leftJoin(
                'tx_myext_domain_model_teacher',
                'tx_myext_domain_model_location_mm',
                'mm',
                (string)$queryBuilder->expr()->and(
                    $queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->quoteIdentifier('tx_myext_domain_model_teacher.uid')),
                    $queryBuilder->expr()->eq('mm.tablenames', $queryBuilder->createNamedParameter('tx_myext_domain_model_teacher', Connection::PARAM_STR)),
                    $queryBuilder->expr()->eq('mm.fieldname', $queryBuilder->createNamedParameter('locations', Connection::PARAM_STR))
                )
            );
            $constraints[] = $queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($locationUid, \PDO::PARAM_INT));
        }

        // ...
        // further contraints
        // ...

        if (count($constraints)) {
            $queryBuilder->where(...$constraints);
        }
        $result = $queryBuilder->executeQuery();
        while ($row = $result->fetchAssociative()) {
            $params['items'][] = ['label' => $row['surname'] . ', ' . $row['given_name'] . ', ' . $row['token'], 'value' => $row['uid'], 'icon' => 'EXT:myext/Resources/Public/Icons/teacher.svg'];
        }
    }

Actions #4

Updated by Benni Mack 11 months ago

  • Status changed from New to Closed

Closing the ticket as discussed in Slack.

Actions

Also available in: Atom PDF