From 3b45f407618ecb5894966ae3bf7e7808a0472a9a Mon Sep 17 00:00:00 2001 From: Oliver Eilhard Date: Thu, 18 Jan 2018 13:22:10 +0100 Subject: [PATCH] Add Field Capabilities API, remove Field Stats API This commit adds the Field Capabilities API. It replaces the depreacted Field Stats API (in 5.4.0). Field Stats API has also been removed because it no longer exists in Elasticsearch 6.x. See https://www.elastic.co/guide/en/elasticsearch/reference/6.1/search-field-caps.html for documentation. See #675 --- client.go | 6 +- field_caps.go | 202 +++++++++++++++++++++++++++++++ field_caps_test.go | 146 +++++++++++++++++++++++ field_stats.go | 265 ----------------------------------------- field_stats_test.go | 282 -------------------------------------------- setup_test.go | 16 +++ 6 files changed, 367 insertions(+), 550 deletions(-) create mode 100644 field_caps.go create mode 100644 field_caps_test.go delete mode 100644 field_stats.go delete mode 100644 field_stats_test.go diff --git a/client.go b/client.go index 41168836f..c27b2a0cf 100644 --- a/client.go +++ b/client.go @@ -1432,9 +1432,9 @@ func (c *Client) Explain(index, typ, id string) *ExplainService { // TODO Search Exists API // TODO Validate API -// FieldStats returns statistical information about fields in indices. -func (c *Client) FieldStats(indices ...string) *FieldStatsService { - return NewFieldStatsService(c).Index(indices...) +// FieldCaps returns statistical information about fields in indices. +func (c *Client) FieldCaps(indices ...string) *FieldCapsService { + return NewFieldCapsService(c).Index(indices...) } // Exists checks if a document exists. diff --git a/field_caps.go b/field_caps.go new file mode 100644 index 000000000..393cd3ce8 --- /dev/null +++ b/field_caps.go @@ -0,0 +1,202 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package elastic + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/olivere/elastic/uritemplates" +) + +// FieldCapsService allows retrieving the capabilities of fields among multiple indices. +// +// See https://www.elastic.co/guide/en/elasticsearch/reference/6.1/search-field-caps.html +// for details +type FieldCapsService struct { + client *Client + pretty bool + index []string + allowNoIndices *bool + expandWildcards string + fields []string + ignoreUnavailable *bool + bodyJson interface{} + bodyString string +} + +// NewFieldCapsService creates a new FieldCapsService +func NewFieldCapsService(client *Client) *FieldCapsService { + return &FieldCapsService{ + client: client, + } +} + +// Index is a list of index names; use `_all` or empty string to perform +// the operation on all indices. +func (s *FieldCapsService) Index(index ...string) *FieldCapsService { + s.index = append(s.index, index...) + return s +} + +// AllowNoIndices indicates whether to ignore if a wildcard indices expression +// resolves into no concrete indices. +// (This includes `_all` string or when no indices have been specified). +func (s *FieldCapsService) AllowNoIndices(allowNoIndices bool) *FieldCapsService { + s.allowNoIndices = &allowNoIndices + return s +} + +// ExpandWildcards indicates whether to expand wildcard expression to +// concrete indices that are open, closed or both. +func (s *FieldCapsService) ExpandWildcards(expandWildcards string) *FieldCapsService { + s.expandWildcards = expandWildcards + return s +} + +// Fields is a list of fields for to get field capabilities. +func (s *FieldCapsService) Fields(fields ...string) *FieldCapsService { + s.fields = append(s.fields, fields...) + return s +} + +// IgnoreUnavailable is documented as: Whether specified concrete indices should be ignored when unavailable (missing or closed). +func (s *FieldCapsService) IgnoreUnavailable(ignoreUnavailable bool) *FieldCapsService { + s.ignoreUnavailable = &ignoreUnavailable + return s +} + +// Pretty indicates that the JSON response be indented and human readable. +func (s *FieldCapsService) Pretty(pretty bool) *FieldCapsService { + s.pretty = pretty + return s +} + +// 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. +func (s *FieldCapsService) BodyJson(body interface{}) *FieldCapsService { + s.bodyJson = body + return s +} + +// 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. +func (s *FieldCapsService) BodyString(body string) *FieldCapsService { + s.bodyString = body + return s +} + +// buildURL builds the URL for the operation. +func (s *FieldCapsService) buildURL() (string, url.Values, error) { + // Build URL + var err error + var path string + if len(s.index) > 0 { + path, err = uritemplates.Expand("/{index}/_field_caps", map[string]string{ + "index": strings.Join(s.index, ","), + }) + } else { + path = "/_field_caps" + } + if err != nil { + return "", url.Values{}, err + } + + // Add query string parameters + params := url.Values{} + if s.pretty { + params.Set("pretty", "true") + } + if s.allowNoIndices != nil { + params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices)) + } + if s.expandWildcards != "" { + params.Set("expand_wildcards", s.expandWildcards) + } + if len(s.fields) > 0 { + params.Set("fields", strings.Join(s.fields, ",")) + } + if s.ignoreUnavailable != nil { + params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable)) + } + return path, params, nil +} + +// Validate checks if the operation is valid. +func (s *FieldCapsService) Validate() error { + return nil +} + +// Do executes the operation. +func (s *FieldCapsService) Do(ctx context.Context) (*FieldCapsResponse, error) { + // Check pre-conditions + if err := s.Validate(); err != nil { + return nil, err + } + + // Get URL for request + path, params, err := s.buildURL() + if err != nil { + return nil, err + } + + // Setup HTTP request body + var body interface{} + if s.bodyJson != nil { + body = s.bodyJson + } else { + body = s.bodyString + } + + // Get HTTP response + res, err := s.client.PerformRequest(ctx, PerformRequestOptions{ + Method: "POST", + Path: path, + Params: params, + Body: body, + IgnoreErrors: []int{http.StatusNotFound}, + }) + if err != nil { + return nil, err + } + + // TODO(oe): Is 404 really a valid response here? + if res.StatusCode == http.StatusNotFound { + return &FieldCapsResponse{}, nil + } + + // Return operation response + ret := new(FieldCapsResponse) + if err := s.client.decoder.Decode(res.Body, ret); err != nil { + return nil, err + } + return ret, nil +} + +// -- Request -- + +// FieldCapsRequest can be used to set up the body to be used in the +// Field Capabilities API. +type FieldCapsRequest struct { + Fields []string `json:"fields"` +} + +// -- Response -- + +// FieldCapsResponse contains field capabilities. +type FieldCapsResponse struct { + Fields map[string]FieldCaps `json:"fields,omitempty"` +} + +// FieldCaps contains capabilities of an individual field. +type FieldCaps struct { + Type string `json:"type"` + Searchable bool `json:"searchable"` + Aggregatable bool `json:"aggregatable"` + Indices []string `json:"indices,omitempty"` + NonSearchableIndices []string `json:"non_searchable_indices,omitempty"` + NonAggregatableIndices []string `json:"non_aggregatable_indices,omitempty"` +} diff --git a/field_caps_test.go b/field_caps_test.go new file mode 100644 index 000000000..e299fd516 --- /dev/null +++ b/field_caps_test.go @@ -0,0 +1,146 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package elastic + +import ( + "context" + "encoding/json" + "net/url" + "reflect" + "sort" + "testing" +) + +func TestFieldCapsURLs(t *testing.T) { + tests := []struct { + Service *FieldCapsService + ExpectedPath string + ExpectedParams url.Values + }{ + { + Service: &FieldCapsService{}, + ExpectedPath: "/_field_caps", + ExpectedParams: url.Values{}, + }, + { + Service: &FieldCapsService{ + index: []string{"index1", "index2"}, + }, + ExpectedPath: "/index1%2Cindex2/_field_caps", + ExpectedParams: url.Values{}, + }, + { + Service: &FieldCapsService{ + index: []string{"index_*"}, + pretty: true, + }, + ExpectedPath: "/index_%2A/_field_caps", + ExpectedParams: url.Values{"pretty": []string{"true"}}, + }, + } + + for _, test := range tests { + gotPath, gotParams, err := test.Service.buildURL() + if err != nil { + t.Fatalf("expected no error; got: %v", err) + } + if gotPath != test.ExpectedPath { + t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath) + } + if gotParams.Encode() != test.ExpectedParams.Encode() { + t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams) + } + } +} + +func TestFieldCapsRequestSerialize(t *testing.T) { + req := &FieldCapsRequest{ + Fields: []string{"creation_date", "answer_count"}, + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("marshaling to JSON failed: %v", err) + } + got := string(data) + expected := `{"fields":["creation_date","answer_count"]}` + if got != expected { + t.Fatalf("expected\n%s\n,got:\n%s", expected, got) + } +} + +func TestFieldCapsRequestDeserialize(t *testing.T) { + body := `{ + "fields" : ["creation_date", "answer_count"] + }` + + var request FieldCapsRequest + if err := json.Unmarshal([]byte(body), &request); err != nil { + t.Fatalf("unexpected error during unmarshalling: %v", err) + } + + sort.Sort(lexicographically{request.Fields}) + + expectedFields := []string{"answer_count", "creation_date"} + if !reflect.DeepEqual(request.Fields, expectedFields) { + t.Fatalf("expected fields to be %v, got %v", expectedFields, request.Fields) + } +} + +func TestFieldCapsResponseUnmarshalling(t *testing.T) { + clusterStats := `{ + "_shards": { + "total": 1, + "successful": 1, + "failed": 0 + }, + "fields": { + "creation_date": { + "type": "date", + "searchable": true, + "aggregatable": true, + "indices": ["index1", "index2"], + "non_searchable_indices": null, + "non_aggregatable_indices": null + }, + "answer": { + "type": "keyword", + "searchable": true, + "aggregatable": true + } + } + }` + + var resp FieldCapsResponse + if err := json.Unmarshal([]byte(clusterStats), &resp); err != nil { + t.Errorf("unexpected error during unmarshalling: %v", err) + } + + caps, ok := resp.Fields["creation_date"] + if !ok { + t.Errorf("expected creation_date to be in the fields map, didn't find it") + } + if want, have := true, caps.Searchable; want != have { + t.Errorf("expected creation_date searchable to be %v, got %v", want, have) + } + if want, have := true, caps.Aggregatable; want != have { + t.Errorf("expected creation_date aggregatable to be %v, got %v", want, have) + } + if want, have := []string{"index1", "index2"}, caps.Indices; !reflect.DeepEqual(want, have) { + t.Errorf("expected creation_date indices to be %v, got %v", want, have) + } +} + +func TestFieldCaps123(t *testing.T) { + client := setupTestClientAndCreateIndexAndAddDocs(t) + // client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", 0))) + + res, err := client.FieldCaps("_all").Fields("user", "message", "retweets", "created").Pretty(true).Do(context.TODO()) + if err != nil { + t.Fatalf("expected no error; got: %v", err) + } + if res == nil { + t.Fatalf("expected response; got: %v", res) + } +} diff --git a/field_stats.go b/field_stats.go deleted file mode 100644 index a25a2677e..000000000 --- a/field_stats.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2012-present Oliver Eilhard. All rights reserved. -// Use of this source code is governed by a MIT-license. -// See http://olivere.mit-license.org/license.txt for details. - -package elastic - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/olivere/elastic/uritemplates" -) - -const ( - FieldStatsClusterLevel = "cluster" - FieldStatsIndicesLevel = "indices" -) - -// FieldStatsService allows finding statistical properties of a field without executing a search, -// but looking up measurements that are natively available in the Lucene index. -// -// See https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-field-stats.html -// for details -type FieldStatsService struct { - client *Client - pretty bool - level string - index []string - allowNoIndices *bool - expandWildcards string - fields []string - ignoreUnavailable *bool - bodyJson interface{} - bodyString string -} - -// NewFieldStatsService creates a new FieldStatsService -func NewFieldStatsService(client *Client) *FieldStatsService { - return &FieldStatsService{ - client: client, - index: make([]string, 0), - fields: make([]string, 0), - } -} - -// Index is a list of index names; use `_all` or empty string to perform -// the operation on all indices. -func (s *FieldStatsService) Index(index ...string) *FieldStatsService { - s.index = append(s.index, index...) - return s -} - -// AllowNoIndices indicates whether to ignore if a wildcard indices expression -// resolves into no concrete indices. -// (This includes `_all` string or when no indices have been specified). -func (s *FieldStatsService) AllowNoIndices(allowNoIndices bool) *FieldStatsService { - s.allowNoIndices = &allowNoIndices - return s -} - -// ExpandWildcards indicates whether to expand wildcard expression to -// concrete indices that are open, closed or both. -func (s *FieldStatsService) ExpandWildcards(expandWildcards string) *FieldStatsService { - s.expandWildcards = expandWildcards - return s -} - -// Fields is a list of fields for to get field statistics -// for (min value, max value, and more). -func (s *FieldStatsService) Fields(fields ...string) *FieldStatsService { - s.fields = append(s.fields, fields...) - return s -} - -// IgnoreUnavailable is documented as: Whether specified concrete indices should be ignored when unavailable (missing or closed). -func (s *FieldStatsService) IgnoreUnavailable(ignoreUnavailable bool) *FieldStatsService { - s.ignoreUnavailable = &ignoreUnavailable - return s -} - -// Level sets if stats should be returned on a per index level or on a cluster wide level; -// should be one of 'cluster' or 'indices'; defaults to former -func (s *FieldStatsService) Level(level string) *FieldStatsService { - s.level = level - return s -} - -// ClusterLevel is a helper that sets Level to "cluster". -func (s *FieldStatsService) ClusterLevel() *FieldStatsService { - s.level = FieldStatsClusterLevel - return s -} - -// IndicesLevel is a helper that sets Level to "indices". -func (s *FieldStatsService) IndicesLevel() *FieldStatsService { - s.level = FieldStatsIndicesLevel - return s -} - -// Pretty indicates that the JSON response be indented and human readable. -func (s *FieldStatsService) Pretty(pretty bool) *FieldStatsService { - s.pretty = pretty - return s -} - -// 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. -func (s *FieldStatsService) BodyJson(body interface{}) *FieldStatsService { - s.bodyJson = body - return s -} - -// 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. -func (s *FieldStatsService) BodyString(body string) *FieldStatsService { - s.bodyString = body - return s -} - -// buildURL builds the URL for the operation. -func (s *FieldStatsService) buildURL() (string, url.Values, error) { - // Build URL - var err error - var path string - if len(s.index) > 0 { - path, err = uritemplates.Expand("/{index}/_field_stats", map[string]string{ - "index": strings.Join(s.index, ","), - }) - } else { - path = "/_field_stats" - } - if err != nil { - return "", url.Values{}, err - } - - // Add query string parameters - params := url.Values{} - if s.allowNoIndices != nil { - params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices)) - } - if s.expandWildcards != "" { - params.Set("expand_wildcards", s.expandWildcards) - } - if len(s.fields) > 0 { - params.Set("fields", strings.Join(s.fields, ",")) - } - if s.ignoreUnavailable != nil { - params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable)) - } - if s.level != "" { - params.Set("level", s.level) - } - return path, params, nil -} - -// Validate checks if the operation is valid. -func (s *FieldStatsService) Validate() error { - var invalid []string - if s.level != "" && (s.level != FieldStatsIndicesLevel && s.level != FieldStatsClusterLevel) { - invalid = append(invalid, "Level") - } - if len(invalid) != 0 { - return fmt.Errorf("missing or invalid required fields: %v", invalid) - } - return nil -} - -// Do executes the operation. -func (s *FieldStatsService) Do(ctx context.Context) (*FieldStatsResponse, error) { - // Check pre-conditions - if err := s.Validate(); err != nil { - return nil, err - } - - // Get URL for request - path, params, err := s.buildURL() - if err != nil { - return nil, err - } - - // Setup HTTP request body - var body interface{} - if s.bodyJson != nil { - body = s.bodyJson - } else { - body = s.bodyString - } - - // Get HTTP response - res, err := s.client.PerformRequest(ctx, PerformRequestOptions{ - Method: "POST", - Path: path, - Params: params, - Body: body, - IgnoreErrors: []int{http.StatusNotFound}, - }) - if err != nil { - return nil, err - } - - // TODO(oe): Is 404 really a valid response here? - if res.StatusCode == http.StatusNotFound { - return &FieldStatsResponse{make(map[string]IndexFieldStats)}, nil - } - - // Return operation response - ret := new(FieldStatsResponse) - if err := s.client.decoder.Decode(res.Body, ret); err != nil { - return nil, err - } - return ret, nil -} - -// -- Request -- - -// FieldStatsRequest can be used to set up the body to be used in the -// Field Stats API. -type FieldStatsRequest struct { - Fields []string `json:"fields"` - IndexConstraints map[string]*FieldStatsConstraints `json:"index_constraints,omitempty"` -} - -// FieldStatsConstraints is a constraint on a field. -type FieldStatsConstraints struct { - Min *FieldStatsComparison `json:"min_value,omitempty"` - Max *FieldStatsComparison `json:"max_value,omitempty"` -} - -// FieldStatsComparison contain all comparison operations that can be used -// in FieldStatsConstraints. -type FieldStatsComparison struct { - Lte interface{} `json:"lte,omitempty"` - Lt interface{} `json:"lt,omitempty"` - Gte interface{} `json:"gte,omitempty"` - Gt interface{} `json:"gt,omitempty"` -} - -// -- Response -- - -// FieldStatsResponse is the response body content -type FieldStatsResponse struct { - Indices map[string]IndexFieldStats `json:"indices,omitempty"` -} - -// IndexFieldStats contains field stats for an index -type IndexFieldStats struct { - Fields map[string]FieldStats `json:"fields,omitempty"` -} - -// FieldStats contains stats of an individual field -type FieldStats struct { - Type string `json:"type"` - MaxDoc int64 `json:"max_doc"` - DocCount int64 `json:"doc_count"` - Density int64 `json:"density"` - SumDocFrequeny int64 `json:"sum_doc_freq"` - SumTotalTermFrequency int64 `json:"sum_total_term_freq"` - Searchable bool `json:"searchable"` - Aggregatable bool `json:"aggregatable"` - MinValue interface{} `json:"min_value"` - MinValueAsString string `json:"min_value_as_string"` - MaxValue interface{} `json:"max_value"` - MaxValueAsString string `json:"max_value_as_string"` -} diff --git a/field_stats_test.go b/field_stats_test.go deleted file mode 100644 index 48e973840..000000000 --- a/field_stats_test.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2012-present Oliver Eilhard. All rights reserved. -// Use of this source code is governed by a MIT-license. -// See http://olivere.mit-license.org/license.txt for details. - -package elastic - -import ( - "encoding/json" - "net/url" - "reflect" - "sort" - "testing" -) - -func TestFieldStatsURLs(t *testing.T) { - tests := []struct { - Service *FieldStatsService - ExpectedPath string - ExpectedParams url.Values - }{ - { - Service: &FieldStatsService{}, - ExpectedPath: "/_field_stats", - ExpectedParams: url.Values{}, - }, - { - Service: &FieldStatsService{ - level: FieldStatsClusterLevel, - }, - ExpectedPath: "/_field_stats", - ExpectedParams: url.Values{"level": []string{FieldStatsClusterLevel}}, - }, - { - Service: &FieldStatsService{ - level: FieldStatsIndicesLevel, - }, - ExpectedPath: "/_field_stats", - ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}}, - }, - { - Service: &FieldStatsService{ - level: FieldStatsClusterLevel, - index: []string{"index1"}, - }, - ExpectedPath: "/index1/_field_stats", - ExpectedParams: url.Values{"level": []string{FieldStatsClusterLevel}}, - }, - { - Service: &FieldStatsService{ - level: FieldStatsIndicesLevel, - index: []string{"index1", "index2"}, - }, - ExpectedPath: "/index1%2Cindex2/_field_stats", - ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}}, - }, - { - Service: &FieldStatsService{ - level: FieldStatsIndicesLevel, - index: []string{"index_*"}, - }, - ExpectedPath: "/index_%2A/_field_stats", - ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}}, - }, - } - - for _, test := range tests { - gotPath, gotParams, err := test.Service.buildURL() - if err != nil { - t.Fatalf("expected no error; got: %v", err) - } - if gotPath != test.ExpectedPath { - t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath) - } - if gotParams.Encode() != test.ExpectedParams.Encode() { - t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams) - } - } -} - -func TestFieldStatsValidate(t *testing.T) { - tests := []struct { - Service *FieldStatsService - Valid bool - }{ - { - Service: &FieldStatsService{}, - Valid: true, - }, - { - Service: &FieldStatsService{ - fields: []string{"field"}, - }, - Valid: true, - }, - { - Service: &FieldStatsService{ - bodyJson: &FieldStatsRequest{ - Fields: []string{"field"}, - }, - }, - Valid: true, - }, - { - Service: &FieldStatsService{ - level: FieldStatsClusterLevel, - bodyJson: &FieldStatsRequest{ - Fields: []string{"field"}, - }, - }, - Valid: true, - }, - { - Service: &FieldStatsService{ - level: FieldStatsIndicesLevel, - bodyJson: &FieldStatsRequest{ - Fields: []string{"field"}, - }, - }, - Valid: true, - }, - { - Service: &FieldStatsService{ - level: "random", - }, - Valid: false, - }, - } - - for _, test := range tests { - err := test.Service.Validate() - isValid := err == nil - if isValid != test.Valid { - t.Errorf("expected validity to be %v, got %v", test.Valid, isValid) - } - } -} - -func TestFieldStatsRequestSerialize(t *testing.T) { - req := &FieldStatsRequest{ - Fields: []string{"creation_date", "answer_count"}, - IndexConstraints: map[string]*FieldStatsConstraints{ - "creation_date": &FieldStatsConstraints{ - Min: &FieldStatsComparison{Gte: "2014-01-01T00:00:00.000Z"}, - Max: &FieldStatsComparison{Lt: "2015-01-01T10:00:00.000Z"}, - }, - }, - } - data, err := json.Marshal(req) - if err != nil { - t.Fatalf("marshaling to JSON failed: %v", err) - } - got := string(data) - expected := `{"fields":["creation_date","answer_count"],"index_constraints":{"creation_date":{"min_value":{"gte":"2014-01-01T00:00:00.000Z"},"max_value":{"lt":"2015-01-01T10:00:00.000Z"}}}}` - if got != expected { - t.Errorf("expected\n%s\n,got:\n%s", expected, got) - } -} - -func TestFieldStatsRequestDeserialize(t *testing.T) { - body := `{ - "fields" : ["creation_date", "answer_count"], - "index_constraints" : { - "creation_date" : { - "min_value" : { - "gte" : "2014-01-01T00:00:00.000Z" - }, - "max_value" : { - "lt" : "2015-01-01T10:00:00.000Z" - } - } - } - }` - - var request FieldStatsRequest - if err := json.Unmarshal([]byte(body), &request); err != nil { - t.Errorf("unexpected error during unmarshalling: %v", err) - } - - sort.Sort(lexicographically{request.Fields}) - - expectedFields := []string{"answer_count", "creation_date"} - if !reflect.DeepEqual(request.Fields, expectedFields) { - t.Errorf("expected fields to be %v, got %v", expectedFields, request.Fields) - } - - constraints, ok := request.IndexConstraints["creation_date"] - if !ok { - t.Errorf("expected field creation_date, didn't find it!") - } - if constraints.Min.Lt != nil { - t.Errorf("expected min value less than constraint to be empty, got %v", constraints.Min.Lt) - } - if constraints.Min.Gte != "2014-01-01T00:00:00.000Z" { - t.Errorf("expected min value >= %v, found %v", "2014-01-01T00:00:00.000Z", constraints.Min.Gte) - } - if constraints.Max.Lt != "2015-01-01T10:00:00.000Z" { - t.Errorf("expected max value < %v, found %v", "2015-01-01T10:00:00.000Z", constraints.Max.Lt) - } -} - -func TestFieldStatsResponseUnmarshalling(t *testing.T) { - clusterStats := `{ - "_shards": { - "total": 1, - "successful": 1, - "failed": 0 - }, - "indices": { - "_all": { - "fields": { - "creation_date": { - "type": "date", - "max_doc": 1326564, - "doc_count": 564633, - "density": 42, - "sum_doc_freq": 2258532, - "sum_total_term_freq": -1, - "searchable": true, - "aggregatable": true, - "min_value":1483016404000, - "min_value_as_string": "2016-12-29T13:00:04.000Z", - "max_value":1484152326000, - "max_value_as_string": "2017-01-11T16:32:06.000Z" - }, - "answer_count": { - "max_doc": 1326564, - "doc_count": 139885, - "density": 10, - "sum_doc_freq": 559540, - "sum_total_term_freq": -1, - "searchable": true, - "aggregatable": true, - "min_value":1483016404000, - "min_value_as_string": "2016-12-29T13:00:04.000Z", - "max_value":1484152326000, - "max_value_as_string": "2017-01-11T16:32:06.000Z" - } - } - } - } - }` - - var response FieldStatsResponse - if err := json.Unmarshal([]byte(clusterStats), &response); err != nil { - t.Errorf("unexpected error during unmarshalling: %v", err) - } - - stats, ok := response.Indices["_all"] - if !ok { - t.Errorf("expected _all to be in the indices map, didn't find it") - } - - fieldStats, ok := stats.Fields["creation_date"] - if !ok { - t.Errorf("expected creation_date to be in the fields map, didn't find it") - } - if want, have := true, fieldStats.Searchable; want != have { - t.Errorf("expected creation_date searchable to be %v, got %v", want, have) - } - if want, have := true, fieldStats.Aggregatable; want != have { - t.Errorf("expected creation_date aggregatable to be %v, got %v", want, have) - } - if want, have := "2016-12-29T13:00:04.000Z", fieldStats.MinValueAsString; want != have { - t.Errorf("expected creation_date min value string to be %q, got %q", want, have) - } -} - -type lexicographically struct { - strings []string -} - -func (l lexicographically) Len() int { - return len(l.strings) -} - -func (l lexicographically) Less(i, j int) bool { - return l.strings[i] < l.strings[j] -} - -func (l lexicographically) Swap(i, j int) { - l.strings[i], l.strings[j] = l.strings[j], l.strings[i] -} diff --git a/setup_test.go b/setup_test.go index de1d0042d..480ae5d20 100644 --- a/setup_test.go +++ b/setup_test.go @@ -427,3 +427,19 @@ func randomString(n int) string { } return string(b) } + +type lexicographically struct { + strings []string +} + +func (l lexicographically) Len() int { + return len(l.strings) +} + +func (l lexicographically) Less(i, j int) bool { + return l.strings[i] < l.strings[j] +} + +func (l lexicographically) Swap(i, j int) { + l.strings[i], l.strings[j] = l.strings[j], l.strings[i] +}