sparkql 1.2.8 → 1.3.1
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 +4 -4
- data/.github/workflows/ci.yml +51 -0
- data/.rubocop.yml +111 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/Rakefile +2 -3
- data/VERSION +1 -1
- data/lib/sparkql/errors.rb +68 -71
- data/lib/sparkql/evaluator.rb +121 -117
- data/lib/sparkql/expression_resolver.rb +2 -3
- data/lib/sparkql/expression_state.rb +7 -9
- data/lib/sparkql/function_resolver.rb +15 -10
- data/lib/sparkql/geo/record_circle.rb +1 -1
- data/lib/sparkql/lexer.rb +54 -56
- data/lib/sparkql/parser.rb +497 -520
- data/lib/sparkql/parser_compatibility.rb +97 -76
- data/lib/sparkql/parser_tools.rb +159 -139
- data/lib/sparkql/token.rb +25 -25
- data/lib/sparkql/version.rb +1 -1
- data/script/bootstrap +3 -2
- data/sparkql.gemspec +2 -1
- data/test/unit/errors_test.rb +4 -5
- data/test/unit/evaluator_test.rb +26 -20
- data/test/unit/expression_state_test.rb +14 -15
- data/test/unit/function_resolver_test.rb +125 -161
- data/test/unit/geo/record_circle_test.rb +2 -2
- data/test/unit/lexer_test.rb +15 -16
- data/test/unit/parser_compatability_test.rb +177 -151
- data/test/unit/parser_test.rb +90 -90
- metadata +28 -22
@@ -1,23 +1,21 @@
|
|
1
|
-
# Custom fields need to add a table join to the customfieldsearch table when AND'd together,
|
1
|
+
# Custom fields need to add a table join to the customfieldsearch table when AND'd together,
|
2
2
|
# but not when they are OR'd or nested. This class maintains the state for all custom field expressions
|
3
3
|
# lets the parser know when to do either.
|
4
4
|
class Sparkql::ExpressionState
|
5
|
-
|
6
5
|
def initialize
|
7
|
-
@expressions = {0=>[]}
|
6
|
+
@expressions = { 0 => [] }
|
8
7
|
@last_conjunction = "And" # always start with a join
|
9
8
|
@block_group = 0
|
10
9
|
end
|
11
|
-
|
10
|
+
|
12
11
|
def push(expression)
|
13
12
|
@block_group = expression[:block_group]
|
14
|
-
@expressions[@block_group] ||= []
|
13
|
+
@expressions[@block_group] ||= []
|
15
14
|
@expressions[@block_group] << expression
|
16
15
|
@last_conjunction = expression[:conjunction]
|
17
16
|
end
|
18
|
-
|
17
|
+
|
19
18
|
def needs_join?
|
20
|
-
|
19
|
+
@expressions[@block_group].size == 1 || %w[Not And].include?(@last_conjunction)
|
21
20
|
end
|
22
|
-
|
23
|
-
end
|
21
|
+
end
|
@@ -225,14 +225,19 @@ module Sparkql
|
|
225
225
|
}
|
226
226
|
}.freeze
|
227
227
|
|
228
|
+
def self.lookup(function_name)
|
229
|
+
SUPPORTED_FUNCTIONS[function_name.to_sym]
|
230
|
+
end
|
231
|
+
|
228
232
|
# Construct a resolver instance for a function
|
229
233
|
# name: function name (String)
|
230
234
|
# args: array of literal hashes of the format {:type=><literal_type>, :value=><escaped_literal_value>}.
|
231
235
|
# Empty arry for functions that have no arguments.
|
232
|
-
def initialize(name, args)
|
236
|
+
def initialize(name, args, options = {})
|
233
237
|
@name = name
|
234
238
|
@args = args
|
235
239
|
@errors = []
|
240
|
+
@current_timestamp = options[:current_timestamp]
|
236
241
|
end
|
237
242
|
|
238
243
|
# Validate the function instance prior to calling it. All validation failures will show up in the
|
@@ -541,18 +546,16 @@ module Sparkql
|
|
541
546
|
today += weeks * 7
|
542
547
|
|
543
548
|
# Now iterate on the remaining weekdays
|
544
|
-
remaining.times do |
|
549
|
+
remaining.times do |_i|
|
545
550
|
today += direction
|
546
|
-
while today.saturday? || today.sunday?
|
547
|
-
today += direction
|
548
|
-
end
|
551
|
+
today += direction while today.saturday? || today.sunday?
|
549
552
|
end
|
550
553
|
|
551
554
|
# If we end on the weekend, bump accordingly
|
552
555
|
while today.saturday? || today.sunday?
|
553
556
|
# If we start and end on the weekend, wind things back to the next
|
554
557
|
# appropriate weekday.
|
555
|
-
if weekend_start && remaining
|
558
|
+
if weekend_start && remaining.zero?
|
556
559
|
today -= direction
|
557
560
|
else
|
558
561
|
today += direction
|
@@ -637,7 +640,8 @@ module Sparkql
|
|
637
640
|
end
|
638
641
|
|
639
642
|
def months(num_months)
|
640
|
-
|
643
|
+
# DateTime usage. There's a better means to do this with Time via rails
|
644
|
+
d = (current_timestamp.to_datetime >> num_months).to_time
|
641
645
|
{
|
642
646
|
type: :date,
|
643
647
|
value: d.strftime(STRFTIME_DATE_FORMAT)
|
@@ -645,7 +649,8 @@ module Sparkql
|
|
645
649
|
end
|
646
650
|
|
647
651
|
def years(num_years)
|
648
|
-
|
652
|
+
# DateTime usage. There's a better means to do this with Time via rails
|
653
|
+
d = (current_timestamp.to_datetime >> (num_years * 12)).to_time
|
649
654
|
{
|
650
655
|
type: :date,
|
651
656
|
value: d.strftime(STRFTIME_DATE_FORMAT)
|
@@ -832,11 +837,11 @@ module Sparkql
|
|
832
837
|
end
|
833
838
|
|
834
839
|
def current_time
|
835
|
-
current_timestamp
|
840
|
+
current_timestamp
|
836
841
|
end
|
837
842
|
|
838
843
|
def current_timestamp
|
839
|
-
@current_timestamp ||=
|
844
|
+
@current_timestamp ||= Time.now
|
840
845
|
end
|
841
846
|
|
842
847
|
private
|
data/lib/sparkql/lexer.rb
CHANGED
@@ -2,9 +2,9 @@ require 'strscan'
|
|
2
2
|
|
3
3
|
class Sparkql::Lexer < StringScanner
|
4
4
|
include Sparkql::Token
|
5
|
-
|
5
|
+
|
6
6
|
attr_accessor :level, :block_group_identifier
|
7
|
-
|
7
|
+
|
8
8
|
attr_reader :last_field, :current_token_value, :token_index
|
9
9
|
|
10
10
|
def initialize(str)
|
@@ -14,51 +14,50 @@ class Sparkql::Lexer < StringScanner
|
|
14
14
|
@block_group_identifier = 0
|
15
15
|
@expression_count = 0
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Lookup the next matching token
|
19
19
|
def shift
|
20
20
|
@token_index = self.pos
|
21
21
|
|
22
|
-
token =
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
22
|
+
token = if (@current_token_value = scan(SPACE))
|
23
|
+
[:SPACE, @current_token_value]
|
24
|
+
elsif (@current_token_value = scan(LPAREN))
|
25
|
+
levelup
|
26
|
+
[:LPAREN, @current_token_value]
|
27
|
+
elsif (@current_token_value = scan(RPAREN))
|
28
|
+
# leveldown: do this after parsing group
|
29
|
+
[:RPAREN, @current_token_value]
|
30
|
+
elsif (@current_token_value = scan(/,/))
|
31
|
+
[:COMMA, @current_token_value]
|
32
|
+
elsif (@current_token_value = scan(NULL))
|
33
|
+
literal :NULL, "NULL"
|
34
|
+
elsif (@current_token_value = scan(STANDARD_FIELD))
|
35
|
+
check_standard_fields(@current_token_value)
|
36
|
+
elsif (@current_token_value = scan(DATETIME))
|
37
|
+
literal :DATETIME, @current_token_value
|
38
|
+
elsif (@current_token_value = scan(DATE))
|
39
|
+
literal :DATE, @current_token_value
|
40
|
+
elsif (@current_token_value = scan(TIME))
|
41
|
+
literal :TIME, @current_token_value
|
42
|
+
elsif (@current_token_value = scan(DECIMAL))
|
43
|
+
literal :DECIMAL, @current_token_value
|
44
|
+
elsif (@current_token_value = scan(INTEGER))
|
45
|
+
literal :INTEGER, @current_token_value
|
46
|
+
elsif (@current_token_value = scan(/-/))
|
47
|
+
[:UMINUS, @current_token_value]
|
48
|
+
elsif (@current_token_value = scan(CHARACTER))
|
49
|
+
literal :CHARACTER, @current_token_value
|
50
|
+
elsif (@current_token_value = scan(BOOLEAN))
|
51
|
+
literal :BOOLEAN, @current_token_value
|
52
|
+
elsif (@current_token_value = scan(KEYWORD))
|
53
|
+
check_keywords(@current_token_value)
|
54
|
+
elsif (@current_token_value = scan(CUSTOM_FIELD))
|
55
|
+
[:CUSTOM_FIELD, @current_token_value]
|
56
|
+
elsif eos?
|
57
|
+
[false, false] # end of file, \Z don't work with StringScanner
|
58
|
+
else
|
59
|
+
[:UNKNOWN, "ERROR: '#{self.string}'"]
|
60
|
+
end
|
62
61
|
|
63
62
|
token.freeze
|
64
63
|
end
|
@@ -66,13 +65,13 @@ class Sparkql::Lexer < StringScanner
|
|
66
65
|
def check_reserved_words(value)
|
67
66
|
u_value = value.capitalize
|
68
67
|
if OPERATORS.include?(u_value)
|
69
|
-
[:OPERATOR,u_value]
|
68
|
+
[:OPERATOR, u_value]
|
70
69
|
elsif RANGE_OPERATOR == u_value
|
71
|
-
[:RANGE_OPERATOR,u_value]
|
70
|
+
[:RANGE_OPERATOR, u_value]
|
72
71
|
elsif CONJUNCTIONS.include?(u_value)
|
73
|
-
[:CONJUNCTION,u_value]
|
72
|
+
[:CONJUNCTION, u_value]
|
74
73
|
elsif UNARY_CONJUNCTIONS.include?(u_value)
|
75
|
-
[:UNARY_CONJUNCTION,u_value]
|
74
|
+
[:UNARY_CONJUNCTION, u_value]
|
76
75
|
elsif ADD == u_value
|
77
76
|
[:ADD, u_value]
|
78
77
|
elsif SUB == u_value
|
@@ -87,12 +86,12 @@ class Sparkql::Lexer < StringScanner
|
|
87
86
|
[:UNKNOWN, "ERROR: '#{self.string}'"]
|
88
87
|
end
|
89
88
|
end
|
90
|
-
|
89
|
+
|
91
90
|
def check_standard_fields(value)
|
92
91
|
result = check_reserved_words(value)
|
93
92
|
if result.first == :UNKNOWN
|
94
93
|
@last_field = value
|
95
|
-
result = [:STANDARD_FIELD,value]
|
94
|
+
result = [:STANDARD_FIELD, value]
|
96
95
|
end
|
97
96
|
result
|
98
97
|
end
|
@@ -100,26 +99,25 @@ class Sparkql::Lexer < StringScanner
|
|
100
99
|
def check_keywords(value)
|
101
100
|
result = check_reserved_words(value)
|
102
101
|
if result.first == :UNKNOWN
|
103
|
-
result = [:KEYWORD,value]
|
102
|
+
result = [:KEYWORD, value]
|
104
103
|
end
|
105
104
|
result
|
106
105
|
end
|
107
|
-
|
106
|
+
|
108
107
|
def levelup
|
109
108
|
@level += 1
|
110
109
|
@block_group_identifier += 1
|
111
110
|
end
|
112
|
-
|
111
|
+
|
113
112
|
def leveldown
|
114
113
|
@level -= 1
|
115
114
|
end
|
116
|
-
|
115
|
+
|
117
116
|
def literal(symbol, value)
|
118
117
|
node = {
|
119
|
-
:
|
120
|
-
:
|
118
|
+
type: symbol.to_s.downcase.to_sym,
|
119
|
+
value: value
|
121
120
|
}
|
122
121
|
[symbol, node]
|
123
122
|
end
|
124
|
-
|
125
123
|
end
|