// Package index provides index-level operations for OpenSearch. package index import ( "bytes" "context" "encoding/json" "fmt" "io" "os" "es-demo/client" "github.com/opensearch-project/opensearch-go/v2/opensearchapi" ) // SearchQuery represents a search query configuration. type SearchQuery struct { // Query is the search query DSL Query map[string]any `json:"query,omitempty"` // Size is the maximum number of hits to return (default: 10) Size int `json:"size,omitempty"` // From is the starting offset (for pagination) From int `json:"from,omitempty"` // Sort defines the sort order Sort []map[string]any `json:"sort,omitempty"` // Source defines which fields to return Source any `json:"_source,omitempty"` } // SearchResult represents search results. type SearchResult struct { Took int64 `json:"took"` Hits struct { Total struct { Value int64 `json:"value"` Relation string `json:"relation"` } `json:"total"` MaxScore *float64 `json:"max_score"` Hits []Hit `json:"hits"` } `json:"hits"` } // Hit represents a single search result hit. type Hit struct { Index string `json:"_index"` ID string `json:"_id"` Score *float64 `json:"_score"` Source map[string]any `json:"_source"` } // Search performs a search query on an index. func Search(ctx context.Context, c *client.Client, indexName string, query *SearchQuery) (*SearchResult, error) { if indexName == "" { return nil, ErrInvalidIndexName } if query == nil { query = &SearchQuery{} } // Set defaults if query.Size == 0 { query.Size = 10 } data, err := json.Marshal(query) if err != nil { return nil, fmt.Errorf("failed to marshal search query: %w", err) } req := opensearchapi.SearchRequest{ Index: []string{indexName}, Body: bytes.NewReader(data), } res, err := req.Do(ctx, c.GetClient()) if err != nil { return nil, fmt.Errorf("failed to execute search request: %w", err) } defer func() { if closeErr := res.Body.Close(); closeErr != nil { fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr) } }() if res.IsError() { bodyBytes, _ := io.ReadAll(res.Body) return nil, fmt.Errorf("search failed with status %s: %s", res.Status(), string(bodyBytes)) } var result SearchResult if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return nil, fmt.Errorf("failed to decode search result: %w", err) } return &result, nil } // MatchQuery creates a match query for full-text search. func MatchQuery(field string, value any) map[string]any { return map[string]any{ "match": map[string]any{ field: value, }, } } // TermQuery creates a term query for exact matching. func TermQuery(field string, value any) map[string]any { return map[string]any{ "term": map[string]any{ field: value, }, } } // RangeQuery creates a range query. func RangeQuery(field string, gte, lte any) map[string]any { rangeMap := make(map[string]any) if gte != nil { rangeMap["gte"] = gte } if lte != nil { rangeMap["lte"] = lte } return map[string]any{ "range": map[string]any{ field: rangeMap, }, } } // BoolQuery creates a bool query for combining multiple queries. type BoolQuery struct { Must []map[string]any `json:"must,omitempty"` Should []map[string]any `json:"should,omitempty"` MustNot []map[string]any `json:"must_not,omitempty"` Filter []map[string]any `json:"filter,omitempty"` } // ToBoolQuery converts BoolQuery to query DSL. func (b *BoolQuery) ToBoolQuery() map[string]any { return map[string]any{ "bool": b, } } // MatchAllQuery creates a match_all query. func MatchAllQuery() map[string]any { return map[string]any{ "match_all": map[string]any{}, } } // MultiMatchQuery creates a multi_match query for searching across multiple fields. func MultiMatchQuery(value any, fields ...string) map[string]any { return map[string]any{ "multi_match": map[string]any{ "query": value, "fields": fields, }, } } // WildcardQuery creates a wildcard query. func WildcardQuery(field string, value string) map[string]any { return map[string]any{ "wildcard": map[string]any{ field: value, }, } } // PrefixQuery creates a prefix query. func PrefixQuery(field string, value string) map[string]any { return map[string]any{ "prefix": map[string]any{ field: value, }, } }