sparkql 1.2.3 → 1.2.4

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
- ZTIxMGFmY2I2YTljYzEwNGNlMzMzOGFkMDg1NjM0YmY1MDZjZTliOA==
4
+ YmIyYTljOGIyZmFjZTEyZmY1MzU2ODZjYTg4ZWU1NDI2NWNkOWRkYw==
5
5
  data.tar.gz: !binary |-
6
- YmVkZGFkODQ5OWE1Mzc4MDdmNGNjZjM0NzM2YWI5OWRlOTk0MmM3Nw==
6
+ NDkwMjczNDY2N2JlOGRmMzA5MDQ1YmI4Njg3ZTM5MTJlMzBiODVhMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZjgxMWJlMDFlODY5NGJhZGE4NzU2ZjE5NmI2YzI1MWM2MDU3MzgwZTc1YzA3
10
- MWQ1Njg4MTI0MjBjNWRlZTU0ZWU5NTc2MjUwYmU1ZTVjMDkxMDQ4ZjMxYjFh
11
- OWFkMDU1OWVlODg4NTZhZDQ1NjY4ODU4MDI1NTgxNjlkZWQ0NTQ=
9
+ OTk2NTgwODMyNjExMjBiZThmODdjNzczZmM4M2MzMmY0MWNiNTkxODU5NThk
10
+ Y2UwYzY0ZTQ1NGRhNmFjYjI1ODIwYzBiMmMyMDM2NzFmNmNhNTI1OGNkYjNj
11
+ NzJhZTRjMDk4MzVmYjJlNDM4YTE2ZWFjYTVjNzlmMGZlNzM4NjQ=
12
12
  data.tar.gz: !binary |-
13
- YjZkMDdlYTZkZWZlZDgxMzkyZjQ5MWY5MDA4ZjRmNGQ2OGIxN2U5YWRlZTMw
14
- NTMyYmFjM2E0M2FhOGY2NGI4NDFiZDUzZGEwZGNmMGIyMmY0NzA3OTQzMzI0
15
- MTAzODg2ODU4NzA1NTY1MjFhMzdmZWMzN2EyYmZlMjA3MDk5NmY=
13
+ NjhmNWU0NmZjNDVkMmUzMDJhMGM2ZTY0ZWVjMTk3ZGUwZGI3MzM2OTQwN2M4
14
+ MTUzYTk3N2E1NzI2YWFmMTA1YWIxZWY2MTliZTRiOGNjOTU5NjEwMGFlODkz
15
+ YjRmYmVmN2M4NTJkMGUyM2NlNjhmODA0ZGFkYWYzM2Q0OTNlMDU=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ v1.2.4, 2018-12-13
2
+ -------------------
3
+ * [IMPROVEMENT] Support decimal arithmetic
4
+ * [BUGFIX] Correctly handle type checking with arithmetic
5
+
1
6
  v1.2.3, 2018-12-05
2
7
  -------------------
3
8
  * [IMPROVEMENT] Support Arithmetic Grouping and Negation
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.3
1
+ 1.2.4
@@ -161,7 +161,7 @@ module Sparkql::ParserCompatibility
161
161
  def datetime_escape(string)
162
162
  DateTime.parse(string)
163
163
  end
164
-
164
+
165
165
  def time_escape(string)
166
166
  DateTime.parse(string)
167
167
  end
@@ -214,7 +214,8 @@ module Sparkql::ParserCompatibility
214
214
 
215
215
  # Checks the type of an expression with what is expected.
216
216
  def check_type!(expression, expected, supports_nulls = true)
217
- if expected == expression[:type] || check_function_type?(expression, expected) ||
217
+ if (expected == expression[:type] && !expression.key?(:field_manipulations)) ||
218
+ (expression.key?(:field_manipulations) && check_function_type?(expression, expected)) ||
218
219
  (supports_nulls && expression[:type] == :null)
219
220
  return true
220
221
  # If the field will be passed into a function,
@@ -230,7 +231,7 @@ module Sparkql::ParserCompatibility
230
231
  expression[:type] = :datetime
231
232
  expression[:cast] = :date
232
233
  return true
233
- elsif expected == :date && expression[:type] == :datetime
234
+ elsif expected == :date && expression[:type] == :datetime
234
235
  expression[:type] = :date
235
236
  expression[:cast] = :datetime
236
237
  if multiple_values?(expression[:value])
@@ -253,26 +254,44 @@ module Sparkql::ParserCompatibility
253
254
  :message => "expected #{expected} but found #{expression[:type]}",
254
255
  :status => :fatal )
255
256
  end
256
-
257
+
257
258
  # If a function is being applied to a field, we check that the return type of
258
259
  # the function matches what is expected, and that the function supports the
