sparkql 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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