From 8790d1ff88e832f3d183f4e345c44219b570c6ec Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 3 Jan 2025 21:21:49 +0700 Subject: [PATCH 1/2] Added support for JOIN queries, updated tests --- src/Manticoresearch/Query/JoinQuery.php | 81 ++++++++++++++++++ src/Manticoresearch/Search.php | 17 ++++ test/Manticoresearch/SearchTest.php | 106 +++++++++++++++++++++++- 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/Manticoresearch/Query/JoinQuery.php diff --git a/src/Manticoresearch/Query/JoinQuery.php b/src/Manticoresearch/Query/JoinQuery.php new file mode 100644 index 00000000..9897a1e2 --- /dev/null +++ b/src/Manticoresearch/Query/JoinQuery.php @@ -0,0 +1,81 @@ +checkJoinOptions($joinType, $joinLeftFieldType, $joinQuery); + + $this->params['type'] = $joinType; + $this->params['table'] = $joinTable; + if ($joinQuery) { + $this->params['query'] = $joinQuery; + } + $joinLeft = [ + 'field' => $joinLeftField, + ]; + if ($joinLeftFieldType) { + $joinLeft['type'] = $joinLeftFieldType; + } + $joinRight = [ + 'field' => $joinRightField, + 'table' => $joinTable, + ]; + + $this->params['on'] = [ + [ + 'left' => $joinLeft, + 'operator' => 'eq', + 'right' => $joinRight, + ], + ]; + } + + protected function checkJoinOptions($joinType, $joinLeftFieldType, $joinQuery) { + if (!in_array($joinType, static::JOIN_TYPES)) { + throw new \RuntimeException("Unknown join type `{$joinType}` passed"); + } + if ($joinLeftFieldType && !in_array($joinLeftFieldType, static::LEFT_FIELD_TYPES)) { + throw new \RuntimeException("Unknown join field type `{$joinLeftFieldType}` passed"); + } + if (!$joinQuery) { + return; + } + if (!in_array(get_class($joinQuery), static::JOIN_QUERY_TYPES)) { + throw new \RuntimeException("`joinQuery` must be a full-text query object"); + } + } + + public function add($k, $v) { + if ($k === 'main_table') { + foreach ($this->params['on'] as &$joinOn) { + $joinOn['left']['table'] = $v; + } + } else { + parent::add($k, $v); + } + } +} diff --git a/src/Manticoresearch/Search.php b/src/Manticoresearch/Search.php index ab1656c3..05863c90 100755 --- a/src/Manticoresearch/Search.php +++ b/src/Manticoresearch/Search.php @@ -37,6 +37,7 @@ class Search protected $client; protected $query; + protected $join; protected $body; /** * @var array @@ -54,6 +55,7 @@ class Search public function __construct(Client $client) { $this->client = $client; $this->query = new BoolQuery(); + $this->join = []; } public function setIndex($index): self { @@ -120,6 +122,14 @@ public function knn($field, $knnTarget, $docCount): self { return $this; } + public function join($joinQuery = null, $clearJoin = false): self { + if ($clearJoin) { + $this->join = []; + } + $this->join[] = $joinQuery; + return $this; + } + public function match($keywords, $fields = null): self { $f = '*'; if ($fields !== null && is_string($fields)) { @@ -385,6 +395,13 @@ public function compile() { $body['query'] = $query; } } + if ($this->join) { + $body['join'] = []; + foreach ($this->join as $join) { + $join->add('main_table', $this->params['index']); + $body['join'][] = $join->toArray(); + } + } if (isset($this->params['script_fields'])) { $body['script_fields'] = $this->params['script_fields']->toArray(); diff --git a/test/Manticoresearch/SearchTest.php b/test/Manticoresearch/SearchTest.php index dc065441..e96645c1 100755 --- a/test/Manticoresearch/SearchTest.php +++ b/test/Manticoresearch/SearchTest.php @@ -13,6 +13,7 @@ use Manticoresearch\Query\Distance; use Manticoresearch\Query\Equals; use Manticoresearch\Query\In; +use Manticoresearch\Query\JoinQuery; use Manticoresearch\Query\MatchQuery; use Manticoresearch\Query\Range; use Manticoresearch\ResultSet; @@ -44,6 +45,67 @@ protected static function indexDocuments(): Search { 'transport' => empty($_SERVER['TRANSPORT']) ? 'Http' : $_SERVER['TRANSPORT'], ]; $client = new Client($params); + + $client->indices()->drop(['index' => 'movie_years','body' => ['silent' => true]]); + $index = [ + 'index' => 'movie_years', + 'body' => [ + 'columns' => ['year_half' => ['type' => 'text'], + 'movie_year' => ['type' => 'integer'], + 'movie_count' => ['type' => 'integer'], + ], + ], + ]; + $client->indices()->create($index); + + $docs = [ + [ + 'insert' => [ + 'index' => 'movie_years', + 'id' => 1, + 'doc' => [ + 'year_half' => 'first half', + 'movie_year' => 2010, + 'movie_count' => 1400, + ], + ], + ], + [ + 'insert' => [ + 'index' => 'movie_years', + 'id' => 2, + 'doc' => [ + 'year_half' => 'first half', + 'movie_year' => 2011, + 'movie_count' => 1700, + ], + ], + ], + [ + 'insert' => [ + 'index' => 'movie_years', + 'id' => 3, + 'doc' => [ + 'year_half' => 'second half', + 'movie_year' => 2010, + 'movie_count' => 1600, + ], + ], + ], + [ + 'insert' => [ + 'index' => 'movie_years', + 'id' => 4, + 'doc' => [ + 'year_half' => 'second half', + 'movie_year' => 2011, + 'movie_count' => 1200, + ], + ], + ], + ]; + $client->bulk(['body' => $docs]); + $client->indices()->drop(['index' => 'movies','body' => ['silent' => true]]); $index = [ 'index' => 'movies', @@ -68,8 +130,8 @@ protected static function indexDocuments(): Search { ], ], ]; - $client->indices()->create($index); + $docs = [ ['insert' => ['index' => 'movies', 'id' => 2, 'doc' => ['title' => 'Interstellar', @@ -916,4 +978,46 @@ public function testKnnSearchByQueryVectorWithFilter() { } } + public function testJoinSearchWithLeftJoin() { + $join = new JoinQuery('left', 'movie_years', '_year', 'movie_year'); + $results = static::$search->join($join)->get(); + print_r($results); + $this->assertCount(7, $results); + $resultIds = [2,3,3,4,5,6,10]; + foreach ($results as $i => $resultHit) { + $this->assertEquals($resultIds[$i], $resultHit->getId()); + } + } + + public function testJoinSearchWithInnerJoin() { + $join = new JoinQuery('inner', 'movie_years', '_year', 'movie_year'); + $results = static::$search->join()->join($join, true)->get(); + $this->assertCount(2, $results); + $resultIds = [3,3]; + foreach ($results as $i => $resultHit) { + $this->assertEquals($resultIds[$i], $resultHit->getId()); + } + } + + public function testJoinSearchWithMainTableQuery() { + $join = new JoinQuery('left', 'movie_years', '_year', 'movie_year'); + $results = static::$search->match(['query' => 'dream-sharing technology', 'operator' => 'and']) + ->join($join, true)->get(); + $this->assertCount(2, $results); + $resultIds = [3,3]; + foreach ($results as $i => $resultHit) { + $this->assertEquals($resultIds[$i], $resultHit->getId()); + } + } + + public function testJoinSearchWithJoinedTableQuery() { + $joinQuery = new MatchQuery(['query' => 'First half', 'operator' => 'and'], '*'); + $join = new JoinQuery('left', 'movie_years', '_year', 'movie_year', '', $joinQuery); + $results = static::$search->join($join, true)->get(); + $this->assertCount(1, $results); + $resultIds = [3]; + foreach ($results as $i => $resultHit) { + $this->assertEquals($resultIds[$i], $resultHit->getId()); + } + } } From d32d38a44b723ebaef2b68ef2f9e783fe87cc3dd Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 3 Jan 2025 21:32:24 +0700 Subject: [PATCH 2/2] Fixed codestyle --- src/Manticoresearch/Query/JoinQuery.php | 11 +++++++---- test/Manticoresearch/SearchTest.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Manticoresearch/Query/JoinQuery.php b/src/Manticoresearch/Query/JoinQuery.php index 9897a1e2..24c2de25 100644 --- a/src/Manticoresearch/Query/JoinQuery.php +++ b/src/Manticoresearch/Query/JoinQuery.php @@ -16,7 +16,7 @@ class JoinQuery extends Query const JOIN_QUERY_TYPES = [ 'Manticoresearch\Query\MatchPhrase', 'Manticoresearch\Query\MatchQuery', - 'Manticoresearch\Query\QueryString' + 'Manticoresearch\Query\QueryString', ]; public function __construct( @@ -44,7 +44,7 @@ public function __construct( 'field' => $joinRightField, 'table' => $joinTable, ]; - + $this->params['on'] = [ [ 'left' => $joinLeft, @@ -64,9 +64,12 @@ protected function checkJoinOptions($joinType, $joinLeftFieldType, $joinQuery) { if (!$joinQuery) { return; } - if (!in_array(get_class($joinQuery), static::JOIN_QUERY_TYPES)) { - throw new \RuntimeException("`joinQuery` must be a full-text query object"); + foreach (static::JOIN_QUERY_TYPES as $queryType) { + if (is_a($joinQuery, $queryType)) { + return; + } } + throw new \RuntimeException('`joinQuery` must be a full-text query object'); } public function add($k, $v) { diff --git a/test/Manticoresearch/SearchTest.php b/test/Manticoresearch/SearchTest.php index e96645c1..a29f383f 100755 --- a/test/Manticoresearch/SearchTest.php +++ b/test/Manticoresearch/SearchTest.php @@ -998,7 +998,7 @@ public function testJoinSearchWithInnerJoin() { $this->assertEquals($resultIds[$i], $resultHit->getId()); } } - + public function testJoinSearchWithMainTableQuery() { $join = new JoinQuery('left', 'movie_years', '_year', 'movie_year'); $results = static::$search->match(['query' => 'dream-sharing technology', 'operator' => 'and'])