You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
3.9 KiB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package indexer
  5. import (
  6. "os"
  7. "code.gitea.io/gitea/modules/log"
  8. "code.gitea.io/gitea/modules/setting"
  9. "github.com/blevesearch/bleve"
  10. "github.com/blevesearch/bleve/analysis/analyzer/custom"
  11. "github.com/blevesearch/bleve/analysis/token/lowercase"
  12. "github.com/blevesearch/bleve/analysis/tokenizer/unicode"
  13. "github.com/blevesearch/bleve/index/upsidedown"
  14. )
  15. // issueIndexer (thread-safe) index for searching issues
  16. var issueIndexer bleve.Index
  17. // IssueIndexerData data stored in the issue indexer
  18. type IssueIndexerData struct {
  19. RepoID int64
  20. Title string
  21. Content string
  22. Comments []string
  23. }
  24. // IssueIndexerUpdate an update to the issue indexer
  25. type IssueIndexerUpdate struct {
  26. IssueID int64
  27. Data *IssueIndexerData
  28. }
  29. func (update IssueIndexerUpdate) addToBatch(batch *bleve.Batch) error {
  30. return batch.Index(indexerID(update.IssueID), update.Data)
  31. }
  32. const issueIndexerAnalyzer = "issueIndexer"
  33. // InitIssueIndexer initialize issue indexer
  34. func InitIssueIndexer(populateIndexer func() error) {
  35. _, err := os.Stat(setting.Indexer.IssuePath)
  36. if err != nil && !os.IsNotExist(err) {
  37. log.Fatal(4, "InitIssueIndexer: %v", err)
  38. } else if err == nil {
  39. issueIndexer, err = bleve.Open(setting.Indexer.IssuePath)
  40. if err == nil {
  41. return
  42. } else if err != upsidedown.IncompatibleVersion {
  43. log.Fatal(4, "InitIssueIndexer, open index: %v", err)
  44. }
  45. log.Warn("Incompatible bleve version, deleting and recreating issue indexer")
  46. if err = os.RemoveAll(setting.Indexer.IssuePath); err != nil {
  47. log.Fatal(4, "InitIssueIndexer: remove index, %v", err)
  48. }
  49. }
  50. if err = createIssueIndexer(); err != nil {
  51. log.Fatal(4, "InitIssuesIndexer: create index, %v", err)
  52. }
  53. if err = populateIndexer(); err != nil {
  54. log.Fatal(4, "InitIssueIndexer: populate index, %v", err)
  55. }
  56. }
  57. // createIssueIndexer create an issue indexer if one does not already exist
  58. func createIssueIndexer() error {
  59. mapping := bleve.NewIndexMapping()
  60. docMapping := bleve.NewDocumentMapping()
  61. docMapping.AddFieldMappingsAt("RepoID", bleve.NewNumericFieldMapping())
  62. textFieldMapping := bleve.NewTextFieldMapping()
  63. docMapping.AddFieldMappingsAt("Title", textFieldMapping)
  64. docMapping.AddFieldMappingsAt("Content", textFieldMapping)
  65. docMapping.AddFieldMappingsAt("Comments", textFieldMapping)
  66. if err := addUnicodeNormalizeTokenFilter(mapping); err != nil {
  67. return err
  68. } else if err = mapping.AddCustomAnalyzer(issueIndexerAnalyzer, map[string]interface{}{
  69. "type": custom.Name,
  70. "char_filters": []string{},
  71. "tokenizer": unicode.Name,
  72. "token_filters": []string{unicodeNormalizeName, lowercase.Name},
  73. }); err != nil {
  74. return err
  75. }
  76. mapping.DefaultAnalyzer = issueIndexerAnalyzer
  77. mapping.AddDocumentMapping("issues", docMapping)
  78. var err error
  79. issueIndexer, err = bleve.New(setting.Indexer.IssuePath, mapping)
  80. return err
  81. }
  82. // IssueIndexerBatch batch to add updates to
  83. func IssueIndexerBatch() *Batch {
  84. return &Batch{
  85. batch: issueIndexer.NewBatch(),
  86. index: issueIndexer,
  87. }
  88. }
  89. // SearchIssuesByKeyword searches for issues by given conditions.
  90. // Returns the matching issue IDs
  91. func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
  92. indexerQuery := bleve.NewConjunctionQuery(
  93. numericEqualityQuery(repoID, "RepoID"),
  94. bleve.NewDisjunctionQuery(
  95. newMatchPhraseQuery(keyword, "Title", issueIndexerAnalyzer),
  96. newMatchPhraseQuery(keyword, "Content", issueIndexerAnalyzer),
  97. newMatchPhraseQuery(keyword, "Comments", issueIndexerAnalyzer),
  98. ))
  99. search := bleve.NewSearchRequestOptions(indexerQuery, 2147483647, 0, false)
  100. result, err := issueIndexer.Search(search)
  101. if err != nil {
  102. return nil, err
  103. }
  104. issueIDs := make([]int64, len(result.Hits))
  105. for i, hit := range result.Hits {
  106. issueIDs[i], err = idOfIndexerID(hit.ID)
  107. if err != nil {
  108. return nil, err
  109. }
  110. }
  111. return issueIDs, nil
  112. }