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 +8 -8
- data/CHANGELOG.md +5 -0
- data/VERSION +1 -1
- data/lib/sparkql/parser_compatibility.rb +35 -16
- data/lib/sparkql/parser_tools.rb +29 -5
- data/test/unit/parser_compatability_test.rb +15 -0
- data/test/unit/parser_test.rb +48 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YmIyYTljOGIyZmFjZTEyZmY1MzU2ODZjYTg4ZWU1NDI2NWNkOWRkYw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NDkwMjczNDY2N2JlOGRmMzA5MDQ1YmI4Njg3ZTM5MTJlMzBiODVhMQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OTk2NTgwODMyNjExMjBiZThmODdjNzczZmM4M2MzMmY0MWNiNTkxODU5NThk
|
10
|
+
Y2UwYzY0ZTQ1NGRhNmFjYjI1ODIwYzBiMmMyMDM2NzFmNmNhNTI1OGNkYjNj
|
11
|
+
NzJhZTRjMDk4MzVmYjJlNDM4YTE2ZWFjYTVjNzlmMGZlNzM4NjQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NjhmNWU0NmZjNDVkMmUzMDJhMGM2ZTY0ZWVjMTk3ZGUwZGI3MzM2OTQwN2M4
|
14
|
+
MTUzYTk3N2E1NzI2YWFmMTA1YWIxZWY2MTliZTRiOGNjOTU5NjEwMGFlODkz
|
15
|
+
YjRmYmVmN2M4NTJkMGUyM2NlNjhmODA0ZGFkYWYzM2Q0OTNlMDU=
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
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]
|
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
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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]
|
data/lib/sparkql/parser_tools.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/test/unit/parser_test.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2018-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: georuby
|