diff --git a/application/Espo/ORM/Query/SelectingBuilderTrait.php b/application/Espo/ORM/Query/SelectingBuilderTrait.php index 819ea9d92f..9807d61c84 100644 --- a/application/Espo/ORM/Query/SelectingBuilderTrait.php +++ b/application/Espo/ORM/Query/SelectingBuilderTrait.php @@ -185,6 +185,8 @@ trait SelectingBuilderTrait * 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|null $conditions Join conditions. + * + * @todo Support USE INDEX in Join. */ private function joinInternal(string $type, $target, ?string $alias = null, $conditions = null): self { @@ -201,8 +203,12 @@ trait SelectingBuilderTrait throw new InvalidArgumentException("Conditions must be WhereItem or array."); } + $noLeftAlias = false; + if ($conditions instanceof WhereItem) { $conditions = $conditions->getRaw(); + + $noLeftAlias = true; } if (empty($this->params[$type])) { @@ -235,7 +241,13 @@ trait SelectingBuilderTrait return $this; } - $this->params[$type][] = [$target, $alias, $conditions]; + $item = [$target, $alias, $conditions]; + + if ($noLeftAlias) { + $item[] = ['noLeftAlias' => true]; + } + + $this->params[$type][] = $item; return $this; } diff --git a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php index 57c1d87ae4..f598b24dff 100644 --- a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php @@ -3013,7 +3013,8 @@ abstract class BaseQueryComposer implements QueryComposer string $alias, $left, $right, - array $params + array $params, + bool $noLeftAlias = false ): string { $sql = ''; @@ -3028,7 +3029,7 @@ abstract class BaseQueryComposer implements QueryComposer $sqlList = []; foreach ($right as $k => $v) { - $sqlList[] = $this->buildJoinConditionStatement($entity, $alias, $k, $v, $params); + $sqlList[] = $this->buildJoinConditionStatement($entity, $alias, $k, $v, $params, $noLeftAlias); } $sql = implode(' ' .$logicalOperator . ' ', $sqlList); @@ -3071,16 +3072,20 @@ abstract class BaseQueryComposer implements QueryComposer if (!$isComplex) { if (strpos($left, '.') > 0) { - list($alias, $attribute) = explode('.', $left); + list($leftAlias, $attribute) = explode('.', $left); - $alias = $this->sanitize($alias); + $leftAlias = $this->sanitize($leftAlias); $column = $this->toDb($this->sanitize($attribute)); } else { $column = $this->toDb($this->sanitize($left)); + + $leftAlias = $noLeftAlias ? + $this->getFromAlias($params, $entity->getEntityType()) : + $this->sanitize($alias); } - $sql .= "{$alias}.{$column}"; + $sql .= "{$leftAlias}.{$column}"; } if (is_array($right)) { @@ -3161,7 +3166,7 @@ abstract class BaseQueryComposer implements QueryComposer $table = $this->toDb($this->sanitize($name)); - $sql = $prefix . "JOIN " . $this->quoteIdentifier($table) . " AS " . $this->quoteIdentifier($alias) . ""; + $sql = $prefix . "JOIN " . $this->quoteIdentifier($table) . " AS " . $this->quoteIdentifier($alias); if (empty($conditions)) { return $sql; @@ -3177,7 +3182,8 @@ abstract class BaseQueryComposer implements QueryComposer $alias, $left, $right, - $params + $params, + $joinParams['noLeftAlias'] ?? false, ); } diff --git a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php index b30e5acfab..dfb84c8f50 100644 --- a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php +++ b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php @@ -855,6 +855,60 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } + public function testJoinConditions6(): void + { + $query = $this->queryBuilder + ->select('id') + ->from('Note') + ->leftJoin( + 'Post', + 'post', + Condition::equal( + Expression::column('id'), + Expression::column('post.id') + ) + ) + ->withDeleted() + ->build(); + + $sql = $this->query->composeSelect($query); + + $expectedSql = "SELECT note.id AS `id` FROM `note` LEFT JOIN `post` AS `post` ON note.id = post.id"; + + $this->assertEquals($expectedSql, $sql); + } + + public function testJoinConditions7(): void + { + $query = $this->queryBuilder + ->select('id') + ->from('Note') + ->leftJoin( + 'Post', + 'post', + Condition::or( + Condition::equal( + Expression::column('id'), + Expression::column('post.id') + ), + Condition::equal( + Expression::column('post.id'), + Expression::column('id') + ) + ) + ) + ->withDeleted() + ->build(); + + $sql = $this->query->composeSelect($query); + + $expectedSql = + "SELECT note.id AS `id` FROM `note` ". + "LEFT JOIN `post` AS `post` ON (note.id = post.id OR post.id = note.id)"; + + $this->assertEquals($expectedSql, $sql); + } + public function testJoinTable1() { $sql = $this->query->compose(Select::fromRaw([ diff --git a/tests/unit/Espo/ORM/Query/SelectBuilderTest.php b/tests/unit/Espo/ORM/Query/SelectBuilderTest.php index 5d6897b6cf..19e72888d5 100644 --- a/tests/unit/Espo/ORM/Query/SelectBuilderTest.php +++ b/tests/unit/Espo/ORM/Query/SelectBuilderTest.php @@ -616,7 +616,8 @@ class SelectBuilderTest extends \PHPUnit\Framework\TestCase 'table1', [ 'table1.testId=:' => 'id' - ] + ], + ['noLeftAlias' => true] ] ]; @@ -626,7 +627,8 @@ class SelectBuilderTest extends \PHPUnit\Framework\TestCase 'table2', [ 'table2.testId=:' => 'id' - ] + ], + ['noLeftAlias' => true] ] ]; diff --git a/tests/unit/Espo/ORM/Repository/RDBRepositoryTest.php b/tests/unit/Espo/ORM/Repository/RDBRepositoryTest.php index c465726316..5b3f2917fc 100644 --- a/tests/unit/Espo/ORM/Repository/RDBRepositoryTest.php +++ b/tests/unit/Espo/ORM/Repository/RDBRepositoryTest.php @@ -511,12 +511,11 @@ class RDBRepositoryTest extends \PHPUnit\Framework\TestCase public function testJoin5() { - $paramsExpected = Select::fromRaw([ - 'from' => 'Test', - 'joins' => [ - ['Test1', 'test1', ['k=' => 'v']], - ], - ]); + $paramsExpected = $this->queryBuilder + ->select() + ->from('Test') + ->join('Test1', 'test1', Cond::equal(Expr::column('k'), 'v')) + ->build(); $this->mapper ->expects($this->once()) @@ -549,12 +548,11 @@ class RDBRepositoryTest extends \PHPUnit\Framework\TestCase public function testLeftJoin2() { - $paramsExpected = Select::fromRaw([ - 'from' => 'Test', - 'leftJoins' => [ - ['Test1', 'test1', ['k=' => 'v']], - ], - ]); + $paramsExpected = $this->queryBuilder + ->select() + ->from('Test') + ->leftJoin('Test1', 'test1', Cond::equal(Expr::column('k'), 'v')) + ->build(); $this->mapper ->expects($this->once()) @@ -1434,7 +1432,7 @@ class RDBRepositoryTest extends \PHPUnit\Framework\TestCase ->select(['id']) ->distinct() ->where(Cond::equal(Expr::column('name'), 'test')) - ->join('Test', 'test', ['id=:' => 'id']) + ->join('Test', 'test', Cond::equal(Expr::column('test.id'), Expr::column('id'))) ->order('id', 'DESC') ->build(); @@ -1451,7 +1449,7 @@ class RDBRepositoryTest extends \PHPUnit\Framework\TestCase ->join( 'Test', 'test', - Cond::equal(Expr::column('id'), Expr::column('id')) + Cond::equal(Expr::column('test.id'), Expr::column('id')) ) ->order('id', 'DESC') ->find();