Class: Sparkql::FunctionResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/sparkql/function_resolver.rb

Overview

Binding class to all supported function calls in the parser. Current support requires that the resolution of function calls to happen on the fly at parsing time at which point a value and value type is required, just as literals would be returned to the expression tokenization level.

Name and argument requirements for the function should match the function declaration in SUPPORTED_FUNCTIONS which will run validation on the function syntax prior to execution.

Constant Summary collapse

SECONDS_IN_DAY =
60 * 60 * 24
STRFTIME_DATE_FORMAT =
'%Y-%m-%d'
STRFTIME_TIME_FORMAT =
'%H:%M:%S.%N'
VALID_REGEX_FLAGS =
["", "i"]
MIN_DATE_TIME =
Time.new(1970, 1, 1, 0, 0, 0, "+00:00").iso8601
MAX_DATE_TIME =
Time.new(9999, 12, 31, 23, 59, 59, "+00:00").iso8601
VALID_CAST_TYPES =
[:field, :character, :decimal, :integer]
SUPPORTED_FUNCTIONS =
{
  :polygon => {
    :args => [:character],
    :return_type => :shape
  }, 
  :rectangle => {
    :args => [:character],
    :return_type => :shape
  }, 
  :radius => {
    :args => [:character, [:decimal, :integer]],
    :return_type => :shape
  },
  :regex => {
    :args => [:character],
    :opt_args => [{
      :type => :character,
      :default => ''
    }],
    :return_type => :character
  },
  :substring => {
    :args => [[:field, :character], :integer],
    :opt_args => [{
      :type => :integer
    }],
    :resolve_for_type => true,
    :return_type => :character
  },
  :trim => {
    :args => [[:field, :character]],
    :resolve_for_type => true,
    :return_type => :character
  },
  :tolower => {
    :args => [[:field, :character]],
    :resolve_for_type => true,
    :return_type => :character
  },
  :toupper => {
    :args => [[:field, :character]],
    :resolve_for_type => true,
    :return_type => :character
  },
  :length => {
    :args => [[:field, :character]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :indexof => {
    :args => [[:field, :character], :character],
    :return_type => :integer
  },
  :concat => {
    :args => [[:field, :character], :character],
    :resolve_for_type => true,
    :return_type => :character
  },
  :cast => {
    :args => [[:field, :character, :decimal, :integer, :null], :character],
    :resolve_for_type => true,
  },
  :round => {
    :args => [[:field, :decimal]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :ceiling => {
    :args => [[:field, :decimal]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :floor => {
    :args => [[:field, :decimal]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :startswith => {
    :args => [:character],
    :return_type => :startswith
  },
  :endswith => {
    :args => [:character],
    :return_type => :endswith
  },
  :contains => {
    :args => [:character],
    :return_type => :contains
  },
  :linestring => {
    :args => [:character],
    :return_type => :shape
  },
  :days => {
    :args => [:integer],
    :return_type => :datetime
  },
  :months => {
    :args => [:integer],
    :return_type => :datetime
  },
  :years => {
    :args => [:integer],
    :return_type => :datetime
  },
  :now => {
    :args => [],
    :return_type => :datetime
  },
  :maxdatetime => {
    :args => [],
    :return_type => :datetime
  },
  :mindatetime => {
    :args => [],
    :return_type => :datetime
  },
  :date => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :date
  },
  :time => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :time
  },
  :year => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :month => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :day => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :hour => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :minute => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :second => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :integer
  },
  :fractionalseconds => { 
    :args => [[:field,:datetime,:date]],
    :resolve_for_type => true,
    :return_type => :decimal
  },
  :range => {
    :args => [:character, :character],
    :return_type => :character
  },
  :wkt => {
    :args => [:character],
    :return_type => :shape
  }
}

Instance Method Summary collapse

Constructor Details

#initialize(name, args) ⇒ FunctionResolver

Construct a resolver instance for a function name: function name (String) args: array of literal hashes of the format :value=><escaped_literal_value>.

Empty arry for functions that have no arguments.


197
198
199
200
201
# File 'lib/sparkql/function_resolver.rb', line 197

def initialize(name, args)
  @name = name
  @args = args
  @errors = []
end

Instance Method Details

#callObject

Execute the function



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/sparkql/function_resolver.rb', line 273

def call()
  real_vals = @args.map { |i| i[:value]}
  name = @name.to_sym

  field = @args.find do |i|
    i[:type] == :field || i.key?(:field)
  end

  field = field[:type] == :function ? field[:field] : field[:value] unless field.nil?

  required_args = support[name][:args]
  total_args = required_args + Array(support[name][:opt_args]).collect {|args| args[:default]}

  fill_in_optional_args = total_args.drop(real_vals.length)

  fill_in_optional_args.each do |default|
    real_vals << default
  end


  v = if field.nil?
    method = name
    if support[name][:resolve_for_type]
      method_type =  @args.first[:type]
      method = "#{method}_#{method_type}"
    end
    self.send(method, *real_vals)
  else
    {
      :type => :function,
      :return_type => return_type,
      :value => "#{name}",
    }
  end

  return if v.nil?

  if !v.key?(:function_name)
    v.merge!( function_parameters: real_vals,
            function_name: @name)
  end

  v.merge!(args: @args,
          field: field)

  v
end

#errorsObject



260
261
262
# File 'lib/sparkql/function_resolver.rb', line 260

def errors
  @errors
end

#errors?Boolean

Returns:

  • (Boolean)


264
265
266
# File 'lib/sparkql/function_resolver.rb', line 264

def errors?
  @errors.size > 0
end

#return_typeObject



250
251
252
253
254
255
256
257
258
# File 'lib/sparkql/function_resolver.rb', line 250

def return_type
  name = @name.to_sym

  if name == :cast
    @args.last[:value].to_sym
  else
    support[@name.to_sym][:return_type]
  end
end

#supportObject



268
269
270
# File 'lib/sparkql/function_resolver.rb', line 268

def support
  SUPPORTED_FUNCTIONS
end

#validateObject

Validate the function instance prior to calling it. All validation failures will show up in the errors array.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/sparkql/function_resolver.rb', line 205

def validate()
  name = @name.to_sym
  unless support.has_key?(name)
    @errors << Sparkql::ParserError.new(:token => @name, 
      :message => "Unsupported function call '#{@name}' for expression",
      :status => :fatal )
    return
  end

  required_args = support[name][:args]
  total_args = required_args + Array(support[name][:opt_args]).collect {|args| args[:type]}

  if @args.size < required_args.size || @args.size > total_args.size
    @errors << Sparkql::ParserError.new(:token => @name, 
      :message => "Function call '#{@name}' requires #{required_args.size} arguments",
      :status => :fatal )
    return
  end

  count = 0
  @args.each do |arg|
    type = arg[:type] == :function ? arg[:return_type] : arg[:type]
    unless Array(total_args[count]).include?(type)
      @errors << Sparkql::ParserError.new(:token => @name, 
        :message => "Function call '#{@name}' has an invalid argument at #{arg[:value]}",
        :status => :fatal )
    end
    count +=1
  end

  if name == :cast
    type = @args.last[:value]
    if !VALID_CAST_TYPES.include?(type.to_sym)
      @errors << Sparkql::ParserError.new(:token => @name,
                                          :message => "Function call '#{@name}' requires a castable type.",
                                          :status => :fatal )
      return
    end
  end

  if name == :substring && !@args[2].nil?
    substring_index_error?(@args[2][:value])
  end
end