259
260
  # field type as the first argument.
260
261
  def check_function_type?(expression, expected)
261
- return false unless expression.key?(:field_manipulations) && expression[:field_manipulations][:return_type] == expression[:type]
262
- # Lookup the function arguments
263
- function = Sparkql::FunctionResolver::SUPPORTED_FUNCTIONS[deepest_function(expression[:field_manipulations])[:function_name].to_sym]
264
- return false if function.nil?
265
-
266
- Array(function[:args].first).include?(expected)
262
+ validate_manipulation_types(expression[:field_manipulations], expected)
267
263
  end
268
264
 
265
+ def validate_manipulation_types(field_manipulations, expected)
266
+ if field_manipulations[:type] == :function
267
+ function = Sparkql::FunctionResolver::SUPPORTED_FUNCTIONS[field_manipulations[:function_name].to_sym]
268
+ return false if function.nil?
269
+ field_manipulations[:args].each_with_index do |arg, index|
270
+ if arg[:type] == :field
271
+ return false unless function[:args][index].include?(:field)
272
+ end
273
+ end
274
+ elsif field_manipulations[:type] == :arithmetic
275
+ lhs = field_manipulations[:lhs]
276
+ return false unless validate_side(lhs, expected)
269
277
 
270
- def deepest_function(function)
271
- if function[:args].first[:type] == :function
272
- deepest_function(function[:args].first)
273
- else
274
- function
278
+ rhs = field_manipulations[:rhs]
279
+ return false unless rhs.nil? || validate_side(rhs, expected)
280
+ end
281
+ true
282
+ end
283
+
284
+ def validate_side(side, expected)
285
+ if side[:type] == :arithmetic
286
+ return validate_manipulation_types(side, expected)
287
+ elsif side[:type] == :field
288
+ return false unless [:decimal, :integer].include?(expected)
289
+ elsif side[:type] == :function
290
+ return false unless [:decimal, :integer].include?(side[:return_type])
291
+ elsif ![:decimal, :integer].include?(side[:type])
292
+ return false
275
293
  end
294
+ true
276
295
  end
277
296
 
278
297
  # Builds the correct operator based on the type and the value.
@@ -305,7 +324,7 @@ module Sparkql::ParserCompatibility
305
324
  def operator_supports_multiples?(operator)
306
325
  OPERATORS_SUPPORTING_MULTIPLES.include?(operator)
307
326
  end
308
-
327
+
309
328
  def coerce_datetime datetime
310
329
  if datestr = datetime.match(/^(\d{4}-\d{2}-\d{2})/)
311
330
  datestr[0]
@@ -1,4 +1,6 @@
1
1
  # This is the guts of the parser internals and is mixed into the parser for organization.
2
+ require 'bigdecimal'
3
+
2
4
  module Sparkql::ParserTools
3
5
 
4
6
  # Coercible types from highest precision to lowest
@@ -264,19 +266,22 @@ module Sparkql::ParserTools
264
266
  def add_fold(n1, n2)
265
267
  return if arithmetic_error?(n1) || arithmetic_error?(n2)
266
268
 
