123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- package searcher
- import (
- "encoding/json"
- "fmt"
- "strconv"
- "strings"
- "github.com/blevesearch/bleve/v2"
- "github.com/blevesearch/bleve/v2/search/query"
- "github.com/uozi-tech/cosy/logger"
- )
- // QueryBuilder provides high-level query building functionality
- type QueryBuilder struct {
- defaultAnalyzer string
- }
- // NewQueryBuilder creates a new query builder service
- func NewQueryBuilder() *QueryBuilder {
- return &QueryBuilder{
- defaultAnalyzer: "standard",
- }
- }
- // BuildQuery builds a Bleve query from a SearchRequest
- func (qb *QueryBuilder) BuildQuery(req *SearchRequest) (query.Query, error) {
- if req == nil {
- return nil, fmt.Errorf("search request cannot be nil")
- }
- // Build main query
- var mainQuery query.Query
- if req.Query == "" {
- mainQuery = bleve.NewMatchAllQuery()
- } else {
- mainQuery = bleve.NewMatchQuery(req.Query)
- }
- // Create boolean query to combine filters
- boolQuery := bleve.NewBooleanQuery()
- boolQuery.AddMust(mainQuery)
- // Add time range filters
- if req.StartTime != nil || req.EndTime != nil {
- if timeQuery := qb.buildTimeRangeQuery(req.StartTime, req.EndTime); timeQuery != nil {
- boolQuery.AddMust(timeQuery)
- }
- }
- // Add log path filters - use main_log_path for efficient log group queries or file_path for specific files
- if len(req.LogPaths) > 0 {
- var fieldName string
- if req.UseMainLogPath {
- fieldName = "main_log_path"
- } else {
- fieldName = "file_path"
- }
- if logPathQuery := qb.buildTermsQuery(fieldName, req.LogPaths); logPathQuery != nil {
- boolQuery.AddMust(logPathQuery)
- }
- }
- // Add IP address filters
- if len(req.IPAddresses) > 0 {
- if ipQuery := qb.buildTermsQuery("remote_addr", req.IPAddresses); ipQuery != nil {
- boolQuery.AddMust(ipQuery)
- }
- }
- // Add status code filters
- if len(req.StatusCodes) > 0 {
- if statusQuery := qb.buildStatusCodeQuery(req.StatusCodes); statusQuery != nil {
- boolQuery.AddMust(statusQuery)
- }
- }
- // Add method filters
- if len(req.Methods) > 0 {
- if methodQuery := qb.buildTermsQuery("request_method", req.Methods); methodQuery != nil {
- boolQuery.AddMust(methodQuery)
- }
- }
- // Add country filters
- if len(req.Countries) > 0 {
- if countryQuery := qb.buildTermsQuery("region_code", req.Countries); countryQuery != nil {
- boolQuery.AddMust(countryQuery)
- }
- }
- // Log the final query structure for debugging.
- queryBytes, err := json.Marshal(boolQuery)
- if err == nil {
- logger.Debugf("Constructed Bleve Query: %s", string(queryBytes))
- }
-
- // Additional debug: if using main_log_path, also log a diagnostic query without filters
- if len(req.LogPaths) > 0 && req.UseMainLogPath {
- logger.Debugf("DEBUG: Dashboard query using main_log_path field with path: %v", req.LogPaths)
- }
- return boolQuery, nil
- }
- // buildTimeRangeQuery builds a time range query
- func (qb *QueryBuilder) buildTimeRangeQuery(start, end *int64) query.Query {
- if start == nil && end == nil {
- return nil
- }
- var min, max *float64
- if start != nil {
- startFloat := float64(*start)
- min = &startFloat
- }
- if end != nil {
- endFloat := float64(*end)
- max = &endFloat
- }
- rangeQuery := bleve.NewNumericRangeQuery(min, max)
- rangeQuery.SetField("timestamp")
- return rangeQuery
- }
- // buildTermsQuery builds a terms query for multiple values
- func (qb *QueryBuilder) buildTermsQuery(field string, terms []string) query.Query {
- if len(terms) == 0 {
- return nil
- }
- if len(terms) == 1 {
- termQuery := bleve.NewTermQuery(terms[0])
- termQuery.SetField(field)
- return termQuery
- }
- // For multiple terms, use boolean OR
- boolQuery := bleve.NewBooleanQuery()
- for _, term := range terms {
- termQuery := bleve.NewTermQuery(term)
- termQuery.SetField(field)
- boolQuery.AddShould(termQuery)
- }
- boolQuery.SetMinShould(1) // Crucial for OR behavior
- return boolQuery
- }
- // buildStatusCodeQuery builds a status code query
- func (qb *QueryBuilder) buildStatusCodeQuery(codes []int) query.Query {
- if len(codes) == 0 {
- return nil
- }
- // Convert status codes to strings for term query
- terms := make([]string, len(codes))
- for i, code := range codes {
- terms[i] = strconv.Itoa(code)
- }
- return qb.buildTermsQuery("status", terms)
- }
- // ValidateSearchRequest validates a search request
- func (qb *QueryBuilder) ValidateSearchRequest(req *SearchRequest) error {
- if req == nil {
- return fmt.Errorf("search request cannot be nil")
- }
- if req.Limit < 0 {
- return fmt.Errorf("limit cannot be negative")
- }
- if req.Offset < 0 {
- return fmt.Errorf("offset cannot be negative")
- }
- // Validate sort order
- if req.SortOrder != "" && req.SortOrder != SortOrderAsc && req.SortOrder != SortOrderDesc {
- return fmt.Errorf("invalid sort order: %s", req.SortOrder)
- }
- return nil
- }
- // BuildSuggestionQuery builds a query for suggestions (simplified version)
- func (qb *QueryBuilder) BuildSuggestionQuery(text, field string) (query.Query, error) {
- if text == "" {
- return nil, fmt.Errorf("suggestion text cannot be empty")
- }
- // Use a prefix query for suggestions
- if field == "" {
- field = "_all"
- }
- prefixQuery := bleve.NewPrefixQuery(strings.ToLower(text))
- prefixQuery.SetField(field)
- return prefixQuery, nil
- }
|