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
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
  },
  :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.


186
187
188
189
190
# File 'lib/sparkql/function_resolver.rb', line 186

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

Instance Method Details

#callObject

Execute the function



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/sparkql/function_resolver.rb', line 241

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

  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
  method = name
  if support[name][:resolve_for_type]
    method_type =  @args.first[:type]
    method = "#{method}_#{method_type}"
  end
  v = self.send(method, *real_vals)

  unless v.nil? || v.key?(:function_name)
    v[:function_name] = @name
    v[:function_parameters] = real_vals
  end

  v
end

#errorsObject



228
229
230
# File 'lib/sparkql/function_resolver.rb', line 228

def errors
  @errors
end

#errors?Boolean

Returns:

  • (Boolean)


232
233
234
# File 'lib/sparkql/function_resolver.rb', line 232

def errors?
  @errors.size > 0
end

#return_typeObject



224
225
226
# File 'lib/sparkql/function_resolver.rb', line 224

def return_type
  support[@name.to_sym][:return_type]
end

#supportObject



236
237
238
# File 'lib/sparkql/function_resolver.rb', line 236

def support
  SUPPORTED_FUNCTIONS
end

#validateObject

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



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/sparkql/function_resolver.rb', line 194

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