sparkql 1.2.2 → 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGELOG.md +21 -0
- data/GRAMMAR.md +5 -4
- data/Gemfile +1 -2
- data/VERSION +1 -1
- data/lib/sparkql/function_resolver.rb +768 -676
- data/lib/sparkql/parser.rb +198 -178
- data/lib/sparkql/parser.y +4 -2
- data/lib/sparkql/parser_compatibility.rb +36 -17
- data/lib/sparkql/parser_tools.rb +67 -23
- data/sparkql.gemspec +19 -17
- data/test/unit/function_resolver_test.rb +455 -191
- data/test/unit/parser_compatability_test.rb +15 -0
- data/test/unit/parser_test.rb +148 -13
- metadata +34 -35
- data/.ruby-version +0 -1
data/lib/sparkql/parser.y
CHANGED
@@ -98,6 +98,8 @@ rule
|
|
98
98
|
| field_arithmetic_expression MUL field_arithmetic_expression { result = tokenize_arithmetic(val[0], val[1], val[2]) }
|
99
99
|
| field_arithmetic_expression DIV field_arithmetic_expression { result = tokenize_arithmetic(val[0], val[1], val[2]) }
|
100
100
|
| field_arithmetic_expression MOD field_arithmetic_expression { result = tokenize_arithmetic(val[0], val[1], val[2]) }
|
101
|
+
| LPAREN field_arithmetic_expression RPAREN { result = tokenize_arithmetic_group(val[1]) }
|
102
|
+
| UMINUS field_arithmetic_expression { result = tokenize_arithmetic_negation(val[1]) }
|
101
103
|
| literals
|
102
104
|
| field_function_expression
|
103
105
|
;
|
@@ -117,6 +119,8 @@ rule
|
|
117
119
|
: arithmetic_condition
|
118
120
|
| literal_list { result = tokenize_list(val[0]) }
|
119
121
|
| literal
|
122
|
+
| LPAREN condition RPAREN { result = group_fold(val[1]) }
|
123
|
+
| UMINUS condition { result = tokenize_literal_negation(val[1]) }
|
120
124
|
;
|
121
125
|
|
122
126
|
arithmetic_condition
|
@@ -194,8 +198,6 @@ rule
|
|
194
198
|
: INTEGER
|
195
199
|
| DECIMAL
|
196
200
|
| CHARACTER
|
197
|
-
| LPAREN literals RPAREN { result = val[1] }
|
198
|
-
| UMINUS literals { result = tokenize_literal_negation(val[1]) }
|
199
201
|
;
|
200
202
|
|
201
203
|
##### Literal
|
@@ -85,7 +85,7 @@ module Sparkql::ParserCompatibility
|
|
85
85
|
|
86
86
|
# Delegator for methods to process the error list.
|
87
87
|
def process_errors
|
88
|
-
Sparkql::ErrorsProcessor.new(
|
88
|
+
Sparkql::ErrorsProcessor.new(errors)
|
89
89
|
end
|
90
90
|
|
91
91
|
# delegate :errors?, :fatal_errors?, :dropped_errors?, :recovered_errors?, :to => :process_errors
|
@@ -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,10 +1,14 @@
|
|
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
|
5
7
|
DATE_TYPES = [:datetime, :date]
|
6
8
|
NUMBER_TYPES = [:decimal, :integer]
|
7
9
|
ARITHMETIC_TYPES = [:decimal, :integer, :field, :arithmetic]
|
10
|
+
GROUP = 'Group'.freeze
|
11
|
+
NEGATION = 'Negation'.freeze
|
8
12
|
|
9
13
|
def parse(str)
|
10
14
|
@lexer = Sparkql::Lexer.new(str)
|
@@ -24,24 +28,16 @@ module Sparkql::ParserTools
|
|
24
28
|
end
|
25
29
|
|
26
30
|
def arithmetic_field(nested_representation)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
rhs[:field]
|
38
|
-
elsif lhs[:type] == :arithmetic
|
39
|
-
arithmetic_field(lhs)
|
40
|
-
elsif rhs[:type] == :arithmetic
|
41
|
-
arithmetic_field(rhs)
|
42
|
-
else
|
43
|
-
nil
|
44
|
-
end
|
31
|
+
return if nested_representation.nil?
|
32
|
+
|
33
|
+
return nested_representation[:value] if nested_representation[:type] == :field
|
34
|
+
return nested_representation[:field] if nested_representation.key?(:field)
|
35
|
+
|
36
|
+
field = arithmetic_field(nested_representation[:lhs])
|
37
|
+
return field unless field.nil?
|
38
|
+
|
39
|
+
field = arithmetic_field(nested_representation[:rhs])
|
40
|
+
return field unless field.nil?
|
45
41
|
end
|
46
42
|
|
47
43
|
def no_field_error(field, operator)
|
@@ -129,6 +125,26 @@ module Sparkql::ParserTools
|
|
129
125
|
expressions
|
130
126
|
end
|
131
127
|
|
128
|
+
def tokenize_arithmetic_group(lhs)
|
129
|
+
@lexer.leveldown
|
130
|
+
@lexer.block_group_identifier -= 1
|
131
|
+
lhs = {type: :field, value: lhs} if lhs.is_a?(String)
|
132
|
+
{
|
133
|
+
type: :arithmetic,
|
134
|
+
op: GROUP,
|
135
|
+
lhs: lhs
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def tokenize_arithmetic_negation(lhs)
|
140
|
+
lhs = {type: :field, value: lhs} if lhs.is_a?(String)
|
141
|
+
{
|
142
|
+
type: :arithmetic,
|
143
|
+
op: NEGATION,
|
144
|
+
lhs: lhs
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
132
148
|
def tokenize_list(list)
|
133
149
|
return if list.nil?
|
134
150
|
validate_multiple_values list[:value]
|
@@ -245,22 +261,31 @@ module Sparkql::ParserTools
|
|
245
261
|
true
|
246
262
|
end
|
247
263
|
|
264
|
+
def group_fold(exp)
|
265
|
+
@lexer.leveldown
|
266
|
+
@lexer.block_group_identifier -= 1
|
267
|
+
exp
|
268
|
+
end
|
269
|
+
|
248
270
|
def add_fold(n1, n2)
|
249
271
|
return if arithmetic_error?(n1) || arithmetic_error?(n2)
|
250
272
|
|
251
|
-
|
273
|
+
value = escape_arithmetic_value(n1) + escape_arithmetic_value(n2)
|
274
|
+
{ type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
|
252
275
|
end
|
253
276
|
|
254
277
|
def sub_fold(n1, n2)
|
255
278
|
return if arithmetic_error?(n1) || arithmetic_error?(n2)
|
256
279
|
|
257
|
-
|
280
|
+
value = escape_arithmetic_value(n1) - escape_arithmetic_value(n2)
|
281
|
+
{ type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
|
258
282
|
end
|
259
283
|
|
260
284
|
def mul_fold(n1, n2)
|
261
285
|
return if arithmetic_error?(n1) || arithmetic_error?(n2)
|
262
286
|
|
263
|
-
|
287
|
+
value = escape_arithmetic_value(n1) * escape_arithmetic_value(n2)
|
288
|
+
{ type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
|
264
289
|
end
|
265
290
|
|
266
291
|
def div_fold(n1, n2)
|
@@ -268,7 +293,8 @@ module Sparkql::ParserTools
|
|
268
293
|
arithmetic_error?(n2) ||
|
269
294
|
zero_error?(n2)
|
270
295
|
|
271
|
-
|
296
|
+
value = escape_arithmetic_value(n1) / escape_arithmetic_value(n2)
|
297
|
+
{ type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
|
272
298
|
end
|
273
299
|
|
274
300
|
def mod_fold(n1, n2)
|
@@ -276,7 +302,8 @@ module Sparkql::ParserTools
|
|
276
302
|
arithmetic_error?(n2) ||
|
277
303
|
zero_error?(n2)
|
278
304
|
|
279
|
-
|
305
|
+
value = escape_arithmetic_value(n1) % escape_arithmetic_value(n2)
|
306
|
+
{ type: arithmetic_type(n1, n2), value: unescape_arithmetic(value) }
|
280
307
|
end
|
281
308
|
|
282
309
|
def arithmetic_type(num1, num2)
|
@@ -287,6 +314,23 @@ module Sparkql::ParserTools
|
|
287
314
|
end
|
288
315
|
end
|
289
316
|
|
317
|
+
def escape_arithmetic_value(expression)
|
318
|
+
case expression[:type]
|
319
|
+
when :decimal
|
320
|
+
BigDecimal.new(expression[:value])
|
321
|
+
else
|
322
|
+
escape_value(expression)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def unescape_arithmetic(value)
|
327
|
+
if value.is_a?(BigDecimal)
|
328
|
+
value.round(20).to_s('F')
|
329
|
+
else
|
330
|
+
value.to_s
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
290
334
|
def zero_error?(number)
|
291
335
|
return unless escape_value(number) == 0
|
292
336
|
|
data/sparkql.gemspec
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
require 'sparkql/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
7
|
+
s.name = 'sparkql'
|
7
8
|
s.version = Sparkql::VERSION
|
8
|
-
s.authors = [
|
9
|
-
s.email = [
|
10
|
-
s.homepage =
|
11
|
-
s.summary =
|
12
|
-
s.description =
|
9
|
+
s.authors = ['Wade McEwen']
|
10
|
+
s.email = ['wade@fbsdata.com']
|
11
|
+
s.homepage = ''
|
12
|
+
s.summary = 'API Parser engine for filter searching'
|
13
|
+
s.description = 'Specification and base implementation of the Spark API parsing system.'
|
14
|
+
|
15
|
+
s.rubyforge_project = 'sparkql'
|
13
16
|
|
14
|
-
s.rubyforge_project = "sparkql"
|
15
|
-
|
16
17
|
s.license = 'Apache 2.0'
|
17
|
-
|
18
|
+
|
18
19
|
s.files = `git ls-files`.split("\n")
|
19
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
-
s.require_paths = [
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
22
|
+
s.require_paths = ['lib']
|
23
|
+
|
24
|
+
s.required_ruby_version = '>= 2.3'
|
22
25
|
|
23
26
|
s.add_dependency 'georuby', '~> 2.0'
|
27
|
+
s.add_development_dependency 'ci_reporter', '~> 1.6'
|
28
|
+
s.add_development_dependency 'mocha', '~> 0.12.0'
|
24
29
|
s.add_development_dependency 'racc', '~> 1.4.8'
|
25
30
|
s.add_development_dependency 'rake', '~> 0.9.2'
|
26
31
|
s.add_development_dependency 'test-unit', '~> 2.1.0'
|
27
|
-
s.add_development_dependency 'ci_reporter', '~> 1.6'
|
28
|
-
s.add_development_dependency 'mocha', '~> 0.12.0'
|
29
|
-
|
30
32
|
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'sparkql/geo'
|
3
5
|
|
4
6
|
class FunctionResolverTest < Test::Unit::TestCase
|
5
7
|
include Sparkql
|
6
|
-
|
7
|
-
EXAMPLE_DATE = DateTime.parse("2013-07-26T10:22:15.422804")
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
EXAMPLE_DATE = DateTime.parse('2013-07-26T10:22:15.422804')
|
10
|
+
|
11
|
+
test 'function parameters and name preserved' do
|
12
|
+
f = FunctionResolver.new('radius', [{ type: :character,
|
13
|
+
value: '35.12 -68.33' }, { type: :decimal, value: 1.0 }])
|
12
14
|
value = f.call
|
13
15
|
assert_equal 'radius', value[:function_name]
|
14
|
-
assert_equal([
|
16
|
+
assert_equal(['35.12 -68.33', 1.0], value[:function_parameters])
|
15
17
|
end
|
16
18
|
|
17
|
-
test
|
18
|
-
f = FunctionResolver.new('round', [{:
|
19
|
+
test 'round(float)' do
|
20
|
+
f = FunctionResolver.new('round', [{ type: :decimal, value: 0.5 }])
|
19
21
|
f.validate
|
20
22
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
21
23
|
value = f.call
|
@@ -23,22 +25,22 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
23
25
|
assert_equal '1', value[:value]
|
24
26
|
end
|
25
27
|
|
26
|
-
test
|
27
|
-
f = FunctionResolver.new('round', [{:
|
28
|
+
test 'round(Field)' do
|
29
|
+
f = FunctionResolver.new('round', [{ type: :field, value: 'ListPrice' }])
|
28
30
|
f.validate
|
29
31
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
30
32
|
value = f.call
|
31
33
|
|
32
34
|
assert_equal :function, value[:type]
|
33
35
|
assert_equal 'round', value[:value]
|
34
|
-
assert_equal
|
36
|
+
assert_equal 'ListPrice', value[:args].first[:value]
|
35
37
|
end
|
36
38
|
|
37
|
-
test
|
39
|
+
test 'substring character one index' do
|
38
40
|
f = FunctionResolver.new('substring', [
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
{ type: :character, value: 'ListPrice' },
|
42
|
+
{ type: :integer, value: 1 }
|
43
|
+
])
|
42
44
|
|
43
45
|
f.validate
|
44
46
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -48,26 +50,26 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
48
50
|
assert_equal 'istPrice', value[:value]
|
49
51
|
end
|
50
52
|
|
51
|
-
test
|
53
|
+
test 'substring character two indexes' do
|
52
54
|
f = FunctionResolver.new('substring', [
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
{ type: :character, value: 'alfb' },
|
56
|
+
{ type: :integer, value: 1 },
|
57
|
+
{ type: :integer, value: 2 }
|
58
|
+
])
|
57
59
|
|
58
60
|
f.validate
|
59
61
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
60
62
|
value = f.call
|
61
63
|
|
62
64
|
assert_equal :character, value[:type]
|
63
|
-
assert_equal
|
65
|
+
assert_equal 'lf', value[:value]
|
64
66
|
end
|
65
67
|
|
66
|
-
test
|
68
|
+
test 'substring character large first index' do
|
67
69
|
f = FunctionResolver.new('substring', [
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
{ type: :character, value: 'ListPrice' },
|
71
|
+
{ type: :integer, value: 10 }
|
72
|
+
])
|
71
73
|
|
72
74
|
f.validate
|
73
75
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -77,11 +79,11 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
77
79
|
assert_equal '', value[:value]
|
78
80
|
end
|
79
81
|
|
80
|
-
test
|
82
|
+
test 'substring field one index' do
|
81
83
|
f = FunctionResolver.new('substring', [
|
82
|
-
|
83
|
-
|
84
|
-
|
84
|
+
{ type: :field, value: 'ListPrice' },
|
85
|
+
{ type: :integer, value: 1 }
|
86
|
+
])
|
85
87
|
|
86
88
|
f.validate
|
87
89
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -89,16 +91,16 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
89
91
|
|
90
92
|
assert_equal :function, value[:type]
|
91
93
|
assert_equal 'substring', value[:value]
|
92
|
-
assert_equal
|
94
|
+
assert_equal 'ListPrice', value[:args].first[:value]
|
93
95
|
assert_equal 2, value[:args].size
|
94
96
|
end
|
95
97
|
|
96
|
-
test
|
98
|
+
test 'substring field two indexes' do
|
97
99
|
f = FunctionResolver.new('substring', [
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
{ type: :field, value: 'ListPrice' },
|
101
|
+
{ type: :integer, value: 1 },
|
102
|
+
{ type: :integer, value: 2 }
|
103
|
+
])
|
102
104
|
|
103
105
|
f.validate
|
104
106
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -106,28 +108,28 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
106
108
|
|
107
109
|
assert_equal :function, value[:type]
|
108
110
|
assert_equal 'substring', value[:value]
|
109
|
-
assert_equal
|
111
|
+
assert_equal 'ListPrice', value[:args].first[:value]
|
110
112
|
assert_equal 2, value[:args].last[:value]
|
111
113
|
end
|
112
114
|
|
113
|
-
test
|
115
|
+
test 'substring with negative M is a parse error' do
|
114
116
|
f = FunctionResolver.new('substring', [
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
{ type: :field, value: 'ListPrice' },
|
118
|
+
{ type: :integer, value: 1 },
|
119
|
+
{ type: :integer, value: -5 }
|
120
|
+
])
|
119
121
|
|
120
122
|
f.validate
|
121
123
|
f.call
|
122
124
|
assert f.errors?
|
123
125
|
end
|
124
126
|
|
125
|
-
test
|
127
|
+
test 'character substring with negative M is a parse error' do
|
126
128
|
f = FunctionResolver.new('substring', [
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
129
|
+
{ type: :character, value: 'ListPrice' },
|
130
|
+
{ type: :integer, value: 1 },
|
131
|
+
{ type: :integer, value: -5 }
|
132
|
+
])
|
131
133
|
|
132
134
|
f.validate
|
133
135
|
f.call
|
@@ -136,8 +138,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
136
138
|
|
137
139
|
test 'trim with field' do
|
138
140
|
f = FunctionResolver.new('trim', [
|
139
|
-
|
140
|
-
|
141
|
+
{ type: :field, value: 'Name' }
|
142
|
+
])
|
141
143
|
|
142
144
|
f.validate
|
143
145
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -145,13 +147,13 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
145
147
|
|
146
148
|
assert_equal :function, value[:type]
|
147
149
|
assert_equal 'trim', value[:value]
|
148
|
-
assert_equal
|
150
|
+
assert_equal 'Name', value[:args].first[:value]
|
149
151
|
end
|
150
152
|
|
151
153
|
test 'trim with character' do
|
152
154
|
f = FunctionResolver.new('trim', [
|
153
|
-
|
154
|
-
|
155
|
+
{ type: :character, value: ' val ' }
|
156
|
+
])
|
155
157
|
|
156
158
|
f.validate
|
157
159
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -162,7 +164,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
162
164
|
end
|
163
165
|
|
164
166
|
test "tolower('string')" do
|
165
|
-
f = FunctionResolver.new('tolower', [{:
|
167
|
+
f = FunctionResolver.new('tolower', [{ type: :character, value: 'STRING' }])
|
166
168
|
f.validate
|
167
169
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
168
170
|
value = f.call
|
@@ -170,18 +172,18 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
170
172
|
assert_equal "'string'", value[:value]
|
171
173
|
end
|
172
174
|
|
173
|
-
test
|
174
|
-
f = FunctionResolver.new('toupper', [{:
|
175
|
+
test 'toupper(SomeField)' do
|
176
|
+
f = FunctionResolver.new('toupper', [{ type: :field, value: 'City' }])
|
175
177
|
f.validate
|
176
178
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
177
179
|
value = f.call
|
178
180
|
assert_equal :function, value[:type]
|
179
181
|
assert_equal 'toupper', value[:value]
|
180
|
-
assert_equal
|
182
|
+
assert_equal 'City', value[:args].first[:value]
|
181
183
|
end
|
182
184
|
|
183
185
|
test "toupper('string')" do
|
184
|
-
f = FunctionResolver.new('toupper', [{:
|
186
|
+
f = FunctionResolver.new('toupper', [{ type: :character, value: 'string' }])
|
185
187
|
f.validate
|
186
188
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
187
189
|
value = f.call
|
@@ -189,18 +191,18 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
189
191
|
assert_equal "'STRING'", value[:value]
|
190
192
|
end
|
191
193
|
|
192
|
-
test
|
193
|
-
f = FunctionResolver.new('length', [{:
|
194
|
+
test 'length(SomeField)' do
|
195
|
+
f = FunctionResolver.new('length', [{ type: :field, value: 'City' }])
|
194
196
|
f.validate
|
195
197
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
196
198
|
value = f.call
|
197
199
|
assert_equal :function, value[:type]
|
198
200
|
assert_equal 'length', value[:value]
|
199
|
-
assert_equal
|
201
|
+
assert_equal 'City', value[:args].first[:value]
|
200
202
|
end
|
201
203
|
|
202
204
|
test "length('string')" do
|
203
|
-
f = FunctionResolver.new('length', [{:
|
205
|
+
f = FunctionResolver.new('length', [{ type: :character, value: 'string' }])
|
204
206
|
f.validate
|
205
207
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
206
208
|
value = f.call
|
@@ -208,7 +210,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
208
210
|
assert_equal '6', value[:value]
|
209
211
|
end
|
210
212
|
|
211
|
-
test
|
213
|
+
test 'now()' do
|
212
214
|
start = Time.now
|
213
215
|
f = FunctionResolver.new('now', [])
|
214
216
|
f.validate
|
@@ -216,10 +218,11 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
216
218
|
value = f.call
|
217
219
|
assert_equal :datetime, value[:type]
|
218
220
|
test_time = Time.parse(value[:value])
|
219
|
-
assert (
|
221
|
+
assert (test_time - start > -5 && test_time - start < 5),
|
222
|
+
"Time range off by more than five seconds #{test_time - start} '#{test_time} - #{start}'"
|
220
223
|
end
|
221
224
|
|
222
|
-
test
|
225
|
+
test 'mindatetime()' do
|
223
226
|
f = FunctionResolver.new('mindatetime', [])
|
224
227
|
f.validate
|
225
228
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -229,7 +232,7 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
229
232
|
assert_equal '1970-01-01T00:00:00+00:00', value[:value]
|
230
233
|
end
|
231
234
|
|
232
|
-
test
|
235
|
+
test 'maxdatetime()' do
|
233
236
|
f = FunctionResolver.new('maxdatetime', [])
|
234
237
|
f.validate
|
235
238
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
@@ -239,8 +242,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
239
242
|
assert_equal '9999-12-31T23:59:59+00:00', value[:value]
|
240
243
|
end
|
241
244
|
|
242
|
-
test
|
243
|
-
f = FunctionResolver.new('floor', [{:
|
245
|
+
test 'floor(float)' do
|
246
|
+
f = FunctionResolver.new('floor', [{ type: :decimal, value: 0.5 }])
|
244
247
|
f.validate
|
245
248
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
246
249
|
value = f.call
|
@@ -248,19 +251,19 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
248
251
|
assert_equal '0', value[:value]
|
249
252
|
end
|
250
253
|
|
251
|
-
test
|
252
|
-
f = FunctionResolver.new('floor', [{:
|
254
|
+
test 'floor(Field)' do
|
255
|
+
f = FunctionResolver.new('floor', [{ type: :field, value: 'ListPrice' }])
|
253
256
|
f.validate
|
254
257
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
255
258
|
value = f.call
|
256
259
|
|
257
260
|
assert_equal :function, value[:type]
|
258
261
|
assert_equal 'floor', value[:value]
|
259
|
-
assert_equal
|
262
|
+
assert_equal 'ListPrice', value[:args].first[:value]
|
260
263
|
end
|
261
264
|
|
262
|
-
test
|
263
|
-
f = FunctionResolver.new('ceiling', [{:
|
265
|
+
test 'ceiling(float)' do
|
266
|
+
f = FunctionResolver.new('ceiling', [{ type: :decimal, value: 0.5 }])
|
264
267
|
f.validate
|
265
268
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
266
269
|
value = f.call
|
@@ -268,93 +271,344 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
268
271
|
assert_equal '1', value[:value]
|
269
272
|
end
|
270
273
|
|
271
|
-
test
|
272
|
-
f = FunctionResolver.new('ceiling', [{:
|
274
|
+
test 'ceiling(Field)' do
|
275
|
+
f = FunctionResolver.new('ceiling', [{ type: :field, value: 'ListPrice' }])
|
273
276
|
f.validate
|
274
277
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
275
278
|
value = f.call
|
276
279
|
|
277
280
|
assert_equal :function, value[:type]
|
278
281
|
assert_equal 'ceiling', value[:value]
|
279
|
-
assert_equal
|
282
|
+
assert_equal 'ListPrice', value[:args].first[:value]
|
283
|
+
end
|
284
|
+
|
285
|
+
test 'seconds()' do
|
286
|
+
test_time = Time.new(2019, 4, 1, 8, 30, 20, 0)
|
287
|
+
|
288
|
+
f = FunctionResolver.new('seconds', [{ type: :integer, value: 7 }])
|
289
|
+
f.expects(:current_time).returns(test_time)
|
290
|
+
f.validate
|
291
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
292
|
+
value = f.call
|
293
|
+
|
294
|
+
assert_equal :datetime, value[:type]
|
295
|
+
d = DateTime.parse(value[:value])
|
296
|
+
assert_equal test_time.year, d.year
|
297
|
+
assert_equal test_time.month, d.month
|
298
|
+
assert_equal test_time.mday, d.mday
|
299
|
+
assert_equal test_time.hour, d.hour
|
300
|
+
assert_equal test_time.min, d.min
|
301
|
+
assert_equal test_time.sec + 7, d.sec
|
302
|
+
|
303
|
+
f = FunctionResolver.new('seconds', [{ type: :integer, value: -21 }])
|
304
|
+
f.expects(:current_time).returns(test_time)
|
305
|
+
f.validate
|
306
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
307
|
+
value = f.call
|
308
|
+
|
309
|
+
assert_equal :datetime, value[:type]
|
310
|
+
d = DateTime.parse(value[:value])
|
311
|
+
assert_equal test_time.year, d.year
|
312
|
+
assert_equal test_time.month, d.month
|
313
|
+
assert_equal test_time.mday, d.mday
|
314
|
+
assert_equal test_time.hour, d.hour
|
315
|
+
assert_equal test_time.min - 1, d.min
|
316
|
+
assert_equal 29, d.min
|
317
|
+
assert_equal 59, d.sec
|
318
|
+
|
319
|
+
f = FunctionResolver.new('seconds', [{ type: :integer,
|
320
|
+
value: -Sparkql::FunctionResolver::SECONDS_IN_DAY }])
|
321
|
+
f.expects(:current_time).returns(test_time)
|
322
|
+
f.validate
|
323
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
324
|
+
value = f.call
|
325
|
+
|
326
|
+
assert_equal :datetime, value[:type]
|
327
|
+
d = DateTime.parse(value[:value])
|
328
|
+
assert_equal test_time.year, d.year
|
329
|
+
assert_equal test_time.month - 1, d.month
|
330
|
+
assert_equal 31, d.mday
|
331
|
+
assert_equal test_time.hour, d.hour
|
332
|
+
assert_equal test_time.min, d.min
|
333
|
+
assert_equal test_time.min, d.min
|
334
|
+
assert_equal test_time.sec, d.sec
|
280
335
|
end
|
281
336
|
|
282
|
-
test
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
f
|
337
|
+
test 'minutes()' do
|
338
|
+
test_time = Time.new(2019, 4, 1, 8, 30, 20, 0)
|
339
|
+
|
340
|
+
f = FunctionResolver.new('minutes', [{ type: :integer, value: 7 }])
|
341
|
+
f.expects(:current_time).returns(test_time)
|
342
|
+
f.validate
|
343
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
344
|
+
value = f.call
|
345
|
+
|
346
|
+
assert_equal :datetime, value[:type]
|
347
|
+
d = DateTime.parse(value[:value])
|
348
|
+
assert_equal test_time.year, d.year
|
349
|
+
assert_equal test_time.month, d.month
|
350
|
+
assert_equal test_time.mday, d.mday
|
351
|
+
assert_equal test_time.hour, d.hour
|
352
|
+
assert_equal test_time.min + 7, d.min
|
353
|
+
assert_equal test_time.sec, d.sec
|
354
|
+
|
355
|
+
f = FunctionResolver.new('minutes', [{ type: :integer, value: -37 }])
|
356
|
+
f.expects(:current_time).returns(test_time)
|
357
|
+
f.validate
|
358
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
359
|
+
value = f.call
|
360
|
+
|
361
|
+
assert_equal :datetime, value[:type]
|
362
|
+
d = DateTime.parse(value[:value])
|
363
|
+
assert_equal test_time.year, d.year
|
364
|
+
assert_equal test_time.month, d.month
|
365
|
+
assert_equal test_time.mday, d.mday
|
366
|
+
assert_equal test_time.hour - 1, d.hour
|
367
|
+
assert_equal 53, d.min
|
368
|
+
|
369
|
+
f = FunctionResolver.new('minutes', [{ type: :integer,
|
370
|
+
value: -1440 }])
|
371
|
+
f.expects(:current_time).returns(test_time)
|
372
|
+
f.validate
|
373
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
374
|
+
value = f.call
|
375
|
+
|
376
|
+
assert_equal :datetime, value[:type]
|
377
|
+
d = DateTime.parse(value[:value])
|
378
|
+
assert_equal test_time.year, d.year
|
379
|
+
assert_equal test_time.month - 1, d.month
|
380
|
+
assert_equal 31, d.mday
|
381
|
+
assert_equal test_time.hour, d.hour
|
382
|
+
assert_equal test_time.min, d.min
|
383
|
+
end
|
384
|
+
|
385
|
+
test 'hours(), same day' do
|
386
|
+
test_time = Time.new(2019, 4, 1, 8, 30, 20, 0)
|
387
|
+
tests = [1, -1, 5, -5, 12]
|
388
|
+
|
389
|
+
tests.each do |offset|
|
390
|
+
f = FunctionResolver.new('hours', [{ type: :integer,
|
391
|
+
value: offset }])
|
392
|
+
f.expects(:current_time).returns(test_time)
|
393
|
+
f.validate
|
394
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
395
|
+
value = f.call
|
396
|
+
|
397
|
+
assert_equal :datetime, value[:type]
|
398
|
+
d = DateTime.parse(value[:value])
|
399
|
+
assert_equal test_time.year, d.year
|
400
|
+
assert_equal test_time.month, d.month
|
401
|
+
assert_equal test_time.mday, d.mday
|
402
|
+
assert_equal test_time.hour + offset, d.hour
|
403
|
+
assert_equal test_time.min, d.min
|
404
|
+
assert_equal test_time.sec, d.sec
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
test 'hours(), wrap day' do
|
409
|
+
test_time = Time.new(2019, 4, 1, 8, 30, 20, 0)
|
410
|
+
|
411
|
+
# Jump forward a few days, and a few hours.
|
412
|
+
f = FunctionResolver.new('hours', [{ type: :integer, value: 52 }])
|
413
|
+
f.expects(:current_time).returns(test_time)
|
414
|
+
f.validate
|
415
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
416
|
+
value = f.call
|
417
|
+
|
418
|
+
assert_equal :datetime, value[:type]
|
419
|
+
d = DateTime.parse(value[:value])
|
420
|
+
assert_equal test_time.year, d.year
|
421
|
+
assert_equal test_time.month, d.month
|
422
|
+
assert_equal test_time.mday + 2, d.mday
|
423
|
+
assert_equal test_time.hour + 4, d.hour
|
424
|
+
assert_equal test_time.min, d.min
|
425
|
+
|
426
|
+
# Drop back to the previous day, which'll also hit the previous month
|
427
|
+
f = FunctionResolver.new('hours', [{ type: :integer, value: -24 }])
|
428
|
+
f.expects(:current_time).returns(test_time)
|
429
|
+
f.validate
|
430
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
431
|
+
value = f.call
|
432
|
+
|
433
|
+
assert_equal :datetime, value[:type]
|
434
|
+
d = DateTime.parse(value[:value])
|
435
|
+
assert_equal test_time.year, d.year
|
436
|
+
assert_equal test_time.month - 1, d.month
|
437
|
+
assert_equal 31, d.mday
|
438
|
+
assert_equal test_time.hour, d.hour
|
439
|
+
assert_equal test_time.min, d.min
|
440
|
+
|
441
|
+
# Drop back one full year's worth of hours.
|
442
|
+
f = FunctionResolver.new('hours', [{ type: :integer, value: -8760 }])
|
443
|
+
f.expects(:current_time).returns(test_time)
|
444
|
+
f.validate
|
445
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
446
|
+
value = f.call
|
447
|
+
|
448
|
+
assert_equal :datetime, value[:type]
|
449
|
+
d = DateTime.parse(value[:value])
|
450
|
+
assert_equal test_time.year - 1, d.year
|
451
|
+
assert_equal test_time.month, d.month
|
452
|
+
assert_equal test_time.mday, d.mday
|
453
|
+
assert_equal test_time.hour, d.hour
|
454
|
+
assert_equal test_time.min, d.min
|
455
|
+
end
|
456
|
+
|
457
|
+
test 'days()' do
|
458
|
+
test_date = Date.new(2012, 10, 20) # Sat, 20 Oct 2012 00:00:00 GMT
|
459
|
+
f = FunctionResolver.new('days', [{ type: :integer, value: 7 }])
|
460
|
+
f.expects(:current_date).returns(test_date)
|
288
461
|
f.validate
|
289
462
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
290
463
|
value = f.call
|
291
464
|
assert_equal :date, value[:type]
|
292
|
-
|
293
|
-
|
465
|
+
assert_equal '2012-10-27', value[:value]
|
466
|
+
end
|
467
|
+
|
468
|
+
test 'weekdays()' do
|
469
|
+
friday = Date.new(2012, 10, 19)
|
470
|
+
saturday = Date.new(2012, 10, 20)
|
471
|
+
sunday = Date.new(2012, 10, 21)
|
472
|
+
monday = Date.new(2012, 10, 22)
|
473
|
+
{
|
474
|
+
friday => [
|
475
|
+
[-5, '2012-10-12'],
|
476
|
+
[-4, '2012-10-15'],
|
477
|
+
[-1, '2012-10-18'],
|
478
|
+
[0, '2012-10-19'],
|
479
|
+
[1, '2012-10-22'],
|
480
|
+
[2, '2012-10-23'],
|
481
|
+
[5, '2012-10-26'],
|
482
|
+
[6, '2012-10-29'],
|
483
|
+
[7, '2012-10-30'],
|
484
|
+
[31, '2012-12-03']
|
485
|
+
],
|
486
|
+
saturday => [
|
487
|
+
[-6, '2012-10-12'],
|
488
|
+
[-5, '2012-10-15'],
|
489
|
+
[-1, '2012-10-19'],
|
490
|
+
[0, '2012-10-22'],
|
491
|
+
[1, '2012-10-22'],
|
492
|
+
[2, '2012-10-23'],
|
493
|
+
[3, '2012-10-24'],
|
494
|
+
[4, '2012-10-25'],
|
495
|
+
[5, '2012-10-26'],
|
496
|
+
[6, '2012-10-29'],
|
497
|
+
[7, '2012-10-30'],
|
498
|
+
[31, '2012-12-03']
|
499
|
+
],
|
500
|
+
sunday => [
|
501
|
+
[-6, '2012-10-12'],
|
502
|
+
[-5, '2012-10-15'],
|
503
|
+
[-1, '2012-10-19'],
|
504
|
+
[0, '2012-10-22'],
|
505
|
+
[1, '2012-10-22'],
|
506
|
+
[2, '2012-10-23'],
|
507
|
+
[5, '2012-10-26'],
|
508
|
+
[6, '2012-10-29'],
|
509
|
+
[7, '2012-10-30'],
|
510
|
+
[31, '2012-12-03']
|
511
|
+
],
|
512
|
+
monday => [
|
513
|
+
[-6, '2012-10-12'],
|
514
|
+
[-5, '2012-10-15'],
|
515
|
+
[-1, '2012-10-19'],
|
516
|
+
[0, '2012-10-22'],
|
517
|
+
[1, '2012-10-23'],
|
518
|
+
[2, '2012-10-24'],
|
519
|
+
[5, '2012-10-29'],
|
520
|
+
[6, '2012-10-30'],
|
521
|
+
[7, '2012-10-31'],
|
522
|
+
[31, '2012-12-04']
|
523
|
+
]
|
524
|
+
}.each do |test_date, weekday_tests|
|
525
|
+
weekday_tests.each do |days, expected_value|
|
526
|
+
f = FunctionResolver.new('weekdays', [{ type: :integer, value: days }])
|
527
|
+
f.expects(:current_date).returns(test_date)
|
528
|
+
f.validate
|
529
|
+
assert !f.errors?, "#{test_date}: #{days} = #{expected_value}"
|
530
|
+
value = f.call
|
531
|
+
assert_equal :date, value[:type]
|
532
|
+
assert_equal expected_value, value[:value], "#{test_date}: #{days} = #{expected_value}"
|
533
|
+
end
|
534
|
+
end
|
294
535
|
end
|
295
536
|
|
296
|
-
test
|
537
|
+
test 'months()' do
|
297
538
|
dt = DateTime.new(2014, 1, 6, 0, 0, 0, 0)
|
298
539
|
DateTime.expects(:now).once.returns(dt)
|
299
540
|
|
300
|
-
f = FunctionResolver.new('months', [{:
|
541
|
+
f = FunctionResolver.new('months', [{ type: :integer, value: 3 }])
|
301
542
|
f.validate
|
302
543
|
assert !f.errors?, "Errors resolving months(): #{f.errors.inspect}"
|
303
544
|
value = f.call
|
304
545
|
assert_equal :date, value[:type]
|
305
546
|
|
306
|
-
assert_equal
|
547
|
+
assert_equal '2014-04-06', value[:value]
|
307
548
|
end
|
308
|
-
|
309
|
-
test
|
549
|
+
|
550
|
+
test 'years()' do
|
310
551
|
dt = DateTime.new(2014, 1, 6, 0, 0, 0, 0)
|
311
552
|
DateTime.expects(:now).once.returns(dt)
|
312
|
-
f = FunctionResolver.new('years', [{:
|
553
|
+
f = FunctionResolver.new('years', [{ type: :integer, value: -4 }])
|
313
554
|
f.validate
|
314
555
|
assert !f.errors?, "Errors resolving years(): #{f.errors.inspect}"
|
315
556
|
value = f.call
|
316
557
|
assert_equal :date, value[:type]
|
317
|
-
assert_equal '2010-01-06', value[:value],
|
558
|
+
assert_equal '2010-01-06', value[:value], 'negative values should go back in time'
|
318
559
|
end
|
319
560
|
|
320
|
-
test
|
321
|
-
[
|
322
|
-
f = FunctionResolver.new(function, [{:
|
561
|
+
test 'year(), month(), and day()' do
|
562
|
+
%w[year month day].each do |function|
|
563
|
+
f = FunctionResolver.new(function, [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
323
564
|
f.validate
|
324
565
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
325
566
|
value = f.call
|
326
567
|
assert_equal :function, value[:type]
|
327
568
|
assert_equal function, value[:value]
|
328
|
-
assert_equal
|
569
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
329
570
|
end
|
330
571
|
end
|
331
572
|
|
332
|
-
test
|
333
|
-
[
|
334
|
-
f = FunctionResolver.new(function, [{:
|
573
|
+
test 'hour(), minute(), and second()' do
|
574
|
+
%w[year month day].each do |function|
|
575
|
+
f = FunctionResolver.new(function, [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
335
576
|
f.validate
|
336
577
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
337
578
|
value = f.call
|
338
579
|
assert_equal :function, value[:type]
|
339
580
|
assert_equal function, value[:value]
|
340
|
-
assert_equal
|
581
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
341
582
|
end
|
342
583
|
end
|
343
584
|
|
344
|
-
test
|
345
|
-
|
585
|
+
test 'day of week and day of year parse' do
|
586
|
+
%w[dayofyear dayofweek].each do |function|
|
587
|
+
f = FunctionResolver.new(function, [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
588
|
+
f.validate
|
589
|
+
assert !f.errors?, "Errors #{f.errors.inspect}"
|
590
|
+
value = f.call
|
591
|
+
assert_equal :function, value[:type]
|
592
|
+
assert_equal function, value[:value]
|
593
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
test 'fractionalseconds()' do
|
598
|
+
f = FunctionResolver.new('fractionalseconds', [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
346
599
|
f.validate
|
347
600
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
348
601
|
value = f.call
|
349
602
|
assert_equal :function, value[:type]
|
350
603
|
assert_equal 'fractionalseconds', value[:value]
|
351
|
-
assert_equal
|
604
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
352
605
|
end
|
353
606
|
|
354
607
|
# Polygon searches
|
355
|
-
|
356
|
-
test
|
357
|
-
f = FunctionResolver.new('radius',
|
608
|
+
|
609
|
+
test 'radius()' do
|
610
|
+
f = FunctionResolver.new('radius',
|
611
|
+
[{ type: :character, value: '35.12 -68.33' }, { type: :decimal, value: 1.0 }])
|
358
612
|
f.validate
|
359
613
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
360
614
|
value = f.call
|
@@ -364,38 +618,41 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
364
618
|
assert_equal 1.0, value[:value].radius, "#{value[:value].inspect} "
|
365
619
|
end
|
366
620
|
|
367
|
-
test
|
368
|
-
f = FunctionResolver.new('radius', [{:
|
369
|
-
|
621
|
+
test 'radius() can be overloaded with a ListingKey' do
|
622
|
+
f = FunctionResolver.new('radius', [{ type: :character, value: '20100000000000000000000000' },
|
623
|
+
{ type: :decimal, value: 1.0 }])
|
370
624
|
f.validate
|
371
625
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
372
626
|
value = f.call
|
373
627
|
assert_equal :shape, value[:type]
|
374
628
|
assert_equal Sparkql::Geo::RecordRadius, value[:value].class
|
375
|
-
assert_equal
|
629
|
+
assert_equal '20100000000000000000000000', value[:value].record_id, "#{value[:value].inspect} "
|
376
630
|
assert_equal 1.0, value[:value].radius, "#{value[:value].inspect} "
|
377
631
|
end
|
378
632
|
|
379
|
-
test
|
380
|
-
f = FunctionResolver.new('radius', [{:
|
381
|
-
|
633
|
+
test 'radius() fails if not given coords or a flex ID' do
|
634
|
+
f = FunctionResolver.new('radius', [{ type: :character, value: '35.12,-68.33' },
|
635
|
+
{ type: :decimal, value: 1.0 }])
|
382
636
|
f.validate
|
383
637
|
f.call
|
384
638
|
assert f.errors?
|
385
639
|
end
|
386
640
|
|
387
|
-
test
|
388
|
-
f = FunctionResolver.new('polygon',
|
641
|
+
test 'polygon()' do
|
642
|
+
f = FunctionResolver.new('polygon',
|
643
|
+
[{ type: :character,
|
644
|
+
value: '35.12 -68.33,35.12 -68.32, 35.13 -68.32,35.13 -68.33' }])
|
389
645
|
f.validate
|
390
646
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
391
647
|
value = f.call
|
392
648
|
assert_equal :shape, value[:type]
|
393
649
|
assert_equal GeoRuby::SimpleFeatures::Polygon, value[:value].class
|
394
|
-
assert_equal [[-68.33, 35.12], [-68.32, 35.12], [-68.32, 35.13], [-68.33, 35.13], [-68.33, 35.12]],
|
650
|
+
assert_equal [[-68.33, 35.12], [-68.32, 35.12], [-68.32, 35.13], [-68.33, 35.13], [-68.33, 35.12]],
|
651
|
+
value[:value].to_coordinates.first, "#{value[:value].inspect} "
|
395
652
|
end
|
396
|
-
|
397
|
-
test
|
398
|
-
f = FunctionResolver.new('linestring', [{:
|
653
|
+
|
654
|
+
test 'linestring()' do
|
655
|
+
f = FunctionResolver.new('linestring', [{ type: :character, value: '35.12 -68.33,35.12 -68.32' }])
|
399
656
|
f.validate
|
400
657
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
401
658
|
value = f.call
|
@@ -404,70 +661,72 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
404
661
|
assert_equal [[-68.33, 35.12], [-68.32, 35.12]], value[:value].to_coordinates, "#{value[:value].inspect} "
|
405
662
|
end
|
406
663
|
|
407
|
-
test
|
408
|
-
f = FunctionResolver.new('rectangle', [{:
|
664
|
+
test 'rectangle()' do
|
665
|
+
f = FunctionResolver.new('rectangle', [{ type: :character, value: '35.12 -68.33, 35.13 -68.32' }])
|
409
666
|
f.validate
|
410
667
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
411
668
|
value = f.call
|
412
669
|
assert_equal :shape, value[:type]
|
413
670
|
assert_equal GeoRuby::SimpleFeatures::Polygon, value[:value].class
|
414
|
-
assert_equal [[-68.33,35.12], [-68.32,35.12], [-68.32,35.13], [-68.33,35.13], [-68.33,35.12]],
|
671
|
+
assert_equal [[-68.33, 35.12], [-68.32, 35.12], [-68.32, 35.13], [-68.33, 35.13], [-68.33, 35.12]],
|
672
|
+
value[:value].to_coordinates.first, "#{value[:value].inspect} "
|
415
673
|
end
|
416
674
|
|
417
|
-
test
|
418
|
-
f = FunctionResolver.new('range', [{:
|
419
|
-
|
675
|
+
test 'range()' do
|
676
|
+
f = FunctionResolver.new('range', [{ type: :character, value: 'M01' },
|
677
|
+
{ type: :character, value: 'M05' }])
|
420
678
|
f.validate
|
421
679
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
422
680
|
value = f.call
|
423
681
|
assert_equal :character, value[:type]
|
424
|
-
assert_equal [
|
682
|
+
assert_equal %w[M01 M05], value[:value]
|
425
683
|
end
|
426
|
-
|
427
|
-
test
|
428
|
-
f = FunctionResolver.new('now', [{:
|
684
|
+
|
685
|
+
test 'invalid params' do
|
686
|
+
f = FunctionResolver.new('now', [{ type: :character, value: 'bad value' }])
|
429
687
|
f.validate
|
430
688
|
assert f.errors?, "'now' function does not support parameters"
|
431
|
-
|
689
|
+
|
432
690
|
f = FunctionResolver.new('days', [])
|
433
691
|
f.validate
|
434
692
|
assert f.errors?, "'days' function requires one parameter"
|
435
|
-
|
436
|
-
f = FunctionResolver.new('days', [{:
|
693
|
+
|
694
|
+
f = FunctionResolver.new('days', [{ type: :character, value: 'bad value' }])
|
437
695
|
f.validate
|
438
696
|
assert f.errors?, "'days' function needs integer parameter"
|
439
697
|
end
|
440
698
|
|
441
|
-
test
|
442
|
-
f = FunctionResolver.new('radius', [{:
|
443
|
-
|
444
|
-
|
699
|
+
test 'assert nil returned when function called with errors' do
|
700
|
+
f = FunctionResolver.new('radius', [{ type: :character,
|
701
|
+
value: '35.12 -68.33, 35.13 -68.34' }, { type: :decimal,
|
702
|
+
value: 1.0 }])
|
445
703
|
assert_nil f.call
|
446
704
|
end
|
447
|
-
|
448
|
-
test
|
449
|
-
f = FunctionResolver.new('radius', [{:
|
450
|
-
|
451
|
-
|
705
|
+
|
706
|
+
test 'return_type' do
|
707
|
+
f = FunctionResolver.new('radius', [{ type: :character,
|
708
|
+
value: '35.12 -68.33, 35.13 -68.34' }, { type: :decimal,
|
709
|
+
value: 1.0 }])
|
452
710
|
assert_equal :shape, f.return_type
|
453
711
|
end
|
454
712
|
|
455
713
|
test 'return_type for cast()' do
|
456
|
-
f = FunctionResolver.new('cast', [{:
|
457
|
-
|
458
|
-
|
714
|
+
f = FunctionResolver.new('cast', [{ type: :character,
|
715
|
+
value: '1' }, { type: :character,
|
716
|
+
value: 'decimal' }])
|
459
717
|
|
460
718
|
assert_equal :decimal, f.return_type
|
461
719
|
|
462
|
-
f = FunctionResolver.new('cast', [{:
|
463
|
-
|
464
|
-
|
720
|
+
f = FunctionResolver.new('cast', [{ type: :character,
|
721
|
+
value: '1' }, { type: :character,
|
722
|
+
value: 'integer' }])
|
465
723
|
|
466
724
|
assert_equal :integer, f.return_type
|
467
725
|
end
|
468
726
|
|
469
|
-
test
|
470
|
-
f = FunctionResolver.new('cast',
|
727
|
+
test 'cast() decimal to integer' do
|
728
|
+
f = FunctionResolver.new('cast',
|
729
|
+
[{ type: :decimal, value: '1.2' }, { type: :character, value: 'integer' }])
|
471
730
|
f.validate
|
472
731
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
473
732
|
value = f.call
|
@@ -476,8 +735,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
476
735
|
assert_equal '1', value[:value]
|
477
736
|
end
|
478
737
|
|
479
|
-
test
|
480
|
-
f = FunctionResolver.new('cast', [{:
|
738
|
+
test 'cast() integer to decimal' do
|
739
|
+
f = FunctionResolver.new('cast', [{ type: :decimal, value: '1' }, { type: :character, value: 'decimal' }])
|
481
740
|
f.validate
|
482
741
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
483
742
|
value = f.call
|
@@ -486,8 +745,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
486
745
|
assert_equal '1.0', value[:value]
|
487
746
|
end
|
488
747
|
|
489
|
-
test
|
490
|
-
f = FunctionResolver.new('cast', [{:
|
748
|
+
test 'cast() nil to integer' do
|
749
|
+
f = FunctionResolver.new('cast', [{ type: :null, value: 'NULL' }, { type: :character, value: 'integer' }])
|
491
750
|
f.validate
|
492
751
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
493
752
|
value = f.call
|
@@ -496,8 +755,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
496
755
|
assert_equal '0', value[:value]
|
497
756
|
end
|
498
757
|
|
499
|
-
test
|
500
|
-
f = FunctionResolver.new('cast', [{:
|
758
|
+
test 'cast() nil to decimal' do
|
759
|
+
f = FunctionResolver.new('cast', [{ type: :null, value: 'NULL' }, { type: :character, value: 'decimal' }])
|
501
760
|
f.validate
|
502
761
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
503
762
|
value = f.call
|
@@ -506,8 +765,9 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
506
765
|
assert_equal '0.0', value[:value]
|
507
766
|
end
|
508
767
|
|
509
|
-
test
|
510
|
-
f = FunctionResolver.new('cast',
|
768
|
+
test 'cast() nil to character' do
|
769
|
+
f = FunctionResolver.new('cast',
|
770
|
+
[{ type: :null, value: 'NULL' }, { type: :character, value: 'character' }])
|
511
771
|
f.validate
|
512
772
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
513
773
|
value = f.call
|
@@ -516,39 +776,43 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
516
776
|
assert_equal "''", value[:value]
|
517
777
|
end
|
518
778
|
|
519
|
-
test
|
520
|
-
f = FunctionResolver.new('cast',
|
779
|
+
test 'cast() character to decimal' do
|
780
|
+
f = FunctionResolver.new('cast',
|
781
|
+
[{ type: :character, value: '1.1' }, { type: :character, value: 'decimal' }])
|
521
782
|
f.validate
|
522
783
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
523
784
|
value = f.call
|
524
785
|
|
525
786
|
assert_equal :decimal, value[:type]
|
526
|
-
assert_equal
|
787
|
+
assert_equal '1.1', value[:value]
|
527
788
|
end
|
528
789
|
|
529
|
-
test
|
530
|
-
f = FunctionResolver.new('cast',
|
790
|
+
test 'cast() character to integer' do
|
791
|
+
f = FunctionResolver.new('cast',
|
792
|
+
[{ type: :character, value: '1' }, { type: :character, value: 'integer' }])
|
531
793
|
f.validate
|
532
794
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
533
795
|
value = f.call
|
534
796
|
|
535
797
|
assert_equal :integer, value[:type]
|
536
|
-
assert_equal
|
798
|
+
assert_equal '1', value[:value]
|
537
799
|
end
|
538
800
|
|
539
|
-
test
|
540
|
-
f = FunctionResolver.new('cast',
|
801
|
+
test 'cast() Field' do
|
802
|
+
f = FunctionResolver.new('cast',
|
803
|
+
[{ type: :field, value: 'Bedrooms' }, { type: :character, value: 'character' }])
|
541
804
|
f.validate
|
542
805
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
543
806
|
value = f.call
|
544
807
|
|
545
808
|
assert_equal :function, value[:type]
|
546
809
|
assert_equal 'cast', value[:value]
|
547
|
-
assert_equal
|
810
|
+
assert_equal(%w[Bedrooms character], value[:args].map{ |v| v[:value] })
|
548
811
|
end
|
549
812
|
|
550
|
-
test
|
551
|
-
f = FunctionResolver.new('cast',
|
813
|
+
test 'invalid cast returns null' do
|
814
|
+
f = FunctionResolver.new('cast',
|
815
|
+
[{ type: :character, value: '1.1.1' }, { type: :character, value: 'integer' }])
|
552
816
|
f.validate
|
553
817
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
554
818
|
value = f.call
|
@@ -557,14 +821,14 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
557
821
|
assert_equal 'NULL', value[:value]
|
558
822
|
end
|
559
823
|
|
560
|
-
test
|
824
|
+
test 'invalid function' do
|
561
825
|
f = FunctionResolver.new('then', [])
|
562
826
|
f.validate
|
563
827
|
assert f.errors?, "'then' is not a function"
|
564
828
|
end
|
565
829
|
|
566
|
-
test
|
567
|
-
f = FunctionResolver.new('time', [{:
|
830
|
+
test 'time(datetime)' do
|
831
|
+
f = FunctionResolver.new('time', [{ type: :datetime, value: EXAMPLE_DATE }])
|
568
832
|
f.validate
|
569
833
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
570
834
|
value = f.call
|
@@ -572,8 +836,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
572
836
|
assert_equal '10:22:15.422804000', value[:value]
|
573
837
|
end
|
574
838
|
|
575
|
-
test
|
576
|
-
f = FunctionResolver.new('date', [{:
|
839
|
+
test 'date(datetime)' do
|
840
|
+
f = FunctionResolver.new('date', [{ type: :datetime, value: EXAMPLE_DATE }])
|
577
841
|
f.validate
|
578
842
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
579
843
|
value = f.call
|
@@ -581,39 +845,39 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
581
845
|
assert_equal '2013-07-26', value[:value]
|
582
846
|
end
|
583
847
|
|
584
|
-
###
|
585
|
-
# Delayed functions. These functions don't get run immediately and require
|
586
|
-
# resolution by the backing system
|
587
|
-
###
|
588
|
-
|
589
|
-
test
|
590
|
-
f = FunctionResolver.new('time', [{:
|
848
|
+
###
|
849
|
+
# Delayed functions. These functions don't get run immediately and require
|
850
|
+
# resolution by the backing system
|
851
|
+
###
|
852
|
+
|
853
|
+
test 'time(field)' do
|
854
|
+
f = FunctionResolver.new('time', [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
591
855
|
f.validate
|
592
856
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
593
857
|
value = f.call
|
594
858
|
assert_equal :function, value[:type]
|
595
859
|
assert_equal 'time', value[:value]
|
596
|
-
assert_equal
|
860
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
597
861
|
end
|
598
|
-
|
599
|
-
test
|
600
|
-
f = FunctionResolver.new('date', [{:
|
862
|
+
|
863
|
+
test 'date(field)' do
|
864
|
+
f = FunctionResolver.new('date', [{ type: :field, value: 'OriginalEntryTimestamp' }])
|
601
865
|
f.validate
|
602
866
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
603
867
|
value = f.call
|
604
868
|
assert_equal :function, value[:type]
|
605
869
|
assert_equal 'date', value[:value]
|
606
|
-
assert_equal
|
870
|
+
assert_equal 'OriginalEntryTimestamp', value[:args].first[:value]
|
607
871
|
end
|
608
872
|
|
609
|
-
test
|
610
|
-
[{'startswith' =>
|
611
|
-
{'endswith' =>
|
612
|
-
{'contains' =>
|
873
|
+
test 'startswith(), endswith() and contains()' do
|
874
|
+
[{ 'startswith' => '^far' },
|
875
|
+
{ 'endswith' => 'far$' },
|
876
|
+
{ 'contains' => 'far' }].each do |test_case|
|
613
877
|
function = test_case.keys.first
|
614
878
|
expected_value = test_case[function]
|
615
879
|
|
616
|
-
f = FunctionResolver.new(function, [{:
|
880
|
+
f = FunctionResolver.new(function, [{ type: :character, value: 'far' }])
|
617
881
|
f.validate
|
618
882
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
619
883
|
value = f.call
|
@@ -626,8 +890,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
626
890
|
|
627
891
|
test 'wkt()' do
|
628
892
|
f = FunctionResolver.new('wkt',
|
629
|
-
[{:
|
630
|
-
|
893
|
+
[{ type: :character,
|
894
|
+
value: 'SRID=12345;POLYGON((-127.89734578345 45.234534534,-127.89734578345 45.234534534,-127.89734578345 45.234534534,-127.89734578345 45.234534534))' }])
|
631
895
|
f.validate
|
632
896
|
assert !f.errors?, "Errors #{f.errors.inspect}"
|
633
897
|
value = f.call
|
@@ -636,8 +900,8 @@ class FunctionResolverTest < Test::Unit::TestCase
|
|
636
900
|
|
637
901
|
test 'wkt() invalid params' do
|
638
902
|
f = FunctionResolver.new('wkt',
|
639
|
-
[{:
|
640
|
-
|
903
|
+
[{ type: :character,
|
904
|
+
value: 'POLYGON((45.234534534))' }])
|
641
905
|
f.validate
|
642
906
|
f.call
|
643
907
|
assert f.errors?
|