2023-06-23 20:37:56 +08:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package internal
2023-07-31 14:28:53 +08:00
import (
2024-07-17 00:49:05 +02:00
"strconv"
2023-07-31 14:28:53 +08:00
"code.gitea.io/gitea/models/db"
2025-03-13 11:07:48 +08:00
"code.gitea.io/gitea/modules/indexer"
2024-03-02 16:42:31 +01:00
"code.gitea.io/gitea/modules/optional"
2023-07-31 14:28:53 +08:00
"code.gitea.io/gitea/modules/timeutil"
)
2023-06-23 20:37:56 +08:00
// IndexerData data stored in the issue indexer
type IndexerData struct {
2023-07-31 14:28:53 +08:00
ID int64 ` json:"id" `
RepoID int64 ` json:"repo_id" `
IsPublic bool ` json:"is_public" ` // If the repo is public
// Fields used for keyword searching
Title string ` json:"title" `
Content string ` json:"content" `
Comments [ ] string ` json:"comments" `
// Fields used for filtering
IsPull bool ` json:"is_pull" `
IsClosed bool ` json:"is_closed" `
2024-12-12 08:33:31 +09:00
IsArchived bool ` json:"is_archived" `
2023-07-31 14:28:53 +08:00
LabelIDs [ ] int64 ` json:"label_ids" `
NoLabel bool ` json:"no_label" ` // True if LabelIDs is empty
MilestoneID int64 ` json:"milestone_id" `
ProjectID int64 ` json:"project_id" `
2024-05-27 16:59:54 +08:00
ProjectColumnID int64 ` json:"project_board_id" ` // the key should be kept as project_board_id to keep compatible
2023-07-31 14:28:53 +08:00
PosterID int64 ` json:"poster_id" `
AssigneeID int64 ` json:"assignee_id" `
MentionIDs [ ] int64 ` json:"mention_ids" `
ReviewedIDs [ ] int64 ` json:"reviewed_ids" `
ReviewRequestedIDs [ ] int64 ` json:"review_requested_ids" `
SubscriberIDs [ ] int64 ` json:"subscriber_ids" `
UpdatedUnix timeutil . TimeStamp ` json:"updated_unix" `
// Fields used for sorting
// UpdatedUnix is both used for filtering and sorting.
// ID is used for sorting too, to make the sorting stable.
CreatedUnix timeutil . TimeStamp ` json:"created_unix" `
DeadlineUnix timeutil . TimeStamp ` json:"deadline_unix" `
CommentCount int64 ` json:"comment_count" `
2023-06-23 20:37:56 +08:00
}
// Match represents on search result
type Match struct {
ID int64 ` json:"id" `
Score float64 ` json:"score" `
}
// SearchResult represents search results
type SearchResult struct {
Total int64
Hits [ ] Match
}
2023-07-31 14:28:53 +08:00
2023-08-16 23:40:13 +08:00
// SearchOptions represents search options.
//
// It has a slightly different design from database query options.
// In database query options, a field is never a pointer, so it could be confusing when it's zero value:
// Do you want to find data with a field value of 0, or do you not specify the field in the options?
// To avoid this confusion, db introduced db.NoConditionID(-1).
// So zero value means the field is not specified in the search options, and db.NoConditionID means "== 0" or "id NOT IN (SELECT id FROM ...)"
// It's still not ideal, it trapped developers many times.
// And sometimes -1 could be a valid value, like issue ID, negative numbers indicate exclusion.
// Since db.NoConditionID is for "db" (the package name is db), it makes sense not to use it in the indexer:
// Why do bleve/elasticsearch/meilisearch indexers need to know about db.NoConditionID?
// So in SearchOptions, we use pointer for fields which could be not specified,
// and always use the value to filter if it's not nil, even if it's zero or negative.
// It can handle almost all cases, if there is an exception, we can add a new field, like NoLabelOnly.
// Unfortunately, we still use db for the indexer and have to convert between db.NoConditionID and nil for legacy reasons.
2023-07-31 14:28:53 +08:00
type SearchOptions struct {
Keyword string // keyword to search
2025-03-13 11:07:48 +08:00
SearchMode indexer . SearchModeType
2024-03-09 02:39:27 +01:00
2023-07-31 14:28:53 +08:00
RepoIDs [ ] int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories
2024-12-12 08:33:31 +09:00
IsPull optional . Option [ bool ] // if the issues is a pull request
IsClosed optional . Option [ bool ] // if the issues is closed
IsArchived optional . Option [ bool ] // if the repo is archived
2023-07-31 14:28:53 +08:00
IncludedLabelIDs [ ] int64 // labels the issues have
ExcludedLabelIDs [ ] int64 // labels the issues don't have
IncludedAnyLabelIDs [ ] int64 // labels the issues have at least one. It will be ignored if IncludedLabelIDs is not empty. It's an uncommon filter, but it has been supported accidentally by issues.IssuesOptions.IncludedLabelNames.
NoLabelOnly bool // if the issues have no label, if true, IncludedLabelIDs and ExcludedLabelIDs, IncludedAnyLabelIDs will be ignored
MilestoneIDs [ ] int64 // milestones the issues have
2024-05-27 16:59:54 +08:00
ProjectID optional . Option [ int64 ] // project the issues belong to
ProjectColumnID optional . Option [ int64 ] // project column the issues belong to
2023-07-31 14:28:53 +08:00
2025-03-21 05:25:36 +01:00
PosterID string // poster of the issues, "(none)" or "(any)" or a user ID
AssigneeID string // assignee of the issues, "(none)" or "(any)" or a user ID
2023-07-31 14:28:53 +08:00
2024-03-13 09:25:53 +01:00
MentionID optional . Option [ int64 ] // mentioned user of the issues
2023-07-31 14:28:53 +08:00
2024-03-13 09:25:53 +01:00
ReviewedID optional . Option [ int64 ] // reviewer of the issues
ReviewRequestedID optional . Option [ int64 ] // requested reviewer of the issues
2023-07-31 14:28:53 +08:00
2024-03-13 09:25:53 +01:00
SubscriberID optional . Option [ int64 ] // subscriber of the issues
2023-07-31 14:28:53 +08:00
2024-03-13 09:25:53 +01:00
UpdatedAfterUnix optional . Option [ int64 ]
UpdatedBeforeUnix optional . Option [ int64 ]
2023-07-31 14:28:53 +08:00
2024-03-25 02:51:08 +08:00
Paginator * db . ListOptions
2023-07-31 14:28:53 +08:00
SortBy SortBy // sort by field
}
2023-08-23 10:29:17 +08:00
// Copy returns a copy of the options.
// Be careful, it's not a deep copy, so `SearchOptions.RepoIDs = {...}` is OK while `SearchOptions.RepoIDs[0] = ...` is not.
func ( o * SearchOptions ) Copy ( edit ... func ( options * SearchOptions ) ) * SearchOptions {
if o == nil {
return nil
}
v := * o
for _ , e := range edit {
e ( & v )
}
return & v
}
2024-07-17 00:49:05 +02:00
// used for optimized issue index based search
func ( o * SearchOptions ) IsKeywordNumeric ( ) bool {
_ , err := strconv . Atoi ( o . Keyword )
return err == nil
}
2023-07-31 14:28:53 +08:00
type SortBy string
const (
SortByCreatedDesc SortBy = "-created_unix"
SortByUpdatedDesc SortBy = "-updated_unix"
SortByCommentsDesc SortBy = "-comment_count"
SortByDeadlineDesc SortBy = "-deadline_unix"
SortByCreatedAsc SortBy = "created_unix"
SortByUpdatedAsc SortBy = "updated_unix"
SortByCommentsAsc SortBy = "comment_count"
SortByDeadlineAsc SortBy = "deadline_unix"
// Unsupported sort types which are supported by issues.IssuesOptions.SortType:
//
// - "priorityrepo":
// It's impossible to support it in the indexer.
// It is based on the specified repository in the request, so we cannot add static field to the indexer.
// If we do something like that query the issues in the specified repository first then append other issues,
// it will break the pagination.
//
// - "project-column-sorting":
// Although it's possible to support it by adding project.ProjectIssue.Sorting to the indexer,
// but what if the issue belongs to multiple projects?
// Since it's unsupported to search issues with keyword in project page, we don't need to support it.
)