188 lines
4.3 KiB
Go
188 lines
4.3 KiB
Go
// 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,
|
|
},
|
|
}
|
|
}
|