gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package internal
  4. import (
  5. "strconv"
  6. "code.gitea.io/gitea/models/db"
  7. "code.gitea.io/gitea/modules/indexer"
  8. "code.gitea.io/gitea/modules/optional"
  9. "code.gitea.io/gitea/modules/timeutil"
  10. )
  11. // IndexerData data stored in the issue indexer
  12. type IndexerData struct {
  13. ID int64 `json:"id"`
  14. RepoID int64 `json:"repo_id"`
  15. IsPublic bool `json:"is_public"` // If the repo is public
  16. // Fields used for keyword searching
  17. Title string `json:"title"`
  18. Content string `json:"content"`
  19. Comments []string `json:"comments"`
  20. // Fields used for filtering
  21. IsPull bool `json:"is_pull"`
  22. IsClosed bool `json:"is_closed"`
  23. IsArchived bool `json:"is_archived"`
  24. LabelIDs []int64 `json:"label_ids"`
  25. NoLabel bool `json:"no_label"` // True if LabelIDs is empty
  26. MilestoneID int64 `json:"milestone_id"`
  27. ProjectID int64 `json:"project_id"`
  28. ProjectColumnID int64 `json:"project_board_id"` // the key should be kept as project_board_id to keep compatible
  29. PosterID int64 `json:"poster_id"`
  30. AssigneeID int64 `json:"assignee_id"`
  31. MentionIDs []int64 `json:"mention_ids"`
  32. ReviewedIDs []int64 `json:"reviewed_ids"`
  33. ReviewRequestedIDs []int64 `json:"review_requested_ids"`
  34. SubscriberIDs []int64 `json:"subscriber_ids"`
  35. UpdatedUnix timeutil.TimeStamp `json:"updated_unix"`
  36. // Fields used for sorting
  37. // UpdatedUnix is both used for filtering and sorting.
  38. // ID is used for sorting too, to make the sorting stable.
  39. CreatedUnix timeutil.TimeStamp `json:"created_unix"`
  40. DeadlineUnix timeutil.TimeStamp `json:"deadline_unix"`
  41. CommentCount int64 `json:"comment_count"`
  42. }
  43. // Match represents on search result
  44. type Match struct {
  45. ID int64 `json:"id"`
  46. Score float64 `json:"score"`
  47. }
  48. // SearchResult represents search results
  49. type SearchResult struct {
  50. Total int64
  51. Hits []Match
  52. }
  53. // SearchOptions represents search options.
  54. //
  55. // It has a slightly different design from database query options.
  56. // In database query options, a field is never a pointer, so it could be confusing when it's zero value:
  57. // Do you want to find data with a field value of 0, or do you not specify the field in the options?
  58. // To avoid this confusion, db introduced db.NoConditionID(-1).
  59. // 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 ...)"
  60. // It's still not ideal, it trapped developers many times.
  61. // And sometimes -1 could be a valid value, like issue ID, negative numbers indicate exclusion.
  62. // Since db.NoConditionID is for "db" (the package name is db), it makes sense not to use it in the indexer:
  63. // Why do bleve/elasticsearch/meilisearch indexers need to know about db.NoConditionID?
  64. // So in SearchOptions, we use pointer for fields which could be not specified,
  65. // and always use the value to filter if it's not nil, even if it's zero or negative.
  66. // It can handle almost all cases, if there is an exception, we can add a new field, like NoLabelOnly.
  67. // Unfortunately, we still use db for the indexer and have to convert between db.NoConditionID and nil for legacy reasons.
  68. type SearchOptions struct {
  69. Keyword string // keyword to search
  70. SearchMode indexer.SearchModeType
  71. RepoIDs []int64 // repository IDs which the issues belong to
  72. AllPublic bool // if include all public repositories
  73. IsPull optional.Option[bool] // if the issues is a pull request
  74. IsClosed optional.Option[bool] // if the issues is closed
  75. IsArchived optional.Option[bool] // if the repo is archived
  76. IncludedLabelIDs []int64 // labels the issues have
  77. ExcludedLabelIDs []int64 // labels the issues don't have
  78. 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.
  79. NoLabelOnly bool // if the issues have no label, if true, IncludedLabelIDs and ExcludedLabelIDs, IncludedAnyLabelIDs will be ignored
  80. MilestoneIDs []int64 // milestones the issues have
  81. ProjectID optional.Option[int64] // project the issues belong to
  82. ProjectColumnID optional.Option[int64] // project column the issues belong to
  83. PosterID string // poster of the issues, "(none)" or "(any)" or a user ID
  84. AssigneeID string // assignee of the issues, "(none)" or "(any)" or a user ID
  85. MentionID optional.Option[int64] // mentioned user of the issues
  86. ReviewedID optional.Option[int64] // reviewer of the issues
  87. ReviewRequestedID optional.Option[int64] // requested reviewer of the issues
  88. SubscriberID optional.Option[int64] // subscriber of the issues
  89. UpdatedAfterUnix optional.Option[int64]
  90. UpdatedBeforeUnix optional.Option[int64]
  91. Paginator *db.ListOptions
  92. SortBy SortBy // sort by field
  93. }
  94. // Copy returns a copy of the options.
  95. // Be careful, it's not a deep copy, so `SearchOptions.RepoIDs = {...}` is OK while `SearchOptions.RepoIDs[0] = ...` is not.
  96. func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOptions {
  97. if o == nil {
  98. return nil
  99. }
  100. v := *o
  101. for _, e := range edit {
  102. e(&v)
  103. }
  104. return &v
  105. }
  106. // used for optimized issue index based search
  107. func (o *SearchOptions) IsKeywordNumeric() bool {
  108. _, err := strconv.Atoi(o.Keyword)
  109. return err == nil
  110. }
  111. type SortBy string
  112. const (
  113. SortByCreatedDesc SortBy = "-created_unix"
  114. SortByUpdatedDesc SortBy = "-updated_unix"
  115. SortByCommentsDesc SortBy = "-comment_count"
  116. SortByDeadlineDesc SortBy = "-deadline_unix"
  117. SortByCreatedAsc SortBy = "created_unix"
  118. SortByUpdatedAsc SortBy = "updated_unix"
  119. SortByCommentsAsc SortBy = "comment_count"
  120. SortByDeadlineAsc SortBy = "deadline_unix"
  121. // Unsupported sort types which are supported by issues.IssuesOptions.SortType:
  122. //
  123. // - "priorityrepo":
  124. // It's impossible to support it in the indexer.
  125. // It is based on the specified repository in the request, so we cannot add static field to the indexer.
  126. // If we do something like that query the issues in the specified repository first then append other issues,
  127. // it will break the pagination.
  128. //
  129. // - "project-column-sorting":
  130. // Although it's possible to support it by adding project.ProjectIssue.Sorting to the indexer,
  131. // but what if the issue belongs to multiple projects?
  132. // Since it's unsupported to search issues with keyword in project page, we don't need to support it.
  133. )