Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for JOIN queries, updated tests #229

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/Manticoresearch/Query/JoinQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

// Copyright (c) Manticore Software LTD (https://manticoresearch.com)
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

namespace Manticoresearch\Query;

use Manticoresearch\Query;

class JoinQuery extends Query
{
const JOIN_TYPES = ['inner', 'left'];
const LEFT_FIELD_TYPES = ['int', 'string'];
const JOIN_QUERY_TYPES = [
'Manticoresearch\Query\MatchPhrase',
'Manticoresearch\Query\MatchQuery',
'Manticoresearch\Query\QueryString',
];

public function __construct(
$joinType,
$joinTable,
$joinLeftField,
$joinRightField,
$joinLeftFieldType = '',
$joinQuery = ''
) {
$this->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;
}
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) {
if ($k === 'main_table') {
foreach ($this->params['on'] as &$joinOn) {
$joinOn['left']['table'] = $v;
}
} else {
parent::add($k, $v);
}
}
}
17 changes: 17 additions & 0 deletions src/Manticoresearch/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Search
protected $client;

protected $query;
protected $join;
protected $body;
/**
* @var array
Expand All @@ -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 {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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();
Expand Down
106 changes: 105 additions & 1 deletion test/Manticoresearch/SearchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand All @@ -68,8 +130,8 @@ protected static function indexDocuments(): Search {
],
],
];

$client->indices()->create($index);

$docs = [
['insert' => ['index' => 'movies', 'id' => 2, 'doc' =>
['title' => 'Interstellar',
Expand Down Expand Up @@ -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());
}
}
}
Loading