msfl_visitors 1.1.0.dev2 → 1.1.0.dev3

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: 45becd6e90f826ce123e6f4c5ee389e37c4f3337
4
- data.tar.gz: 87baa18d96cba72ef39deecf22447ecb7a7c30f3
3
+ metadata.gz: 8ec3bfda1d028fdcd4c4fb02eaf3e6a26c792e4b
4
+ data.tar.gz: f1e1324598941df2d59dc3da9b80ef2f0a4e3710
5
5
  SHA512:
6
- metadata.gz: cf1b9343cdf9a64ad46c916b36c37d5eb06c8cb43cb224329194722ad7af915e5e0069ce126004c68c4e46cdcd3e437dd0bcaf100986e5855ffe74a874a083dd
7
- data.tar.gz: c034ae9ca6db09fdc7ebc67387a61903e08de3cc8b84e1de735bd0607ac20a3b2688a029203bbd2ce16202ef48b05d51b4be0332e114f6b00c34e11045a0aa49
6
+ metadata.gz: 8f910b468fefd6e905119c76e9fd7e9939490b37789ba76f9d1cc5a32d42d30a565f44aa32c0c72e8cd149ac10e4b564972f66536d9a59ba95aeca592744d408
7
+ data.tar.gz: 9e621c0a5175d457c0dc0c63324ac229719442d01c9008a043ced18a41767c37597a5c14da68f710ed0cdf811c0820c07c17178a793b7820c2afc5a2ae67f0d3
@@ -12,19 +12,30 @@ module MSFLVisitors
12
12
  end
13
13
 
14
14
  def visit(node)
15
- case node
16
- when Nodes::Partial
17
- in_aggregation_mode do
18
- clauses.concat get_visitor.visit(node)
19
- ""
20
- end
21
- else
22
- get_visitor.visit(node)
15
+ if mode == :es_term
16
+ get_visitor.visit(node)
17
+ else
18
+ case node
19
+ when Nodes::Partial
20
+ in_aggregation_mode do
21
+ clauses.concat get_visitor.visit(node)
22
+ ""
23
+ end
24
+ else
25
+ get_visitor.visit(node)
26
+ end
23
27
  end
24
28
  end
25
29
 
26
30
  def get_visitor
27
- (mode == :term ? TermFilterVisitor : AggregationsVisitor).new(self)
31
+ case mode
32
+ when :term
33
+ TermFilterVisitor.new(self)
34
+ when :es_term
35
+ ESTermFilterVisitor.new(self)
36
+ else
37
+ AggregationsVisitor.new(self)
38
+ end
28
39
  end
29
40
 
30
41
  def in_aggregation_mode
@@ -202,6 +213,72 @@ module MSFLVisitors
202
213
 
203
214
  attr_reader :visitor
204
215
  end
