es-query-builder 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01a7f2c3df52977455ef8cc73bb06caffae1b5d8
4
- data.tar.gz: f9b6ddc51b5b9f64fceb830d8a9a12b5e557d362
3
+ metadata.gz: 8b7061462ef887c110a7362d082ecab6170d8800
4
+ data.tar.gz: 273dc7a2f14404b05e8bf8b573fb1f4af62650be
5
5
  SHA512:
6
- metadata.gz: 76e05d352511a51d07b53363273dc10da73ed421d09b8c92acf3bf7274a151f1756fd5ea68037a84efebc39f872c41590304be8df8aae1444bbed0a06aadf1b0
7
- data.tar.gz: 4d19002288f950ad91db2bc5a819cd9fff51f74349a4aafa0bd9536af50cea9540c10defc4ed478e1ffb440a48517c143dad5403360b877766ba6e184af8b0c7
6
+ metadata.gz: 19b39d4e940467281be485a915fa4333f808d903c0dac03d69206570a603f0b592b02e0950357b6cefb90f4229a62bc63ab884adae1394961e615aad981350a5
7
+ data.tar.gz: 36d828651c879de1cdfb76b59bbdc4de8772372c72845acd7fcfb8929a44ecbb3d1c6f4b824553d038cc75f26c432f96bda09bb2e589f2d3594a65eaf115f994
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.0.2 - January 3, 2015
4
+
5
+ - [[#1](https://github.com/increments/es-query-builder/pull/1)] Support nested query and has_child query by [@f-kubotar](https://github.com/f-kubotar).
6
+
7
+ ## 0.0.1 - November 15, 2014
8
+
9
+ - The initial release.
@@ -47,11 +47,14 @@ class EsQueryBuilder
47
47
  #
48
48
  # Returns nothing.
49
49
  def initialize(query_fields: [], filter_fields: [],
50
- all_query_fields: '_all', hierarchy_fields: [])
50
+ all_query_fields: '_all', hierarchy_fields: [],
51
+ nested_fields: {}, child_fields: {})
51
52
  @query_fields = query_fields
52
53
  @filter_fields = filter_fields
53
54
  @all_query_fields = all_query_fields
54
55
  @hierarchy_fields = hierarchy_fields
56
+ @nested_fields = nested_fields
57
+ @child_fields = child_fields
55
58
  end
56
59
 
57
60
  # Public: Convert the given query string into a query object.
@@ -88,6 +91,10 @@ class EsQueryBuilder
88
91
  #
89
92
  # Returns a Parser.
90
93
  def parser
91
- @parser ||= Parser.new(@all_query_fields, @hierarchy_fields)
94
+ @parser ||= Parser.new(
95
+ all_query_fields: @all_query_fields,
96
+ hierarchy_fields: @hierarchy_fields,
97
+ nested_fields: @nested_fields,
98
+ child_fields: @child_fields)
92
99
  end
93
100
  end
@@ -18,9 +18,12 @@ class EsQueryBuilder
18
18
  # character as a hierarchy (default: []).
19
19
  #
20
20
  # Returns nothing.
21
- def initialize(all_query_fields = '_all', hierarchy_fields = [])
21
+ def initialize(all_query_fields: '_all', hierarchy_fields: [],
22
+ nested_fields: {}, child_fields: {})
22
23
  @all_query_fields = all_query_fields
23
24
  @hierarchy_fields = hierarchy_fields
25
+ @nested_fields = nested_fields
26
+ @child_fields = child_fields
24
27
  end
25
28
 
26
29
  # Public: Parse the given tokens and build a query hash.
@@ -155,25 +158,70 @@ class EsQueryBuilder
155
158
  def create_bool_queries(query_tokens)
156
159
  must, must_not = [], []
157
160
  query_tokens.each do |token|
158
- # When the field is not given or invalid one, search by all fields.
159
- field = token.field || @all_query_fields
160
161
  queries = token.minus? ? must_not : must
161
- if field.is_a?(String)
162
- queries << {
163
- match: {
164
- field => token.term
165
- }
162
+
163
+ queries <<
164
+ # When the field is not given or invalid one, search by all fields.
165
+ if token.field.nil?
166
+ should = []
167
+ should << create_match_query(@all_query_fields, token.term)
168
+ @nested_fields.each do |nested_path, nested_field|
169
+ should << create_nested_match_query(nested_path, nested_field, token.term)
170
+ end
171
+ @child_fields.each do |child_type, child_field|
172
+ should << create_has_child_match_query(child_type, child_field, token.term)
173
+ end
174
+ connect_queries(should)
175
+
176
+ # When the specify nested field
177
+ elsif nested_field = @nested_fields[token.field_namespace]
178
+ create_nested_match_query(token.field_namespace, nested_field, token.term)
179
+
180
+ # When the specify child field
181
+ elsif child_field = @child_fields[token.field_namespace]
182
+ create_has_child_match_query(token.field_namespace, child_field, token.term)
183
+
184
+ # When the specify standard field
185
+ else
186
+ create_match_query(token.field, token.term)
187
+ end
188
+ end
189
+ [must, must_not]
190
+ end
191
+
192
+ def create_match_query(field, term)
193
+ if field.is_a?(String)
194
+ {
195
+ match: {
196
+ field => term
166
197
  }
167
- else
168
- queries << {
169
- multi_match: {
170
- fields: field,
171
- query: token.term
172
- }
198
+ }
199
+ else
200
+ {
201
+ multi_match: {
202
+ fields: field,
203
+ query: term
173
204
  }
174
- end
205
+ }
175
206
  end
176
- [must, must_not]
207
+ end
208
+
209
+ def create_nested_match_query(path, field, term)
210
+ {
211
+ nested: {
212
+ path: path.to_s,
213
+ query: create_match_query(field, term)
214
+ }
215
+ }
216
+ end
217
+
218
+ def create_has_child_match_query(child_type, field, term)
219
+ {
220
+ has_child: {
221
+ type: child_type.to_s,
222
+ query: create_match_query(field, term)
223
+ }
224
+ }
177
225
  end
178
226
 
179
227
  # Internal: Create boolean filter based on the filter matches.
@@ -1,6 +1,6 @@
1
1
  class EsQueryBuilder
2
2
  class Token
3
- attr_reader :full, :field, :term
3
+ attr_reader :full, :field, :term, :field_namespace
4
4
 
5
5
  TYPE_KINDS = %i(query filter or).freeze
6
6
 
@@ -10,6 +10,7 @@ class EsQueryBuilder
10
10
  @field = field
11
11
  @term = term
12
12
  @type = type
13
+ @field_namespace = field.to_s[/^(.+?)\./, 1]
13
14
  end
14
15
 
15
16
  def minus?
@@ -14,7 +14,7 @@ class EsQueryBuilder
14
14
 
15
15
  OR_CONDITION = /^OR$/i
16
16
 
17
- # Public: COnstruct the tokenizer object.
17
+ # Public: Construct the tokenizer object.
18
18
  #
19
19
  # filter_fields - An Array of Strings for specifing allowed filtering
20
20
  # types (default: []).
@@ -35,8 +35,8 @@ class EsQueryBuilder
35
35
  #
36
36
  # tokenize('hello OR tag:world')
37
37
  # # => [<Token: @full="hello", @type=:query, ...>,
38
- # <Token: @full="OR", @type=:or, ...>,
39
- # <Token: @full="tag:world", @type=:filter, ...>]
38
+ # # <Token: @full="OR", @type=:or, ...>,
39
+ # # <Token: @full="tag:world", @type=:filter, ...>]
40
40
  #
41
41
  # Returns an Array of Tokens.
42
42
  def tokenize(query_string)
@@ -1,3 +1,3 @@
1
1
  class EsQueryBuilder
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -90,6 +90,426 @@ describe EsQueryBuilder do
90
90
  end
91
91
  end
92
92
  end
93
+
94
+ context 'and it is constructed with nested_fields' do
95
+ let(:param) do
96
+ { nested_fields: { hoge: ['hoge.title'] } }
97
+ end
98
+
99
+ let(:all_query_fields) do
100
+ { nested: { hoge: ['hoge.title'] } }
101
+ end
102
+
103
+ it "returns a nested query for the specified nested path and fields" do
104
+ should eq(
105
+ {
106
+ bool: {
107
+ should: [
108
+ {
109
+ match: {
110
+ '_all' => term
111
+ }
112
+ },
113
+ {
114
+ nested: {
115
+ path: 'hoge',
116
+ query: {
117
+ multi_match: {
118
+ fields: ['hoge.title'],
119
+ query: term
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ]
125
+ }
126
+ }
127
+ )
128
+ end
129
+
130
+ context 'and the query starts with a minus char' do
131
+ let(:query_string) do
132
+ '-' + term
133
+ end
134
+
135
+ it 'returns a must_not query for the specified query fields' do
136
+ should eq(
137
+ bool: {
138
+ must_not: [
139
+ {
140
+ bool: {
141
+ should: [
142
+ {
143
+ match: {
144
+ '_all' => term
145
+ }
146
+ },
147
+ {
148
+ nested: {
149
+ path: 'hoge',
150
+ query: {
151
+ multi_match: {
152
+ fields: ['hoge.title'],
153
+ query: term
154
+ }
155
+ }
156
+ }
157
+ }
158
+ ]
159
+ }
160
+ }
161
+ ]
162
+ }
163
+ )
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'and it is constructed with child_fields' do
169
+ let(:param) do
170
+ { child_fields: { child_type => child_field } }
171
+ end
172
+
173
+ let(:child_type) do
174
+ 'hoge'
175
+ end
176
+
177
+ let(:child_field) do
178
+ ['title']
179
+ end
180
+
181
+ it "returns a has_child query for the specified child field" do
182
+ should eq(
183
+ {
184
+ bool: {
185
+ should: [
186
+ {
187
+ match: {
188
+ '_all' => term
189
+ }
190
+ },
191
+ {
192
+ has_child: {
193
+ type: child_type,
194
+ query: {
195
+ multi_match: {
196
+ fields: child_field,
197
+ query: term
198
+ }
199
+ }
200
+ }
201
+ }
202
+ ]
203
+ }
204
+ }
205
+ )
206
+ end
207
+
208
+ context 'and the query starts with a minus char' do
209
+ let(:query_string) do
210
+ '-' + term
211
+ end
212
+
213
+ it 'returns a must_not query for the specified query fields' do
214
+ should eq(
215
+ bool: {
216
+ must_not: [
217
+ {
218
+ bool: {
219
+ should: [
220
+ {
221
+ match: {
222
+ '_all' => term
223
+ }
224
+ },
225
+ {
226
+ has_child: {
227
+ type: child_type,
228
+ query: {
229
+ multi_match: {
230
+ fields: child_field,
231
+ query: term
232
+ }
233
+ }
234
+ }
235
+ }
236
+ ]
237
+ }
238
+ }
239
+ ]
240
+ }
241
+ )
242
+ end
243
+ end
244
+ end
245
+
246
+ context 'and it is constructed with both all_query_fields and nested fields' do
247
+ let(:param) do
248
+ {
249
+ all_query_fields: ['hoge'],
250
+ nested_fields: { hoge: ['hoge.title'] }
251
+ }
252
+ end
253
+
254
+ it "returns a nested query for the specified nested path and fields" do
255
+ should eq(
256
+ {
257
+ bool: {
258
+ should: [
259
+ {
260
+ multi_match: {
261
+ fields: ['hoge'],
262
+ query: term
263
+ }
264
+ },
265
+ {
266
+ nested: {
267
+ path: 'hoge',
268
+ query: {
269
+ multi_match: {
270
+ fields: ['hoge.title'],
271
+ query: term
272
+ }
273
+ }
274
+ }
275
+ }
276
+ ]
277
+ }
278
+ }
279
+ )
280
+ end
281
+
282
+ context 'and the query starts with a minus char' do
283
+ let(:query_string) do
284
+ '-' + term
285
+ end
286
+
287
+ it 'returns a must_not query for the specified query fields' do
288
+ should eq(
289
+ bool: {
290
+ must_not: [
291
+ {
292
+ bool: {
293
+ should: [
294
+ {
295
+ multi_match: {
296
+ fields: ['hoge'],
297
+ query: term
298
+ }
299
+ },
300
+ {
301
+ nested: {
302
+ path: 'hoge',
303
+ query: {
304
+ multi_match: {
305
+ fields: ['hoge.title'],
306
+ query: term
307
+ }
308
+ }
309
+ }
310
+ }
311
+ ]
312
+ }
313
+ }
314
+ ]
315
+ }
316
+ )
317
+ end
318
+ end
319
+ end
320
+
321
+ context 'and it is constructed with both all_query_fields and child_fields' do
322
+ let(:param) do
323
+ {
324
+ all_query_fields: ['hoge'],
325
+ child_fields: { hoge: ['hoge.title'] }
326
+ }
327
+ end
328
+
329
+ it "returns a nested query for the specified nested path and fields" do
330
+ should eq(
331
+ {
332
+ bool: {
333
+ should: [
334
+ {
335
+ multi_match: {
336
+ fields: ['hoge'],
337
+ query: term
338
+ }
339
+ },
340
+ {
341
+ has_child: {
342
+ type: 'hoge',
343
+ query: {
344
+ multi_match: {
345
+ fields: ['hoge.title'],
346
+ query: term
347
+ }
348
+ }
349
+ }
350
+ }
351
+ ]
352
+ }
353
+ }
354
+ )
355
+ end
356
+
357
+ context 'and the query starts with a minus char' do
358
+ let(:query_string) do
359
+ '-' + term
360
+ end
361
+
362
+ it 'returns a must_not query for the specified query fields' do
363
+ should eq(
364
+ bool: {
365
+ must_not: [
366
+ {
367
+ bool: {
368
+ should: [
369
+ {
370
+ multi_match: {
371
+ fields: ['hoge'],
372
+ query: term
373
+ }
374
+ },
375
+ {
376
+ has_child: {
377
+ type: 'hoge',
378
+ query: {
379
+ multi_match: {
380
+ fields: ['hoge.title'],
381
+ query: term
382
+ }
383
+ }
384
+ }
385
+ }
386
+ ]
387
+ }
388
+ }
389
+ ]
390
+ }
391
+ )
392
+ end
393
+ end
394
+ end
395
+
396
+ context 'and it is constructed with all_query_fields, nested_fields, child_fields' do
397
+ let(:param) do
398
+ {
399
+ all_query_fields: all_query_fields,
400
+ nested_fields: { nested_path => nested_fields },
401
+ child_fields: { child_type => child_fields },
402
+ }
403
+ end
404
+
405
+ let(:all_query_fields) do
406
+ ['parent_field1']
407
+ end
408
+
409
+ let(:nested_path) do
410
+ 'hoge'
411
+ end
412
+
413
+ let(:nested_fields) do
414
+ ['hoge.field1']
415
+ end
416
+
417
+ let(:child_type) do
418
+ 'fuga'
419
+ end
420
+
421
+ let(:child_fields) do
422
+ ['child_field1']
423
+ end
424
+
425
+ it "returns a bool query for the match in nested or child" do
426
+ should eq(
427
+ {
428
+ bool: {
429
+ should: [
430
+ {
431
+ multi_match: {
432
+ fields: all_query_fields,
433
+ query: term
434
+ }
435
+ },
436
+ {
437
+ nested: {
438
+ path: nested_path,
439
+ query: {
440
+ multi_match: {
441
+ fields: nested_fields,
442
+ query: term
443
+ }
444
+ }
445
+ }
446
+ },
447
+ {
448
+ has_child: {
449
+ type: child_type,
450
+ query: {
451
+ multi_match: {
452
+ fields: child_fields,
453
+ query: term
454
+ }
455
+ }
456
+ }
457
+ }
458
+ ]
459
+ }
460
+ }
461
+ )
462
+ end
463
+
464
+ context 'and the query starts with a minus char' do
465
+ let(:query_string) do
466
+ '-' + term
467
+ end
468
+
469
+ it 'returns a must_not query for the specified query fields' do
470
+ should eq(
471
+ bool: {
472
+ must_not: [
473
+ {
474
+ bool: {
475
+ should: [
476
+ {
477
+ multi_match: {
478
+ fields: all_query_fields,
479
+ query: term
480
+ }
481
+ },
482
+ {
483
+ nested: {
484
+ path: nested_path,
485
+ query: {
486
+ multi_match: {
487
+ fields: nested_fields,
488
+ query: term
489
+ }
490
+ }
491
+ }
492
+ },
493
+ {
494
+ has_child: {
495
+ type: child_type,
496
+ query: {
497
+ multi_match: {
498
+ fields: child_fields,
499
+ query: term
500
+ }
501
+ }
502
+ }
503
+ }
504
+ ]
505
+ }
506
+ }
507
+ ]
508
+ }
509
+ )
510
+ end
511
+ end
512
+ end
93
513
  end
94
514
 
95
515
  context 'when term queries are given' do
@@ -206,6 +626,140 @@ describe EsQueryBuilder do
206
626
  )
207
627
  end
208
628
  end
629
+
630
+ context 'and it is constructed with nested_fields' do
631
+ let(:param) do
632
+ { nested_fields: { nested_path => nested_fields } }
633
+ end
634
+
635
+ let(:nested_path) do
636
+ 'hoge'
637
+ end
638
+
639
+ let(:nested_fields) do
640
+ ['hoge.title']
641
+ end
642
+
643
+ it 'returns a bool query with must match queries' do
644
+ should eq(
645
+ bool: {
646
+ must: [
647
+ {
648
+ bool: {
649
+ should: [
650
+ {
651
+ match: {
652
+ '_all' => term_1
653
+ }
654
+ },
655
+ {
656
+ nested: {
657
+ path: nested_path,
658
+ query: {
659
+ multi_match: {
660
+ fields: nested_fields,
661
+ query: term_1,
662
+ }
663
+ }
664
+ }
665
+ }
666
+ ]
667
+ }
668
+ },
669
+ {
670
+ bool: {
671
+ should: [
672
+ {
673
+ match: {
674
+ '_all' => term_2
675
+ }
676
+ },
677
+ {
678
+ nested: {
679
+ path: nested_path,
680
+ query: {
681
+ multi_match: {
682
+ fields: nested_fields,
683
+ query: term_2,
684
+ }
685
+ }
686
+ }
687
+ }
688
+ ]
689
+ }
690
+ }
691
+ ]
692
+ }
693
+ )
694
+ end
695
+ end
696
+
697
+ context 'and it is constructed with child_fields' do
698
+ let(:param) do
699
+ { child_fields: { child_type => child_fields } }
700
+ end
701
+
702
+ let(:child_type) do
703
+ 'hoge'
704
+ end
705
+
706
+ let(:child_fields) do
707
+ ['hoge.title']
708
+ end
709
+
710
+ it 'returns a bool query with must match queries' do
711
+ should eq(
712
+ bool: {
713
+ must: [
714
+ {
715
+ bool: {
716
+ should: [
717
+ {
718
+ match: {
719
+ '_all' => term_1
720
+ }
721
+ },
722
+ {
723
+ has_child: {
724
+ type: child_type,
725
+ query: {
726
+ multi_match: {
727
+ fields: child_fields,
728
+ query: term_1,
729
+ }
730
+ }
731
+ }
732
+ }
733
+ ]
734
+ }
735
+ },
736
+ {
737
+ bool: {
738
+ should: [
739
+ {
740
+ match: {
741
+ '_all' => term_2
742
+ }
743
+ },
744
+ {
745
+ has_child: {
746
+ type: child_type,
747
+ query: {
748
+ multi_match: {
749
+ fields: child_fields,
750
+ query: term_2,
751
+ }
752
+ }
753
+ }
754
+ }
755
+ ]
756
+ }
757
+ }
758
+ ]
759
+ }
760
+ )
761
+ end
762
+ end
209
763
  end
210
764
 
211
765
  context 'when a field query is given' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: es-query-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuku Takahashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-15 00:00:00.000000000 Z
11
+ date: 2015-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -61,6 +61,7 @@ extra_rdoc_files: []
61
61
  files:
62
62
  - ".gitignore"
63
63
  - ".travis.yml"
64
+ - CHANGELOG.md
64
65
  - Gemfile
65
66
  - LICENSE.txt
66
67
  - README.md