chewy_query 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +37 -0
  7. data/Rakefile +2 -0
  8. data/chewy_query.gemspec +27 -0
  9. data/lib/chewy_query.rb +12 -0
  10. data/lib/chewy_query/builder.rb +865 -0
  11. data/lib/chewy_query/builder/compose.rb +64 -0
  12. data/lib/chewy_query/builder/criteria.rb +182 -0
  13. data/lib/chewy_query/builder/filters.rb +227 -0
  14. data/lib/chewy_query/builder/nodes/and.rb +26 -0
  15. data/lib/chewy_query/builder/nodes/base.rb +17 -0
  16. data/lib/chewy_query/builder/nodes/bool.rb +33 -0
  17. data/lib/chewy_query/builder/nodes/equal.rb +34 -0
  18. data/lib/chewy_query/builder/nodes/exists.rb +20 -0
  19. data/lib/chewy_query/builder/nodes/expr.rb +28 -0
  20. data/lib/chewy_query/builder/nodes/field.rb +106 -0
  21. data/lib/chewy_query/builder/nodes/has_child.rb +14 -0
  22. data/lib/chewy_query/builder/nodes/has_parent.rb +14 -0
  23. data/lib/chewy_query/builder/nodes/has_relation.rb +61 -0
  24. data/lib/chewy_query/builder/nodes/match_all.rb +11 -0
  25. data/lib/chewy_query/builder/nodes/missing.rb +20 -0
  26. data/lib/chewy_query/builder/nodes/not.rb +26 -0
  27. data/lib/chewy_query/builder/nodes/or.rb +26 -0
  28. data/lib/chewy_query/builder/nodes/prefix.rb +18 -0
  29. data/lib/chewy_query/builder/nodes/query.rb +20 -0
  30. data/lib/chewy_query/builder/nodes/range.rb +63 -0
  31. data/lib/chewy_query/builder/nodes/raw.rb +15 -0
  32. data/lib/chewy_query/builder/nodes/regexp.rb +33 -0
  33. data/lib/chewy_query/builder/nodes/script.rb +20 -0
  34. data/lib/chewy_query/version.rb +3 -0
  35. data/spec/chewy_query/builder/context_spec.rb +529 -0
  36. data/spec/chewy_query/builder/filters_spec.rb +181 -0
  37. data/spec/chewy_query/builder/nodes/and_spec.rb +16 -0
  38. data/spec/chewy_query/builder/nodes/bool_spec.rb +22 -0
  39. data/spec/chewy_query/builder/nodes/equal_spec.rb +58 -0
  40. data/spec/chewy_query/builder/nodes/exists_spec.rb +16 -0
  41. data/spec/chewy_query/builder/nodes/has_child_spec.rb +79 -0
  42. data/spec/chewy_query/builder/nodes/has_parent_spec.rb +84 -0
  43. data/spec/chewy_query/builder/nodes/match_all_spec.rb +11 -0
  44. data/spec/chewy_query/builder/nodes/missing_spec.rb +14 -0
  45. data/spec/chewy_query/builder/nodes/not_spec.rb +14 -0
  46. data/spec/chewy_query/builder/nodes/or_spec.rb +16 -0
  47. data/spec/chewy_query/builder/nodes/prefix_spec.rb +15 -0
  48. data/spec/chewy_query/builder/nodes/query_spec.rb +17 -0
  49. data/spec/chewy_query/builder/nodes/range_spec.rb +36 -0
  50. data/spec/chewy_query/builder/nodes/raw_spec.rb +11 -0
  51. data/spec/chewy_query/builder/nodes/regexp_spec.rb +45 -0
  52. data/spec/chewy_query/builder/nodes/script_spec.rb +16 -0
  53. data/spec/chewy_query/builder_spec.rb +196 -0
  54. data/spec/chewy_query_spec.rb +0 -0
  55. data/spec/spec_helper.rb +8 -0
  56. metadata +191 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50cc4c9404f4fec945f7e1de80998603db979fe3