216
+
217
+ class ESTermFilterVisitor
218
+ def initialize(visitor)
219
+ @visitor = visitor
220
+ end
221
+
222
+ RANGE_OPERATORS = {
223
+ Nodes::GreaterThan => :gt,
224
+ Nodes::GreaterThanEqual => :gte,
225
+ Nodes::LessThan => :lt,
226
+ Nodes::LessThanEqual => :lte,
227
+ }
228
+
229
+ def visit(node)
230
+ case node
231
+ when Nodes::Partial
232
+ { given: Hash[[node.left.accept(visitor), node.right.accept(visitor)]] }
233
+
234
+ when Nodes::Equal
235
+ { term: { node.left.accept(visitor) => node.right.accept(visitor) } }
236
+ # [{ clause: }]
237
+ when Nodes::Field
238
+ node.value.to_sym
239
+ when Nodes::Date, Nodes::Time
240
+ node.value.iso8601
241
+ when Nodes::Word,
242
+ Nodes::Number,
243
+ Nodes::Boolean,
244
+ Nodes::Dataset
245
+ node.value
246
+ when Nodes::GreaterThan,
247
+ Nodes::GreaterThanEqual,
248
+ Nodes::LessThan,
249
+ Nodes::LessThanEqual
250
+ { range: { node.left.accept(visitor) => { RANGE_OPERATORS[node.class] => node.right.accept(visitor) } } }
251
+ when Nodes::Given
252
+ [:filter, node.contents.first.accept(visitor)]
253
+ when Nodes::ExplicitFilter
254
+ [:filter, node.contents.map { |n| n.accept(visitor) }.reduce({}) { |hsh, x| hsh.merge!(x); hsh } ]
255
+ when Nodes::NamedValue
256
+ [:aggs, {node.name.accept(visitor).to_sym => Hash[[node.value.accept(visitor)]]}]
257
+ when Nodes::Containment
258
+ { terms: {node.left.accept(visitor).to_sym => node.right.accept(visitor)} }
259
+ when Nodes::Set
260
+ node.contents.map { |n| n.accept(visitor) }
261
+ when Nodes::Filter
262
+ if node.contents.count == 1
263
+ node.contents.first.accept visitor
264
+ else
265
+ { and: node.contents.map { |n| n.accept(visitor) } }
266
+ end
267
+ when Nodes::And
268
+ { and: node.set.accept(visitor) }
269
+
270
+ when Nodes::Foreign
271
+ { has_child: Hash[[[:type, node.left.accept(visitor)], node.right.accept(visitor)]] }
272
+
273
+ else
274
+ fail ArgumentError, "ES TermFilter Visitor cannot visit: #{node.class.name}"
275
+ end
276
+ end
277
+
278
+ private
279
+
280
+ attr_reader :visitor
281
+ end
205
282
  end
206
283
  end
207
284
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'msfl_visitors'
3
- s.version = '1.1.0.dev2'
3
+ s.version = '1.1.0.dev3'
4
4
  s.date = '2015-05-28'
5
5
  s.summary = "Convert MSFL to other forms"
6
6
  s.description = "Visitor pattern approach to converting MSFL to other forms."
