Skip to content

Commit

Permalink
Refactor search methods
Browse files Browse the repository at this point in the history
  • Loading branch information
yurabakhtin committed Apr 1, 2024
1 parent d7f759e commit 24ed4ee
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public function actionResults()
{
$resultSet = null;

$this->searchRequest = new SearchRequest(['pageSize' => 3, 'cachePageNumber' => 10]);
$this->searchRequest = new SearchRequest(['pageSize' => 3]);
if ($this->searchRequest->load(Yii::$app->request->get(), '') && $this->searchRequest->validate()) {
$resultSet = $this->module->getSearchDriver()->search($this->searchRequest);
$resultSet = $this->module->getSearchDriver()->searchCached($this->searchRequest, 10);
}

$page = $resultSet ? $resultSet->pagination->getPage() + 1 : 1;
Expand Down
5 changes: 0 additions & 5 deletions protected/humhub/modules/content/search/SearchRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ class SearchRequest extends Model

public int $pageSize = 25;

/**
* @var int Number of pages that should be cached, 0 - don't cache
*/
public int $cachePageNumber = 0;

public $contentType = '';

public ?string $dateFrom = null;
Expand Down
46 changes: 21 additions & 25 deletions protected/humhub/modules/content/search/driver/AbstractDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,53 @@

abstract class AbstractDriver extends Component
{
public ?SearchRequest $request = null;

abstract public function purge(): void;

abstract public function update(Content $content): void;

abstract public function delete(Content $content): void;

/**
* Run search process, result may be cached
* Run search process
*
* // Add private content, which is in Space content containers where the user is member of
* // Add private content, of User content containers where the user is friend or self
*
* // Add all public content
*
* @param SearchRequest $request
* @return ResultSet
*/
abstract public function runSearch(): ResultSet;
abstract public function search(SearchRequest $request): ResultSet;

/**
* Run search process and cache results
*
* @param SearchRequest $request
* @param int Number of pages that should be cached, 0 - don't cache
* @return ResultSet
*/
public function search(SearchRequest $request): ResultSet
public function searchCached(SearchRequest $request, int $cachePageNumber = 0): ResultSet
{
$this->request = $request;

if ($this->request->cachePageNumber < 1) {
if ($cachePageNumber < 1) {
// Search results without caching
return $this->runSearch();
return $this->search($request);
}

// Store original pagination
$origPage = $this->request->page - 1;
$origPageSize = $this->request->pageSize;
$origPage = $request->page - 1;
$origPageSize = $request->pageSize;

// Set pagination to load results from DB or Cache with bigger portion than original page size
$cachePageSize = $origPageSize * $this->request->cachePageNumber;
$cachePage = (int) ceil(($this->request->page * $origPageSize) / $cachePageSize);
$this->request->page = $cachePage;
$this->request->pageSize = $cachePageSize;
$cachePageSize = $origPageSize * $cachePageNumber;
$cachePage = (int) ceil(($request->page * $origPageSize) / $cachePageSize);
$request->page = $cachePage;
$request->pageSize = $cachePageSize;

/* @var ResultSet $resultSet */
// Load results from cache or Search & Cache
$resultSet = Yii::$app->cache->getOrSet($this->getSearchCacheKey(), [$this, 'runSearch']);
$resultSet = Yii::$app->cache->getOrSet($this->getSearchCacheKey($request), function () use ($request) {
return $this->search($request);
});

// Extract part of results only for the current(original requested) page
$slicePageStart = ($origPage - ($cachePageSize * ($cachePage - 1)) / $origPageSize) * $origPageSize;
Expand All @@ -70,15 +70,11 @@ public function search(SearchRequest $request): ResultSet
return $resultSet;
}

protected function getSearchCacheKey(): string
protected function getSearchCacheKey(SearchRequest $request): string
{
if ($this->request instanceof Model) {
$requestFilters = array_filter($this->request->getAttributes(), function ($value) {
return is_scalar($value) || is_array($value);
});
} else {
$requestFilters = [];
}
$requestFilters = array_filter($request->getAttributes(), function ($value) {
return is_scalar($value) || is_array($value);
});

return static::class . Yii::$app->user->id . sha1(json_encode($requestFilters));
}
Expand Down
37 changes: 20 additions & 17 deletions protected/humhub/modules/content/search/driver/MysqlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,48 +56,51 @@ public function delete(Content $content): void
ContentFulltext::deleteAll(['content_id' => $content->id]);
}

public function runSearch(): ResultSet
/**
* @inheritdoc
*/
public function search(SearchRequest $request): ResultSet
{
$query = Content::find();
$query->leftJoin('content_fulltext', 'content_fulltext.content_id=content.id');
$query->andWhere('content_fulltext.content_id IS NOT NULL');

$fullTextQuery = $this->createMysqlFullTextQuery($this->request->getSearchQuery(), [
$fullTextQuery = $this->createMysqlFullTextQuery($request->getSearchQuery(), [
'content_fulltext.contents', 'content_fulltext.comments', 'content_fulltext.files'
]);

$query->addSelect(['content.*', $fullTextQuery . ' as score']);
$query->andWhere($fullTextQuery);

if (!empty($this->request->contentType)) {
$query->andWhere(['content.object_model' => $this->request->contentType]);
if (!empty($request->contentType)) {
$query->andWhere(['content.object_model' => $request->contentType]);
}

if (!empty($this->request->dateFrom)) {
$query->andWhere(['>=', 'content.created_at', $this->request->dateFrom . ' 00:00:00']);
if (!empty($request->dateFrom)) {
$query->andWhere(['>=', 'content.created_at', $request->dateFrom . ' 00:00:00']);
}
if (!empty($this->request->dateTo)) {
$query->andWhere(['<=', 'content.created_at', $this->request->dateTo . ' 23:59:59']);
if (!empty($request->dateTo)) {
$query->andWhere(['<=', 'content.created_at', $request->dateTo . ' 23:59:59']);
}

if (!empty($this->request->topic)) {
if (!empty($request->topic)) {
$query->leftJoin('content_tag_relation', 'content_tag_relation.content_id = content.id')
->andWhere(['IN', 'content_tag_relation.tag_id', $this->request->topic]);
->andWhere(['IN', 'content_tag_relation.tag_id', $request->topic]);
}

if (!empty($this->request->author)) {
if (!empty($request->author)) {
$query->leftJoin('user', 'user.id = content.created_by')
->andWhere(['IN', 'user.guid', $this->request->author]);
->andWhere(['IN', 'user.guid', $request->author]);
}

if (!empty($this->request->space)) {
if (!empty($request->space)) {
$query->andWhere(['contentcontainer.class' => Space::class])
->andWhere(['IN', 'contentcontainer.guid', $this->request->space]);
->andWhere(['IN', 'contentcontainer.guid', $request->space]);
}

$this->addQueryFilterVisibility($query);

if ($this->request->orderBy === SearchRequest::ORDER_BY_CREATION_DATE) {
if ($request->orderBy === SearchRequest::ORDER_BY_CREATION_DATE) {
$query->orderBy(['content.created_at' => SORT_DESC]);
} else {
$query->orderBy(['score' => SORT_DESC]);
Expand All @@ -106,8 +109,8 @@ public function runSearch(): ResultSet
$resultSet = new ResultSet();
$resultSet->pagination = new Pagination();
$resultSet->pagination->totalCount = $query->count();
$resultSet->pagination->pageSize = $this->request->pageSize;
$resultSet->pagination->setPage($this->request->page - 1, true);
$resultSet->pagination->pageSize = $request->pageSize;
$resultSet->pagination->setPage($request->page - 1, true);

$query->offset($resultSet->pagination->offset)->limit($resultSet->pagination->limit);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ public function delete(Content $content): void
$this->commit();
}

public function runSearch(): ResultSet
/**
* @inheritdoc
*/
public function search(SearchRequest $request): ResultSet
{
$query = $this->buildSearchQuery();
$query = $this->buildSearchQuery($request);

if ($this->request->orderBy === SearchRequest::ORDER_BY_CREATION_DATE) {
if ($request->orderBy === SearchRequest::ORDER_BY_CREATION_DATE) {
$hits = new ArrayObject($this->getIndex()->find($query, 'created_at', SORT_DESC));
} else {
$hits = new ArrayObject($this->getIndex()->find($query));
Expand All @@ -107,8 +110,8 @@ public function runSearch(): ResultSet
$resultSet = new ResultSet();
$resultSet->pagination = new Pagination();
$resultSet->pagination->totalCount = count($hits);
$resultSet->pagination->pageSize = $this->request->pageSize;
$resultSet->pagination->setPage($this->request->page - 1, true);
$resultSet->pagination->pageSize = $request->pageSize;
$resultSet->pagination->setPage($request->page - 1, true);

$hits = new \LimitIterator(
$hits->getIterator(),
Expand Down Expand Up @@ -136,65 +139,65 @@ public function runSearch(): ResultSet
return $resultSet;
}

protected function buildSearchQuery(): Boolean
protected function buildSearchQuery(SearchRequest $request): Boolean
{
$query = new Boolean();

Wildcard::setMinPrefixLength(0);

$keywordQuery = new Boolean();
foreach ($this->request->getSearchQuery()->orTerms as $term) {
foreach ($request->getSearchQuery()->orTerms as $term) {
$keywordQuery->addSubquery(new Wildcard(new Term(mb_strtolower($term) . '*')), null);
}

foreach ($this->request->getSearchQuery()->andTerms as $term) {
foreach ($request->getSearchQuery()->andTerms as $term) {
$keywordQuery->addSubquery(new Wildcard(new Term(mb_strtolower($term) . '*')), true);
}

foreach ($this->request->getSearchQuery()->notTerms as $term) {
foreach ($request->getSearchQuery()->notTerms as $term) {
$keywordQuery->addSubquery(new TermQuery(new Term(mb_strtolower($term))), false);
}

if (count($keywordQuery->getSubqueries())) {
$query->addSubquery($keywordQuery, true);
}

if (!empty($this->request->dateFrom) || !empty($this->request->dateTo)) {
$dateFrom = $this->convertRangeValue('created_at', $this->request->dateFrom, ' 00:00:00');
$dateTo = $this->convertRangeValue('created_at', $this->request->dateTo, ' 23:59:59');
if (!empty($request->dateFrom) || !empty($request->dateTo)) {
$dateFrom = $this->convertRangeValue('created_at', $request->dateFrom, ' 00:00:00');
$dateTo = $this->convertRangeValue('created_at', $request->dateTo, ' 23:59:59');
$query->addSubquery(new Range($dateFrom, $dateTo, true), true);
}

if (!empty($this->request->topic)) {
if (!empty($request->topic)) {
$subQuery = new Boolean();
foreach ($this->request->topic as $topic) {
foreach ($request->topic as $topic) {
$subQuery->addSubquery(new Wildcard(new Term('*-' . $topic . '-*', 'tags')));
}
$query->addSubquery($subQuery, true);
}

if ($this->request->author) {
if ($request->author) {
$authors = [];
$signs = [];
foreach ($this->request->author as $author) {
foreach ($request->author as $author) {
$authors[] = new Term($author, 'created_by');
$signs[] = null;
}
$query->addSubquery(new MultiTerm($authors, $signs), true);
}

if ($this->request->space) {
if ($request->space) {
$spaces = [];
$signs = [];
foreach ($this->request->space as $space) {
foreach ($request->space as $space) {
$spaces[] = new Term($space, 'space');
$signs[] = null;
}
$query->addSubquery(new MultiTerm($spaces, $signs), true);
}

if (!empty($this->request->contentType)) {
$query->addSubquery(new TermQuery(new Term($this->request->contentType, 'class')), true);
if (!empty($request->contentType)) {
$query->addSubquery(new TermQuery(new Term($request->contentType, 'class')), true);
}

return $query;
Expand Down

0 comments on commit 24ed4ee

Please sign in to comment.