// 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" ) // FieldMapping represents a field mapping configuration. type FieldMapping struct { Type string `json:"type"` Fields map[string]any `json:"fields,omitempty"` // Common field options Index *bool `json:"index,omitempty"` Store *bool `json:"store,omitempty"` Analyzer string `json:"analyzer,omitempty"` Format string `json:"format,omitempty"` IgnoreAbove int `json:"ignore_above,omitempty"` } // PutMapping updates the mapping for an index by adding new fields. // Note: Existing field mappings cannot be changed in OpenSearch. func PutMapping(ctx context.Context, c *client.Client, indexName string, properties map[string]FieldMapping) error { if indexName == "" { return ErrInvalidIndexName } if len(properties) == 0 { return fmt.Errorf("properties cannot be empty") } // Build mapping request mappingBody := map[string]any{ "properties": properties, } data, err := json.Marshal(mappingBody) if err != nil { return fmt.Errorf("failed to marshal mapping: %w", err) } req := opensearchapi.IndicesPutMappingRequest{ Index: []string{indexName}, Body: bytes.NewReader(data), } res, err := req.Do(ctx, c.GetClient()) if err != nil { return fmt.Errorf("failed to execute put mapping 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 fmt.Errorf("put mapping failed with status %s: %s", res.Status(), string(bodyBytes)) } return nil } // GetMapping retrieves the mapping for an index. func GetMapping(ctx context.Context, c *client.Client, indexName string) (map[string]any, error) { if indexName == "" { return nil, ErrInvalidIndexName } req := opensearchapi.IndicesGetMappingRequest{ Index: []string{indexName}, } res, err := req.Do(ctx, c.GetClient()) if err != nil { return nil, fmt.Errorf("failed to execute get mapping 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.StatusCode == 404 { return nil, ErrIndexNotFound } if res.IsError() { bodyBytes, _ := io.ReadAll(res.Body) return nil, fmt.Errorf("get mapping failed with status %s: %s", res.Status(), string(bodyBytes)) } var response map[string]struct { Mappings map[string]any `json:"mappings"` } if err := json.NewDecoder(res.Body).Decode(&response); err != nil { return nil, fmt.Errorf("failed to decode mapping response: %w", err) } indexData, exists := response[indexName] if !exists { return nil, ErrIndexNotFound } return indexData.Mappings, nil } // AddField adds a new field to the index mapping. // This is a convenience wrapper around PutMapping for adding a single field. func AddField(ctx context.Context, c *client.Client, indexName string, fieldName string, mapping FieldMapping) error { return PutMapping(ctx, c, indexName, map[string]FieldMapping{ fieldName: mapping, }) }