sparkql 1.1.15 → 1.1.16
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/VERSION +1 -1
- data/lib/sparkql/function_resolver.rb +94 -3
- data/lib/sparkql/parser_tools.rb +7 -2
- data/test/unit/function_resolver_test.rb +106 -1
- data/test/unit/parser_test.rb +16 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MDI5NzJhMzM4MGY2MWFlNGM0YjE3N2Y1OWI2NDBhYzhhYTA5ODU2NQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MmJlZTY5NzRlZGEwY2RkYTcyNzU1NDUyMzBiZGY1MDdjN2IwZjI1OA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NWY3MDFkNWIyYmM2ZjNmODMwMTA3MWQzZTZlN2NhMmEzNTA2YzE5M2Q4NzEw
|
10
|
+
MmE0YWJhM2ZlYTkxZWE5ZDAyYWU0NDNiZTVlZjZlM2FkMzgwZTA0ZTI0OTEy
|
11
|
+
ZDAxNzc5NzdmNTgzMWYxN2U4MjAzZDJkYmJjMTdlMGMyNDE2ZmY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTI4YmQwYzFkNjllNDc0Yzg0YWFiYzVjMGZmNDJkNDNlODc5MTcyMzVhYzcy
|
14
|
+
MjhmMjUyMTQwYmUwMjY4N2U1MzIzNzI5MWVjMmFjNmI0MDg3ZDBmMmZiZDFl
|
15
|
+
YzZhNjkxYTAxZTJiZGI1MTZjY2NlMjlmNjYyZGExNmRkMDdjYTU=
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.16
|
@@ -16,6 +16,8 @@ class Sparkql::FunctionResolver
|
|
16
16
|
VALID_REGEX_FLAGS = ["", "i"]
|
17
17
|
MIN_DATE_TIME = Time.new(1970, 1, 1, 0, 0, 0, "+00:00").iso8601
|
18
18
|
MAX_DATE_TIME = Time.new(9999, 12, 31, 23, 59, 59, "+00:00").iso8601
|
19
|
+
VALID_CAST_TYPES = [:field, :character, :decimal, :integer]
|
20
|
+
|
19
21
|
SUPPORTED_FUNCTIONS = {
|
20
22
|
:polygon => {
|
21
23
|
:args => [:character],
|
@@ -69,6 +71,10 @@ class Sparkql::FunctionResolver
|
|
69
71
|
:args => [[:field, :character], :character],
|
70
72
|
:return_type => :integer
|
71
73
|
},
|
74
|
+
:cast => {
|
75
|
+
:args => [[:field, :character, :decimal, :integer, :null], :character],
|
76
|
+
:resolve_for_type => true,
|
77
|
+
},
|
72
78
|
:round => {
|
73
79
|
:args => [[:field, :decimal]],
|
74
80
|
:resolve_for_type => true,
|
@@ -190,7 +196,7 @@ class Sparkql::FunctionResolver
|
|
190
196
|
end
|
191
197
|
|
192
198
|
# Validate the function instance prior to calling it. All validation failures will show up in the
|
193
|
-
# errors array.
|
199
|
+
# errors array.
|
194
200
|
def validate()
|
195
201
|
name = @name.to_sym
|
196
202
|
unless support.has_key?(name)
|
@@ -219,10 +225,26 @@ class Sparkql::FunctionResolver
|
|
219
225
|
end
|
220
226
|
count +=1
|
221
227
|
end
|
228
|
+
|
229
|
+
if name == :cast
|
230
|
+
type = @args.last[:value]
|
231
|
+
if !VALID_CAST_TYPES.include?(type.to_sym)
|
232
|
+
@errors << Sparkql::ParserError.new(:token => @name,
|
233
|
+
:message => "Function call '#{@name}' requires a castable type.",
|
234
|
+
:status => :fatal )
|
235
|
+
return
|
236
|
+
end
|
237
|
+
end
|
222
238
|
end
|
223
239
|
|
224
240
|
def return_type
|
225
|
-
|
241
|
+
name = @name.to_sym
|
242
|
+
|
243
|
+
if name == :cast
|
244
|
+
@args.last[:value].to_sym
|
245
|
+
else
|
246
|
+
support[@name.to_sym][:return_type]
|
247
|
+
end
|
226
248
|
end
|
227
249
|
|
228
250
|
def errors
|
@@ -752,7 +774,76 @@ class Sparkql::FunctionResolver
|
|
752
774
|
:value => [start_str.to_s, end_str.to_s]
|
753
775
|
}
|
754
776
|
end
|
755
|
-
|
777
|
+
|
778
|
+
def cast_field(value, type)
|
779
|
+
{
|
780
|
+
:type => :function,
|
781
|
+
:value => "cast",
|
782
|
+
:args => [value, type]
|
783
|
+
}
|
784
|
+
end
|
785
|
+
|
786
|
+
def cast(value, type)
|
787
|
+
if value == 'NULL'
|
788
|
+
value = nil
|
789
|
+
end
|
790
|
+
|
791
|
+
new_type = type.to_sym
|
792
|
+
{
|
793
|
+
type: new_type,
|
794
|
+
value: cast_literal(value, new_type)
|
795
|
+
}
|
796
|
+
rescue
|
797
|
+
{
|
798
|
+
type: :null,
|
799
|
+
value: 'NULL'
|
800
|
+
}
|
801
|
+
end
|
802
|
+
|
803
|
+
def valid_cast_type?(type)
|
804
|
+
if VALID_CAST_TYPES.key?(type.to_sym)
|
805
|
+
true
|
806
|
+
else
|
807
|
+
@errors << Sparkql::ParserError.new(:token => coords,
|
808
|
+
:message => "Function call 'cast' requires a castable type.",
|
809
|
+
:status => :fatal )
|
810
|
+
false
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
def cast_null(value, type)
|
815
|
+
cast(value, type)
|
816
|
+
end
|
817
|
+
|
818
|
+
def cast_decimal(value, type)
|
819
|
+
cast(value, type)
|
820
|
+
end
|
821
|
+
|
822
|
+
def cast_character(value, type)
|
823
|
+
cast(value, type)
|
824
|
+
end
|
825
|
+
|
826
|
+
def cast_literal(value, type)
|
827
|
+
case type
|
828
|
+
when :character
|
829
|
+
"'#{value.to_s}'"
|
830
|
+
when :integer
|
831
|
+
if value.nil?
|
832
|
+
'0'
|
833
|
+
else
|
834
|
+
Integer(Float(value)).to_s
|
835
|
+
end
|
836
|
+
when :decimal
|
837
|
+
if value.nil?
|
838
|
+
'0.0'
|
839
|
+
else
|
840
|
+
Float(value).to_s
|
841
|
+
end
|
842
|
+
when :null
|
843
|
+
'NULL'
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
756
847
|
private
|
757
848
|
|
758
849
|
def is_coords?(coord_string)
|
data/lib/sparkql/parser_tools.rb
CHANGED
@@ -31,15 +31,20 @@ module Sparkql::ParserTools
|
|
31
31
|
function = Sparkql::FunctionResolver::SUPPORTED_FUNCTIONS[field[:value].to_sym]
|
32
32
|
if !function.nil?
|
33
33
|
field_args[:field_function] = field[:value]
|
34
|
-
field_args[:field_function_type] = function[:return_type]
|
35
34
|
field_args[:args] = field[:args]
|
35
|
+
|
36
|
+
if field_args[:field_function] == 'cast'
|
37
|
+
field_args[:field_function_type] = field[:args].last.to_sym
|
38
|
+
else
|
39
|
+
field_args[:field_function_type] = function[:return_type]
|
40
|
+
end
|
36
41
|
else
|
37
42
|
tokenizer_error(:token => field[:value],
|
38
43
|
:message => "Unsupported function type", :status => :fatal )
|
39
44
|
end
|
40
45
|
field = field[:args].first
|
41
46
|
end
|
42
|
-
custom_field = field.start_with?('"')
|
47
|
+
custom_field = !field.nil? && field.start_with?('"')
|
43
48
|
block_group = (@lexer.level == 0) ? 0 : @lexer.block_group_identifier
|
44
49
|
expression = {:field => field, :operator => operator, :conjunction => 'And',
|
45
50
|
:conjunction_level => 0, :level => @lexer.level,
|
@@ -451,7 +451,112 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
451
451
|
:value => 1.0}])
|
452
452
|
assert_equal :shape, f.return_type
|
453
453
|
end
|
454
|
-
|
454
|
+
|
455
|
+
test 'return_type for cast()' do
|
456
|
+
f = FunctionResolver.new('cast', [{:type => :character,
|
457
|
+
:value => "1"},{:type => :character,
|
458
|
+
:value => 'decimal'}])
|
459
|
+
|
460
|
+
assert_equal :decimal, f.return_type
|
461
|
+
|
462
|
+
f = FunctionResolver.new('cast', [{:type => :character,
|
463
|
+
:value => "1"},{:type => :character,
|
464
|
+
:value => 'integer'}])
|
465
|
+
|
466
|
+
assert_equal :integer, f.return_type
|
467
|
+
end
|
468
|
+
|
469
|
+
test "cast() decimal to integer" do
|
470
|
+
f = FunctionResolver.new('cast', [{:type => :decimal, :value => '1.2'}, {type: :character, :value => 'integer'}])
|
471
|
+
f.validate
|
472
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
473
|
+
value = f.call
|
474
|
+
|
475
|
+
assert_equal :integer, value[:type]
|
476
|
+
assert_equal '1', value[:value]
|
477
|
+
end
|
478
|
+
|
479
|
+
test "cast() integer to decimal" do
|
480
|
+
f = FunctionResolver.new('cast', [{:type => :decimal, :value => '1'}, {type: :character, :value => 'decimal'}])
|
481
|
+
f.validate
|
482
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
483
|
+
value = f.call
|
484
|
+
|
485
|
+
assert_equal :decimal, value[:type]
|
486
|
+
assert_equal '1.0', value[:value]
|
487
|
+
end
|
488
|
+
|
489
|
+
test "cast() nil to integer" do
|
490
|
+
f = FunctionResolver.new('cast', [{:type => :null, :value => 'NULL'}, {type: :character, :value => 'integer'}])
|
491
|
+
f.validate
|
492
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
493
|
+
value = f.call
|
494
|
+
|
495
|
+
assert_equal :integer, value[:type]
|
496
|
+
assert_equal '0', value[:value]
|
497
|
+
end
|
498
|
+
|
499
|
+
test "cast() nil to decimal" do
|
500
|
+
f = FunctionResolver.new('cast', [{:type => :null, :value => 'NULL'}, {type: :character, :value => 'decimal'}])
|
501
|
+
f.validate
|
502
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
503
|
+
value = f.call
|
504
|
+
|
505
|
+
assert_equal :decimal, value[:type]
|
506
|
+
assert_equal '0.0', value[:value]
|
507
|
+
end
|
508
|
+
|
509
|
+
test "cast() nil to character" do
|
510
|
+
f = FunctionResolver.new('cast', [{:type => :null, :value => 'NULL'}, {type: :character, :value => 'character'}])
|
511
|
+
f.validate
|
512
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
513
|
+
value = f.call
|
514
|
+
|
515
|
+
assert_equal :character, value[:type]
|
516
|
+
assert_equal "''", value[:value]
|
517
|
+
end
|
518
|
+
|
519
|
+
test "cast() character to decimal" do
|
520
|
+
f = FunctionResolver.new('cast', [{:type => :character, :value => "1.1"}, {type: :character, :value => 'decimal'}])
|
521
|
+
f.validate
|
522
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
523
|
+
value = f.call
|
524
|
+
|
525
|
+
assert_equal :decimal, value[:type]
|
526
|
+
assert_equal "1.1", value[:value]
|
527
|
+
end
|
528
|
+
|
529
|
+
test "cast() character to integer" do
|
530
|
+
f = FunctionResolver.new('cast', [{:type => :character, :value => "1"}, {type: :character, :value => 'integer'}])
|
531
|
+
f.validate
|
532
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
533
|
+
value = f.call
|
534
|
+
|
535
|
+
assert_equal :integer, value[:type]
|
536
|
+
assert_equal "1", value[:value]
|
537
|
+
end
|
538
|
+
|
539
|
+
test "cast() Field" do
|
540
|
+
f = FunctionResolver.new('cast', [{:type => :field, :value => 'Bedrooms'}, {type: :character, :value => 'character'}])
|
541
|
+
f.validate
|
542
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
543
|
+
value = f.call
|
544
|
+
|
545
|
+
assert_equal :function, value[:type]
|
546
|
+
assert_equal 'cast', value[:value]
|
547
|
+
assert_equal ['Bedrooms', 'character'], value[:args]
|
548
|
+
end
|
549
|
+
|
550
|
+
test "invalid cast returns null" do
|
551
|
+
f = FunctionResolver.new('cast', [{:type => :character, :value => '1.1.1'}, {type: :character, :value => 'integer'}])
|
552
|
+
f.validate
|
553
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
554
|
+
value = f.call
|
555
|
+
|
556
|
+
assert_equal :null, value[:type]
|
557
|
+
assert_equal 'NULL', value[:value]
|
558
|
+
end
|
559
|
+
|
455
560
|
test "invalid function" do
|
456
561
|
f = FunctionResolver.new('then', [])
|
457
562
|
f.validate
|
data/test/unit/parser_test.rb
CHANGED
@@ -753,6 +753,22 @@ class ParserTest < Test::Unit::TestCase
|
|
753
753
|
assert_equal(["FieldName"], expression[:function_parameters])
|
754
754
|
end
|
755
755
|
|
756
|
+
def test_cast_with_field
|
757
|
+
filter = "cast(ListPrice, 'character') Eq '100000'"
|
758
|
+
@parser = Parser.new
|
759
|
+
expression = @parser.parse(filter).first
|
760
|
+
assert !@parser.errors?, "Filter '#{filter}' failed: #{@parser.errors.first.inspect}"
|
761
|
+
|
762
|
+
assert_equal 'cast', expression[:field_function]
|
763
|
+
assert_equal "'100000'", expression[:condition]
|
764
|
+
assert_equal(:character, expression[:field_function_type])
|
765
|
+
end
|
766
|
+
|
767
|
+
def test_cast_with_invalid_type
|
768
|
+
parser_errors("cast(ListPrice, 'bogus') Eq '10'")
|
769
|
+
parser_errors("ListPrice Eq cast('10', 'bogus')")
|
770
|
+
end
|
771
|
+
|
756
772
|
private
|
757
773
|
|
758
774
|
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.1.
|
4
|
+
version: 1.1.16
|
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-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: georuby
|