sparkql 0.1.8
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.
- data/.gitignore +5 -0
- data/.rvmrc +2 -0
- data/Gemfile +5 -0
- data/README.md +54 -0
- data/Rakefile +18 -0
- data/VERSION +1 -0
- data/lib/sparkql/errors.rb +81 -0
- data/lib/sparkql/expression_state.rb +20 -0
- data/lib/sparkql/function_resolver.rb +106 -0
- data/lib/sparkql/lexer.rb +114 -0
- data/lib/sparkql/parser.rb +268 -0
- data/lib/sparkql/parser.y +93 -0
- data/lib/sparkql/parser_compatibility.rb +231 -0
- data/lib/sparkql/parser_tools.rb +93 -0
- data/lib/sparkql/token.rb +21 -0
- data/lib/sparkql/version.rb +3 -0
- data/lib/sparkql.rb +13 -0
- data/sparkql.gemspec +28 -0
- data/test/test_helper.rb +2 -0
- data/test/unit/expression_state_test.rb +57 -0
- data/test/unit/function_resolver_test.rb +50 -0
- data/test/unit/lexer_test.rb +29 -0
- data/test/unit/parser_compatability_test.rb +437 -0
- data/test/unit/parser_test.rb +215 -0
- metadata +183 -0
@@ -0,0 +1,437 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ParserCompatabilityTest < Test::Unit::TestCase
|
4
|
+
include Sparkql
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@expression_keys = [:field, :operator, :value]
|
8
|
+
@multiple_types = [:character,:integer]
|
9
|
+
@bad_character_strings = ["'Fargo's Boat'", "Fargo", "''Fargo''", "'Fargo''s'",
|
10
|
+
"'Fargo", "Fargo'", "\\'Fargo\\'"]
|
11
|
+
@bad_multiple_character_strings = ["'Fargo's Boat'", "Fargo", "''Fargo''", "'Fargo''s'",
|
12
|
+
"'Fargo", "Fargo'", "\\'Fargo\\'"]
|
13
|
+
@all_bad_strings = @bad_character_strings + @bad_multiple_character_strings
|
14
|
+
@test_filters = [
|
15
|
+
{
|
16
|
+
:string => "City Eq 'Fargo'",
|
17
|
+
:type => :character,
|
18
|
+
:operator => "Eq"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
:string => "City Ne 'Fargo'",
|
22
|
+
:type => :character,
|
23
|
+
:operator => "Not Eq"
|
24
|
+
},
|
25
|
+
{
|
26
|
+
:string => "City Eq 'Fargo','Moorhead'",
|
27
|
+
:type => :character,
|
28
|
+
:operator => "In"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
:string => "City Eq 'Fargo','Moorhead','Bemidji','Duluth'",
|
32
|
+
:type => :character,
|
33
|
+
:operator => "In"
|
34
|
+
},
|
35
|
+
{
|
36
|
+
:string => "City Ne 'Fargo','Moorhead','Bemidji','Duluth'",
|
37
|
+
:type => :character,
|
38
|
+
:operator => "Not In"
|
39
|
+
},
|
40
|
+
{
|
41
|
+
:string => "IntegerField Eq 2001",
|
42
|
+
:type => :integer,
|
43
|
+
:operator => "Eq"
|
44
|
+
},
|
45
|
+
{
|
46
|
+
:string => "IntegerField Eq -2001",
|
47
|
+
:type => :integer,
|
48
|
+
:operator => "Eq"
|
49
|
+
},
|
50
|
+
{
|
51
|
+
:string => "IntegerField Eq 2001,2002",
|
52
|
+
:type => :integer,
|
53
|
+
:operator => "In"
|
54
|
+
},
|
55
|
+
{
|
56
|
+
:string => "IntegerField Eq -2001,-2002",
|
57
|
+
:type => :integer,
|
58
|
+
:operator => "In"
|
59
|
+
},
|
60
|
+
{
|
61
|
+
:string => "FloatField Eq 2001.120",
|
62
|
+
:type => :decimal,
|
63
|
+
:operator => "Eq"
|
64
|
+
},
|
65
|
+
{
|
66
|
+
:string => "FloatField Eq -2001.120",
|
67
|
+
:type => :decimal,
|
68
|
+
:operator => "Eq"
|
69
|
+
},
|
70
|
+
{
|
71
|
+
:string => "FloatField Eq -2001.120,-2002.0",
|
72
|
+
:type => :decimal,
|
73
|
+
:operator => "In"
|
74
|
+
},
|
75
|
+
{
|
76
|
+
:string => "DateField Eq 2010-10-10",
|
77
|
+
:type => :date,
|
78
|
+
:operator => "Eq"
|
79
|
+
},
|
80
|
+
{
|
81
|
+
:string => "TimestampField Eq 2010-10-10T10:10:30.000000",
|
82
|
+
:type => :datetime,
|
83
|
+
:operator => "Eq"
|
84
|
+
},
|
85
|
+
{
|
86
|
+
:string => "TimestampField Lt 2010-10-10T10:10:30.000000",
|
87
|
+
:type => :datetime,
|
88
|
+
:operator => "Lt"
|
89
|
+
},
|
90
|
+
{
|
91
|
+
:string => "TimestampField Gt 2010-10-10T10:10:30.000000",
|
92
|
+
:type => :datetime,
|
93
|
+
:operator => "Gt"
|
94
|
+
},
|
95
|
+
{
|
96
|
+
:string => "TimestampField Ge 2010-10-10T10:10:30.000000",
|
97
|
+
:type => :datetime,
|
98
|
+
:operator => "Ge"
|
99
|
+
},
|
100
|
+
{
|
101
|
+
:string => "TimestampField Le 2010-10-10T10:10:30.000000",
|
102
|
+
:type => :datetime,
|
103
|
+
:operator => "Le"
|
104
|
+
},
|
105
|
+
{
|
106
|
+
:string => "BooleanField Eq true",
|
107
|
+
:type => :boolean,
|
108
|
+
:operator => "Eq"
|
109
|
+
},
|
110
|
+
{
|
111
|
+
:string => "BooleanField Eq false",
|
112
|
+
:type => :boolean,
|
113
|
+
:operator => "Eq"
|
114
|
+
}]
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
def compare_expression_to_tokens( expression, tokens )
|
119
|
+
counter = 0
|
120
|
+
@expression_keys.each do |key|
|
121
|
+
assert_equal tokens[counter], expression[key]
|
122
|
+
counter += 1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def find_operator(string)
|
127
|
+
["Eq","Ne","Gt","Ge","Lt","Le"].each do |op|
|
128
|
+
return op if string.include? " #{op} "
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
test "simple tokenize" do
|
134
|
+
filter = "City Eq 'Fargo'"
|
135
|
+
filter_tokens = filter.split(" ")
|
136
|
+
parser = Parser.new
|
137
|
+
expressions = parser.tokenize( filter )
|
138
|
+
|
139
|
+
assert !parser.errors?
|
140
|
+
assert_equal 1, expressions.size, "#Expressions {expressions.inspect}"
|
141
|
+
compare_expression_to_tokens(expressions.first, filter_tokens)
|
142
|
+
end
|
143
|
+
|
144
|
+
test "types" do
|
145
|
+
@test_filters.each do |elem|
|
146
|
+
parser = Parser.new
|
147
|
+
expressions = parser.tokenize( elem[:string] )
|
148
|
+
|
149
|
+
assert !parser.errors?, "Query: #{elem.inspect}"
|
150
|
+
assert_equal elem[:type], expressions.first[:type]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
test "operators" do
|
155
|
+
@test_filters.each do |elem|
|
156
|
+
parser = Parser.new
|
157
|
+
expressions = parser.tokenize( elem[:string] )
|
158
|
+
assert !parser.errors?, "Query: #{elem.inspect}"
|
159
|
+
assert_equal elem[:operator], expressions.first[:operator]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
test "tokenize with and" do
|
164
|
+
filter = "City Eq 'Fargo' And PropertyType Eq 'A'"
|
165
|
+
filter_a = filter.split(" And ")
|
166
|
+
filter_tokens = []
|
167
|
+
filter_a.each do |f|
|
168
|
+
filter_tokens << f.split(" ")
|
169
|
+
end
|
170
|
+
parser = Parser.new
|
171
|
+
expressions = parser.tokenize( filter )
|
172
|
+
|
173
|
+
assert !parser.errors?
|
174
|
+
assert_equal 2, expressions.size
|
175
|
+
|
176
|
+
counter = 0
|
177
|
+
filter_tokens.each do |t|
|
178
|
+
compare_expression_to_tokens(expressions[counter], t)
|
179
|
+
counter += 1
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
test "tokenize with or" do
|
184
|
+
filter = "City Eq 'Fargo' Or PropertyType Eq 'A'"
|
185
|
+
filter_a = filter.split(" Or ")
|
186
|
+
filter_tokens = []
|
187
|
+
filter_a.each do |f|
|
188
|
+
filter_tokens << f.split(" ")
|
189
|
+
end
|
190
|
+
parser = Parser.new
|
191
|
+
expressions = parser.tokenize( filter )
|
192
|
+
|
193
|
+
assert !parser.errors?
|
194
|
+
assert_equal 2, expressions.size
|
195
|
+
|
196
|
+
counter = 0
|
197
|
+
filter_tokens.each do |t|
|
198
|
+
compare_expression_to_tokens(expressions[counter], t)
|
199
|
+
counter += 1
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
test "tokenize fail on missing" do
|
204
|
+
# We want to cut out each piece of this individually, and make sure
|
205
|
+
# tokenization fails
|
206
|
+
filter = "City Eq 'Fargo' And PropertyType Eq 'A'"
|
207
|
+
filter_tokens = filter.split(" ")
|
208
|
+
|
209
|
+
filter_tokens.each do |token|
|
210
|
+
f = filter.gsub(token, "").gsub(/\s+/," ")
|
211
|
+
parser = Parser.new
|
212
|
+
expressions = parser.tokenize( f )
|
213
|
+
assert_nil expressions
|
214
|
+
assert parser.errors?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
test "tokenize fail on invalid string operator" do
|
219
|
+
|
220
|
+
filter = "City Eq "
|
221
|
+
|
222
|
+
@bad_character_strings.each do |string|
|
223
|
+
f = filter + string
|
224
|
+
parser = Parser.new
|
225
|
+
expressions = parser.tokenize( f )
|
226
|
+
assert_nil expressions
|
227
|
+
assert parser.errors?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
test "tokenize fail on invalid operator or field" do
|
232
|
+
filters = ["Eq Eq 'Fargo'","City City 'Fargo'", "And Eq 'Fargo'",
|
233
|
+
"City And 'Fargo'", "city eq 'Fargo'"]
|
234
|
+
filters.each do |f|
|
235
|
+
parser = Parser.new
|
236
|
+
expressions = parser.tokenize( f )
|
237
|
+
assert_nil expressions, "filter: #{f}"
|
238
|
+
assert parser.errors?
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
test "tokenize edge case string value" do
|
243
|
+
good_strings = ["'Fargo\\'s Boat'", "'Fargo'", "'Fargo\\'\\'s'",
|
244
|
+
"' Fargo '", " 'Fargo' "]
|
245
|
+
|
246
|
+
filters = ["City Eq ","City Eq ", "City Eq "]
|
247
|
+
|
248
|
+
filters.each do |filter|
|
249
|
+
good_strings.each do |string|
|
250
|
+
f = filter + string
|
251
|
+
parser = Parser.new
|
252
|
+
expressions = parser.tokenize( f )
|
253
|
+
assert !parser.errors?
|
254
|
+
assert_equal 1, expressions.size
|
255
|
+
assert_equal string.strip, expressions.first[:value]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
test "get multiple values" do
|
261
|
+
@test_filters.each do |f|
|
262
|
+
op = find_operator f[:string]
|
263
|
+
next unless @multiple_types.include?(f[:type]) || op.nil?
|
264
|
+
parser = Parser.new
|
265
|
+
val = f[:string].split(" #{op} ")[1]
|
266
|
+
vals = parser.tokenize(f[:string]).first[:value]
|
267
|
+
assert_equal val, Array(vals).join(',')
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
test "multiples fail with unsupported operators" do
|
272
|
+
["Gt","Ge","Lt","Le"].each do |op|
|
273
|
+
f = "IntegerType #{op} 100,200"
|
274
|
+
parser = Parser.new
|
275
|
+
expressions = parser.tokenize( f )
|
276
|
+
assert parser.errors?
|
277
|
+
assert_equal op, parser.errors.first.token
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
test "bad multiples" do
|
282
|
+
@all_bad_strings.each do |bad|
|
283
|
+
parser = Parser.new
|
284
|
+
ex = parser.tokenize("City Eq #{bad}")
|
285
|
+
assert parser.errors?
|
286
|
+
assert_nil ex
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
test "max out values" do
|
291
|
+
parser = Parser.new
|
292
|
+
to_the_max = []
|
293
|
+
35.times do |x|
|
294
|
+
to_the_max << x
|
295
|
+
end
|
296
|
+
ex = parser.tokenize("City Eq #{to_the_max.join(',')}")
|
297
|
+
vals = ex.first[:value]
|
298
|
+
assert_equal 25, vals.size
|
299
|
+
end
|
300
|
+
|
301
|
+
test "max out expressions" do
|
302
|
+
parser = Parser.new
|
303
|
+
to_the_max = []
|
304
|
+
60.times do |x|
|
305
|
+
to_the_max << "City Eq 'Fargo'"
|
306
|
+
end
|
307
|
+
vals = parser.tokenize(to_the_max.join(" And "))
|
308
|
+
assert_equal 50, vals.size
|
309
|
+
end
|
310
|
+
|
311
|
+
test "API-107 And/Or in string spiel" do
|
312
|
+
search_strings = ['Tom And Jerry', 'Tom Or Jerry', 'And Or Eq', 'City Eq \\\'Fargo\\\'',
|
313
|
+
' And Eq Or ', 'Or And Or']
|
314
|
+
search_strings.each do |s|
|
315
|
+
parser = Parser.new
|
316
|
+
parser.tokenize("City Eq '#{s}' And PropertyType Eq 'A'")
|
317
|
+
assert !parser.errors?
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
test "general paren test" do
|
322
|
+
[
|
323
|
+
"(City Eq 'Fargo')",
|
324
|
+
"(City Eq 'Fargo') And PropertyType Eq 'A'",
|
325
|
+
"(City Eq 'Fargo') And (City Eq 'Moorhead')"
|
326
|
+
].each do |filter|
|
327
|
+
parser = Parser.new
|
328
|
+
p = parser.tokenize(filter)
|
329
|
+
assert !parser.errors?
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
test "general nesting fail test" do
|
334
|
+
[
|
335
|
+
"((City Eq 'Fargo')",
|
336
|
+
"((City Eq 'Fargo') And PropertyType Eq 'A'",
|
337
|
+
"(City Eq 'Fargo')) And (City Eq 'Moorhead')",
|
338
|
+
"City Eq 'Fargo')",
|
339
|
+
"(City Eq 'Fargo') And PropertyType Eq 'A')",
|
340
|
+
"City Eq 'Fargo' (And) City Eq 'Moorhead'"
|
341
|
+
].each do |filter|
|
342
|
+
parser = Parser.new
|
343
|
+
p = parser.tokenize(filter)
|
344
|
+
assert parser.errors?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
test "block group" do
|
349
|
+
parser = Parser.new
|
350
|
+
p = parser.tokenize("(City Eq 'Fargo' Or TotalBr Eq 2) And (City Eq 'Moorhead')")
|
351
|
+
assert !parser.errors?
|
352
|
+
assert p.first[:block_group] == p[1][:block_group]
|
353
|
+
assert p.first[:block_group] == p[2][:block_group] - 1
|
354
|
+
end
|
355
|
+
|
356
|
+
test "proper nesting" do
|
357
|
+
parser = Parser.new
|
358
|
+
p = parser.tokenize("(City Eq 'Fargo' Or TotalBr Eq 2) And PropertyType Eq 'A'")
|
359
|
+
assert !parser.errors?
|
360
|
+
p.each do |token|
|
361
|
+
if ["City","TotalBr"].include?(token[:field])
|
362
|
+
assert_equal 1, token[:level], "Token: #{token.inspect}"
|
363
|
+
else
|
364
|
+
assert_equal 0, token[:level]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
parser = Parser.new
|
369
|
+
p = parser.tokenize("(City Eq 'Fargo' Or TotalBr Eq 2 Or City Eq 'Moorhead') " +
|
370
|
+
"And PropertyType Eq 'A' And (TotalBr Eq 1 And TotalBr Eq 2)")
|
371
|
+
assert !parser.errors?
|
372
|
+
p.each do |token|
|
373
|
+
if ["City","TotalBr"].include?(token[:field])
|
374
|
+
assert_equal 1, token[:level]
|
375
|
+
else
|
376
|
+
assert_equal 0, token[:level]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
test "maximum nesting of 2" do
|
382
|
+
parser = Parser.new
|
383
|
+
p = parser.tokenize("(City Eq 'Fargo' Or (TotalBr Eq 2 And (City Eq 'Moorhead'))) And PropertyType Eq 'A'")
|
384
|
+
assert parser.errors?
|
385
|
+
assert_equal "You have exceeded the maximum nesting level. Please nest no more than 2 levels deep.", parser.errors.first.message
|
386
|
+
end
|
387
|
+
|
388
|
+
test "tokenize custom field" do
|
389
|
+
filter = '"General Property Description"."Zoning" Eq \'Commercial\''
|
390
|
+
filter_tokens = ['"General Property Description"."Zoning"', 'Eq', "'Commercial'"]
|
391
|
+
parser = Parser.new
|
392
|
+
expressions = parser.tokenize( filter )
|
393
|
+
|
394
|
+
assert !parser.errors?, "Parser errrors [#{filter}]: #{parser.errors.inspect}"
|
395
|
+
assert_equal 1, expressions.size, "Expression #{expressions.inspect}"
|
396
|
+
compare_expression_to_tokens(expressions.first, filter_tokens)
|
397
|
+
assert expressions.first[:custom_field], "Expression #{expressions.first.inspect}"
|
398
|
+
end
|
399
|
+
|
400
|
+
test "tokenize custom field with special characters" do
|
401
|
+
filter = '"Security"."@R080T$\' ` ` `#" Eq \'R2D2\''
|
402
|
+
filter_tokens = ['"Security"."@R080T$\' ` ` `#"', 'Eq', "'R2D2'"]
|
403
|
+
parser = Parser.new
|
404
|
+
expressions = parser.tokenize( filter )
|
405
|
+
assert !parser.errors?, "Parser errrors [#{filter}]: #{parser.errors.inspect}"
|
406
|
+
assert_equal 1, expressions.size, "Expression #{expressions.inspect}"
|
407
|
+
compare_expression_to_tokens(expressions.first, filter_tokens)
|
408
|
+
assert expressions.first[:custom_field], "Expression #{expressions.first.inspect}"
|
409
|
+
end
|
410
|
+
|
411
|
+
test "custom field supports all types" do
|
412
|
+
types = {
|
413
|
+
:character => "'character'",
|
414
|
+
:integer => 1234,
|
415
|
+
:decimal => 12.34,
|
416
|
+
:boolean => true
|
417
|
+
}
|
418
|
+
types.each_pair do |key, value|
|
419
|
+
filter = '"Details"."Random" Eq ' + "#{value}"
|
420
|
+
filter_tokens = ['"Details"."Random"', 'Eq', "#{value}"]
|
421
|
+
parser = Parser.new
|
422
|
+
expressions = parser.tokenize( filter )
|
423
|
+
|
424
|
+
assert !parser.errors?, "Parser errrors [#{filter}]: #{parser.errors.inspect}"
|
425
|
+
assert_equal 1, expressions.size, "Expression #{expressions.inspect}"
|
426
|
+
compare_expression_to_tokens(expressions.first, filter_tokens)
|
427
|
+
assert expressions.first[:custom_field], "Expression #{expressions.first.inspect}"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
test "escape boolean value" do
|
432
|
+
parser = Parser.new
|
433
|
+
expressions = parser.tokenize( "BooleanField Eq true" )
|
434
|
+
assert_equal true, parser.escape_value(expressions.first)
|
435
|
+
end
|
436
|
+
|
437
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ParserTest < Test::Unit::TestCase
|
4
|
+
include Sparkql
|
5
|
+
|
6
|
+
def test_simple
|
7
|
+
@parser = Parser.new
|
8
|
+
parse 'Test Eq 10',10.to_s
|
9
|
+
parse 'Test Eq 10.0',10.0.to_s
|
10
|
+
parse 'Test Eq true',true.to_s
|
11
|
+
parse "Test Eq 'false'","'false'"
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_conjunction
|
15
|
+
@parser = Parser.new
|
16
|
+
expression = @parser.parse('Test Eq 10 And Test Ne 11')
|
17
|
+
assert_equal 10.to_s, expression.first[:value]
|
18
|
+
assert_equal 11.to_s, expression.last[:value]
|
19
|
+
assert_equal 'And', expression.last[:conjunction]
|
20
|
+
expression = @parser.parse('Test Eq 10 Or Test Ne 11')
|
21
|
+
assert_equal 10.to_s, expression.first[:value]
|
22
|
+
assert_equal 11.to_s, expression.last[:value]
|
23
|
+
assert_equal 'Or', expression.last[:conjunction]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_tough_conjunction
|
27
|
+
@parser = Parser.new
|
28
|
+
expression = @parser.parse('Test Eq 10 Or Test Ne 11 And Test Ne 9')
|
29
|
+
assert_equal 9.to_s, expression.last[:value]
|
30
|
+
assert_equal 'And', expression.last[:conjunction]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_grouping
|
34
|
+
@parser = Parser.new
|
35
|
+
expression = @parser.parse('(Test Eq 10)').first
|
36
|
+
assert_equal 10.to_s, expression[:value]
|
37
|
+
expression = @parser.parse('(Test Eq 10 Or Test Ne 11)')
|
38
|
+
assert_equal 10.to_s, expression.first[:value]
|
39
|
+
assert_equal 11.to_s, expression.last[:value]
|
40
|
+
assert_equal 'Or', expression.last[:conjunction]
|
41
|
+
expression = @parser.parse('(Test Eq 10 Or (Test Ne 11))')
|
42
|
+
assert_equal 10.to_s, expression.first[:value]
|
43
|
+
assert_equal 11.to_s, expression.last[:value]
|
44
|
+
assert_equal 'Or', expression.last[:conjunction]
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_multiples
|
48
|
+
@parser = Parser.new
|
49
|
+
expression = @parser.parse('(Test Eq 10,11,12)').first
|
50
|
+
assert_equal [10.to_s,11.to_s,12.to_s], expression[:value]
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse(q,v)
|
54
|
+
expressions = @parser.parse(q)
|
55
|
+
assert !@parser.errors?, "Unexpected error parsing #{q} #{@parser.errors.inspect}"
|
56
|
+
assert_equal v, expressions.first[:value], "Expression #{expressions.inspect}"
|
57
|
+
assert !expressions.first[:custom_field], "Unexepected custom field #{expressions.inspect}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_invalid_syntax
|
61
|
+
@parser = Parser.new
|
62
|
+
expression = @parser.parse('Test Eq DERP')
|
63
|
+
assert @parser.errors?, "Should be nil: #{expression}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_nesting
|
67
|
+
assert_nesting(
|
68
|
+
"City Eq 'Fargo' Or (BathsFull Eq 1 Or BathsFull Eq 2) Or City Eq 'Moorhead' Or City Eq 'Dilworth'",
|
69
|
+
[0,1,1,0,0]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_multilevel_nesting
|
74
|
+
assert_nesting(
|
75
|
+
"(City Eq 'Fargo' And (BathsFull Eq 1 Or BathsFull Eq 2)) Or City Eq 'Moorhead' Or City Eq 'Dilworth'",
|
76
|
+
[1,2,2,0,0]
|
77
|
+
)
|
78
|
+
|
79
|
+
# API-629
|
80
|
+
assert_nesting(
|
81
|
+
"((MlsStatus Eq 'A') Or (MlsStatus Eq 'D' And CloseDate Ge 2011-05-17)) And ListPrice Ge 150000.0 And PropertyType Eq 'A'",
|
82
|
+
[2,2,2,0,0],
|
83
|
+
[2,3,3,0,0]
|
84
|
+
)
|
85
|
+
assert_nesting(
|
86
|
+
"ListPrice Ge 150000.0 And PropertyType Eq 'A' And ((MlsStatus Eq 'A') Or (MlsStatus Eq 'D' And CloseDate Ge 2011-05-17))",
|
87
|
+
[0,0,2,2,2],
|
88
|
+
[0,0,2,3,3]
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# verify each expression in the query is at the right nesting level and group
|
93
|
+
def assert_nesting(sparkql, levels=[], block_groups=nil)
|
94
|
+
block_groups = levels.clone if block_groups.nil?
|
95
|
+
parser = Parser.new
|
96
|
+
expressions = parser.parse(sparkql)
|
97
|
+
assert !parser.errors?, "Unexpected error parsing #{sparkql}: #{parser.errors.inspect}"
|
98
|
+
count = 0
|
99
|
+
expressions.each do |ex|
|
100
|
+
assert_equal levels[count], ex[:level], "Nesting level wrong for #{ex.inspect}"
|
101
|
+
assert_equal(block_groups[count], ex[:block_group], "Nesting block group wrong for #{ex.inspect}")
|
102
|
+
count +=1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_bad_queries
|
107
|
+
filter = "City IsLikeA 'Town'"
|
108
|
+
@parser = Parser.new
|
109
|
+
expressions = @parser.parse(filter)
|
110
|
+
assert @parser.errors?, "Should be nil: #{expressions}"
|
111
|
+
assert @parser.fatal_errors?, "Should be nil: #{@parser.errors.inspect}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_function_days
|
115
|
+
d = Date.today
|
116
|
+
dt = DateTime.new(d.year, d.month,d.day, 0,0,0, DateTime.now.offset)
|
117
|
+
start = Time.parse(dt.to_s)
|
118
|
+
filter = "OriginalEntryTimestamp Ge days(-7)"
|
119
|
+
@parser = Parser.new
|
120
|
+
expressions = @parser.parse(filter)
|
121
|
+
assert !@parser.errors?, "errors #{@parser.errors.inspect}"
|
122
|
+
test_time = Time.parse(expressions.first[:value])
|
123
|
+
|
124
|
+
assert (-605000 < test_time - start && -604000 > test_time - start), "Time range off by more than five seconds #{test_time - start} '#{test_time} - #{start}'"
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_function_now
|
128
|
+
start = Time.now
|
129
|
+
filter = "City Eq now()"
|
130
|
+
@parser = Parser.new
|
131
|
+
expressions = @parser.parse(filter)
|
132
|
+
assert !@parser.errors?, "errors #{@parser.errors.inspect}"
|
133
|
+
test_time = Time.parse(expressions.first[:value])
|
134
|
+
assert 5 > test_time - start, "Time range off by more than five seconds #{test_time - start}"
|
135
|
+
assert -5 < test_time - start, "Time range off by more than five seconds #{test_time - start}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_for_reserved_words_first_literals_second
|
139
|
+
["OrOrOr Eq true", "Equador Eq true", "Oregon Ge 10"].each do |filter|
|
140
|
+
@parser = Parser.new
|
141
|
+
expressions = @parser.parse(filter)
|
142
|
+
assert !@parser.errors?, "Filter '#{filter}' errors: #{@parser.errors.inspect}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_custom_fields
|
147
|
+
filter = '"General Property Description"."Taxes" Lt 500.0'
|
148
|
+
@parser = Parser.new
|
149
|
+
expressions = @parser.parse(filter)
|
150
|
+
assert !@parser.errors?, "errors #{@parser.errors.inspect}"
|
151
|
+
assert_equal '"General Property Description"."Taxes"', expressions.first[:field], "Custom field expression #{expressions.inspect}"
|
152
|
+
assert expressions.first[:custom_field], "Custom field expression #{expressions.inspect}"
|
153
|
+
assert_equal '500.0', expressions.first[:value], "Custom field expression #{expressions.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_valid_custom_field_filters
|
157
|
+
['"General Property Description"."Taxes$" Lt 500.0',
|
158
|
+
'"General Property Desc\'"."Taxes" Lt 500.0',
|
159
|
+
'"General Property Description"."Taxes" Lt 500.0',
|
160
|
+
'"General \'Property\' Description"."Taxes" Lt 500.0',
|
161
|
+
'"General Property Description"."Taxes #" Lt 500.0',
|
162
|
+
'"General$Description"."Taxes" Lt 500.0',
|
163
|
+
'" a "." b " Lt 500.0'
|
164
|
+
].each do |filter|
|
165
|
+
@parser = Parser.new
|
166
|
+
expressions = @parser.parse(filter)
|
167
|
+
assert !@parser.errors?, "errors '#{filter}'\n#{@parser.errors.inspect}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_invalid_custom_field_filters
|
172
|
+
['"$General Property Description"."Taxes" Lt 500.0',
|
173
|
+
'"General Property Description"."$Taxes" Lt 500.0',
|
174
|
+
'"General Property Description"."Tax.es" Lt 500.0',
|
175
|
+
'"General Property Description".".Taxes" Lt 500.0',
|
176
|
+
'"General Property Description".".Taxes"."SUB" Lt 500.0',
|
177
|
+
'"General.Description"."Taxes" Lt 500.0',
|
178
|
+
'""."" Lt 500.0'
|
179
|
+
].each do |filter|
|
180
|
+
@parser = Parser.new
|
181
|
+
expressions = @parser.parse(filter)
|
182
|
+
assert @parser.errors?, "No errors? '#{filter}'\n#{@parser.inspect}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_case_insensitve_ops_and_conjunctions
|
187
|
+
@parser = Parser.new
|
188
|
+
parse 'Test EQ 10',10.to_s
|
189
|
+
parse 'Test eq 10.0',10.0.to_s
|
190
|
+
parse 'Test eQ true',true.to_s
|
191
|
+
parse 'Test EQ 10 AND Test NE 11', 10.to_s
|
192
|
+
parse 'Test eq 10 or Test ne 11', 10.to_s
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_null
|
196
|
+
@parser = Parser.new
|
197
|
+
parse 'Test Eq NULL', "NULL"
|
198
|
+
parse 'Test Eq NULL Or Test Ne 11', "NULL"
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_invalid_operators
|
202
|
+
(Sparkql::Token::OPERATORS - Sparkql::Token::EQUALITY_OPERATORS).each do |o|
|
203
|
+
["NULL", "true", "'My String'"].each do |v|
|
204
|
+
parser_errors("Test #{o} #{v}")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def parser_errors(filter)
|
210
|
+
@parser = Parser.new
|
211
|
+
expression = @parser.parse(filter)
|
212
|
+
assert @parser.errors?, "Should find errors for '#{filter}': #{expression}"
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|