query_builder.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. // QueryBuilder provides high-level query building functionality
  12. type QueryBuilder struct {
  13. defaultAnalyzer string
  14. }
  15. // NewQueryBuilder creates a new query builder service
  16. func NewQueryBuilder() *QueryBuilder {
  17. return &QueryBuilder{
  18. defaultAnalyzer: "standard",
  19. }
  20. }
  21. // BuildQuery builds a Bleve query from a SearchRequest
  22. func (qb *QueryBuilder) 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 - use main_log_path for efficient log group queries or file_path for specific files
  43. if len(req.LogPaths) > 0 {
  44. var fieldName string
  45. if req.UseMainLogPath {
  46. fieldName = "main_log_path"
  47. } else {
  48. fieldName = "file_path"
  49. }
  50. if logPathQuery := qb.buildTermsQuery(fieldName, req.LogPaths); logPathQuery != nil {
  51. boolQuery.AddMust(logPathQuery)
  52. }
  53. }
  54. // Add IP address filters
  55. if len(req.IPAddresses) > 0 {
  56. if ipQuery := qb.buildTermsQuery("remote_addr", req.IPAddresses); ipQuery != nil {
  57. boolQuery.AddMust(ipQuery)
  58. }
  59. }
  60. // Add status code filters
  61. if len(req.StatusCodes) > 0 {
  62. if statusQuery := qb.buildStatusCodeQuery(req.StatusCodes); statusQuery != nil {
  63. boolQuery.AddMust(statusQuery)
  64. }
  65. }
  66. // Add method filters
  67. if len(req.Methods) > 0 {
  68. if methodQuery := qb.buildTermsQuery("request_method", req.Methods); methodQuery != nil {
  69. boolQuery.AddMust(methodQuery)
  70. }
  71. }
  72. // Add country filters
  73. if len(req.Countries) > 0 {
  74. if countryQuery := qb.buildTermsQuery("region_code", req.Countries); countryQuery != nil {
  75. boolQuery.AddMust(countryQuery)
  76. }
  77. }
  78. // Log the final query structure for debugging.
  79. queryBytes, err := json.Marshal(boolQuery)
  80. if err == nil {
  81. logger.Debugf("Constructed Bleve Query: %s", string(queryBytes))
  82. }
  83. // Additional debug: if using main_log_path, also log a diagnostic query without filters
  84. if len(req.LogPaths) > 0 && req.UseMainLogPath {
  85. logger.Debugf("DEBUG: Dashboard query using main_log_path field with path: %v", req.LogPaths)
  86. }
  87. return boolQuery, nil
  88. }
  89. // buildTimeRangeQuery builds a time range query
  90. func (qb *QueryBuilder) buildTimeRangeQuery(start, end *int64) query.Query {
  91. if start == nil && end == nil {
  92. return nil
  93. }
  94. var min, max *float64
  95. if start != nil {
  96. startFloat := float64(*start)
  97. min = &startFloat
  98. }
  99. if end != nil {
  100. endFloat := float64(*end)
  101. max = &endFloat
  102. }
  103. rangeQuery := bleve.NewNumericRangeQuery(min, max)
  104. rangeQuery.SetField("timestamp")
  105. return rangeQuery
  106. }
  107. // buildTermsQuery builds a terms query for multiple values
  108. func (qb *QueryBuilder) buildTermsQuery(field string, terms []string) query.Query {
  109. if len(terms) == 0 {
  110. return nil
  111. }
  112. if len(terms) == 1 {
  113. termQuery := bleve.NewTermQuery(terms[0])
  114. termQuery.SetField(field)
  115. return termQuery
  116. }
  117. // For multiple terms, use boolean OR
  118. boolQuery := bleve.NewBooleanQuery()
  119. for _, term := range terms {
  120. termQuery := bleve.NewTermQuery(term)
  121. termQuery.SetField(field)
  122. boolQuery.AddShould(termQuery)
  123. }
  124. boolQuery.SetMinShould(1) // Crucial for OR behavior
  125. return boolQuery
  126. }
  127. // buildStatusCodeQuery builds a status code query
  128. func (qb *QueryBuilder) buildStatusCodeQuery(codes []int) query.Query {
  129. if len(codes) == 0 {
  130. return nil
  131. }
  132. // Convert status codes to strings for term query
  133. terms := make([]string, len(codes))
  134. for i, code := range codes {
  135. terms[i] = strconv.Itoa(code)
  136. }
  137. return qb.buildTermsQuery("status", terms)
  138. }
  139. // ValidateSearchRequest validates a search request
  140. func (qb *QueryBuilder) ValidateSearchRequest(req *SearchRequest) error {
  141. if req == nil {
  142. return fmt.Errorf("search request cannot be nil")
  143. }
  144. if req.Limit < 0 {
  145. return fmt.Errorf("limit cannot be negative")
  146. }
  147. if req.Offset < 0 {
  148. return fmt.Errorf("offset cannot be negative")
  149. }
  150. // Validate sort order
  151. if req.SortOrder != "" && req.SortOrder != SortOrderAsc && req.SortOrder != SortOrderDesc {
  152. return fmt.Errorf("invalid sort order: %s", req.SortOrder)
  153. }
  154. return nil
  155. }
  156. // BuildSuggestionQuery builds a query for suggestions (simplified version)
  157. func (qb *QueryBuilder) BuildSuggestionQuery(text, field string) (query.Query, error) {
  158. if text == "" {
  159. return nil, fmt.Errorf("suggestion text cannot be empty")
  160. }
  161. // Use a prefix query for suggestions
  162. if field == "" {
  163. field = "_all"
  164. }
  165. prefixQuery := bleve.NewPrefixQuery(strings.ToLower(text))
  166. prefixQuery.SetField(field)
  167. return prefixQuery, nil
  168. }