sparkql 1.2.5 → 1.3.0

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.
@@ -1,6 +1,5 @@
1
1
  # Required interface for existing parser implementations
2
2
  module Sparkql::ParserCompatibility
3
-
4
3
  MAXIMUM_MULTIPLE_VALUES = 200
5
4
  MAXIMUM_EXPRESSIONS = 75
6
5
  MAXIMUM_LEVEL_DEPTH = 2
@@ -9,72 +8,71 @@ module Sparkql::ParserCompatibility
9
8
  # Ordered by precedence.
10
9
  FILTER_VALUES = [
11
10
  {
12
- :type => :datetime,
13
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
11
+ type: :datetime,
12
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
14
13
  },
15
14
  {
16
- :type => :date,
17
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
15
+ type: :date,
16
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
18
17
  },
19
18
  {
20
- :type => :time,
21
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
19
+ type: :time,
20
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
22
21
  },
23
22
  {
24
- :type => :character,
25
- :multiple => /^'([^'\\]*(\\.[^'\\]*)*)'/,
26
- :operators => Sparkql::Token::EQUALITY_OPERATORS
23
+ type: :character,
24
+ multiple: /^'([^'\\]*(\\.[^'\\]*)*)'/,
25
+ operators: Sparkql::Token::EQUALITY_OPERATORS
27
26
  },
28
27
  {
29
- :type => :integer,
30
- :multiple => /^\-?[0-9]+/,
31
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
28
+ type: :integer,
29
+ multiple: /^-?[0-9]+/,
30
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
32
31
  },
33
32
  {
34
- :type => :decimal,
35
- :multiple => /^\-?[0-9]+\.[0-9]+/,
36
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
33
+ type: :decimal,
34
+ multiple: /^-?[0-9]+\.[0-9]+/,
35
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
37
36
  },
38
37
  {
39
- :type => :shape,
40
- :operators => Sparkql::Token::EQUALITY_OPERATORS
38
+ type: :shape,
39
+ operators: Sparkql::Token::EQUALITY_OPERATORS
41
40
  },
42
41
  {
43
- :type => :boolean,
44
- :operators => Sparkql::Token::EQUALITY_OPERATORS
42
+ type: :boolean,
43
+ operators: Sparkql::Token::EQUALITY_OPERATORS
45
44
  },
46
45
  {
47
- :type => :null,
48
- :operators => Sparkql::Token::EQUALITY_OPERATORS
46
+ type: :null,
47
+ operators: Sparkql::Token::EQUALITY_OPERATORS
49
48
  },
50
49
  {
51
- :type => :function,
52
- :operators => Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
53
- },
54
- ]
50
+ type: :function,
51
+ operators: Sparkql::Token::OPERATORS + [Sparkql::Token::RANGE_OPERATOR]
52
+ }
53
+ ].freeze
55
54
 
56
- OPERATORS_SUPPORTING_MULTIPLES = ["Eq","Ne"]
55
+ OPERATORS_SUPPORTING_MULTIPLES = %w[Eq Ne].freeze
57
56
 
58
57
  # To be implemented by child class.
59
58
  # Shall return a valid query string for the respective database,
60
59
  # or nil if the source could not be processed. It may be possible to return a valid
61
60
  # SQL string AND have errors ( as checked by errors? ), but this will be left
62
61
  # to the discretion of the child class.
63
- def compile( source, mapper )
64
- raise NotImplementedError
62
+ def compile(source, mapper)
63
+ raise NotImplementedError
65
64
  end
66
65
 
67
66
  # Returns a list of expressions tokenized in the following format:
68
67
  # [{ :field => IdentifierName, :operator => "Eq", :value => "'Fargo'", :type => :character, :conjunction => "And" }]
69
68
  # This step will set errors if source is not syntactically correct.
70
- def tokenize( source )
69
+ def tokenize(source)
71
70
  raise ArgumentError, "You must supply a source string to tokenize!" unless source.is_a?(String)
72
71
 
73
72
  # Reset the parser error stack
74
73
  @errors = []
75
74
 
76
- expressions = self.parse(source)
77
- expressions
75
+ self.parse(source)
78
76
  end
79
77
 
80
78
  # Returns an array of errors. This is an array of ParserError objects
@@ -85,7 +83,7 @@ module Sparkql::ParserCompatibility
85
83
 
86
84
  # Delegator for methods to process the error list.
87
85
  def process_errors
88
- Sparkql::ErrorsProcessor.new(@errors)
86
+ Sparkql::ErrorsProcessor.new(errors)
89
87
  end
90
88
 
91
89
  # delegate :errors?, :fatal_errors?, :dropped_errors?, :recovered_errors?, :to => :process_errors
@@ -93,32 +91,36 @@ module Sparkql::ParserCompatibility
93
91
  def errors?
