join only middle, filters change
This commit is contained in:
@@ -38,6 +38,8 @@ use Espo\Entities\ArrayValue;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs as ORMDefs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Join;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
@@ -1319,9 +1321,11 @@ class ItemGeneralConverter implements ItemConverter
|
||||
|
||||
if ($relationType == Entity::MANY_MANY) {
|
||||
$key = $defs->getForeignMidKey();
|
||||
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
// IN-sub-query performs faster than EXISTS on MariaDB when multiple IDs.
|
||||
// Left-join performs faster than Inner-join.
|
||||
// Not joining a foreign table as it affects performance in MySQL.
|
||||
$subQuery = QueryBuilder::create()
|
||||
@@ -1385,20 +1389,26 @@ class ItemGeneralConverter implements ItemConverter
|
||||
|
||||
if ($relationType == Entity::MANY_MANY) {
|
||||
$key = $defs->getForeignMidKey();
|
||||
$nearKey = $defs->getMidKey();
|
||||
$middleEntityType = ucfirst($defs->getRelationshipName());
|
||||
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select('id')
|
||||
->from($this->entityType)
|
||||
->leftJoin($middleEntityType, $alias, [
|
||||
"{$alias}.{$nearKey}:" => 'id',
|
||||
"{$alias}.deleted" => false,
|
||||
])
|
||||
->where(["{$alias}.{$key}=" => $value])
|
||||
->build();
|
||||
|
||||
return ['id!=s' => $subQuery->getRaw()];
|
||||
// MariaDB and MySQL performs slow, PostgreSQL – fast.
|
||||
return Cond::not(
|
||||
Cond::exists(
|
||||
QueryBuilder::create()
|
||||
->from($this->entityType, 'sq')
|
||||
->join(
|
||||
Join::create($link, $alias)
|
||||
->withOnlyMiddle()
|
||||
)
|
||||
->where(["{$alias}.{$key}" => $value])
|
||||
->where(
|
||||
Cond::equal(
|
||||
Cond::column('sq.id'),
|
||||
Cond::column(lcfirst($this->entityType) . '.id')
|
||||
)
|
||||
)
|
||||
->build()
|
||||
)
|
||||
)->getRaw();
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1455,6 +1465,7 @@ class ItemGeneralConverter implements ItemConverter
|
||||
$whereList = [];
|
||||
|
||||
foreach ($value as $targetId) {
|
||||
// Only-middle join performs slower on MariaDB.
|
||||
$sq = QueryBuilder::create()
|
||||
->from($this->entityType)
|
||||
->select('id')
|
||||
|
||||
@@ -41,6 +41,7 @@ class Join
|
||||
private string $target;
|
||||
private ?string $alias = null;
|
||||
private ?WhereItem $conditions = null;
|
||||
private bool $onlyMiddle = false;
|
||||
|
||||
private function __construct(string $target, ?string $alias = null)
|
||||
{
|
||||
@@ -87,6 +88,11 @@ class Join
|
||||
return !$this->isTable();
|
||||
}
|
||||
|
||||
public function isOnlyMiddle(): bool
|
||||
{
|
||||
return $this->onlyMiddle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*
|
||||
@@ -137,4 +143,15 @@ class Join
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join only middle table. For many-to-many relationships.
|
||||
*/
|
||||
public function withOnlyMiddle(bool $onlyMiddle = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->onlyMiddle = $onlyMiddle;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,15 +184,18 @@ trait SelectingBuilderTrait
|
||||
* @param Join|string $target
|
||||
* A relation name or table. A relation name should be in camelCase, a table in CamelCase.
|
||||
* @param string|null $alias An alias.
|
||||
* @param WhereItem|array<mixed, mixed>|null $conditions Join conditions.
|
||||
* @param WhereItem|array<string|int, mixed>|null $conditions Join conditions.
|
||||
*
|
||||
* @todo Support USE INDEX in Join.
|
||||
*/
|
||||
private function joinInternal(string $type, $target, ?string $alias = null, $conditions = null): self
|
||||
{
|
||||
$onlyMiddle = false;
|
||||
|
||||
if ($target instanceof Join) {
|
||||
$alias = $alias ?? $target->getAlias();
|
||||
$conditions = $conditions ?? $target->getConditions();
|
||||
$onlyMiddle = $target->isOnlyMiddle();
|
||||
$target = $target->getTarget();
|
||||
}
|
||||
|
||||
@@ -229,6 +232,22 @@ trait SelectingBuilderTrait
|
||||
return $this;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($noLeftAlias) {
|
||||
$params['noLeftAlias'] = true;
|
||||
}
|
||||
|
||||
if ($onlyMiddle) {
|
||||
$params['onlyMiddle'] = true;
|
||||
}
|
||||
|
||||
if ($params !== []) {
|
||||
$this->params[$type][] = [$target, $alias, $conditions, $params];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_null($alias) && is_null($conditions)) {
|
||||
$this->params[$type][] = $target;
|
||||
|
||||
@@ -241,13 +260,7 @@ trait SelectingBuilderTrait
|
||||
return $this;
|
||||
}
|
||||
|
||||
$item = [$target, $alias, $conditions];
|
||||
|
||||
if ($noLeftAlias) {
|
||||
$item[] = ['noLeftAlias' => true];
|
||||
}
|
||||
|
||||
$this->params[$type][] = $item;
|
||||
$this->params[$type][] = [$target, $alias, $conditions];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -3274,7 +3274,11 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
|
||||
$distantTable = $this->toDb($foreignEntityType);
|
||||
|
||||
$midAlias = $alias . 'Middle';
|
||||
$onlyMiddle = $joinParams['onlyMiddle'] ?? false;
|
||||
|
||||
$midAlias = $onlyMiddle ?
|
||||
$alias :
|
||||
$alias . 'Middle';
|
||||
|
||||
$indexKeyList = null;
|
||||
$indexList = $joinParams['useIndex'] ?? null;
|
||||
@@ -3339,8 +3343,6 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$sql .= " AND " . implode(" AND ", $joinSqlList);
|
||||
}
|
||||
|
||||
$onlyMiddle = $joinParams['onlyMiddle'] ?? false;
|
||||
|
||||
if (!$onlyMiddle) {
|
||||
$rightKeyColumn = $this->quoteColumn("{$alias}." . $this->toDb($foreignKey));
|
||||
$middleDistantKeyColumn = $this->quoteColumn("{$midAlias}." . $this->toDb($distantKey));
|
||||
|
||||
@@ -954,7 +954,7 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$expectedSql =
|
||||
"SELECT post.id AS `id` FROM `post` " .
|
||||
"LEFT JOIN `post_tag` AS `tagsMiddle` ON post.id = tagsMiddle.post_id AND tagsMiddle.deleted = 0 " .
|
||||
"LEFT JOIN `post_tag` AS `tags` ON post.id = tags.post_id AND tags.deleted = 0 " .
|
||||
"WHERE post.deleted = 0";
|
||||
|
||||
$this->assertEquals($expectedSql, $sql);
|
||||
|
||||
Reference in New Issue
Block a user