1// Copyright 2012-present Oliver Eilhard. All rights reserved. 2// Use of this source code is governed by a MIT-license. 3// See http://olivere.mit-license.org/license.txt for details. 4 5package elastic 6 7import ( 8 "context" 9 "fmt" 10 "net/http" 11 "net/url" 12 "strings" 13 14 "gopkg.in/olivere/elastic.v5/uritemplates" 15) 16 17const ( 18 FieldStatsClusterLevel = "cluster" 19 FieldStatsIndicesLevel = "indices" 20) 21 22// FieldStatsService allows finding statistical properties of a field without executing a search, 23// but looking up measurements that are natively available in the Lucene index. 24// 25// See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-field-stats.html 26// for details 27type FieldStatsService struct { 28 client *Client 29 pretty bool 30 level string 31 index []string 32 allowNoIndices *bool 33 expandWildcards string 34 fields []string 35 ignoreUnavailable *bool 36 bodyJson interface{} 37 bodyString string 38} 39 40// NewFieldStatsService creates a new FieldStatsService 41func NewFieldStatsService(client *Client) *FieldStatsService { 42 return &FieldStatsService{ 43 client: client, 44 index: make([]string, 0), 45 fields: make([]string, 0), 46 } 47} 48 49// Index is a list of index names; use `_all` or empty string to perform 50// the operation on all indices. 51func (s *FieldStatsService) Index(index ...string) *FieldStatsService { 52 s.index = append(s.index, index...) 53 return s 54} 55 56// AllowNoIndices indicates whether to ignore if a wildcard indices expression 57// resolves into no concrete indices. 58// (This includes `_all` string or when no indices have been specified). 59func (s *FieldStatsService) AllowNoIndices(allowNoIndices bool) *FieldStatsService { 60 s.allowNoIndices = &allowNoIndices 61 return s 62} 63 64// ExpandWildcards indicates whether to expand wildcard expression to 65// concrete indices that are open, closed or both. 66func (s *FieldStatsService) ExpandWildcards(expandWildcards string) *FieldStatsService { 67 s.expandWildcards = expandWildcards 68 return s 69} 70 71// Fields is a list of fields for to get field statistics 72// for (min value, max value, and more). 73func (s *FieldStatsService) Fields(fields ...string) *FieldStatsService { 74 s.fields = append(s.fields, fields...) 75 return s 76} 77 78// IgnoreUnavailable is documented as: Whether specified concrete indices should be ignored when unavailable (missing or closed). 79func (s *FieldStatsService) IgnoreUnavailable(ignoreUnavailable bool) *FieldStatsService { 80 s.ignoreUnavailable = &ignoreUnavailable 81 return s 82} 83 84// Level sets if stats should be returned on a per index level or on a cluster wide level; 85// should be one of 'cluster' or 'indices'; defaults to former 86func (s *FieldStatsService) Level(level string) *FieldStatsService { 87 s.level = level 88 return s 89} 90 91// ClusterLevel is a helper that sets Level to "cluster". 92func (s *FieldStatsService) ClusterLevel() *FieldStatsService { 93 s.level = FieldStatsClusterLevel 94 return s 95} 96 97// IndicesLevel is a helper that sets Level to "indices". 98func (s *FieldStatsService) IndicesLevel() *FieldStatsService { 99 s.level = FieldStatsIndicesLevel 100 return s 101} 102 103// Pretty indicates that the JSON response be indented and human readable. 104func (s *FieldStatsService) Pretty(pretty bool) *FieldStatsService { 105 s.pretty = pretty 106 return s 107} 108 109// BodyJson is documented as: Field json objects containing the name and optionally a range to filter out indices result, that have results outside the defined bounds. 110func (s *FieldStatsService) BodyJson(body interface{}) *FieldStatsService { 111 s.bodyJson = body 112 return s 113} 114 115// BodyString is documented as: Field json objects containing the name and optionally a range to filter out indices result, that have results outside the defined bounds. 116func (s *FieldStatsService) BodyString(body string) *FieldStatsService { 117 s.bodyString = body 118 return s 119} 120 121// buildURL builds the URL for the operation. 122func (s *FieldStatsService) buildURL() (string, url.Values, error) { 123 // Build URL 124 var err error 125 var path string 126 if len(s.index) > 0 { 127 path, err = uritemplates.Expand("/{index}/_field_stats", map[string]string{ 128 "index": strings.Join(s.index, ","), 129 }) 130 } else { 131 path = "/_field_stats" 132 } 133 if err != nil { 134 return "", url.Values{}, err 135 } 136 137 // Add query string parameters 138 params := url.Values{} 139 if s.allowNoIndices != nil { 140 params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices)) 141 } 142 if s.expandWildcards != "" { 143 params.Set("expand_wildcards", s.expandWildcards) 144 } 145 if len(s.fields) > 0 { 146 params.Set("fields", strings.Join(s.fields, ",")) 147 } 148 if s.ignoreUnavailable != nil { 149 params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable)) 150 } 151 if s.level != "" { 152 params.Set("level", s.level) 153 } 154 return path, params, nil 155} 156 157// Validate checks if the operation is valid. 158func (s *FieldStatsService) Validate() error { 159 var invalid []string 160 if s.level != "" && (s.level != FieldStatsIndicesLevel && s.level != FieldStatsClusterLevel) { 161 invalid = append(invalid, "Level") 162 } 163 if len(invalid) != 0 { 164 return fmt.Errorf("missing or invalid required fields: %v", invalid) 165 } 166 return nil 167} 168 169// Do executes the operation. 170func (s *FieldStatsService) Do(ctx context.Context) (*FieldStatsResponse, error) { 171 // Check pre-conditions 172 if err := s.Validate(); err != nil { 173 return nil, err 174 } 175 176 // Get URL for request 177 path, params, err := s.buildURL() 178 if err != nil { 179 return nil, err 180 } 181 182 // Setup HTTP request body 183 var body interface{} 184 if s.bodyJson != nil { 185 body = s.bodyJson 186 } else { 187 body = s.bodyString 188 } 189 190 // Get HTTP response 191 res, err := s.client.PerformRequest(ctx, "POST", path, params, body, http.StatusNotFound) 192 if err != nil { 193 return nil, err 194 } 195 196 // TODO(oe): Is 404 really a valid response here? 197 if res.StatusCode == http.StatusNotFound { 198 return &FieldStatsResponse{make(map[string]IndexFieldStats)}, nil 199 } 200 201 // Return operation response 202 ret := new(FieldStatsResponse) 203 if err := s.client.decoder.Decode(res.Body, ret); err != nil { 204 return nil, err 205 } 206 return ret, nil 207} 208 209// -- Request -- 210 211// FieldStatsRequest can be used to set up the body to be used in the 212// Field Stats API. 213type FieldStatsRequest struct { 214 Fields []string `json:"fields"` 215 IndexConstraints map[string]*FieldStatsConstraints `json:"index_constraints,omitempty"` 216} 217 218// FieldStatsConstraints is a constraint on a field. 219type FieldStatsConstraints struct { 220 Min *FieldStatsComparison `json:"min_value,omitempty"` 221 Max *FieldStatsComparison `json:"max_value,omitempty"` 222} 223 224// FieldStatsComparison contain all comparison operations that can be used 225// in FieldStatsConstraints. 226type FieldStatsComparison struct { 227 Lte interface{} `json:"lte,omitempty"` 228 Lt interface{} `json:"lt,omitempty"` 229 Gte interface{} `json:"gte,omitempty"` 230 Gt interface{} `json:"gt,omitempty"` 231} 232 233// -- Response -- 234 235// FieldStatsResponse is the response body content 236type FieldStatsResponse struct { 237 Indices map[string]IndexFieldStats `json:"indices,omitempty"` 238} 239 240// IndexFieldStats contains field stats for an index 241type IndexFieldStats struct { 242 Fields map[string]FieldStats `json:"fields,omitempty"` 243} 244 245// FieldStats contains stats of an individual field 246type FieldStats struct { 247 Type string `json:"type"` 248 MaxDoc int64 `json:"max_doc"` 249 DocCount int64 `json:"doc_count"` 250 Density int64 `json:"density"` 251 SumDocFrequeny int64 `json:"sum_doc_freq"` 252 SumTotalTermFrequency int64 `json:"sum_total_term_freq"` 253 Searchable bool `json:"searchable"` 254 Aggregatable bool `json:"aggregatable"` 255 MinValue interface{} `json:"min_value"` 256 MinValueAsString string `json:"min_value_as_string"` 257 MaxValue interface{} `json:"max_value"` 258 MaxValueAsString string `json:"max_value_as_string"` 259} 260