94
92
  process_errors.errors?
95
93
  end
94
+
96
95
  def fatal_errors?
97
96
  process_errors.fatal_errors?
98
97
  end
98
+
99
99
  def dropped_errors?
100
100
  process_errors.dropped_errors?
101
101
  end
102
+
102
103
  def recovered_errors?
103
104
  process_errors.recovered_errors?
104
105
  end
105
106
 
106
- def escape_value_list( expression )
107
+ def escape_value_list(expression)
107
108
  final_list = []
108
- expression[:value].each do | value |
109
+ expression[:value].each do |value|
109
110
  new_exp = {
110
- :value => value,
111
- :type => expression[:type]
111
+ value: value,
112
+ type: expression[:type]
112
113
  }
113
114
  final_list << escape_value(new_exp)
114
115
  end
115
116
  expression[:value] = final_list
116
117
  end
117
118
 
118
- def escape_value( expression )
119
+ def escape_value(expression)
119
120
  if expression[:value].is_a? Array
120
- return escape_value_list( expression )
121
+ return escape_value_list(expression)
121
122
  end
123
+
122
124
  case expression[:type]
123
125
  when :character
124
126
  return character_escape(expression[:value])
@@ -142,15 +144,15 @@ module Sparkql::ParserCompatibility
142
144
 
143
145
  # processes escape characters for a given string. May be overridden by
144
146
  # child classes.
