| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package db
-
- import (
- "context"
- "strings"
- "sync"
-
- "code.gitea.io/gitea/models/db"
- issue_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/indexer"
- indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
- inner_db "code.gitea.io/gitea/modules/indexer/internal/db"
- "code.gitea.io/gitea/modules/indexer/issues/internal"
- "code.gitea.io/gitea/modules/util"
-
- "xorm.io/builder"
- )
-
- var _ internal.Indexer = (*Indexer)(nil)
-
- // Indexer implements Indexer interface to use database's like search
- type Indexer struct {
- indexer_internal.Indexer
- }
-
- func (i *Indexer) SupportedSearchModes() []indexer.SearchMode {
- return indexer.SearchModesExactWords()
- }
-
- var GetIndexer = sync.OnceValue(func() *Indexer {
- return &Indexer{Indexer: &inner_db.Indexer{}}
- })
-
- // Index dummy function
- func (i *Indexer) Index(_ context.Context, _ ...*internal.IndexerData) error {
- return nil
- }
-
- // Delete dummy function
- func (i *Indexer) Delete(_ context.Context, _ ...int64) error {
- return nil
- }
-
- func buildMatchQuery(mode indexer.SearchModeType, colName, keyword string) builder.Cond {
- if mode == indexer.SearchModeExact {
- return db.BuildCaseInsensitiveLike(colName, keyword)
- }
-
- // match words
- cond := builder.NewCond()
- fields := strings.Fields(keyword)
- if len(fields) == 0 {
- return builder.Expr("1=1")
- }
- for _, field := range fields {
- if field == "" {
- continue
- }
- cond = cond.And(db.BuildCaseInsensitiveLike(colName, field))
- }
- return cond
- }
-
- // Search searches for issues
- func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (*internal.SearchResult, error) {
- // FIXME: I tried to avoid importing models here, but it seems to be impossible.
- // We can provide a function to register the search function, so models/issues can register it.
- // So models/issues will import modules/indexer/issues, it's OK because it's by design.
- // But modules/indexer/issues has already imported models/issues to do UpdateRepoIndexer and UpdateIssueIndexer.
- // And to avoid circular import, we have to move the functions to another package.
- // I believe it should be services/indexer, sounds great!
- // But the two functions are used in modules/notification/indexer, that means we will import services/indexer in modules/notification/indexer.
- // So that's the root problem:
- // The notification is defined in modules, but it's using lots of things should be in services.
-
- cond := builder.NewCond()
-
- if options.Keyword != "" {
- repoCond := builder.In("repo_id", options.RepoIDs)
- if len(options.RepoIDs) == 1 {
- repoCond = builder.Eq{"repo_id": options.RepoIDs[0]}
- }
- subQuery := builder.Select("id").From("issue").Where(repoCond)
- searchMode := util.IfZero(options.SearchMode, i.SupportedSearchModes()[0].ModeValue)
- cond = builder.Or(
- buildMatchQuery(searchMode, "issue.name", options.Keyword),
- buildMatchQuery(searchMode, "issue.content", options.Keyword),
- builder.In("issue.id", builder.Select("issue_id").
- From("comment").
- Where(builder.And(
- builder.Eq{"type": issue_model.CommentTypeComment},
- builder.In("issue_id", subQuery),
- buildMatchQuery(searchMode, "content", options.Keyword),
- )),
- ),
- )
-
- if options.IsKeywordNumeric() {
- cond = cond.Or(
- builder.Eq{"`index`": options.Keyword},
- )
- }
- }
-
- opt, err := ToDBOptions(ctx, options)
- if err != nil {
- return nil, err
- }
-
- // If pagesize == 0, return total count only. It's a special case for search count.
- if options.Paginator != nil && options.Paginator.PageSize == 0 {
- total, err := issue_model.CountIssues(ctx, opt, cond)
- if err != nil {
- return nil, err
- }
- return &internal.SearchResult{
- Total: total,
- }, nil
- }
-
- return i.FindWithIssueOptions(ctx, opt, cond)
- }
-
- func (i *Indexer) FindWithIssueOptions(ctx context.Context, opt *issue_model.IssuesOptions, otherConds ...builder.Cond) (*internal.SearchResult, error) {
- ids, total, err := issue_model.IssueIDs(ctx, opt, otherConds...)
- if err != nil {
- return nil, err
- }
-
- hits := make([]internal.Match, 0, len(ids))
- for _, id := range ids {
- hits = append(hits, internal.Match{
- ID: id,
- })
- }
- return &internal.SearchResult{
- Total: total,
- Hits: hits,
- }, nil
- }
|