sparkql 1.1.17 → 1.2.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 +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
|