-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
6 changed files
with
367 additions
and
550 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
Oops, something went wrong.