sparkql 1.1.17 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +4 -0
- data/GRAMMAR.md +16 -3
- data/VERSION +1 -1
- data/lib/sparkql/function_resolver.rb +42 -164
- data/lib/sparkql/lexer.rb +0 -3
- data/lib/sparkql/parser.rb +146 -99
- data/lib/sparkql/parser.y +21 -5
- data/lib/sparkql/parser_compatibility.rb +11 -17
- data/lib/sparkql/parser_tools.rb +60 -25
- data/test/unit/function_resolver_test.rb +16 -16
- data/test/unit/parser_compatability_test.rb +17 -0
- data/test/unit/parser_test.rb +71 -25
- metadata +2 -2
@@ -31,7 +31,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
31
31
|
|
32
32
|
assert_equal :function, value[:type]
|
33
33
|
assert_equal 'round', value[:value]
|
34
|
-
assert_equal "ListPrice", value[:args].first
|
34
|
+
assert_equal "ListPrice", value[:args].first[:value]
|
35
35
|
end
|
36
36
|
|
37
37
|
test "substring character one index" do
|
@@ -89,8 +89,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
89
89
|
|
90
90
|
assert_equal :function, value[:type]
|
91
91
|
assert_equal 'substring', value[:value]
|
92
|
-
assert_equal "ListPrice", value[:args].first
|
93
|
-
|
92
|
+
assert_equal "ListPrice", value[:args].first[:value]
|
93
|
+
assert_equal 2, value[:args].size
|
94
94
|
end
|
95
95
|
|
96
96
|
test "substring field two indexes" do
|
@@ -106,8 +106,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
106
106
|
|
107
107
|
assert_equal :function, value[:type]
|
108
108
|
assert_equal 'substring', value[:value]
|
109
|
-
assert_equal "ListPrice", value[:args].first
|
110
|
-
assert_equal 2, value[:args].last
|
109
|
+
assert_equal "ListPrice", value[:args].first[:value]
|
110
|
+
assert_equal 2, value[:args].last[:value]
|
111
111
|
end
|
112
112
|
|
113
113
|
test "substring with negative M is a parse error" do
|
@@ -145,7 +145,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
145
145
|
|
146
146
|
assert_equal :function, value[:type]
|
147
147
|
assert_equal 'trim', value[:value]
|
148
|
-
assert_equal "Name", value[:args].first
|
148
|
+
assert_equal "Name", value[:args].first[:value]
|
149
149
|
end
|
150
150
|
|
151
151
|
test 'trim with character' do
|
@@ -177,7 +177,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
177
177
|
value = f.call
|
178
178
|
assert_equal :function, value[:type]
|
179
179
|
assert_equal 'toupper', value[:value]
|
180
|
-
assert_equal "City", value[:args].first
|
180
|
+
assert_equal "City", value[:args].first[:value]
|
181
181
|
end
|
182
182
|
|
183
183
|
test "toupper('string')" do
|
@@ -196,7 +196,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
196
196
|
value = f.call
|
197
197
|
assert_equal :function, value[:type]
|
198
198
|
assert_equal 'length', value[:value]
|
199
|
-
assert_equal "City", value[:args].first
|
199
|
+
assert_equal "City", value[:args].first[:value]
|
200
200
|
end
|
201
201
|
|
202
202
|
test "length('string')" do
|
@@ -256,7 +256,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
256
256
|
|
257
257
|
assert_equal :function, value[:type]
|
258
258
|
assert_equal 'floor', value[:value]
|
259
|
-
assert_equal "ListPrice", value[:args].first
|
259
|
+
assert_equal "ListPrice", value[:args].first[:value]
|
260
260
|
end
|
261
261
|
|
262
262
|
test "ceiling(float)" do
|
@@ -276,7 +276,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
276
276
|
|
277
277
|
assert_equal :function, value[:type]
|
278
278
|
assert_equal 'ceiling', value[:value]
|
279
|
-
assert_equal "ListPrice", value[:args].first
|
279
|
+
assert_equal "ListPrice", value[:args].first[:value]
|
280
280
|
end
|
281
281
|
|
282
282
|
test "days()" do
|
@@ -325,7 +325,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
325
325
|
value = f.call
|
326
326
|
assert_equal :function, value[:type]
|
327
327
|
assert_equal function, value[:value]
|
328
|
-
assert_equal "OriginalEntryTimestamp", value[:args].first
|
328
|
+
assert_equal "OriginalEntryTimestamp", value[:args].first[:value]
|
329
329
|
end
|
330
330
|
end
|
331
331
|
|
@@ -337,7 +337,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
337
337
|
value = f.call
|
338
338
|
assert_equal :function, value[:type]
|
339
339
|
assert_equal function, value[:value]
|
340
|
-
assert_equal "OriginalEntryTimestamp", value[:args].first
|
340
|
+
assert_equal "OriginalEntryTimestamp", value[:args].first[:value]
|
341
341
|
end
|
342
342
|
end
|
343
343
|
|
@@ -348,7 +348,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
348
348
|
value = f.call
|
349
349
|
assert_equal :function, value[:type]
|
350
350
|
assert_equal 'fractionalseconds', value[:value]
|
351
|
-
assert_equal "OriginalEntryTimestamp", value[:args].first
|
351
|
+
assert_equal "OriginalEntryTimestamp", value[:args].first[:value]
|
352
352
|
end
|
353
353
|
|
354
354
|
# Polygon searches
|
@@ -544,7 +544,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
544
544
|
|
545
545
|
assert_equal :function, value[:type]
|
546
546
|
assert_equal 'cast', value[:value]
|
547
|
-
assert_equal ['Bedrooms', 'character'], value[:args]
|
547
|
+
assert_equal ['Bedrooms', 'character'], value[:args].map { |v| v[:value]}
|
548
548
|
end
|
549
549
|
|
550
550
|
test "invalid cast returns null" do
|
@@ -593,7 +593,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
593
593
|
value = f.call
|
594
594
|
assert_equal :function, value[:type]
|
595
595
|
assert_equal 'time', value[:value]
|
596
|
-
assert_equal "OriginalEntryTimestamp", value[:args].first
|
596
|
+
assert_equal "OriginalEntryTimestamp", value[:args].first[:value]
|
597
597
|
end
|
598
598
|
|
599
599
|
test "date(field)" do
|
@@ -603,7 +603,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
603
603
|
value = f.call
|
604
604
|
assert_equal :function, value[:type]
|
605
605
|
assert_equal 'date', value[:value]
|
606
|
-
assert_equal "OriginalEntryTimestamp", value[:args].first
|
606
|
+
assert_equal "OriginalEntryTimestamp", value[:args].first[:value]
|
607
607
|
end
|
608
608
|
|
609
609
|
test "startswith(), endswith() and contains()" do
|
@@ -346,6 +346,23 @@ class ParserCompatabilityTest < Test::Unit::TestCase
|
|
346
346
|
assert parser.errors?
|
347
347
|
end
|
348
348
|
|
349
|
+
test "max out nested functions of 5" do
|
350
|
+
field = "tolower(City)"
|
351
|
+
|
352
|
+
4.times do
|
353
|
+
field = "tolower(#{field})"
|
354
|
+
end
|
355
|
+
|
356
|
+
parser = Parser.new
|
357
|
+
parser.parse("#{field} Eq 'Fargo'")
|
358
|
+
assert !parser.errors?
|
359
|
+
|
360
|
+
parser = Parser.new
|
361
|
+
field = "tolower(#{field})"
|
362
|
+
parser.parse("#{field} Eq 'Fargo'")
|
363
|
+
assert parser.errors?, 'should error on too many nested functions'
|
364
|
+
end
|
365
|
+
|
349
366
|
test "max out function args" do
|
350
367
|
parser = Parser.new
|
351
368
|
to_the_max = []
|
data/test/unit/parser_test.rb
CHANGED
@@ -170,6 +170,11 @@ class ParserTest < Test::Unit::TestCase
|
|
170
170
|
assert_equal 'City', expression[:field]
|
171
171
|
assert_equal '13', expression[:value]
|
172
172
|
assert_equal '4131800000000', expression[:args].last
|
173
|
+
|
174
|
+
assert_equal 'indexof', expression[:field_manipulations][:function_name]
|
175
|
+
assert_equal :function, expression[:field_manipulations][:type]
|
176
|
+
assert_equal :integer, expression[:field_manipulations][:return_type]
|
177
|
+
assert_equal ['City', '4131800000000'], expression[:field_manipulations][:args].map {|v| v[:value]}
|
173
178
|
end
|
174
179
|
|
175
180
|
test "function data preserved in expression" do
|
@@ -207,8 +212,6 @@ class ParserTest < Test::Unit::TestCase
|
|
207
212
|
end
|
208
213
|
|
209
214
|
test "mixed list" do
|
210
|
-
# TODO This is an unrealistic example. We need number functions or support
|
211
|
-
# for dates in lists
|
212
215
|
filter = "OriginalEntryTimestamp Eq 2014,days(-7)"
|
213
216
|
@parser = Parser.new
|
214
217
|
expressions = @parser.parse(filter)
|
@@ -217,14 +220,11 @@ class ParserTest < Test::Unit::TestCase
|
|
217
220
|
assert_equal '2014,days(-7)', expressions.first[:condition]
|
218
221
|
end
|
219
222
|
|
223
|
+
def test_errors_on_left_hand_field_function
|
224
|
+
parser_errors("Field Eq ceiling(Field)")
|
225
|
+
end
|
226
|
+
|
220
227
|
def test_function_date
|
221
|
-
filter = "OnMarketDate Eq date(OriginalEntryTimestamp)"
|
222
|
-
@parser = Parser.new
|
223
|
-
expressions = @parser.parse(filter)
|
224
|
-
assert !@parser.errors?, "errors #{@parser.errors.inspect}"
|
225
|
-
assert_equal 'date(OriginalEntryTimestamp)', expressions.first[:condition]
|
226
|
-
assert_equal 'date', expressions.first[:value]
|
227
|
-
assert_equal :function, expressions.first[:type]
|
228
228
|
# Run using a static value, we just resolve the type
|
229
229
|
filter = "OnMarketDate Eq date(2013-07-26T10:22:15.111-0100)"
|
230
230
|
@parser = Parser.new
|
@@ -685,14 +685,17 @@ class ParserTest < Test::Unit::TestCase
|
|
685
685
|
end
|
686
686
|
|
687
687
|
def test_round_with_field
|
688
|
-
filter = "ListPrice Eq
|
688
|
+
filter = "round(ListPrice) Eq 1"
|
689
689
|
@parser = Parser.new
|
690
690
|
expression = @parser.parse(filter).first
|
691
691
|
assert !@parser.errors?, "Filter '#{filter}' failed: #{@parser.errors.first.inspect}"
|
692
692
|
|
693
|
-
assert_equal 'round', expression[:
|
694
|
-
assert_equal
|
695
|
-
|
693
|
+
assert_equal 'round', expression[:field_function]
|
694
|
+
assert_equal(["ListPrice"], expression[:args])
|
695
|
+
|
696
|
+
assert_equal 'round', expression[:field_manipulations][:function_name]
|
697
|
+
assert_equal :function, expression[:field_manipulations][:type]
|
698
|
+
assert_equal ['ListPrice'], expression[:field_manipulations][:args].map {|v| v[:value]}
|
696
699
|
end
|
697
700
|
|
698
701
|
def test_ceiling_with_literal
|
@@ -714,14 +717,13 @@ class ParserTest < Test::Unit::TestCase
|
|
714
717
|
end
|
715
718
|
|
716
719
|
def test_ceiling_with_field
|
717
|
-
filter = "ListPrice Eq
|
720
|
+
filter = "ceiling(ListPrice) Eq 4"
|
718
721
|
@parser = Parser.new
|
719
722
|
expression = @parser.parse(filter).first
|
720
723
|
assert !@parser.errors?, "Filter '#{filter}' failed: #{@parser.errors.first.inspect}"
|
721
724
|
|
722
|
-
assert_equal 'ceiling', expression[:
|
723
|
-
assert_equal
|
724
|
-
assert_equal(["FieldName"], expression[:function_parameters])
|
725
|
+
assert_equal 'ceiling', expression[:field_function]
|
726
|
+
assert_equal(["ListPrice"], expression[:args])
|
725
727
|
end
|
726
728
|
|
727
729
|
def test_floor_with_literal
|
@@ -743,26 +745,33 @@ class ParserTest < Test::Unit::TestCase
|
|
743
745
|
end
|
744
746
|
|
745
747
|
def test_floor_with_field
|
746
|
-
filter = "ListPrice Eq
|
748
|
+
filter = "floor(ListPrice) Eq 1"
|
747
749
|
@parser = Parser.new
|
748
750
|
expression = @parser.parse(filter).first
|
749
751
|
assert !@parser.errors?, "Filter '#{filter}' failed: #{@parser.errors.first.inspect}"
|
750
752
|
|
751
|
-
assert_equal 'floor', expression[:
|
752
|
-
assert_equal
|
753
|
-
|
753
|
+
assert_equal 'floor', expression[:field_function]
|
754
|
+
assert_equal(["ListPrice"], expression[:args])
|
755
|
+
|
756
|
+
assert_equal 'floor', expression[:field_manipulations][:function_name]
|
757
|
+
assert_equal :function, expression[:field_manipulations][:type]
|
758
|
+
assert_equal ['ListPrice'], expression[:field_manipulations][:args].map {|v| v[:value]}
|
754
759
|
end
|
755
760
|
|
756
761
|
def test_concat_with_field
|
757
|
-
filter = "
|
762
|
+
filter = "concat(City, 'b') Eq 'Fargob'"
|
758
763
|
@parser = Parser.new
|
759
764
|
expression = @parser.parse(filter).first
|
760
765
|
assert !@parser.errors?, "Filter '#{filter}' failed: #{@parser.errors.first.inspect}"
|
761
766
|
|
762
|
-
assert_equal :
|
763
|
-
assert_equal 'concat', expression[:
|
764
|
-
assert_equal(["City", 'b'], expression[:
|
767
|
+
assert_equal :character, expression[:type]
|
768
|
+
assert_equal 'concat', expression[:field_function]
|
769
|
+
assert_equal(["City", 'b'], expression[:args])
|
765
770
|
assert_equal("City", expression[:field])
|
771
|
+
|
772
|
+
assert_equal 'concat', expression[:field_manipulations][:function_name]
|
773
|
+
assert_equal :function, expression[:field_manipulations][:type]
|
774
|
+
assert_equal ['City', 'b'], expression[:field_manipulations][:args].map {|v| v[:value]}
|
766
775
|
end
|
767
776
|
|
768
777
|
def test_concat_with_literal
|
@@ -786,6 +795,10 @@ class ParserTest < Test::Unit::TestCase
|
|
786
795
|
assert_equal 'cast', expression[:field_function]
|
787
796
|
assert_equal "'100000'", expression[:condition]
|
788
797
|
assert_equal(:character, expression[:field_function_type])
|
798
|
+
|
799
|
+
assert_equal 'cast', expression[:field_manipulations][:function_name]
|
800
|
+
assert_equal :function, expression[:field_manipulations][:type]
|
801
|
+
assert_equal ['ListPrice', 'character'], expression[:field_manipulations][:args].map {|v| v[:value]}
|
789
802
|
end
|
790
803
|
|
791
804
|
def test_cast_with_invalid_type
|
@@ -793,6 +806,39 @@ class ParserTest < Test::Unit::TestCase
|
|
793
806
|
parser_errors("ListPrice Eq cast('10', 'bogus')")
|
794
807
|
end
|
795
808
|
|
809
|
+
test 'nested functions on field side' do
|
810
|
+
@parser = Parser.new
|
811
|
+
filter = "tolower(toupper(City)) Eq 'Fargo'"
|
812
|
+
expression = @parser.parse(filter).first
|
813
|
+
assert_equal 'City', expression[:field]
|
814
|
+
assert expression.key?(:field_manipulations)
|
815
|
+
function1 = expression[:field_manipulations]
|
816
|
+
assert_equal :function, function1[:type]
|
817
|
+
assert_equal 'tolower', function1[:function_name]
|
818
|
+
assert_equal 'tolower', expression[:field_function]
|
819
|
+
|
820
|
+
function2 = function1[:args].first
|
821
|
+
assert_equal :function, function2[:type]
|
822
|
+
assert_equal 'toupper', function2[:function_name]
|
823
|
+
assert_equal({:type=>:field, :value=>"City"}, function2[:args].first)
|
824
|
+
end
|
825
|
+
|
826
|
+
test 'nested functions with multiple params' do
|
827
|
+
filter = "concat(tolower(City), 'b') Eq 'fargob'"
|
828
|
+
@parser = Parser.new
|
829
|
+
expression = @parser.parse(filter).first
|
830
|
+
assert expression.key?(:field_manipulations)
|
831
|
+
function1 = expression[:field_manipulations]
|
832
|
+
assert_equal :function, function1[:type]
|
833
|
+
assert_equal 'concat', function1[:function_name]
|
834
|
+
assert_equal({type: :character, value: 'b'}, function1[:args].last)
|
835
|
+
|
836
|
+
function2 = function1[:args].first
|
837
|
+
assert_equal :function, function2[:type]
|
838
|
+
assert_equal 'tolower', function2[:function_name]
|
839
|
+
assert_equal({:type=>:field, :value=>"City"}, function2[:args].first)
|
840
|
+
end
|
841
|
+
|
796
842
|
private
|
797
843
|
|
798
844
|
def parser_errors(filter)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sparkql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wade McEwen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: georuby
|