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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZWExMjRhNmFjYzM3NTdjYWNkMjdmOWRjYzBhYTJiNGVjMTRjOTViZg==
4
+ MDI5NzJhMzM4MGY2MWFlNGM0YjE3N2Y1OWI2NDBhYzhhYTA5ODU2NQ==
5
5
  data.tar.gz: !binary |-
6
- YzhiYTlhMWM0OTRkNWE4ZTFjMTQzYzE4MmNjYzJiZWQ3MDdhMmFiYQ==
6
+ MmJlZTY5NzRlZGEwY2RkYTcyNzU1NDUyMzBiZGY1MDdjN2IwZjI1OA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Mzk5MGUxM2M0ZWQ2M2QxMTQ2ZDM3MTMxNTEwODVhZTc0NzJlZWJhYzkzODA4
10
- NDIxZTUxNzE4OTYxYmI3MTJiNzkxMGU1MzVjMGQ3ZDdkMzIzZDQ5OGExMmI5
11
- YmM1NDU2M2IzM2JhMWU2OTY3Y2NhYzM5MDNiMjllNmVkYjA2NDM=
9
+ NWY3MDFkNWIyYmM2ZjNmODMwMTA3MWQzZTZlN2NhMmEzNTA2YzE5M2Q4NzEw
10
+ MmE0YWJhM2ZlYTkxZWE5ZDAyYWU0NDNiZTVlZjZlM2FkMzgwZTA0ZTI0OTEy
11
+ ZDAxNzc5NzdmNTgzMWYxN2U4MjAzZDJkYmJjMTdlMGMyNDE2ZmY=
12
12
  data.tar.gz: !binary |-
13
- MTRjZDEyN2U5N2IwZTA5NGEwZjZjMmEyMzM5ZTA4YWUwMDcxNWEwZTZjODli
14
- OWNmZDBjNmU0YzNiNWZiNjc3MWRkYmRjNjc1ZTg4MjNlNWVlNzAzYTVhY2Uz
15
- NzU2OTg2NzAzZTA4ZjdmMDFkZDdhMzY3NTU4ZTQxNTRhODkyNDQ=
13
+ YTI4YmQwYzFkNjllNDc0Yzg0YWFiYzVjMGZmNDJkNDNlODc5MTcyMzVhYzcy
14
+ MjhmMjUyMTQwYmUwMjY4N2U1MzIzNzI5MWVjMmFjNmI0MDg3ZDBmMmZiZDFl
15
+ YzZhNjkxYTAxZTJiZGI1MTZjY2NlMjlmNjYyZGExNmRkMDdjYTU=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ v1.1.16, 2018-07-26
2
+ -------------------
3
+ * [IMPROVEMENT] New Function: cast()
4
+
1
5
  v1.1.15, 2018-07-12
2
6
  -------------------
3
7
  * [IMPROVEMENT] New Functions: ceiling(), floor()
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.15
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
- support[@name.to_sym][:return_type]
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)
@@ -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
@@ -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.15
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-07-19 00:00:00.000000000 Z
11
+ date: 2018-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: georuby