267
- { type: arithmetic_type(n1, n2), value: (escape_value(n1) + escape_value(n2)).to_s }
269
+ value = escape_arithmetic_value(n1) + escape_arithmetic_value(n2)
270
+ { type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
268
271
  end
269
272
 
270
273
  def sub_fold(n1, n2)
271
274
  return if arithmetic_error?(n1) || arithmetic_error?(n2)
272
275
 
273
- { type: arithmetic_type(n1, n2), value: (escape_value(n1) - escape_value(n2)).to_s }
276
+ value = escape_arithmetic_value(n1) - escape_arithmetic_value(n2)
277
+ { type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
274
278
  end
275
279
 
276
280
  def mul_fold(n1, n2)
277
281
  return if arithmetic_error?(n1) || arithmetic_error?(n2)
278
282
 
279
- { type: arithmetic_type(n1, n2), value: (escape_value(n1) * escape_value(n2)).to_s }
283
+ value = escape_arithmetic_value(n1) * escape_arithmetic_value(n2)
284
+ { type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
280
285
  end
281
286
 
282
287
  def div_fold(n1, n2)
@@ -284,7 +289,8 @@ module Sparkql::ParserTools
284
289
  arithmetic_error?(n2) ||
285
290
  zero_error?(n2)
286
291
 
287
- { type: arithmetic_type(n1, n2), value: (escape_value(n1) / escape_value(n2)).to_s }
292
+ value = escape_arithmetic_value(n1) / escape_arithmetic_value(n2)
293
+ { type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
288
294
  end
289
295
 
290
296
  def mod_fold(n1, n2)
@@ -292,7 +298,8 @@ module Sparkql::ParserTools
292
298
  arithmetic_error?(n2) ||
293
299
  zero_error?(n2)
294
300
 
295
- { type: arithmetic_type(n1, n2), value: (escape_value(n1) % escape_value(n2)).to_s }
301
+ value = escape_arithmetic_value(n1) % escape_arithmetic_value(n2)
302
+ { type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
296
303
  end
297
304
 
298
305
  def arithmetic_type(num1, num2)
@@ -303,6 +310,23 @@ module Sparkql::ParserTools
303
310
  end
304
311
  end
305
312
 
313
+ def escape_arithmetic_value(expression)
314
+ case expression[:type]
315
+ when :decimal
316
+ BigDecimal.new(expression[:value])
317
+ else
318
+ escape_value(expression)
319
+ end
320
+ end
321
+
322
+ def unescape_arithmetic(value)
323
+ if value.is_a?(BigDecimal)
324
+ value.round(20).to_s('F')
325
+ else
326
+ value.to_s
327
+ end
328
+ end
329
+
306
330
  def zero_error?(number)
307
331
  return unless escape_value(number) == 0
308
332
 
@@ -565,4 +565,19 @@ class ParserCompatabilityTest < Test::Unit::TestCase
565
565
  assert parser.send(:check_type!, expression, :datetime)
566
566
  assert_equal '3', parser.escape_value(expression)
567
567
  end
568
+
569
+ test "function with field and arithmetic" do
570
+ filter = "year(CloseDate) add 1 Eq 2017"
571
+ parser = Parser.new
572
+ expression = parser.tokenize(filter).first
573
+ assert parser.send(:check_type!, expression, :datetime)
574
+ end
575
+
576
+ test "Cannot perform arithmetic on a String field" do
577
+ filter = "City Add 3.0 Eq 'Fargo'"
578
+ parser = Parser.new
579
+ expression = parser.tokenize(filter).first
580
+ # Type mismatch
581
+ assert !parser.send(:check_type!, expression, :datetime)
582
+ end
568
583
  end
@@ -232,6 +232,54 @@ class ParserTest < Test::Unit::TestCase
232
232
  assert_equal 'Mod', field_manipulations[:op]
233
233
  end
234
234
 
235
+ test 'Mod returns decimal precision' do
236
+ @parser = Parser.new
237
+ filter = "Baths Eq 32.7 Mod 20.7"
238
+ expressions = @parser.parse(filter)
239
+ assert !@parser.errors?, @parser.errors.inspect
240
+ assert_equal '12.0', expressions.first[:value]
241
+ end
242
+
243
+ test 'Adding returns decimal precision' do
244
+ @parser = Parser.new
245
+ filter = "Baths Eq 0.1 Add 0.2"
246
+ expressions = @parser.parse(filter)
247
+ assert !@parser.errors?, @parser.errors.inspect
248
+ assert_equal '0.3', expressions.first[:value]
249
+ end
250
+
251
+ test 'Subtracting returns decimal precision' do
252
+ @parser = Parser.new
253
+ filter = "Baths Eq 0.3 Sub 0.1"
254
+ expressions = @parser.parse(filter)
255
+ assert !@parser.errors?, @parser.errors.inspect
256
+ assert_equal '0.2', expressions.first[:value]
257
+ end
258
+
259
+ test 'Division returns decimal precision' do
260
+ @parser = Parser.new
261
+ filter = "Baths Eq 0.6 Div 0.2"
262
+ expressions = @parser.parse(filter)
263
+ assert !@parser.errors?, @parser.errors.inspect
264
+ assert_equal '3.0', expressions.first[:value]
265
+ end
266
+
267
+ test 'Arithmetic rounds to 20 decimal places' do
268
+ @parser = Parser.new
269
+ filter = "Baths Eq 7 Div 10.1"
270
+ expressions = @parser.parse(filter)
271
+ assert !@parser.errors?, @parser.errors.inspect
272
+ assert_equal '0.69306930693069306931', expressions.first[:value]
273
+ end
274
+
275
+ test 'Multiplication returns decimal precision' do
276
+ @parser = Parser.new
277
+ filter = "Baths Eq 7 Mul 0.1"
278
+ expressions = @parser.parse(filter)
279
+ assert !@parser.errors?, @parser.errors.inspect
280
+ assert_equal '0.7', expressions.first[:value]
281
+ end
282
+
235
283
  test 'arithmetic with field function' do
236
284
  @parser = Parser.new
237
285
  filter = "floor(Baths) Add 2 Eq 1"
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.2.3
4
+ version: 1.2.4
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-12-05 00:00:00.000000000 Z
11
+ date: 2018-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: georuby