145
- def character_escape( string )
146
- string.gsub(/^\'/,'').gsub(/\'$/,'').gsub(/\\'/, "'")
147
+ def character_escape(string)
148
+ string.gsub(/^'/, '').gsub(/'$/, '').gsub(/\\'/, "'")
147
149
  end
148
150
 
149
- def integer_escape( string )
151
+ def integer_escape(string)
150
152
  string.to_i
151
153
  end
152
154
 
153
- def decimal_escape( string )
155
+ def decimal_escape(string)
154
156
  string.to_f
155
157
  end
156
158
 
@@ -158,20 +160,29 @@ module Sparkql::ParserCompatibility
158
160
  Date.parse(string)
159
161
  end
160
162
 
163
+ # datetime may have timezone info. Given that, we should honor it it when
164
+ # present or setting an appropriate default when not. Either way, we should
165
+ # convert to local appropriate for the parser when we're done.
166
+ #
167
+ # DateTime in ruby is deprecated as of ruby 3.0. We've switched to the Time
168
+ # class to be future compatible. The :time type in sparkql != a ruby Time
169
+ # instance
161
170
  def datetime_escape(string)
162
- DateTime.parse(string)
171
+ Time.parse(string)
163
172
  end
164
173
 
174
+ # Per the lexer, times don't have any timezone info. When parsing, pick the
175
+ # proper offset to set things at.
165
176
  def time_escape(string)
166
- DateTime.parse(string)
177
+ Time.parse("#{string}#{offset}")
167
178
  end
168
179
 
169
180
  def boolean_escape(string)
170
- "true" == string
181
+ string == "true"
171
182
  end
172
183
 
173
184
  # Returns the rule hash for a given type
174
- def rules_for_type( type )
185
+ def rules_for_type(type)
175
186
  FILTER_VALUES.each do |rule|
176
187
  return rule if rule[:type] == type
177
188
  end
@@ -179,8 +190,8 @@ module Sparkql::ParserCompatibility
179
190
  end
180
191
 
181
192
  # true if a given type supports multiple values
182
- def supports_multiple?( type )
183
- rules_for_type(type).include?( :multiple )
193
+ def supports_multiple?(type)
194
+ rules_for_type(type).include?(:multiple)
184
195
  end
185
196
 
186
197
  # Maximum supported nesting level for the parser filters
@@ -202,21 +213,20 @@ module Sparkql::ParserCompatibility
202
213
 
203
214
  private
204
215
 
205
- def tokenizer_error( error_hash )
206
-
216
+ def tokenizer_error(error_hash)
207
217
  if @lexer
208
218
  error_hash[:token_index] = @lexer.token_index
209
219
  end
210
220
 
211
- self.errors << Sparkql::ParserError.new( error_hash )
221
+ self.errors << Sparkql::ParserError.new(error_hash)
212
222
  end
213
- alias :compile_error :tokenizer_error
223
+ alias compile_error tokenizer_error
214
224
 
215
225
  # Checks the type of an expression with what is expected.
216
226
  def check_type!(expression, expected, supports_nulls = true)
217
227
  if (expected == expression[:type] && !expression.key?(:field_manipulations)) ||
218
- (expression.key?(:field_manipulations) && check_function_type?(expression, expected)) ||
219
- (supports_nulls && expression[:type] == :null)
228
+ (expression.key?(:field_manipulations) && check_function_type?(expression, expected)) ||
229
+ (supports_nulls && expression[:type] == :null)
220
230
  return true
221
231
  # If the field will be passed into a function,
222
232
  # check the type of the return value of the function
@@ -235,7 +245,7 @@ module Sparkql::ParserCompatibility
235
245
  expression[:type] = :date
236
246
  expression[:cast] = :datetime
237
247
  if multiple_values?(expression[:value])
238
- expression[:value].map!{ |val| coerce_datetime val }
248
+ expression[:value].map! { |val| coerce_datetime val }
239
249
  else
240
250
  expression[:value] = coerce_datetime expression[:value]
241
251
  end
@@ -245,14 +255,15 @@ module Sparkql::ParserCompatibility
245
255
  expression[:cast] = :integer
246
256
  return true
247
257
  end
258
+
248
259
  type_error(expression, expected)
249
260
  false
250
261
  end
251
262
 
252
- def type_error( expression, expected )
253
- compile_error(:token => expression[:field], :expression => expression,
254
- :message => "expected #{expected} but found #{expression[:type]}",
255
- :status => :fatal )
263
+ def type_error(expression, expected)
264
+ compile_error(token: expression[:field], expression: expression,
265
+ message: "expected #{expected} but found #{expression[:type]}",
266
+ status: :fatal)
256
267
  end
257
268
 
258
269
  # If a function is being applied to a field, we check that the return type of
@@ -263,15 +274,17 @@ module Sparkql::ParserCompatibility
263
274
  end
264
275
 
265
276
  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?
277
+ case field_manipulations[:type]
278
+ when :function
279
+ return false unless supported_function?(field_manipulations[:function_name])
280
+
281
+ function = lookup_function(field_manipulations[:function_name])
269
282
  field_manipulations[:args].each_with_index do |arg, index|
270
- if arg[:type] == :field
271
- return false unless function[:args][index].include?(:field)
283
+ if arg[:type] == :field && !function[:args][index].include?(:field)
284
+ return false
272
285
  end
273
286
  end
274
- elsif field_manipulations[:type] == :arithmetic
287
+ when :arithmetic
275
288
  lhs = field_manipulations[:lhs]
276
289
  return false unless validate_side(lhs, expected)
277
290
 
@@ -285,31 +298,34 @@ module Sparkql::ParserCompatibility
285
298
  if side[:type] == :arithmetic
286
299
  return validate_manipulation_types(side, expected)
287
300
  elsif side[:type] == :field
288
- return false unless [:decimal, :integer].include?(expected)
301
+ return false unless %i[decimal integer].include?(expected)
289
302
  elsif side[:type] == :function
290
- return false unless [:decimal, :integer].include?(side[:return_type])
291
- elsif ![:decimal, :integer].include?(side[:type])
303
+ return false unless %i[decimal integer].include?(side[:return_type])
304
+ elsif !%i[decimal integer].include?(side[:type])
292
305
  return false
293
306
  end
307
+
294
308
  true
295
309
  end
296
310
 
297
311
  # Builds the correct operator based on the type and the value.
298
312
  # default should be the operator provided in the actual filter string
299
- def get_operator(expression, default )
313
+ def get_operator(expression, default)
300
314
  f = rules_for_type(expression[:type])
301
315
  if f[:operators].include?(default)
302
316
  if f[:multiple] && range?(expression[:value]) && default == 'Bt'
303
317
  return "Bt"
304
318
  elsif f[:multiple] && multiple_values?(expression[:value])
305
319
  return nil unless operator_supports_multiples?(default)
320
+
306
321
  return default == "Ne" ? "Not In" : "In"
307
322
  elsif default == "Ne"
308
323
  return "Not Eq"
309
324
  end
310
- return default
325
+
326
+ default
311
327
  else
312
- return nil
328
+ nil
313
329
  end
314
330
  end
315
331
 
@@ -325,12 +341,17 @@ module Sparkql::ParserCompatibility
325
341
  OPERATORS_SUPPORTING_MULTIPLES.include?(operator)
326
342
  end
327
343
 
328
- def coerce_datetime datetime
329
- if datestr = datetime.match(/^(\d{4}-\d{2}-\d{2})/)
330
- datestr[0]
344
+ # Datetime coercion to date factors in the current time zone when selecting a
345
+ # date.
346
+ def coerce_datetime(datetime_string)
347
+ case datetime_string
348
+ when /^(\d{4}-\d{2}-\d{2})$/
349
+ datetime_string
350
+ when /^(\d{4}-\d{2}-\d{2})/
351
+ datetime = datetime_escape(datetime_string)
352
+ datetime.strftime(Sparkql::FunctionResolver::STRFTIME_DATE_FORMAT)
331
353
  else
332
- datetime
354
+ datetime_string
333
355
  end
334
356
  end
335
-
336
357
  end