query_builder.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package searcher
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "github.com/blevesearch/bleve/v2"
  8. "github.com/blevesearch/bleve/v2/search/query"
  9. "github.com/uozi-tech/cosy/logger"
  10. )
  11. // QueryBuilderService provides high-level query building functionality
  12. type QueryBuilderService struct {
  13. defaultAnalyzer string
  14. }
  15. // NewQueryBuilderService creates a new query builder service
  16. func NewQueryBuilderService() *QueryBuilderService {
  17. return &QueryBuilderService{
  18. defaultAnalyzer: "standard",
  19. }
  20. }
  21. // BuildQuery builds a Bleve query from a SearchRequest
  22. func (qb *QueryBuilderService) BuildQuery(req *SearchRequest) (query.Query, error) {
  23. if req == nil {
  24. return nil, fmt.Errorf("search request cannot be nil")
  25. }
  26. // Build main query
  27. var mainQuery query.Query
  28. if req.Query == "" {
  29. mainQuery = bleve.NewMatchAllQuery()
  30. } else {
  31. mainQuery = bleve.NewMatchQuery(req.Query)
  32. }
  33. // Create boolean query to combine filters
  34. boolQuery := bleve.NewBooleanQuery()
  35. boolQuery.AddMust(mainQuery)
  36. // Add time range filters
  37. if req.StartTime != nil || req.EndTime != nil {
  38. if timeQuery := qb.buildTimeRangeQuery(req.StartTime, req.EndTime); timeQuery != nil {
  39. boolQuery.AddMust(timeQuery)
  40. }
  41. }
  42. // Add log path filters
  43. if len(req.LogPaths) > 0 {
  44. if logPathQuery := qb.buildTermsQuery("file_path", req.LogPaths); logPathQuery != nil {
  45. boolQuery.AddMust(logPathQuery)
  46. }
  47. }
  48. // Add IP address filters
  49. if len(req.IPAddresses) > 0 {
  50. if ipQuery := qb.buildTermsQuery("remote_addr", req.IPAddresses); ipQuery != nil {
  51. boolQuery.AddMust(ipQuery)
  52. }
  53. }
  54. // Add status code filters
  55. if len(req.StatusCodes) > 0 {
  56. if statusQuery := qb.buildStatusCodeQuery(req.StatusCodes); statusQuery != nil {
  57. boolQuery.AddMust(statusQuery)
  58. }
  59. }
  60. // Add method filters
  61. if len(req.Methods) > 0 {
  62. if methodQuery := qb.buildTermsQuery("request_method", req.Methods); methodQuery != nil {
  63. boolQuery.AddMust(methodQuery)
  64. }
  65. }
  66. // Add country filters
  67. if len(req.Countries) > 0 {
  68. if countryQuery := qb.buildTermsQuery("region_code", req.Countries); countryQuery != nil {
  69. boolQuery.AddMust(countryQuery)
  70. }
  71. }
  72. // Log the final query structure for debugging.
  73. queryBytes, err := json.Marshal(boolQuery)
  74. if err == nil {
  75. logger.Debugf("Constructed Bleve Query: %s", string(queryBytes))
  76. }
  77. return boolQuery, nil
  78. }
  79. // buildTimeRangeQuery builds a time range query
  80. func (qb *QueryBuilderService) buildTimeRangeQuery(start, end *int64) query.Query {
  81. if start == nil && end == nil {
  82. return nil
  83. }
  84. var min, max *float64
  85. if start != nil {
  86. startFloat := float64(*start)
  87. min = &startFloat
  88. }
  89. if end != nil {
  90. endFloat := float64(*end)
  91. max = &endFloat
  92. }
  93. rangeQuery := bleve.NewNumericRangeQuery(min, max)
  94. rangeQuery.SetField("timestamp")
  95. return rangeQuery
  96. }
  97. // buildTermsQuery builds a terms query for multiple values
  98. func (qb *QueryBuilderService) buildTermsQuery(field string, terms []string) query.Query {
  99. if len(terms) == 0 {
  100. return nil
  101. }
  102. if len(terms) == 1 {
  103. termQuery := bleve.NewTermQuery(terms[0])
  104. termQuery.SetField(field)
  105. return termQuery
  106. }
  107. // For multiple terms, use boolean OR
  108. boolQuery := bleve.NewBooleanQuery()
  109. for _, term := range terms {
  110. termQuery := bleve.NewTermQuery(term)
  111. termQuery.SetField(field)
  112. boolQuery.AddShould(termQuery)
  113. }
  114. boolQuery.SetMinShould(1) // Crucial for OR behavior
  115. return boolQuery
  116. }
  117. // buildStatusCodeQuery builds a status code query
  118. func (qb *QueryBuilderService) buildStatusCodeQuery(codes []int) query.Query {
  119. if len(codes) == 0 {
  120. return nil
  121. }
  122. // Convert status codes to strings for term query
  123. terms := make([]string, len(codes))
  124. for i, code := range codes {
  125. terms[i] = strconv.Itoa(code)
  126. }
  127. return qb.buildTermsQuery("status", terms)
  128. }
  129. // ValidateSearchRequest validates a search request
  130. func (qb *QueryBuilderService) ValidateSearchRequest(req *SearchRequest) error {
  131. if req == nil {
  132. return fmt.Errorf("search request cannot be nil")
  133. }
  134. if req.Limit < 0 {
  135. return fmt.Errorf("limit cannot be negative")
  136. }
  137. if req.Offset < 0 {
  138. return fmt.Errorf("offset cannot be negative")
  139. }
  140. // Validate sort order
  141. if req.SortOrder != "" && req.SortOrder != SortOrderAsc && req.SortOrder != SortOrderDesc {
  142. return fmt.Errorf("invalid sort order: %s", req.SortOrder)
  143. }
  144. return nil
  145. }
  146. // BuildSuggestionQuery builds a query for suggestions (simplified version)
  147. func (qb *QueryBuilderService) BuildSuggestionQuery(text, field string) (query.Query, error) {
  148. if text == "" {
  149. return nil, fmt.Errorf("suggestion text cannot be empty")
  150. }
  151. // Use a prefix query for suggestions
  152. if field == "" {
  153. field = "_all"
  154. }
  155. prefixQuery := bleve.NewPrefixQuery(strings.ToLower(text))
  156. prefixQuery.SetField(field)
  157. return prefixQuery, nil
  158. }