1// Copyright 2012-2015 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	"fmt"
9	"net/url"
10	"strings"
11
12	"golang.org/x/net/context"
13
14	"gopkg.in/olivere/elastic.v3/uritemplates"
15)
16
17// DeleteByQueryService deletes documents that match a query.
18// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/master/docs-delete-by-query.html.
19type DeleteByQueryService struct {
20	client            *Client
21	indices           []string
22	types             []string
23	analyzer          string
24	consistency       string
25	defaultOper       string
26	df                string
27	ignoreUnavailable *bool
28	allowNoIndices    *bool
29	expandWildcards   string
30	replication       string
31	routing           string
32	timeout           string
33	pretty            bool
34	q                 string
35	query             Query
36}
37
38// NewDeleteByQueryService creates a new DeleteByQueryService.
39// You typically use the client's DeleteByQuery to get a reference to
40// the service.
41func NewDeleteByQueryService(client *Client) *DeleteByQueryService {
42	builder := &DeleteByQueryService{
43		client: client,
44	}
45	return builder
46}
47
48// Index sets the indices on which to perform the delete operation.
49func (s *DeleteByQueryService) Index(indices ...string) *DeleteByQueryService {
50	if s.indices == nil {
51		s.indices = make([]string, 0)
52	}
53	s.indices = append(s.indices, indices...)
54	return s
55}
56
57// Type limits the delete operation to the given types.
58func (s *DeleteByQueryService) Type(types ...string) *DeleteByQueryService {
59	if s.types == nil {
60		s.types = make([]string, 0)
61	}
62	s.types = append(s.types, types...)
63	return s
64}
65
66// Analyzer to use for the query string.
67func (s *DeleteByQueryService) Analyzer(analyzer string) *DeleteByQueryService {
68	s.analyzer = analyzer
69	return s
70}
71
72// Consistency represents the specific write consistency setting for the operation.
73// It can be one, quorum, or all.
74func (s *DeleteByQueryService) Consistency(consistency string) *DeleteByQueryService {
75	s.consistency = consistency
76	return s
77}
78
79// DefaultOperator for query string query (AND or OR).
80func (s *DeleteByQueryService) DefaultOperator(defaultOperator string) *DeleteByQueryService {
81	s.defaultOper = defaultOperator
82	return s
83}
84
85// DF is the field to use as default where no field prefix is given in the query string.
86func (s *DeleteByQueryService) DF(defaultField string) *DeleteByQueryService {
87	s.df = defaultField
88	return s
89}
90
91// DefaultField is the field to use as default where no field prefix is given in the query string.
92// It is an alias to the DF func.
93func (s *DeleteByQueryService) DefaultField(defaultField string) *DeleteByQueryService {
94	s.df = defaultField
95	return s
96}
97
98// IgnoreUnavailable indicates whether specified concrete indices should be
99// ignored when unavailable (missing or closed).
100func (s *DeleteByQueryService) IgnoreUnavailable(ignore bool) *DeleteByQueryService {
101	s.ignoreUnavailable = &ignore
102	return s
103}
104
105// AllowNoIndices indicates whether to ignore if a wildcard indices
106// expression resolves into no concrete indices (including the _all string
107// or when no indices have been specified).
108func (s *DeleteByQueryService) AllowNoIndices(allow bool) *DeleteByQueryService {
109	s.allowNoIndices = &allow
110	return s
111}
112
113// ExpandWildcards indicates whether to expand wildcard expression to
114// concrete indices that are open, closed or both. It can be "open" or "closed".
115func (s *DeleteByQueryService) ExpandWildcards(expand string) *DeleteByQueryService {
116	s.expandWildcards = expand
117	return s
118}
119
120// Replication sets a specific replication type (sync or async).
121func (s *DeleteByQueryService) Replication(replication string) *DeleteByQueryService {
122	s.replication = replication
123	return s
124}
125
126// Q specifies the query in Lucene query string syntax. You can also use
127// Query to programmatically specify the query.
128func (s *DeleteByQueryService) Q(query string) *DeleteByQueryService {
129	s.q = query
130	return s
131}
132
133// QueryString is an alias to Q. Notice that you can also use Query to
134// programmatically set the query.
135func (s *DeleteByQueryService) QueryString(query string) *DeleteByQueryService {
136	s.q = query
137	return s
138}
139
140// Routing sets a specific routing value.
141func (s *DeleteByQueryService) Routing(routing string) *DeleteByQueryService {
142	s.routing = routing
143	return s
144}
145
146// Timeout sets an explicit operation timeout, e.g. "1s" or "10000ms".
147func (s *DeleteByQueryService) Timeout(timeout string) *DeleteByQueryService {
148	s.timeout = timeout
149	return s
150}
151
152// Pretty indents the JSON output from Elasticsearch.
153func (s *DeleteByQueryService) Pretty(pretty bool) *DeleteByQueryService {
154	s.pretty = pretty
155	return s
156}
157
158// Query sets the query programmatically.
159func (s *DeleteByQueryService) Query(query Query) *DeleteByQueryService {
160	s.query = query
161	return s
162}
163
164// Do executes the delete-by-query operation.
165func (s *DeleteByQueryService) Do() (*DeleteByQueryResult, error) {
166	return s.DoC(nil)
167}
168
169// DoC executes the delete-by-query operation.
170func (s *DeleteByQueryService) DoC(ctx context.Context) (*DeleteByQueryResult, error) {
171	var err error
172
173	// Build url
174	path := "/"
175
176	// Indices part
177	var indexPart []string
178	for _, index := range s.indices {
179		index, err = uritemplates.Expand("{index}", map[string]string{
180			"index": index,
181		})
182		if err != nil {
183			return nil, err
184		}
185		indexPart = append(indexPart, index)
186	}
187	if len(indexPart) > 0 {
188		path += strings.Join(indexPart, ",")
189	}
190
191	// Types part
192	var typesPart []string
193	for _, typ := range s.types {
194		typ, err = uritemplates.Expand("{type}", map[string]string{
195			"type": typ,
196		})
197		if err != nil {
198			return nil, err
199		}
200		typesPart = append(typesPart, typ)
201	}
202	if len(typesPart) > 0 {
203		path += "/" + strings.Join(typesPart, ",")
204	}
205
206	// Search
207	path += "/_query"
208
209	// Parameters
210	params := make(url.Values)
211	if s.analyzer != "" {
212		params.Set("analyzer", s.analyzer)
213	}
214	if s.consistency != "" {
215		params.Set("consistency", s.consistency)
216	}
217	if s.defaultOper != "" {
218		params.Set("default_operator", s.defaultOper)
219	}
220	if s.df != "" {
221		params.Set("df", s.df)
222	}
223	if s.ignoreUnavailable != nil {
224		params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
225	}
226	if s.allowNoIndices != nil {
227		params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
228	}
229	if s.expandWildcards != "" {
230		params.Set("expand_wildcards", s.expandWildcards)
231	}
232	if s.replication != "" {
233		params.Set("replication", s.replication)
234	}
235	if s.routing != "" {
236		params.Set("routing", s.routing)
237	}
238	if s.timeout != "" {
239		params.Set("timeout", s.timeout)
240	}
241	if s.pretty {
242		params.Set("pretty", fmt.Sprintf("%v", s.pretty))
243	}
244	if s.q != "" {
245		params.Set("q", s.q)
246	}
247
248	// Set body if there is a query set
249	var body interface{}
250	if s.query != nil {
251		src, err := s.query.Source()
252		if err != nil {
253			return nil, err
254		}
255		query := make(map[string]interface{})
256		query["query"] = src
257		body = query
258	}
259
260	// Get response
261	res, err := s.client.PerformRequestC(ctx, "DELETE", path, params, body)
262	if err != nil {
263		return nil, err
264	}
265
266	// Return result
267	ret := new(DeleteByQueryResult)
268	if err := s.client.decoder.Decode(res.Body, ret); err != nil {
269		return nil, err
270	}
271	return ret, nil
272}
273
274// DeleteByQueryResult is the outcome of executing Do with DeleteByQueryService.
275type DeleteByQueryResult struct {
276	Took     int64                               `json:"took"`
277	TimedOut bool                                `json:"timed_out"`
278	Indices  map[string]IndexDeleteByQueryResult `json:"_indices"`
279	Failures []shardOperationFailure             `json:"failures"`
280}
281
282// IndexNames returns the names of the indices the DeleteByQuery touched.
283func (res DeleteByQueryResult) IndexNames() []string {
284	var indices []string
285	for index := range res.Indices {
286		indices = append(indices, index)
287	}
288	return indices
289}
290
291// All returns the index delete-by-query result of all indices.
292func (res DeleteByQueryResult) All() IndexDeleteByQueryResult {
293	all, _ := res.Indices["_all"]
294	return all
295}
296
297// IndexDeleteByQueryResult is the result of a delete-by-query for a specific
298// index.
299type IndexDeleteByQueryResult struct {
300	// Found documents, matching the query.
301	Found int `json:"found"`
302	// Deleted documents, successfully, from the given index.
303	Deleted int `json:"deleted"`
304	// Missing documents when trying to delete them.
305	Missing int `json:"missing"`
306	// Failed documents to be deleted for the given index.
307	Failed int `json:"failed"`
308}
309