diff --git a/application/Espo/ORM/DB/Query/Base.php b/application/Espo/ORM/DB/Query/Base.php index 3acfa27c1c..461e32d677 100644 --- a/application/Espo/ORM/DB/Query/Base.php +++ b/application/Espo/ORM/DB/Query/Base.php @@ -107,6 +107,14 @@ abstract class Base 'LENGTH' ]; + protected $matchFunctionList = ['MATCH_BOOLEAN', 'MATCH_NATURAL_LANGUAGE', 'MATCH_QUERY_EXPANSION']; + + protected $matchFunctionMap = [ + 'MATCH_BOOLEAN' => 'IN BOOLEAN MODE', + 'MATCH_NATURAL_LANGUAGE' => 'IN NATURAL LANGUAGE MODE', + 'MATCH_QUERY_EXPANSION' => 'WITH QUERY EXPANSION' + ]; + protected $entityFactory; protected $pdo; @@ -308,6 +316,43 @@ abstract class Base return $function . '(' . $part . ')'; } + protected function convertMatchExpression($entity, $expression) + { + $delimiterPosition = strpos($expression, ':'); + if ($delimiterPosition === false) { + throw new \Exception("Bad MATCH usage."); + } + + $function = substr($expression, 0, $delimiterPosition); + $rest = substr($expression, $delimiterPosition + 1); + + if (empty($rest)) { + throw new \Exception("Empty MATCH parameters."); + } + + $delimiterPosition = strpos($rest, ':'); + if ($delimiterPosition === false) { + throw new \Exception("Bad MATCH usage."); + } + + $columns = substr($rest, 0, $delimiterPosition); + $query = substr($rest, $delimiterPosition + 1); + + $columnList = explode(',', $columns); + + foreach ($columnList as $i => $column) { + $columnList[$i] = $this->sanitize($column); + } + + $query = $this->quote($query); + + if (!in_array($function, $this->matchFunctionList)) return; + $modePart = ' ' . $this->matchFunctionMap[$function]; + + $result = "MATCH (" . implode(',', $columnList) . ") AGAINST (" . $query . "" . $modePart . ")"; + + return $result; + } protected function convertComplexExpression($entity, $field, $distinct = false) { @@ -317,7 +362,14 @@ abstract class Base $entityType = $entity->getEntityType(); if (strpos($field, ':')) { - list($function, $field) = explode(':', $field); + $dilimeterPosition = strpos($field, ':'); + $function = substr($field, 0, $dilimeterPosition); + + if (in_array($function, $this->matchFunctionList)) { + return $this->convertMatchExpression($entity, $field); + } + + $field = substr($field, $dilimeterPosition + 1); } if (!empty($function)) { $function = preg_replace('/[^A-Za-z0-9_]+/', '', $function); @@ -724,6 +776,13 @@ abstract class Base foreach ($whereClause as $field => $value) { if (is_int($field)) { + if (is_string($value)) { + if (strpos($value, 'MATCH_') === 0) { + $rightPart = $this->convertMatchExpression($entity, $value); + $whereParts[] = $rightPart; + continue; + } + } $field = 'AND'; } diff --git a/tests/unit/Espo/ORM/DB/QueryTest.php b/tests/unit/Espo/ORM/DB/QueryTest.php index 6b5baaf48f..3fccbabcf6 100644 --- a/tests/unit/Espo/ORM/DB/QueryTest.php +++ b/tests/unit/Espo/ORM/DB/QueryTest.php @@ -442,4 +442,57 @@ class QueryTest extends \PHPUnit\Framework\TestCase "HAVING COUNT(comment.id) > '1'"; $this->assertEquals($expectedSql, $sql); } + + public function testMatch1() + { + $sql = $this->query->createSelectQuery('Article', array( + 'select' => ['id', 'name'], + 'whereClause' => [ + 'MATCH_BOOLEAN:name,description:test +hello', + 'id!=' => null + ] + )); + + $expectedSql = + "SELECT article.id AS `id`, article.name AS `name` FROM `article` " . + "WHERE MATCH (name,description) AGAINST ('test +hello' IN BOOLEAN MODE) AND article.id IS NOT NULL AND article.deleted = '0'"; + + $this->assertEquals($expectedSql, $sql); + } + + public function testMatch2() + { + $sql = $this->query->createSelectQuery('Article', array( + 'select' => ['id', 'name'], + 'whereClause' => [ + 'MATCH_NATURAL_LANGUAGE:description:"test hello"' + ] + )); + + $expectedSql = + "SELECT article.id AS `id`, article.name AS `name` FROM `article` " . + "WHERE MATCH (description) AGAINST ('\"test hello\"' IN NATURAL LANGUAGE MODE) AND article.deleted = '0'"; + + $this->assertEquals($expectedSql, $sql); + } + + public function testMatch3() + { + $sql = $this->query->createSelectQuery('Article', array( + 'select' => ['id', 'MATCH_BOOLEAN:description:test'], + 'whereClause' => [ + 'MATCH_BOOLEAN:description:test' + ], + 'orderBy' => [ + [2, 'DESC'] + ] + )); + + $expectedSql = + "SELECT article.id AS `id`, MATCH (description) AGAINST ('test' IN BOOLEAN MODE) AS `MATCH_BOOLEAN:description:test` FROM `article` " . + "WHERE MATCH (description) AGAINST ('test' IN BOOLEAN MODE) AND article.deleted = '0' " . + "ORDER BY 2 DESC"; + + $this->assertEquals($expectedSql, $sql); + } } diff --git a/tests/unit/testData/DB/Entities.php b/tests/unit/testData/DB/Entities.php index 6b1e7d0a4e..93117edf1c 100644 --- a/tests/unit/testData/DB/Entities.php +++ b/tests/unit/testData/DB/Entities.php @@ -242,5 +242,3 @@ class Article extends TEntity ) ); } - -