4
+ data.tar.gz: a4507b1b3aefe9a7924dcb487d248c4bac2de514
5
+ SHA512:
6
+ metadata.gz: 0258c2c32f63a25f7e249b30b0dcf878263d3a78eea13b34dfed4b3bed4a835a9205edd954c0e4053cda2642d55618e2956f4ab6b6e96b695aafdd55639c58ff
7
+ data.tar.gz: 6c84d4f21a27d42e1439b49e24749ff1f4cecabaf4c32fb0ef734aaa813a073fa5faeaef220ec2d40d61cc8ee0e996e51ec853cca9f550967e80e6e2d0d7cd6c
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --backtrace
3
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chewy_query.gemspec
4
+ gemspec
5
+
6
+ gem 'pry'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 undr
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # ChewyQuery
2
+
3
+ The query builder for ElasticSearch which was extracted from [chewy](https://github.com/toptal/chewy) gem.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'chewy_query'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install chewy_query
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ See [chewy querying](https://github.com/toptal/chewy#index-querying).
28
+
29
+ Many thanks to @pyromaniac for his [chewy](https://github.com/toptal/chewy).
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/undr/chewy_query/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chewy_query/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'chewy_query'
8
+ spec.version = ChewyQuery::VERSION
9
+ spec.authors = ['pyromaniac', 'undr']
10
+ spec.email = ['kinwizard@gmail.com', 'undr@yandex.ru']
11
+ spec.summary = %q{The query builder for ElasticSearch which was extracted from Chewy.}
12
+ spec.description = %q{The query builder for ElasticSearch which was extracted from Chewy.}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activesupport', '>= 3.2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.6'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'its'
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'active_support/concern'
4
+ require 'active_support/json'
5
+ require 'i18n/core_ext/hash'
6
+
7
+ require "chewy_query/version"
8
+ require 'chewy_query/builder'
9
+
10
+ module ChewyQuery
11
+
12
+ end
@@ -0,0 +1,865 @@
1
+ require 'chewy_query/builder/criteria'
2
+ require 'chewy_query/builder/filters'
3
+
4
+ module ChewyQuery
5
+ # Builder allows you to create ES search requests with convenient
6
+ # chainable DSL. Queries are lazy evaluated and might be merged.
7
+ #
8
+ # builder = ChewyQuery::Builder.new(:users, types: ['admin', 'manager', 'user'])
9
+ # builder.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
10
+ # builder = ChewyQuery::Builder.new(:users, types: 'admin')
11
+ # builder.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
12
+ #
13
+ class Builder
14
+ attr_reader :index, :options, :criteria
15
+
16
+ def initialize(index, options = {})
17
+ @index, @options = index, options
18
+ @types = Array.wrap(options.delete(:types))
19
+ @criteria = Criteria.new(options)
20
+ reset
21
+ end
22
+
23
+ # Comparation with other query or collection
24
+ # If other is collection - search request is executed and
25
+ # result is used for comparation
26
+ #
27
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Johny'}) # => true
28
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Johny'}).to_a # => true
29
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Winnie'}) # => false
30
+ #
31
+ def ==(other)
32
+ super || if other.is_a?(self.class)
33
+ other.criteria == criteria
34
+ else
35
+ to_a == other
36
+ end
37
+ end
38
+
39
+ # Adds <tt>explain</tt> parameter to search request.
40
+ #
41
+ # builder.filter(term: {name: 'Johny'}).explain
42
+ # builder.filter(term: {name: 'Johny'}).explain(true)
43
+ # builder.filter(term: {name: 'Johny'}).explain(false)
44
+ #
45
+ # Calling explain without any arguments sets explanation flag to true.
46
+ #
47
+ # builder.filter(term: {name: 'Johny'}).explain
48
+ #
49
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-explain.html
50
+ #
51
+ def explain(value = nil)
52
+ chain{ criteria.update_request_options explain: (value.nil? ? true : value) }
53
+ end
54
+
55
+ # Adds <tt>version</tt> parameter to search request.
56
+ #
57
+ # builder.filter(term: {name: 'Johny'}).version
58
+ # builder.filter(term: {name: 'Johny'}).version(true)
59
+ # builder.filter(term: {name: 'Johny'}).version(false)
60
+ #
61
+ # Calling version without any arguments sets version flag to true.
62
+ #
63
+ # builder.filter(term: {name: 'Johny'}).version
64
+ #
65
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-version.html
66
+ #
67
+ def version(value = nil)
68
+ chain{ criteria.update_request_options version: (value.nil? ? true : value) }
69
+ end
70
+
71
+ # Sets query compilation mode for search request.
72
+ # Not used if only one filter for search is specified.
73
+ # Possible values:
74
+ #
75
+ # * <tt>:must</tt>
76
+ # Default value. Query compiles into a bool <tt>must</tt> query.
77
+ #
78
+ # Ex:
79
+ #
80
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
81
+ # # => {body: {
82
+ # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
83
+ # }}
84
+ #
85
+ # * <tt>:should</tt>
86
+ # Query compiles into a bool <tt>should</tt> query.
87
+ #
88
+ # Ex:
89
+ #
90
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:should)
91
+ # # => {body: {
92
+ # query: {bool: {should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
93
+ # }}
94
+ #
95
+ # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
96
+ # Query compiles into a bool <tt>should</tt> query with <tt>minimum_should_match</tt> set.
97
+ #
98
+ # Ex:
99
+ #
100
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode('50%')
101
+ # # => {body: {
102
+ # query: {bool: {
103
+ # should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
104
+ # minimum_should_match: '50%'
105
+ # }}
106
+ # }}
107
+ #
108
+ # * <tt>:dis_max</tt>
109
+ # Query compiles into a <tt>dis_max</tt> query.
110
+ #
111
+ # Ex:
112
+ #
113
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:dis_max)
114
+ # # => {body: {
115
+ # query: {dis_max: {queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
116
+ # }}
117
+ #
118
+ # * Any Float value (0.0, 0.7, 1.0)
119
+ # Query compiles into a <tt>dis_max</tt> query with <tt>tie_breaker</tt> option set.
120
+ #
121
+ # Ex:
122
+ #
123
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(0.7)
124
+ # # => {body: {
125
+ # query: {dis_max: {
126
+ # queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
127
+ # tie_breaker: 0.7
128
+ # }}
129
+ # }}
130
+ #
131
+ # Default value for <tt>:query_mode</tt> might be changed
132
+ # with <tt>ChewyQuery.query_mode</tt> config option.
133
+ #
134
+ # ChewyQuery.query_mode = :dis_max
135
+ # ChewyQuery.query_mode = '50%'
136
+ #
137
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html
138
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
139
+ #
140
+ def query_mode(value)
141
+ chain{ criteria.update_options query_mode: value }
142
+ end
143
+
144
+ # Sets query compilation mode for search request.
145
+ # Not used if only one filter for search is specified.
146
+ # Possible values:
147
+ #
148
+ # * <tt>:and</tt>
149
+ # Default value. Filter compiles into an <tt>and</tt> filter.
150
+ #
151
+ # Ex:
152
+ #
153
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }
154
+ # # => {body: {query: {filtered: {
155
+ # query: {...},
156
+ # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
157
+ # }}}}
158
+ #
159
+ # * <tt>:or</tt>
160
+ # Filter compiles into an <tt>or</tt> filter.
161
+ #
162
+ # Ex:
163
+ #
164
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:or)
165
+ # # => {body: {query: {filtered: {
166
+ # query: {...},
167
+ # filter: {or: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
168
+ # }}}}
169
+ #
170
+ # * <tt>:must</tt>
171
+ # Filter compiles into a bool <tt>must</tt> filter.
172
+ #
173
+ # Ex:
174
+ #
175
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:must)
176
+ # # => {body: {query: {filtered: {
177
+ # query: {...},
178
+ # filter: {bool: {must: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
179
+ # }}}}
180
+ #
181
+ # * <tt>:should</tt>
182
+ # Filter compiles into a bool <tt>should</tt> filter.
183
+ #
184
+ # Ex:
185
+ #
186
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:should)
187
+ # # => {body: {query: {filtered: {
188
+ # query: {...},
189
+ # filter: {bool: {should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
190
+ # }}}}
191
+ #
192
+ # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
193
+ # Filter compiles into bool <tt>should</tt> filter with <tt>minimum_should_match</tt> set.
194
+ #
195
+ # Ex:
196
+ #
197
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode('50%')
198
+ # # => {body: {query: {filtered: {
199
+ # query: {...},
200
+ # filter: {bool: {
201
+ # should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
202
+ # minimum_should_match: '50%'
203
+ # }}
204
+ # }}}}
205
+ #
206
+ # Default value for <tt>:filter_mode</tt> might be changed
207
+ # with <tt>ChewyQuery.filter_mode</tt> config option.
208
+ #
209
+ # ChewyQuery.filter_mode = :should
210
+ # ChewyQuery.filter_mode = '50%'
211
+ #
212
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-and-filter.html
213
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-filter.html
214
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-or-filter.html
215
+ #
216
+ def filter_mode(value)
217
+ chain{ criteria.update_options filter_mode: value }
218
+ end
219
+
220
+ # Acts the same way as `filter_mode`, but used for `post_filter`.
221
+ # Note that it fallbacks by default to `ChewyQuery.filter_mode` if
222
+ # `ChewyQuery.post_filter_mode` is nil.
223
+ #
224
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:and)
225
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:should)
226
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode('50%')
227
+ #
228
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-and-filter.html
229
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-filter.html
230
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-or-filter.html
231
+ #
232
+ def post_filter_mode(value)
233
+ chain{ criteria.update_options post_filter_mode: value }
234
+ end
235
+
236
+ # Sets elasticsearch <tt>size</tt> search request param
237
+ # Default value is set in the elasticsearch and is 10.
238
+ #
239
+ # builder.filter{ name == 'Johny' }.limit(100)
240
+ # # => {body: {
241
+ # query: {...},
242
+ # size: 100
243
+ # }}
244
+ #
245
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
246
+ #
247
+ def limit(value)
248
+ chain{ criteria.update_request_options size: Integer(value) }
249
+ end
250
+
251
+ # Sets elasticsearch <tt>from</tt> search request param
252
+ #
253
+ # builder.filter{ name == 'Johny' }.offset(300)
254
+ # # => {body: {
255
+ # query: {...},
256
+ # from: 300
257
+ # }}
258
+ #
259
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
260
+ #
261
+ def offset(value)
262
+ chain{ criteria.update_request_options from: Integer(value) }
263
+ end
264
+
265
+ # Elasticsearch highlight query option support
266
+ #
267
+ # builder.query(...).highlight(fields: { ... })
268
+ #
269
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html
270
+ #
271
+ def highlight(value)
272
+ chain{ criteria.update_request_options highlight: value }
273
+ end
274
+
275
+ # Elasticsearch rescore query option support
276
+ #
277
+ # builder.query(...).rescore(query: { ... })
278
+ #
279
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-rescore.html
280
+ #
281
+ def rescore(value)
282
+ chain{ criteria.update_request_options(rescore: value) }
283
+ end
284
+
285
+ # Adds facets section to the search request.
286
+ # All the chained facets a merged and added to the
287
+ # search request
288
+ #
289
+ # builder.facets(tags: {terms: {field: 'tags'}}).facets(ages: {terms: {field: 'age'}})
290
+ # # => {body: {
291
+ # query: {...},
292
+ # facets: {tags: {terms: {field: 'tags'}}, ages: {terms: {field: 'age'}}}
293
+ # }}
294
+ #
295
+ # If called parameterless - returns result facets from ES performing request.
296
+ # Returns empty hash if no facets was requested or resulted.
297
+ #
298
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html
299
+ #
300
+ def facets(params)
301
+ chain{ criteria.update_facets(params) }
302
+ end
303
+
304
+ # Adds a script function to score the search request. All scores are
305
+ # added to the search request and combinded according to
306
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
307
+ #
308
+ # builder.script_score("doc['boost'].value", filter: { term: {foo: :bar} })
309
+ # # => {body:
310
+ # query: {
311
+ # function_score: {
312
+ # query: { ...},
313
+ # functions: [{
314
+ # script_score: {
315
+ # script: "doc['boost'].value"
316
+ # },
317
+ # filter: { term: { foo: :bar } }
318
+ # }
319
+ # }]
320
+ # } } }
321
+ #
322
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_script_score
323
+ #
324
+ def script_score(script, options = {})
325
+ scoring = options.merge(script_score: { script: script })
326
+ chain{ criteria.update_scores(scoring) }
327
+ end
328
+
329
+ # Adds a boost factor to the search request. All scores are
330
+ # added to the search request and combinded according to
331
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
332
+ #
333
+ # This probably only makes sense if you specifiy a filter
334
+ # for the boost factor as well
335
+ #
336
+ # builder.boost_factor(23, filter: { term: { foo: :bar} })
337
+ # # => {body:
338
+ # query: {
339
+ # function_score: {
340
+ # query: { ...},
341
+ # functions: [{
342
+ # boost_factor: 23,
343
+ # filter: { term: { foo: :bar } }
344
+ # }]
345
+ # } } }
346
+ #
347
+ # @see
348
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_boost_factor
349
+ #
350
+ def boost_factor(factor, options = {})
351
+ scoring = options.merge(boost_factor: factor.to_i)
352
+ chain{ criteria.update_scores(scoring) }
353
+ end
354
+
355
+ # Adds a random score to the search request. All scores are
356
+ # added to the search request and combinded according to
357
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
358
+ #
359
+ # This probably only makes sense if you specifiy a filter
360
+ # for the random score as well.
361
+ #
362
+ # If you do not pass in a seed value, Time.now will be used
363
+ #
364
+ # builder.random_score(23, filter: { foo: :bar})
365
+ # # => {body:
366
+ # query: {
367
+ # function_score: {
368
+ # query: { ...},
369
+ # functions: [{
370
+ # random_score: { seed: 23 },
371
+ # filter: { foo: :bar }
372
+ # }]
373
+ # } } }
374
+ #
375
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_random
376
+ #
377
+ def random_score(seed = Time.now, options = {})
378
+ scoring = options.merge(random_score: { seed: seed.to_i })
379
+ chain{ criteria.update_scores(scoring) }
380
+ end
381
+
382
+ # Add a field value scoring to the search. All scores are
383
+ # added to the search request and combinded according to
384
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
385
+ #
386
+ # This function is only available in Elasticsearch 1.2 and
387
+ # greater
388
+ #
389
+ # builder.field_value_factor(
390
+ # {
391
+ # field: :boost,
392
+ # factor: 1.2,
393
+ # modifier: :sqrt
394
+ # }, filter: { foo: :bar})
395
+ # # => {body:
396
+ # query: {
397
+ # function_score: {
398
+ # query: { ...},
399
+ # functions: [{
400
+ # field_value_factor: {
401
+ # field: :boost,
402
+ # factor: 1.2,
403
+ # modifier: :sqrt
404
+ # },
405
+ # filter: { foo: :bar }
406
+ # }]
407
+ # } } }
408
+ #
409
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_field_value_factor
410
+ #
411
+ def field_value_factor(settings, options = {})
412
+ scoring = options.merge(field_value_factor: settings)
413
+ chain{ criteria.update_scores(scoring) }
414
+ end
415
+
416
+ # Add a decay scoring to the search. All scores are
417
+ # added to the search request and combinded according to
418
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
419
+ #
420
+ # The parameters have default values, but those may not
421
+ # be very useful for most applications.
422
+ #
423
+ # builder.decay(
424
+ # :gauss,
425
+ # :field,
426
+ # origin: '11, 12',
427
+ # scale: '2km',
428
+ # offset: '5km'
429
+ # decay: 0.4
430
+ # filter: { foo: :bar})
431
+ # # => {body:
432
+ # query: {
433
+ # gauss: {
434
+ # query: { ...},
435
+ # functions: [{
436
+ # gauss: {
437
+ # field: {
438
+ # origin: '11, 12',
439
+ # scale: '2km',
440
+ # offset: '5km',
441
+ # decay: 0.4
442
+ # }
443
+ # },
444
+ # filter: { foo: :bar }
445
+ # }]
446
+ # } } }
447
+ #
448
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions
449
+ #
450
+ def decay(function, field, options = {})
451
+ field_options = {
452
+ origin: options.delete(:origin) || 0,
453
+ scale: options.delete(:scale) || 1,
454
+ offset: options.delete(:offset) || 0,
455
+ decay: options.delete(:decay) || 0.1
456
+ }
457
+ scoring = options.merge(function => {
458
+ field => field_options
459
+ })
460
+ chain{ criteria.update_scores(scoring) }
461
+ end
462
+
463
+ # Sets elasticsearch <tt>aggregations</tt> search request param
464
+ #
465
+ # builder.filter{ name == 'Johny' }.aggregations(category_id: {terms: {field: 'category_ids'}})
466
+ # # => {body: {
467
+ # query: {...},
468
+ # aggregations: {
469
+ # terms: {
470
+ # field: 'category_ids'
471
+ # }
472
+ # }
473
+ # }}
474
+ #
475
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html
476
+ #
477
+ def aggregations(params = nil)
478
+ chain{ criteria.update_aggregations params }
479
+ end
480
+ alias :aggs :aggregations
481
+
482
+ # Sets elasticsearch <tt>suggest</tt> search request param
483
+ #
484
+ # builder.suggest(name: {text: 'Joh', term: {field: 'name'}})
485
+ # # => {body: {
486
+ # query: {...},
487
+ # suggest: {
488
+ # text: 'Joh',
489
+ # term: {
490
+ # field: 'name'
491
+ # }
492
+ # }
493
+ # }}
494
+ #
495
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html
496
+ #
497
+ def suggest(params = nil)
498
+ chain{ criteria.update_suggest params }
499
+ end
500
+
501
+ # Marks the criteria as having zero records. This scope always returns empty array
502
+ # without touching the elasticsearch server.
503
+ # All the chained calls of methods don't affect the result
504
+ #
505
+ # UsersIndex.none.to_a
506
+ # # => []
507
+ # UsersIndex.query(text: {name: 'Johny'}).none.to_a
508
+ # # => []
509
+ # UsersIndex.none.query(text: {name: 'Johny'}).to_a
510
+ # # => []
511
+ def none
512
+ chain{ criteria.update_options(none: true) }
513
+ end
514
+
515
+ # Setups strategy for top-level filtered query
516
+ #
517
+ # builder.filter { name == 'Johny'}.strategy(:leap_frog)
518
+ # # => {body: {
519
+ # query: { filtered: {
520
+ # filter: { term: { name: 'Johny' } },
521
+ # strategy: 'leap_frog'
522
+ # } }
523
+ # }}
524
+ #
525
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html#_filter_strategy
526
+ #
527
+ def strategy(value = nil)
528
+ chain{ criteria.update_options(strategy: value) }
529
+ end
530
+
531
+ # Adds one or more query to the search request
532
+ # Internally queries are stored as an array
533
+ # While the full query compilation this array compiles
534
+ # according to <tt>:query_mode</tt> option value
535
+ #
536
+ # By default it joines inside <tt>must</tt> query
537
+ # See <tt>#query_mode</tt> chainable method for more info.
538
+ #
539
+ # builder.query(match: {name: 'Johny'}).query(range: {age: {lte: 42}})
540
+ # # => {body: {
541
+ # query: {bool: {must: [{match: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
542
+ # }}
543
+ #
544
+ # If only one query was specified, it will become a result
545
+ # query as is, without joining.
546
+ #
547
+ # builder.query(match: {name: 'Johny'})
548
+ # # => {body: {
549
+ # query: {match: {name: 'Johny'}}
550
+ # }}
551
+ #
552
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-queries.html
553
+ #
554
+ def query(params)
555
+ chain{ criteria.update_queries(params) }
556
+ end
557
+
558
+ # Adds one or more filter to the search request
559
+ # Internally filters are stored as an array
560
+ # While the full query compilation this array compiles
561
+ # according to <tt>:filter_mode</tt> option value
562
+ #
563
+ # By default it joins inside <tt>and</tt> filter
564
+ # See <tt>#filter_mode</tt> chainable method for more info.
565
+ #
566
+ # Also this method supports block DSL.
567
+ # See <tt>ChewyQuery::Builder::Filters</tt> for more info.
568
+ #
569
+ # builder.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
570
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }
571
+ # # => {body: {query: {filtered: {
572
+ # query: {...},
573
+ # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
574
+ # }}}}
575
+ #
576
+ # If only one filter was specified, it will become a result
577
+ # filter as is, without joining.
578
+ #
579
+ # builder.filter(term: {name: 'Johny'})
580
+ # # => {body: {query: {filtered: {
581
+ # query: {...},
582
+ # filter: {term: {name: 'Johny'}}
583
+ # }}}}
584
+ #
585
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filters.html
586
+ #
587
+ def filter(params = nil, &block)
588
+ params = Filters.new(&block).__render__ if block
589
+ chain{ criteria.update_filters(params) }
590
+ end
591
+
592
+ # Adds one or more post_filter to the search request
593
+ # Internally post_filters are stored as an array
594
+ # While the full query compilation this array compiles
595
+ # according to <tt>:post_filter_mode</tt> option value
596
+ #
597
+ # By default it joins inside <tt>and</tt> filter
598
+ # See <tt>#post_filter_mode</tt> chainable method for more info.
599
+ #
600
+ # Also this method supports block DSL.
601
+ # See <tt>ChewyQuery::Builder::Filters</tt> for more info.
602
+ #
603
+ # builder.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
604
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }
605
+ # # => {body: {
606
+ # post_filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
607
+ # }}
608
+ #
609
+ # If only one post_filter was specified, it will become a result
610
+ # post_filter as is, without joining.
611
+ #
612
+ # builder.post_filter(term: {name: 'Johny'})
613
+ # # => {body: {
614
+ # post_filter: {term: {name: 'Johny'}}
615
+ # }}
616
+ #
617
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-post-filter.html
618
+ #
619
+ def post_filter(params = nil, &block)
620
+ params = Filters.new(&block).__render__ if block
621
+ chain{ criteria.update_post_filters(params) }
622
+ end
623
+
624
+ # Sets the boost mode for custom scoring/boosting.
625
+ # Not used if no score functions are specified
626
+ # Possible values:
627
+ #
628
+ # * <tt>:multiply</tt>
629
+ # Default value. Query score and function result are multiplied.
630
+ #
631
+ # Ex:
632
+ #
633
+ # builder.boost_mode('multiply').script_score('doc['boost'].value')
634
+ # # => {body: {query: function_score: {
635
+ # query: {...},
636
+ # boost_mode: 'multiply',
637
+ # functions: [ ... ]
638
+ # }}}
639
+ #
640
+ # * <tt>:replace</tt>
641
+ # Only function result is used, query score is ignored.
642
+ #
643
+ # * <tt>:sum</tt>
644
+ # Query score and function score are added.
645
+ #
646
+ # * <tt>:avg</tt>
647
+ # Average of query and function score.
648
+ #
649
+ # * <tt>:max</tt>
650
+ # Max of query and function score.
651
+ #
652
+ # * <tt>:min</tt>
653
+ # Min of query and function score.
654
+ #
655
+ # Default value for <tt>:boost_mode</tt> might be changed
656
+ # with <tt>ChewyQuery.score_mode</tt> config option.
657
+ #
658
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
659
+ #
660
+ def boost_mode(value)
661
+ chain{ criteria.update_options(boost_mode: value) }
662
+ end
663
+
664
+ # Sets the scoring mode for combining function scores/boosts
665
+ # Not used if no score functions are specified.
666
+ # Possible values:
667
+ #
668
+ # * <tt>:multiply</tt>
669
+ # Default value. Scores are multiplied.
670
+ #
671
+ # Ex:
672
+ #
673
+ # builder.score_mode('multiply').script_score('doc['boost'].value')
674
+ # # => {body: {query: function_score: {
675
+ # query: {...},
676
+ # score_mode: 'multiply',
677
+ # functions: [ ... ]
678
+ # }}}
679
+ #
680
+ # * <tt>:sum</tt>
681
+ # Scores are summed.
682
+ #
683
+ # * <tt>:avg</tt>
684
+ # Scores are averaged.
685
+ #
686
+ # * <tt>:first</tt>
687
+ # The first function that has a matching filter is applied.
688
+ #
689
+ # * <tt>:max</tt>
690
+ # Maximum score is used.
691
+ #
692
+ # * <tt>:min</tt>
693
+ # Minimum score is used
694
+ #
695
+ # Default value for <tt>:score_mode</tt> might be changed
696
+ # with <tt>ChewyQuery.score_mode</tt> config option.
697
+ #
698
+ # ChewyQuery.score_mode = :first
699
+ #
700
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
701
+ #
702
+ def score_mode(value)
703
+ chain{ criteria.update_options(score_mode: value) }
704
+ end
705
+
706
+ # Sets search request sorting
707
+ #
708
+ # builder.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
709
+ # # => {body: {
710
+ # query: {...},
711
+ # sort: ['first_name', 'last_name', {age: 'desc'}, {price: {order: 'asc', mode: 'avg'}}]
712
+ # }}
713
+ #
714
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
715
+ #
716
+ def order(*params)
717
+ chain{ criteria.update_sort(params) }
718
+ end
719
+
720
+ # Cleans up previous search sorting and sets the new one
721
+ #
722
+ # builder.order(:first_name, :last_name).order(age: :desc).reorder(price: {order: :asc, mode: :avg})
723
+ # # => {body: {
724
+ # query: {...},
725
+ # sort: [{price: {order: 'asc', mode: 'avg'}}]
726
+ # }}
727
+ #
728
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
729
+ #
730
+ def reorder(*params)
731
+ chain{ criteria.update_sort(params, purge: true) }
732
+ end
733
+
734
+ # Sets search request field list
735
+ #
736
+ # builder.only(:first_name, :last_name).only(:age)
737
+ # # => {body: {
738
+ # query: {...},
739
+ # fields: ['first_name', 'last_name', 'age']
740
+ # }}
741
+ #
742
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
743
+ #
744
+ def only(*params)
745
+ chain{ criteria.update_fields(params) }
746
+ end
747
+
748
+ # Cleans up previous search field list and sets the new one
749
+ #
750
+ # builder.only(:first_name, :last_name).only!(:age)
751
+ # # => {body: {
752
+ # query: {...},
753
+ # fields: ['age']
754
+ # }}
755
+ #
756
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
757
+ #
758
+ def only!(*params)
759
+ chain{ criteria.update_fields(params, purge: true) }
760
+ end
761
+
762
+ # Specify types participating in the search result
763
+ # Works via <tt>types</tt> filter. Always merged with another filters
764
+ # with the <tt>and</tt> filter.
765
+ #
766
+ # builder.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }
767
+ # # => {body: {query: {filtered: {
768
+ # query: {...},
769
+ # filter: {and: [
770
+ # {or: [
771
+ # {type: {value: 'admin'}},
772
+ # {type: {value: 'manager'}}
773
+ # ]},
774
+ # {term: {name: 'Johny'}},
775
+ # {range: {age: {lte: 42}}}
776
+ # ]}
777
+ # }}}}
778
+ #
779
+ # builder.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }.filter_mode(:or)
780
+ # # => {body: {query: {filtered: {
781
+ # query: {...},
782
+ # filter: {and: [
783
+ # {or: [
784
+ # {type: {value: 'admin'}},
785
+ # {type: {value: 'manager'}}
786
+ # ]},
787
+ # {or: [
788
+ # {term: {name: 'Johny'}},
789
+ # {range: {age: {lte: 42}}}
790
+ # ]}
791
+ # ]}
792
+ # }}}}
793
+ #
794
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-type-filter.html
795
+ #
796
+ def types(*params)
797
+ if params.any?
798
+ chain{ criteria.update_types(params) }
799
+ else
800
+ @types
801
+ end
802
+ end
803
+
804
+ # Acts the same way as <tt>types</tt>, but cleans up previously set types
805
+ #
806
+ # builder.types(:admin).types!(:manager)
807
+ # # => {body: {query: {filtered: {
808
+ # query: {...},
809
+ # filter: {type: {value: 'manager'}}
810
+ # }}}}
811
+ #
812
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-type-filter.html
813
+ #
814
+ def types!(*params)
815
+ chain{ criteria.update_types(params, purge: true) }
816
+ end
817
+
818
+ # Merges two queries.
819
+ # Merges all the values in criteria with the same rules as values added manually.
820
+ #
821
+ # scope1 = builder.filter{ name == 'Johny' }
822
+ # scope2 = builder.filter{ age <= 42 }
823
+ # scope3 = builder.filter{ name == 'Johny' }.filter{ age <= 42 }
824
+ #
825
+ # scope1.merge(scope2) == scope3 # => true
826
+ #
827
+ def merge(other)
828
+ chain{ criteria.merge!(other.criteria) }
829
+ end
830
+
831
+ def delete_all_request
832
+ @delete_all_request ||= criteria.delete_all_request_body.merge(index: index_name, type: types)
833
+ end
834
+
835
+
836
+ def request
837
+ @request ||= criteria.request_body.merge(index: index_name, type: types)
838
+ end
839
+
840
+ def inspect
841
+ "#<%s:%#016x @request=%s>" % [self.class, (object_id << 1), request]
842
+ end
843
+
844
+ protected
845
+
846
+ def reset
847
+ @request, @delete_all_request = nil
848
+ end
849
+
850
+ def initialize_clone(other)
851
+ @criteria = other.criteria.clone
852
+ reset
853
+ end
854
+
855
+ private
856
+
857
+ def index_name
858
+ index.respond_to?(:index_name) ? index.index_name : index
859
+ end
860
+
861
+ def chain(&block)
862
+ clone.tap{|q| q.instance_eval(&block) }
863
+ end
864
+ end
865
+ end