@@ -0,0 +1,434 @@
1
+ require 'spec_helper'
2
+
3
+ describe MSFLVisitors::Visitor do
4
+
5
+ let(:node) { fail ArgumentError, "You must define the node variable in each scope." }
6
+
7
+ let(:visitor) { described_class.new }
8
+
9
+ let(:left) { MSFLVisitors::Nodes::Field.new "lhs" }
10
+
11
+ let(:right) { MSFLVisitors::Nodes::Word.new "rhs" }
12
+
13
+ subject(:result) { node.accept visitor }
14
+
15
+ context "when using the ESTermFilter visitor" do
16
+
17
+ before { visitor.mode = :es_term }
18
+
19
+ context "when visiting" do
20
+
21
+ describe "an unsupported node" do
22
+
23
+ class UnsupportedNode
24
+
25
+ def accept(visitor)
26
+ visitor.visit self
27
+ end
28
+ end
29
+
30
+ let(:node) { UnsupportedNode.new }
31
+
32
+ it "raises an ArgumentError" do
33
+ expect { subject }.to raise_error ArgumentError
34
+ end
35
+ end
36
+
37
+ describe "a Partial node" do
38
+
39
+ let(:node) { MSFLVisitors::Nodes::Partial.new given_node, named_value }
40
+
41
+ let(:given_node) { MSFLVisitors::Nodes::Given.new [given_equal_node] }
42
+
43
+ let(:given_equal_node) { MSFLVisitors::Nodes::Equal.new given_field_node, given_value_node }
44
+
45
+ let(:given_field_node) { MSFLVisitors::Nodes::Field.new :make }
46
+
47
+ let(:given_value_node) { MSFLVisitors::Nodes::Word.new "Toyota" }
48
+
49
+
50
+ let(:named_value) { MSFLVisitors::Nodes::NamedValue.new MSFLVisitors::Nodes::Word.new("partial"), explicit_filter_node }
51
+
52
+ let(:explicit_filter_node) { MSFLVisitors::Nodes::ExplicitFilter.new [greater_than_node] }
53
+
54
+ let(:greater_than_node) { MSFLVisitors::Nodes::GreaterThan.new field_node, value_node }
55
+
56
+ let(:field_node) { MSFLVisitors::Nodes::Field.new :age }
57
+
58
+ let(:value_node) { MSFLVisitors::Nodes::Number.new 10 }
59
+
60
+
61
+ subject { visitor.visit_tree node }
62
+
63
+ it "results in the appropriate clause" do
64
+ exp = [{
65
+ clause: {
66
+ given: {
67
+ filter: {
68
+ term: { make: "Toyota" }
69
+ },
70
+ aggs: {
71
+ partial: {
72
+ filter: { range: { age: { gt: 10 }}}
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }]
78
+ expect(subject).to eq exp
79
+ end
80
+ end
81
+
82
+ describe "a Given node" do
83
+
84
+ let(:node) { MSFLVisitors::Nodes::Given.new [given_equal_node] }
85
+
86
+ let(:given_equal_node) { MSFLVisitors::Nodes::Equal.new given_field_node, given_value_node }
87
+
88
+ let(:given_field_node) { MSFLVisitors::Nodes::Field.new :make }
89
+
90
+ let(:given_value_node) { MSFLVisitors::Nodes::Word.new "Toyota" }
91
+
92
+
93
+ it "results in: [:filter, { term: { make: \"Toyota\" } }]" do
94
+ expect(subject).to eq([:filter, { term: { make: "Toyota" } }])
95
+ end
96
+ end
97
+
98
+ describe "a Foreign node" do
99
+
100
+ let(:node) { MSFLVisitors::Nodes::Foreign.new dataset_node, filter_node }
101
+
102
+ let(:dataset_node) { MSFLVisitors::Nodes::Dataset.new "person" }
103
+
104
+ let(:filter_node) { MSFLVisitors::Nodes::ExplicitFilter.new [equal_node] }
105
+
106
+ let(:equal_node) { MSFLVisitors::Nodes::Equal.new left, right }
107
+
108
+ let(:left) { MSFLVisitors::Nodes::Field.new :age }
109
+
110
+ let(:right) { MSFLVisitors::Nodes::Number.new 25 }
111
+
112
+ it "results in: { has_child: { type: \"person\", filter: { term: { age: 25 } } } }" do
113
+ expect(subject).to eq({ has_child: { type: "person", filter: { term: { age: 25 } } } })
114
+ end
115
+ end
116
+
117
+ describe "a Containment node" do
118
+
119
+ let(:node) { MSFLVisitors::Nodes::Containment.new field, values }
120
+
121
+ let(:values) { MSFLVisitors::Nodes::Set.new(MSFL::Types::Set.new([item_one, item_two, item_three])) }
122
+
123
+ let(:item_one) { MSFLVisitors::Nodes::Word.new "item_one" }
124
+
125
+ let(:item_two) { MSFLVisitors::Nodes::Word.new "item_two" }
126
+
127
+ let(:item_three) { MSFLVisitors::Nodes::Word.new "item_three" }
128
+
129
+ let(:field) { left }
130
+
131
+ it "results in: { terms: { lhs: [\"item_one\", \"item_two\", \"item_three\"] } }" do
132
+ expect(subject).to eq({ terms: { lhs: ["item_one", "item_two", "item_three"] } })
133
+ end
134
+ end
135
+
136
+ describe "a Set node" do
137
+
138
+ let(:node) { MSFLVisitors::Nodes::Set.new values }
139
+
140
+ let(:values) { MSFL::Types::Set.new([item_one, item_two]) }
141
+
142
+ let(:item_one) { MSFLVisitors::Nodes::Word.new "item_one" }
143
+
144
+ let(:item_two) { MSFLVisitors::Nodes::Word.new "item_two" }
145
+
146
+ it "results in: [\"item_one\", \"item_two\"]" do
147
+ expect(result).to eq ["item_one", "item_two"]
148
+ end
149
+ end
150
+
151
+ describe "an Equal node" do
152
+
153
+ let(:node) { MSFLVisitors::Nodes::Equal.new left, right }
154
+
155
+ it "results in: { term: { lhs: \"rhs\" } }" do
156
+ expect(result).to eq({ term: { lhs: "rhs" } })
157
+ end
158
+ end
159
+
160
+ describe "a GreaterThan node" do
161
+
162
+ let(:node) { MSFLVisitors::Nodes::GreaterThan.new left, right }
163
+
164
+ let(:right) { MSFLVisitors::Nodes::Number.new 1000 }
165
+
166
+ it "results in: { range: { lhs: { gt: 1000 } } }" do
167
+ expect(result).to eq({ range: { lhs: { gt: 1000 } } })
168
+ end
169
+ end
170
+
171
+ describe "a GreaterThanEqual node" do
172
+
173
+ let(:node) { MSFLVisitors::Nodes::GreaterThanEqual.new left, right }
174
+
175
+ let(:right) { MSFLVisitors::Nodes::Number.new 10.52 }
176
+
177
+ it "results in: { range: { lhs: { gte: 10.52 } } }" do
178
+ expect(result).to eq({ range: { lhs: { gte: 10.52 } } })
179
+ end
180
+ end
181
+
182
+ describe "a LessThan node" do
183
+
184
+ let(:node) { MSFLVisitors::Nodes::LessThan.new left, right }
185
+
186
+ let(:right) { MSFLVisitors::Nodes::Number.new 133.7 }
187
+
188
+ it "returns: { range: { lhs: { lt: 133.7 } } }" do
189
+ expect(result).to eq({ range: { lhs: { lt: 133.7 } } })
190
+ end
191
+ end
192
+
193
+ describe "a LessThanEqual node" do
194
+
195
+ let(:node) { MSFLVisitors::Nodes::LessThanEqual.new left, right }
196
+
197
+ let(:right) { MSFLVisitors::Nodes::Date.new Date.today }
198
+
199
+ it "returns: { range: { lhs: { lte: \"#{Date.today}\" } } }" do
200
+ expect(result).to eq({ range: { lhs: { lte: "#{Date.today}" } } })
201
+ end
202
+ end
203
+
204
+ describe "a Filter node" do
205
+
206
+ let(:node) { MSFLVisitors::Nodes::Filter.new filtered_nodes }
207
+
208
+ let(:filtered_nodes) do
209
+ [
210
+ MSFLVisitors::Nodes::GreaterThanEqual.new(
211
+ MSFLVisitors::Nodes::Field.new(:value),
212
+ MSFLVisitors::Nodes::Number.new(1000))
213
+ ]
214
+ end
215
+
216
+ it "returns: { range: { value: { gte: 1000 } } }" do
217
+ expect(result).to eq({ range: { value: { gte: 1000 } } })
218
+ end
219
+
220
+ context "when the filter has multiple children" do
221
+
222
+ let(:filtered_nodes) do
223
+ [
224
+ MSFLVisitors::Nodes::Equal.new(
225
+ MSFLVisitors::Nodes::Field.new(:make),
226
+ MSFLVisitors::Nodes::Word.new("Chevy")
227
+ ),
228
+ MSFLVisitors::Nodes::GreaterThanEqual.new(
229
+ MSFLVisitors::Nodes::Field.new(:value),
230
+ MSFLVisitors::Nodes::Number.new(1000))
231
+ ]
232
+ end
233
+
234
+ it "returns: { and: [{ term: { make: \"Chevy\" } },{ range: { value: { gte: 1000 } } }] }" do
235
+ expect(result).to eq({ and: [{ term: { make: "Chevy" } },{ range: { value: { gte: 1000 } } }] })
236
+ end
237
+ end
238
+ end
239
+
240
+ describe "an And node" do
241
+
242
+ let(:first_field) { MSFLVisitors::Nodes::Field.new "first_field" }
243
+
244
+ let(:first_value) { MSFLVisitors::Nodes::Word.new "first_word" }
245
+
246
+ let(:first) { MSFLVisitors::Nodes::Equal.new(first_field, first_value) }
247
+
248
+ let(:second_field) { MSFLVisitors::Nodes::Field.new "second_field" }
249
+
250
+ let(:second_value) { MSFLVisitors::Nodes::Word.new "second_word" }
251
+
252
+ let(:second) { MSFLVisitors::Nodes::Equal.new(second_field, second_value) }
253
+
254
+ let(:third_field) { MSFLVisitors::Nodes::Field.new "third_field" }
255
+
256
+ let(:third_value) { MSFLVisitors::Nodes::Word.new "third_word" }
257
+
258
+ let(:third) { MSFLVisitors::Nodes::Equal.new(third_field, third_value) }
259
+
260
+ let(:node) { MSFLVisitors::Nodes::And.new(set_node) }
261
+
262
+ context "when the And node has zero items" do
263
+
264
+ let(:set_node) { MSFLVisitors::Nodes::Set.new [] }
265
+
266
+ it "returns: { and: [] }" do
267
+ expect(result).to eq({ and: [] })
268
+ end
269
+ end
270
+
271
+ context "when the node has one item" do
272
+
273
+ let(:set_node) { MSFLVisitors::Nodes::Set.new [first] }
274
+
275
+ it "returns: { and: [{ term: { first_field: \"first_word\" }] }" do
276
+ expect(result).to eq({ and: [{ term: { first_field: "first_word" } }] })
277
+ end
278
+ end
279
+
280
+ context "when the node has two items" do
281
+
282
+ let(:set_node) { MSFLVisitors::Nodes::Set.new [first, second] }
283
+
284
+ it "returns: { and: [{ term: { first_field: \"first_word\" } },{ term: { second_field: \"second_word\" } }] }" do
285
+ expect(result).to eq({ and: [{ term: { first_field: "first_word" }}, { term: { second_field: "second_word" } }] })
286
+ end
287
+ end
288
+
289
+ context "when the node has three items" do
290
+
291
+ let(:set_node) { MSFLVisitors::Nodes::Set.new [first, second, third] }
292
+
293
+ it "returns: { and: [{ term: { first_field: \"first_word\" } },{ term: { second_field: \"second_word\" } },{ term: { third_field: \"third_word\" } }] }" do
294
+ expect(result).to eq({ and: [{ term: { first_field: "first_word" } },{ term: { second_field: "second_word" } },{ term: { third_field: "third_word" } }] })
295
+ end
296
+ end
297
+
298
+ context "when one of the node's items is a containment node" do
299
+
300
+ let(:node) do
301
+ MSFLVisitors::Nodes::And.new(
302
+ MSFLVisitors::Nodes::Set.new(
303
+ [
304
+ MSFLVisitors::Nodes::Filter.new(
305
+ [
306
+ MSFLVisitors::Nodes::Containment.new(
307
+ MSFLVisitors::Nodes::Field.new(:make),
308
+ MSFLVisitors::Nodes::Set.new(
309
+ [
310
+ MSFLVisitors::Nodes::Word.new("Honda"),
311
+ MSFLVisitors::Nodes::Word.new("Chevy"),
312
+ MSFLVisitors::Nodes::Word.new("Volvo")
313
+ ]
314
+ )
315
+ )
316
+ ]
317
+ ),
318
+ MSFLVisitors::Nodes::Filter.new(
319
+ [
320
+ MSFLVisitors::Nodes::GreaterThanEqual.new(
321
+ MSFLVisitors::Nodes::Field.new(:value),
322
+ MSFLVisitors::Nodes::Number.new(1000)
323
+ )
324
+ ]
325
+ )
326
+ ]
327
+ )
328
+ )
329
+ end
330
+ it "returns: { and: [{ terms: { make: [\"Honda\",\"Chevy\",\"Volvo\"]} }, { range: { value: { gte: 1000 } } }] }" do
331
+ expected = { and: [{ terms: { make: ["Honda", "Chevy", "Volvo"]} }, { range: { value: { gte: 1000 } } }] }
332
+ expect(result).to eq expected
333
+ end
334
+ end
335
+ end
336
+
337
+ describe "value nodes" do
338
+ describe "a Boolean node" do
339
+
340
+ let(:node) { MSFLVisitors::Nodes::Boolean.new value }
341
+
342
+ subject(:result) { node.accept visitor }
343
+
344
+ context "with a value of true" do
345
+
346
+ let(:value) { true }
347
+
348
+ it "returns: true" do
349
+ expect(result).to eq true
350
+ end
351
+ end
352
+
353
+ context "with a value of false" do
354
+
355
+ let(:value) { false }
356
+
357
+ it "returns: false" do
358
+ expect(result).to eq false
359
+ end
360
+ end
361
+ end
362
+
363
+ describe "a Word node" do
364
+
365
+ let(:word) { "node_content" }
366
+
367
+ let(:node) { MSFLVisitors::Nodes::Word.new word }
368
+
369
+ it "returns: the literal string" do
370
+ expect(result).to eq "#{word}"
371
+ end
372
+ end
373
+ end
374
+
375
+ describe "range value nodes" do
376
+
377
+ subject(:result) { node.accept visitor }
378
+
379
+ describe "a Date node" do
380
+
381
+ let(:today) { Date.today }
382
+
383
+ let(:node) { MSFLVisitors::Nodes::Date.new today }
384
+
385
+ it "returns: the date using iso8601 formatting" do
386
+ expect(result).to eq "#{today.iso8601}"
387
+ end
388
+ end
389
+
390
+ describe "a Time node" do
391
+
392
+ let(:now) { Time.now }
393
+
394
+ let(:node) { MSFLVisitors::Nodes::Time.new now }
395
+
396
+ it "returns: the date using iso8601 formatting" do
397
+ expect(result).to eq "#{now.iso8601}"
398
+ end
399
+ end
400
+
401
+ describe "a DateTime node" do
402
+
403
+ let(:now) { DateTime.now }
404
+
405
+ let(:node) { MSFLVisitors::Nodes::DateTime.new now }
406
+
407
+ it "returns: the date and time using iso8601 formatting" do
408
+ expect(result).to eq "#{now.iso8601}"
409
+ end
410
+ end
411
+
412
+ describe "a Number node" do
413
+
414
+ let(:number) { 123 }
415
+
416
+ let(:node) { MSFLVisitors::Nodes::Number.new number }
417
+
418
+ it "returns: 123" do
419
+ expect(result).to eq number
420
+ end
421
+
422
+ context "when the number is a float" do
423
+
424
+ let(:number) { 123.456 }
425
+
426
+ it "returns: the number with the same precision" do
427
+ expect(result).to eq number
428
+ end
429
+ end
430
+ end
431
+ end
432
+ end
433
+ end
434
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msfl_visitors
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.dev2
4
+ version: 1.1.0.dev3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Courtland Caldwell
@@ -154,6 +154,7 @@ files:
154
154
  - spec/msfl_visitors/parsers/msfl_parser_spec.rb
155
155
  - spec/msfl_visitors/visitor_spec.rb
156
156
  - spec/msfl_visitors/visitors/chewy_term_filter_spec.rb
157
+ - spec/msfl_visitors/visitors/es_term_filter_spec.rb
157
158
  - spec/msfl_visitors_spec.rb
158
159
  - spec/spec_helper.rb
159
160
  homepage: https://github.com/Referly/msfl_visitors
@@ -186,6 +187,7 @@ test_files:
186
187
  - spec/msfl_visitors/parsers/msfl_parser_spec.rb
187
188
  - spec/msfl_visitors/visitor_spec.rb
188
189
  - spec/msfl_visitors/visitors/chewy_term_filter_spec.rb
190
+ - spec/msfl_visitors/visitors/es_term_filter_spec.rb
189
191
  - spec/msfl_visitors_spec.rb
190
192
  - spec/spec_helper.rb
191
193
  has_rdoc: