es_query_builder 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/constants.rb +4 -0
- data/lib/elastic_search_query.rb +462 -0
- data/lib/fetch_es_data.rb +30 -0
- data/lib/housing_es_query_builder.rb +8 -0
- data/lib/indexer.rb +68 -0
- data/lib/token_query_builder.rb +76 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 90f99bbca380c475dd8e32b11b7d6bf8eaed0847
|
4
|
+
data.tar.gz: 5e0218899c63aa6ba9489fe6c254f84b1510f278
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be2e9c5a52e306fd76263b23752ea054249d8e5abb793115b8e30cd0150ab8a3e7a5ba2d115b9f0775683d6ed139f1ebbf6eaab2ab202b863606aae63676656c
|
7
|
+
data.tar.gz: b1f12714aa7d81e8c3b7f8386dc9a3bd90a0c927dcc94e349813de523c01c7a0f8267cb41ee13b167a743a0266ff7ac869cd96f5c8daed7d518953547d142f63
|
data/lib/constants.rb
ADDED
@@ -0,0 +1,462 @@
|
|
1
|
+
class ElasticSearchQuery
|
2
|
+
|
3
|
+
|
4
|
+
#### All Get Query ================================================================
|
5
|
+
|
6
|
+
# returns the structure for ids matching
|
7
|
+
def self.get_ids_query_structure
|
8
|
+
{
|
9
|
+
:ids => {
|
10
|
+
:values => []
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
# returns constant score filter structure
|
16
|
+
def self.get_constant_score_filter_structure filter = {}, boost = 1
|
17
|
+
{
|
18
|
+
:constant_score => {
|
19
|
+
:filter => filter,
|
20
|
+
:boost => boost
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# returns constant score query structure
|
26
|
+
def self.get_constant_score_query_structure query = {}, boost = 1
|
27
|
+
{
|
28
|
+
constant_score: {
|
29
|
+
filter: {
|
30
|
+
bool: {
|
31
|
+
must: query
|
32
|
+
}
|
33
|
+
},
|
34
|
+
boost: boost
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns exists filter
|
40
|
+
def self.get_exists_filter field
|
41
|
+
{
|
42
|
+
constant_score: {
|
43
|
+
filter: {
|
44
|
+
exists: {
|
45
|
+
field: field
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# With Elasticsearch 6.1 nested filter has been replaced with nested query
|
53
|
+
# nested filter structure
|
54
|
+
def self.get_nested_filter_structure path, query = {}
|
55
|
+
{
|
56
|
+
:nested => {
|
57
|
+
:path => path,
|
58
|
+
:query => query
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns nested query structure
|
64
|
+
def self.get_nested_query_structure path, query = {}, score_mode=nil
|
65
|
+
raise ArgumentError.new("path has to be a string") unless (path.is_a? String)
|
66
|
+
subquery = {
|
67
|
+
:nested => {
|
68
|
+
:path => path,
|
69
|
+
:query => query
|
70
|
+
}
|
71
|
+
}
|
72
|
+
if Constants::FUNCTION_SCORE_METHODS.include? score_mode
|
73
|
+
subquery[:nested][:score_mode] = score_mode
|
74
|
+
end
|
75
|
+
return subquery
|
76
|
+
end
|
77
|
+
|
78
|
+
# returns structure for match_phrase_prefix
|
79
|
+
def self.get_match_phrase_prefix_query field, prefix
|
80
|
+
raise ArgumentError.new("field and prefix should be strings") unless (field.is_a? String) && (prefix.is_a? String)
|
81
|
+
return {
|
82
|
+
:match_phrase_prefix => {
|
83
|
+
field => prefix
|
84
|
+
}
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# term filter query
|
89
|
+
def self.get_term_filter_query(field, value, cache_flag = false)
|
90
|
+
{
|
91
|
+
term: {
|
92
|
+
field => value
|
93
|
+
}
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
# term boost query
|
98
|
+
def self.get_term_boost_query field, value, boost
|
99
|
+
{
|
100
|
+
:term => {
|
101
|
+
field => {
|
102
|
+
:value => value,
|
103
|
+
:boost => boost
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# returns terms_filter_query
|
110
|
+
def self.get_terms_filter_query(field, value)
|
111
|
+
raise "Cannot append terms query to #{value} which is not an array" unless value.is_a?(Array)
|
112
|
+
{
|
113
|
+
terms: {
|
114
|
+
field => value
|
115
|
+
}
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
# returns filtered_structure
|
120
|
+
def self.get_filtered_structure
|
121
|
+
{
|
122
|
+
bool: {
|
123
|
+
must: [],
|
124
|
+
should: [],
|
125
|
+
must_not: [],
|
126
|
+
filter: {
|
127
|
+
bool: {
|
128
|
+
must: [],
|
129
|
+
should: [],
|
130
|
+
must_not: []
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
# returns range query
|
138
|
+
def self.get_range_query field, from, to
|
139
|
+
q = {
|
140
|
+
:range=> {
|
141
|
+
field => {}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
q[:range][field][:from] = from if from.present?
|
145
|
+
q[:range][field][:to] = to if to.present?
|
146
|
+
return q
|
147
|
+
end
|
148
|
+
|
149
|
+
# returns nested terms query
|
150
|
+
def self.get_nested_terms_query(path, field, value)
|
151
|
+
raise "Cannot append terms query to #{value} which is not an array" unless value.is_a?(Array)
|
152
|
+
{
|
153
|
+
nested: {
|
154
|
+
path: path,
|
155
|
+
query: {
|
156
|
+
terms: {
|
157
|
+
field => value
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
# returns basic bool structure with should, must and mus_not clause
|
165
|
+
def self.get_bool_filter_structure
|
166
|
+
{
|
167
|
+
bool: {
|
168
|
+
must: [],
|
169
|
+
should: [],
|
170
|
+
must_not: []
|
171
|
+
}
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
# returns query bool structure
|
176
|
+
def self.get_query_bool_structure
|
177
|
+
{
|
178
|
+
query:{
|
179
|
+
bool: {
|
180
|
+
filter:{
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# used to append sort query so that the result is sorted based on the sort_fields provided
|
189
|
+
def self.get_sort_subquery sort_fields=[]
|
190
|
+
raise "Cannot append sort query which is not an array" unless sort_fields.is_a?(Array)
|
191
|
+
sort = []
|
192
|
+
sort_fields.each do |field|
|
193
|
+
if field.is_a? Hash
|
194
|
+
field.each do |key, order|
|
195
|
+
next unless (order=="asc" || order=="desc")
|
196
|
+
sort << {key => {"order" => order}}
|
197
|
+
end
|
198
|
+
elsif (field.is_a? String) || (field.is_a? Symbol)
|
199
|
+
sort << field.to_s
|
200
|
+
end
|
201
|
+
end
|
202
|
+
return sort
|
203
|
+
end
|
204
|
+
|
205
|
+
# constraucts a top_hits structure
|
206
|
+
# this aggregator is intended to be used as a sub aggregator,
|
207
|
+
# so that the top matching documents can be aggregated per bucket
|
208
|
+
# @param name [String] name of the aggregation
|
209
|
+
# @param size [Integer] specifying number of top results to be returned
|
210
|
+
# @param sort [Array] specifying the sorting order
|
211
|
+
# @param source [Array] specifying the data fields to be present in the result
|
212
|
+
def self.get_top_hits_aggregations name, size, sort, source = []
|
213
|
+
query = {
|
214
|
+
name => {
|
215
|
+
"top_hits": {
|
216
|
+
"size": size
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}.with_indifferent_access
|
220
|
+
query[name]["top_hits"]["sort"] = sort if sort.present?
|
221
|
+
if source.present?
|
222
|
+
query[name]["top_hits"]["_source"] = {
|
223
|
+
"include": source
|
224
|
+
}
|
225
|
+
end
|
226
|
+
query
|
227
|
+
end
|
228
|
+
|
229
|
+
# returns a reverse nested structure
|
230
|
+
def self.get_reverse_nested_aggs name, aggregations
|
231
|
+
{
|
232
|
+
name => {
|
233
|
+
"reverse_nested": {},
|
234
|
+
"aggs": aggregations
|
235
|
+
}
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.get_terms_aggregation_structure name, field_name, include_array = [], script = "", size = nil
|
240
|
+
query = {
|
241
|
+
name => {
|
242
|
+
terms: {
|
243
|
+
field: field_name
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
query[name][:terms][:include] = include_array if include_array.present?
|
248
|
+
if script.present?
|
249
|
+
query[name][:terms].delete(:field)
|
250
|
+
query[name][:terms][:script] = script
|
251
|
+
end
|
252
|
+
if size.present?
|
253
|
+
size = 1 if size == 0 #size 0 is not supported since ES 5
|
254
|
+
query[name][:terms][:size] = size
|
255
|
+
end
|
256
|
+
query
|
257
|
+
end
|
258
|
+
|
259
|
+
# returns a generic metrics aggregation by providing the comparator
|
260
|
+
# @param name [String] name of the aggregation
|
261
|
+
# @param comparator [String] the metric to be used (eg. cardinality, avg)
|
262
|
+
# @param field_name [String] field on which aggregation is used
|
263
|
+
def self.get_metrics_aggregations_query name, comparator, field_name
|
264
|
+
query = {
|
265
|
+
name => {
|
266
|
+
comparator => {
|
267
|
+
field: field_name
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
# this helps to contruct a structure where we can use aggregations on the nested objects also.
|
274
|
+
# @param name [String] name of the aggregation
|
275
|
+
# @param path [String] path to the nested object
|
276
|
+
# @param field_name [String] field on which aggregation is used
|
277
|
+
# @param aggregation [Hash] specifying the aggregations (eg. Average)
|
278
|
+
def self.get_nested_aggregation_query name, path, aggregation
|
279
|
+
query = {
|
280
|
+
name => {
|
281
|
+
nested: {
|
282
|
+
path: path
|
283
|
+
},
|
284
|
+
aggregations: aggregation
|
285
|
+
}
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
# builds ids query with provided values
|
290
|
+
def self.get_ids_filter_query ids
|
291
|
+
ids_query_structure = get_ids_query_structure
|
292
|
+
ids_query_structure[:ids][:values] = ids
|
293
|
+
ids_query_structure
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
# constructs a structure that defines a single bucket which matches a specified filter
|
298
|
+
# @param name [String] name of the aggregation
|
299
|
+
# @param aggregation [Hash] specifying the aggregations (eg. Average)
|
300
|
+
# @params filter [Hash] the matching condition (eg. { "term": { "type": "t-shirt" } })
|
301
|
+
def self.filtered_aggregation name, aggregation, filter
|
302
|
+
{
|
303
|
+
:aggs => {
|
304
|
+
name.intern => {
|
305
|
+
filter: filter,
|
306
|
+
aggs: aggregation
|
307
|
+
}
|
308
|
+
}
|
309
|
+
}
|
310
|
+
end
|
311
|
+
|
312
|
+
# calculates percentiles based on the field and provided percentile points
|
313
|
+
# @param aggregation_name [String] name of the aggregation
|
314
|
+
# @param field [String] on which aggregation is to be performed
|
315
|
+
# @param percentile_points [Array] percentile points in which we are interested
|
316
|
+
def self.percentile_aggregation aggregation_name, field, percentile_points
|
317
|
+
{
|
318
|
+
aggregation_name.intern => {
|
319
|
+
:percentiles => {
|
320
|
+
:field => field,
|
321
|
+
:percents => percentile_points
|
322
|
+
}
|
323
|
+
}
|
324
|
+
}
|
325
|
+
end
|
326
|
+
|
327
|
+
# used for bucketing the response based on the field and range provided
|
328
|
+
# sample range [{ "to" : 100.0 },{ "from" : 100.0, "to" : 200.0 },{ "from" : 200.0 }]
|
329
|
+
# @param aggregation_name [String] name of the aggregation
|
330
|
+
# @param field [String] on which aggregation is to be performed
|
331
|
+
# @param ranges [Array] specifying ranges
|
332
|
+
def self.range_aggregation aggregation_name, field, ranges
|
333
|
+
{
|
334
|
+
aggregation_name.intern => {
|
335
|
+
:range => {
|
336
|
+
:field => field,
|
337
|
+
:ranges => ranges
|
338
|
+
}
|
339
|
+
}
|
340
|
+
}
|
341
|
+
end
|
342
|
+
|
343
|
+
# constructs an aggregation structure based on field_name provided
|
344
|
+
# this dynamically builds buckets on the basis of field_name and provides the aggregations accordingly
|
345
|
+
# if field_name = genre and genre has values (rock, jazz, thrash metal), then aggregations will be based on these three genres
|
346
|
+
# @param name [String] name of the aggregation
|
347
|
+
# @param field_name [String] on which aggregation is to be performed
|
348
|
+
# @param aggregation [Array] specifying aggregations
|
349
|
+
# @param include_array [Array] specifying conditions on the field_name
|
350
|
+
# @param script [Hash], to be executed for aggregation
|
351
|
+
def self.get_terms_structure_with_aggregation name, field_name, aggregation, include_array = [], script = ""
|
352
|
+
query = get_terms_aggregation_structure name, field_name, include_array, script
|
353
|
+
query[name][:aggs] = aggregation
|
354
|
+
return query
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.function_score query, seed
|
358
|
+
{
|
359
|
+
function_score: {
|
360
|
+
query: query,
|
361
|
+
functions: [
|
362
|
+
{
|
363
|
+
random_score: {
|
364
|
+
seed: seed
|
365
|
+
}
|
366
|
+
}
|
367
|
+
]
|
368
|
+
}
|
369
|
+
}
|
370
|
+
end
|
371
|
+
|
372
|
+
# returns a structure of dis_max query.
|
373
|
+
# @param queries [Array], array of queries used for union
|
374
|
+
def self.dis_max_query queries=[]
|
375
|
+
raise ArgumentError.new("queries is not an Array") unless queries.instance_of? Array
|
376
|
+
return {
|
377
|
+
dis_max: {
|
378
|
+
queries: queries
|
379
|
+
}
|
380
|
+
}
|
381
|
+
end
|
382
|
+
|
383
|
+
# to modify the score of documents that are retrieved by a query
|
384
|
+
# @param query [Hash]
|
385
|
+
# @param functions [Array] specifying the conditions and scores
|
386
|
+
# @param boost_mode [String] specifying boost_mode (replace, multiply)
|
387
|
+
def self.script_scoring_query(query, functions, boost_mode="replace")
|
388
|
+
return query.except(:query).merge({
|
389
|
+
query: {
|
390
|
+
function_score: {
|
391
|
+
query: query[:query],
|
392
|
+
functions: functions,
|
393
|
+
boost_mode: boost_mode
|
394
|
+
}
|
395
|
+
}
|
396
|
+
})
|
397
|
+
end
|
398
|
+
|
399
|
+
def self.get_script_score_function_structure
|
400
|
+
return {
|
401
|
+
script_score: {
|
402
|
+
script: {
|
403
|
+
params: {},
|
404
|
+
inline: ''
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
end
|
409
|
+
|
410
|
+
# get nested query to search on nested objects
|
411
|
+
def self.get_nested_exists_query field_name
|
412
|
+
{
|
413
|
+
nested: {
|
414
|
+
path: field_name,
|
415
|
+
query: {
|
416
|
+
match_all: {}
|
417
|
+
}
|
418
|
+
}
|
419
|
+
}
|
420
|
+
end
|
421
|
+
|
422
|
+
# this appends bools structure containing must, should and must_not in an existing query under the filter context
|
423
|
+
# if filter is not present in the existing query, the entire bool structure is assigned,
|
424
|
+
# if filter is present, then the bool structure is merged with the existing structure under the filter context
|
425
|
+
# finally the filter priovided in the method params is appended in the must clause
|
426
|
+
# @param query [Hash] main query
|
427
|
+
# @param filter [Hash] assigned in the must clause of the main query
|
428
|
+
def self.append_query_filter(query, filter)
|
429
|
+
bool_query = get_bool_filter_structure
|
430
|
+
if query[:query][:bool][:filter].nil?
|
431
|
+
query[:query][:bool][:filter] = bool_query
|
432
|
+
else
|
433
|
+
bool_query[:bool].each { |key, val|
|
434
|
+
query[:query][:bool][:filter][:bool][key] = [] unless query[:query][:bool][:filter][:bool].key? (key)
|
435
|
+
}
|
436
|
+
end
|
437
|
+
query[:query][:bool][:filter][:bool][:must].push(filter)
|
438
|
+
query
|
439
|
+
end
|
440
|
+
|
441
|
+
# sets the max number of results to be returned by the query
|
442
|
+
def self.append_size_filter(query, size)
|
443
|
+
query[:size] = size
|
444
|
+
query
|
445
|
+
end
|
446
|
+
|
447
|
+
# merges bool queries into the main_query
|
448
|
+
# @param main_query [Hash] query to be modified
|
449
|
+
# @param query [Hash] query whose bool params are to be merged in main_query
|
450
|
+
def self.merge_bool_query(main_query, query)
|
451
|
+
query[:bool].each { |key, val|
|
452
|
+
if main_query[:bool].key?(key)
|
453
|
+
main_query[:bool][key] = Array.new([main_query[:bool][key]]) << val
|
454
|
+
main_query[:bool][key].flatten!
|
455
|
+
else
|
456
|
+
main_query[:bool][key] = val
|
457
|
+
end
|
458
|
+
}
|
459
|
+
main_query
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# having methods to fetch data from Elastic search
|
2
|
+
class FetchEsData
|
3
|
+
|
4
|
+
# initializing elastic search host and port
|
5
|
+
# @param search_host [String] Elastic Host
|
6
|
+
# @param search_port [String] Elastic port
|
7
|
+
# @return [FetchEsData] to perform search
|
8
|
+
def initialize(search_host, search_port)
|
9
|
+
@search_host = search_host
|
10
|
+
@search_port = search_port
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# fetches data from elastic search
|
15
|
+
# @param query [String] the input
|
16
|
+
# @param index_name [String] name of the index
|
17
|
+
# @param type_name [String] index type
|
18
|
+
# @param extension [String] extension to Elastic seach path (eg. '_search', '_msearch')
|
19
|
+
# @return [String, Hash]
|
20
|
+
def fetch_shortlisted_data_from_es(query:, index_name:, type_name:, extension: '_search')
|
21
|
+
uri = URI("http://#{@search_host}:#{@search_port}/#{index_name}/#{type_name}/#{extension}")
|
22
|
+
req = Net::HTTP::Post.new(uri, initheader = {'Content-Type' =>'application/json'})
|
23
|
+
req.body = "#{query.to_json}\n"
|
24
|
+
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
25
|
+
http.request(req)
|
26
|
+
end
|
27
|
+
body = JSON.parse(res.body) rescue {}
|
28
|
+
return res.code, body
|
29
|
+
end
|
30
|
+
end
|
data/lib/indexer.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# used to index data in almost realtime to Elastic Search
|
2
|
+
|
3
|
+
class Indexer
|
4
|
+
|
5
|
+
# get elastic client object
|
6
|
+
# @param host [String] Elastic Host
|
7
|
+
# @param port [String] Elastic Port
|
8
|
+
# @return [Indexer] to use indexing methods
|
9
|
+
def initialize(host, port)
|
10
|
+
@client = Elasticsearch::Client.new(host: host, port: port)
|
11
|
+
end
|
12
|
+
|
13
|
+
# expects data in the form of
|
14
|
+
# [{update:{_index: index, _type: _doc, _id: 23, data: {doc: data }}},{index: {_index: index2, _type: _doc, _id: 28, data: data}}]
|
15
|
+
# @param data [Array] Data to be indexed
|
16
|
+
def bulk_index(data)
|
17
|
+
check_bulk_index_params(data)
|
18
|
+
response = @client.bulk body: data
|
19
|
+
response = response.with_indifferent_access
|
20
|
+
if response[:errors] == true
|
21
|
+
raise "Not able to index with errors as #{(response["items"].map{|t| t["index"]["error"]}.compact)}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# checks whether the record exists in the given index with given type and id
|
26
|
+
# @param index_name [String] name of the index
|
27
|
+
# @param type_name_name [String] name of the type
|
28
|
+
# @param id [String] doc_id
|
29
|
+
# @param parent_id [String] parent_id (optional)
|
30
|
+
# @return [True] if record found
|
31
|
+
# @return [False] if record not found
|
32
|
+
def record_exists?(index_name, type_name, id, parent_id = nil)
|
33
|
+
options_hash = generate_options_hash(index_name, type_name, id, parent_id)
|
34
|
+
@client.exists options_hash
|
35
|
+
end
|
36
|
+
|
37
|
+
# deletes the record if exists in the given index with given type and id,
|
38
|
+
# raises DocumentNotFoundException if record is not found
|
39
|
+
# @param index_name [String] name of the index
|
40
|
+
# @param index_name [String] name of the type
|
41
|
+
# @param id [String] doc_id
|
42
|
+
# @param parent_id [String] parent_id
|
43
|
+
def delete_record(index_name, type_name, id, parent_id = nil)
|
44
|
+
if record_exists?(index_name, type_name, id, parent_id)
|
45
|
+
@client.delete generate_options_hash(index_name, type_name, id, parent_id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def generate_options_hash(name, type, id, parent_id)
|
53
|
+
options_hash = {
|
54
|
+
index: name,
|
55
|
+
type: type,
|
56
|
+
id: id
|
57
|
+
}
|
58
|
+
options_hash[:parent] = parent_id if parent_id
|
59
|
+
options_hash
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_bulk_index_params(data)
|
63
|
+
raise "Please provide and array of documents" unless data.is_a?(Array)
|
64
|
+
count = data.size
|
65
|
+
raise "Record count should be less than #{Constants::MAX_BULK_INDEX_SIZE}" if count > Constants::MAX_BULK_INDEX_SIZE
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'elastic_search_query'
|
2
|
+
class TokenQueryBuilder
|
3
|
+
|
4
|
+
# constructs match filter based on the field_name and analyzer provided
|
5
|
+
# @param analyzer [String] Type of analyzer to be used
|
6
|
+
# @param field_name [String] field to be searched
|
7
|
+
# @param query [String] input data
|
8
|
+
# @param opts [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
|
9
|
+
# @return [Hash]
|
10
|
+
def self.construct_match_filter(analyzer, field_name, query, opts)
|
11
|
+
default_options = {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "or"}
|
12
|
+
opts = default_options.merge(opts)
|
13
|
+
field = analyzer.present? ? "#{field_name}.#{analyzer}": "#{field_name}"
|
14
|
+
{
|
15
|
+
match: {
|
16
|
+
field => {
|
17
|
+
query: query,
|
18
|
+
operator: opts[:operator],
|
19
|
+
fuzziness: opts[:fuzziness],
|
20
|
+
prefix_length: opts[:prefix_length]
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# wraps the query in the constant_score structure
|
27
|
+
# @param filters [Array] filters to be included
|
28
|
+
# @param boost [Float] to influence the relevance score
|
29
|
+
# @return [Hash]
|
30
|
+
def self.wrap_constant_score_query(filters: [], boost: 0)
|
31
|
+
{
|
32
|
+
constant_score: {
|
33
|
+
boost: boost,
|
34
|
+
filter: {
|
35
|
+
bool: {
|
36
|
+
should: filters
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
# constructs match filter based on keys array
|
44
|
+
# @param analyzer [String] Type of analyzer to be used
|
45
|
+
# @param query [String] input data
|
46
|
+
# @param opts [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
|
47
|
+
# @param keys [Array] fields to be queried
|
48
|
+
# @param boost [Float] to influence the relevance score
|
49
|
+
# @return [Hash]
|
50
|
+
def self.cs_with_multiple_filter(analyzer, query, opts, keys = [], boost: 0)
|
51
|
+
filters = []
|
52
|
+
(keys.compact).each do |key|
|
53
|
+
filter = construct_match_filter(analyzer, key, query, opts)
|
54
|
+
filters.push(filter)
|
55
|
+
end
|
56
|
+
wrap_constant_score_query(filters: filters, boost: boost)
|
57
|
+
end
|
58
|
+
|
59
|
+
# constructs simgle term query
|
60
|
+
def self.cs_with_single_filter(query, key = "")
|
61
|
+
ElasticSearchQuery.get_term_filter_query key, query, true
|
62
|
+
end
|
63
|
+
|
64
|
+
# constructs a constant_score wrapped match query based on the analyzer provided
|
65
|
+
# @param analyzer [String] Type of analyzer to be used (Analyzer provided here must be defined in the index definition first)
|
66
|
+
# @param query [String] input data
|
67
|
+
# @param key [String] field to be searched
|
68
|
+
# @param alias_key [String] alias of the field to be searched
|
69
|
+
# @param boost [Float] to influence relevance score
|
70
|
+
# @param options [Hash] options to be provided for search (eg. {fuzziness: 0, prefix_length: 1, max_expansions: 20, operator: "and"})
|
71
|
+
# @return [Hash]
|
72
|
+
def self.constant_score_match_query(analyzer, query, key, alias_key = nil, boost = 1, options = {})
|
73
|
+
cs_with_multiple_filter(analyzer, query, options, [key, alias_key], boost: boost)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: es_query_builder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mohib Yousuf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.0.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 4.0.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: elasticsearch
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
description:
|
48
|
+
email: mohib.yousuf@hotmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/constants.rb
|
54
|
+
- lib/elastic_search_query.rb
|
55
|
+
- lib/fetch_es_data.rb
|
56
|
+
- lib/housing_es_query_builder.rb
|
57
|
+
- lib/indexer.rb
|
58
|
+
- lib/token_query_builder.rb
|
59
|
+
homepage: https://github.com/elarahq/es.query.builder
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.6.12
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: For Building Elastic Search Queries
|
83
|
+
test_files: []
|