Bug #93337
Updated by Harald Witt almost 4 years ago
BTW: Unfortunately I cannot change the Priority to *must have* .
QueryBuilder creates wrong query when using foreign_table_field and/or foreign_match_fields in a 1:n inline relation.
The conditions for foreign_table_field and/or foreign_match_fields are not properly nested.
They always appear as a conjunction outside all other conditions.
Here's my detailled demonstration. I'll use my own table names here ...
ext_tables.sql:
<pre>
#
# Table structure for table 'tx_intranet_domain_model_childtable'
#
CREATE TABLE tx_intranet_domain_model_childtable (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
parent_table varchar(255) DEFAULT '' NOT NULL,
parent_uid int(11) DEFAULT '0' NOT NULL,
type smallint(5) unsigned DEFAULT '0' NOT NULL,
child_content varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid),
KEY `parent` (`parent_table`,`parent_uid`)
);
#
# Table structure for table 'tx_intranet_domain_model_parenttable'
#
CREATE TABLE tx_intranet_domain_model_parenttable (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
children int(11) DEFAULT '0' NOT NULL,
parent_content varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid)
);
</pre>
The important TCA-part of the column children of the table tx_intranet_domain_model_parenttable
<pre>
'children' => [
'exclude' => false,
'label' => 'Child records',
'config' => [
'type' => 'inline',
'foreign_table' => 'tx_intranet_domain_model_childtable',
'foreign_field' => 'parent_uid',
'foreign_table_field' => 'parent_table',
'foreign_match_fields' => [ 'type' => 3, ],
'overrideChildTca' => [
'columns' => [ 'type' => [ 'config' => [ 'default' => 3, ], ], ],
'types' => [ '3' => [ 'showitem' => '--div--;General,parent_content,child_content' ], ], // only an example here
],
'appearance' => [
'newRecordLinkTitle' => 'New child record',
]
],
],
</pre>
Now we have to create the domain models, a Controller with an action. It's not shown here.
Please create a parent record in the backend with "who is who" in the field parent_content.
You may create child-records, but these should NOT contain the word "who" for now.
Now I'd like to search for the word "who" which could be in both, the field parent_content and/or the field child_content.
So I build this query in a method of my repositoty:
<pre>
class ParenttableRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
/**
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings
*/
protected $defaultQuerySettings;
public function initializeObject()
{
$this->defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class);
$this->defaultQuerySettings->setRespectStoragePage(false);
}
public function bugDemo() {
$query = $this->createQuery();
$query->matching(
$query->logicalOr(
$query->like('parent_content', '%who%'),
$query->like('children.child_content', '%who%')
)
)
->execute();
// BEGIN: Let's debug
\TYPO3\CMS\Core\Utility\DebugUtility::debug($result);
$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
echo $queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL();
// END: Let's debug
return $result;
}
}
</pre>
The result is empty and we got the following SQL-Query:
<pre>
SELECT `tx_intranet_domain_model_parenttable`.*
FROM `tx_intranet_domain_model_parenttable` `tx_intranet_domain_model_parenttable`
LEFT JOIN `tx_intranet_domain_model_childtable` `tx_intranet_domain_model_childtable`
ON `tx_intranet_domain_model_parenttable`.`uid` = `tx_intranet_domain_model_childtable`.`parent_uid`
WHERE
(
(`tx_intranet_domain_model_childtable`.`type` = :dcValue2)
AND
(`tx_intranet_domain_model_childtable`.`parent_table` = :dcValue3)
)
AND
(
(`tx_intranet_domain_model_parenttable`.`parent_content` LIKE :dcValue1)
OR
(`tx_intranet_domain_model_childtable`.`child_content` LIKE :dcValue4)
)
</pre>
As you can see, the two fields parent_table and type are used in a conjuction (AND) outside the OR-clause. This completely destroys the Query-Result.
The correct SQL-Statement should be:
<pre>
SELECT `tx_intranet_domain_model_parenttable`.*
FROM `tx_intranet_domain_model_parenttable` `tx_intranet_domain_model_parenttable`
LEFT JOIN `tx_intranet_domain_model_childtable` `tx_intranet_domain_model_childtable`
ON `tx_intranet_domain_model_parenttable`.`uid` = `tx_intranet_domain_model_childtable`.`parent_uid`
WHERE
(
(`tx_intranet_domain_model_parenttable`.`parent_content` LIKE :dcValue1)
OR
(
(`tx_intranet_domain_model_childtable`.`type` = :dcValue2)
AND
(`tx_intranet_domain_model_childtable`.`parent_table` = :dcValue3)
AND
(`tx_intranet_domain_model_childtable`.`child_content` LIKE :dcValue4)
)
)
</pre>
Another possibility (and surely easier to implement) wold be:
<pre>
SELECT `tx_intranet_domain_model_parenttable`.*
FROM `tx_intranet_domain_model_parenttable` `tx_intranet_domain_model_parenttable`
LEFT JOIN `tx_intranet_domain_model_childtable` `tx_intranet_domain_model_childtable`
ON `tx_intranet_domain_model_parenttable`.`uid` = `tx_intranet_domain_model_childtable`.`parent_uid`
WHERE
(
(
(`tx_intranet_domain_model_childtable`.`type` = :dcValue2)
AND
(`tx_intranet_domain_model_childtable`.`parent_table` = :dcValue3)
)
OR
(`tx_intranet_domain_model_childtable`.`uid` IS NULL)
)
AND
(
(`tx_intranet_domain_model_parenttable`.`parent_content` LIKE :dcValue1)
OR
(`tx_intranet_domain_model_childtable`.`child_content` LIKE :dcValue4)
)
</pre>
I see only two workarounds:
# I could built the complete statement manually. But that would be very complicated because there are lots of other fields in my case.
# I don't use parent_table and type anymore. In that case I'd have to maintain 8 tables instead of one.
So I hope you'll accept this as a bug and will fix it ASAP.
Greetings
Harald