gitea源码

db.go 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package db
  4. import (
  5. "context"
  6. "strings"
  7. "sync"
  8. "code.gitea.io/gitea/models/db"
  9. issue_model "code.gitea.io/gitea/models/issues"
  10. "code.gitea.io/gitea/modules/indexer"
  11. indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
  12. inner_db "code.gitea.io/gitea/modules/indexer/internal/db"
  13. "code.gitea.io/gitea/modules/indexer/issues/internal"
  14. "code.gitea.io/gitea/modules/util"
  15. "xorm.io/builder"
  16. )
  17. var _ internal.Indexer = (*Indexer)(nil)
  18. // Indexer implements Indexer interface to use database's like search
  19. type Indexer struct {
  20. indexer_internal.Indexer
  21. }
  22. func (i *Indexer) SupportedSearchModes() []indexer.SearchMode {
  23. return indexer.SearchModesExactWords()
  24. }
  25. var GetIndexer = sync.OnceValue(func() *Indexer {
  26. return &Indexer{Indexer: &inner_db.Indexer{}}
  27. })
  28. // Index dummy function
  29. func (i *Indexer) Index(_ context.Context, _ ...*internal.IndexerData) error {
  30. return nil
  31. }
  32. // Delete dummy function
  33. func (i *Indexer) Delete(_ context.Context, _ ...int64) error {
  34. return nil
  35. }
  36. func buildMatchQuery(mode indexer.SearchModeType, colName, keyword string) builder.Cond {
  37. if mode == indexer.SearchModeExact {
  38. return db.BuildCaseInsensitiveLike(colName, keyword)
  39. }
  40. // match words
  41. cond := builder.NewCond()
  42. fields := strings.Fields(keyword)
  43. if len(fields) == 0 {
  44. return builder.Expr("1=1")
  45. }
  46. for _, field := range fields {
  47. if field == "" {
  48. continue
  49. }
  50. cond = cond.And(db.BuildCaseInsensitiveLike(colName, field))
  51. }
  52. return cond
  53. }
  54. // Search searches for issues
  55. func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (*internal.SearchResult, error) {
  56. // FIXME: I tried to avoid importing models here, but it seems to be impossible.
  57. // We can provide a function to register the search function, so models/issues can register it.
  58. // So models/issues will import modules/indexer/issues, it's OK because it's by design.
  59. // But modules/indexer/issues has already imported models/issues to do UpdateRepoIndexer and UpdateIssueIndexer.
  60. // And to avoid circular import, we have to move the functions to another package.
  61. // I believe it should be services/indexer, sounds great!
  62. // But the two functions are used in modules/notification/indexer, that means we will import services/indexer in modules/notification/indexer.
  63. // So that's the root problem:
  64. // The notification is defined in modules, but it's using lots of things should be in services.
  65. cond := builder.NewCond()
  66. if options.Keyword != "" {
  67. repoCond := builder.In("repo_id", options.RepoIDs)
  68. if len(options.RepoIDs) == 1 {
  69. repoCond = builder.Eq{"repo_id": options.RepoIDs[0]}
  70. }
  71. subQuery := builder.Select("id").From("issue").Where(repoCond)
  72. searchMode := util.IfZero(options.SearchMode, i.SupportedSearchModes()[0].ModeValue)
  73. cond = builder.Or(
  74. buildMatchQuery(searchMode, "issue.name", options.Keyword),
  75. buildMatchQuery(searchMode, "issue.content", options.Keyword),
  76. builder.In("issue.id", builder.Select("issue_id").
  77. From("comment").
  78. Where(builder.And(
  79. builder.Eq{"type": issue_model.CommentTypeComment},
  80. builder.In("issue_id", subQuery),
  81. buildMatchQuery(searchMode, "content", options.Keyword),
  82. )),
  83. ),
  84. )
  85. if options.IsKeywordNumeric() {
  86. cond = cond.Or(
  87. builder.Eq{"`index`": options.Keyword},
  88. )
  89. }
  90. }
  91. opt, err := ToDBOptions(ctx, options)
  92. if err != nil {
  93. return nil, err
  94. }
  95. // If pagesize == 0, return total count only. It's a special case for search count.
  96. if options.Paginator != nil && options.Paginator.PageSize == 0 {
  97. total, err := issue_model.CountIssues(ctx, opt, cond)
  98. if err != nil {
  99. return nil, err
  100. }
  101. return &internal.SearchResult{
  102. Total: total,
  103. }, nil
  104. }
  105. return i.FindWithIssueOptions(ctx, opt, cond)
  106. }
  107. func (i *Indexer) FindWithIssueOptions(ctx context.Context, opt *issue_model.IssuesOptions, otherConds ...builder.Cond) (*internal.SearchResult, error) {
  108. ids, total, err := issue_model.IssueIDs(ctx, opt, otherConds...)
  109. if err != nil {
  110. return nil, err
  111. }
  112. hits := make([]internal.Match, 0, len(ids))
  113. for _, id := range ids {
  114. hits = append(hits, internal.Match{
  115. ID: id,
  116. })
  117. }
  118. return &internal.SearchResult{
  119. Total: total,
  120. Hits: hits,
  121. }, nil
  122. }