Class: Sneaql::Core::ExpressionHandler
- Inherits:
-
Object
- Object
- Sneaql::Core::ExpressionHandler
- Defined in:
- lib/sneaql_lib/expressions.rb
Overview
Handles variables, expression evaluation, and comparisons. A single ExpressionHandler is created per transform. This object will get passed around to the various commands as well as other manager objects attached to the transform class.
Instance Method Summary collapse
-
#array_has_boolean_value?(arr) ⇒ Boolean
checks to see if any element of the array has a boolean value.
-
#coerce_boolean(value) ⇒ Object
evaluates string or fixnum values to coerce into boolean.
-
#coerce_data_types(exp1, exp2) ⇒ Array<Float, Fixnum, String>
coerces the data types for both expressions to match for valid comparison.
- #coerce_datetime(obj) ⇒ Object
-
#compare_expressions(operator, exp1, exp2) ⇒ Object
provides a standardized method of comparing two expressions.
-
#compare_values(operator, exp1, exp2) ⇒ Boolean
performs the actual comparison between two values.
-
#evaluate_all_expressions(statement) ⇒ String
evaluates all expressions in a given SQL statement.
-
#evaluate_environment_variables(statement) ⇒ String
evaluates all session variables in a given SQL statement.
-
#evaluate_expression(expression) ⇒ String, ...
or reference to session or environment variable.
-
#evaluate_session_variables(statement) ⇒ String
evaluates all environment variables in a given SQL statement.
- #evaluate_session_variables_braces(statement) ⇒ String deprecated Deprecated.
-
#filtered_environment_variables ⇒ Object
create a hash built from supplied environment variables.
- #get_environment_variable(var_name) ⇒ String
- #get_session_variable(var_name) ⇒ String, ...
-
#initialize(logger = nil) ⇒ ExpressionHandler
constructor
A new instance of ExpressionHandler.
-
#like_operator(left_operand, like_right_operand) ⇒ Boolean
performs SQL style LIKE comparison between inputs.
-
#output_all_session_variables ⇒ Object
convenience method, outputs all session variables to the logger.
- #set_session_variable(var_name, var_value) ⇒ Object
-
#sql_injection_filter(value) ⇒ Boolean
basic regex filtering to help combat sql injection by way of environment variables.
-
#text_to_boolean(value) ⇒ Boolean
converts text representation of boolean to boolean.
-
#valid_expression_reference?(expr) ⇒ Boolean
validates that this would make a suitable reference at run time.
-
#valid_operators ⇒ Array<String>
Operators valid for expression comparison.
-
#valid_session_variable_name?(var_name) ⇒ Boolean
validates that this would make a suitable variable name.
-
#validate_environment_variables ⇒ Object
insures that all environment variables pass SQL injection test.
-
#wildcard_to_regex(wildcard) ⇒ Regexp
converts a SQL LIKE wildcard expression to a Regexp.
Constructor Details
#initialize(logger = nil) ⇒ ExpressionHandler
Returns a new instance of ExpressionHandler.
13 14 15 16 17 18 19 |
# File 'lib/sneaql_lib/expressions.rb', line 13 def initialize(logger = nil) @logger = logger ? logger : Logger.new(STDOUT) @environment_variables = filtered_environment_variables validate_environment_variables unless ENV['SNEAQL_DISABLE_SQL_INJECTION_CHECK'] @environment_variables.freeze @session_variables = {} end |
Instance Method Details
#array_has_boolean_value?(arr) ⇒ Boolean
checks to see if any element of the array has a boolean value
254 255 256 257 258 |
# File 'lib/sneaql_lib/expressions.rb', line 254 def array_has_boolean_value?(arr) tmp = arr.map { |e| e.class } # [exp1.class, exp2.class].include?(FalseClass) || [exp1.class, exp2.class].include?(TrueClass) tmp.include?(FalseClass) || tmp.include?(TrueClass) end |
#coerce_boolean(value) ⇒ Object
evaluates string or fixnum values to coerce into boolean
263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/sneaql_lib/expressions.rb', line 263 def coerce_boolean(value) retval = nil if array_has_boolean_value?([value]) retval = value elsif value.class == Fixnum retval = true if value == 1 retval = false if value.zero? elsif value.class == String retval = text_to_boolean(value) end retval end |
#coerce_data_types(exp1, exp2) ⇒ Array<Float, Fixnum, String>
coerces the data types for both expressions to match for valid comparison
203 204 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 |
# File 'lib/sneaql_lib/expressions.rb', line 203 def coerce_data_types(exp1, exp2) # coerce data types to make for a good comparison @logger.debug("coercing types #{[exp1.class, exp2.class]}") if exp1.class == exp2.class nil # nothing to do... continue with comparison elsif array_has_boolean_value?([exp1, exp2]) # if one of the values is an actual Boolean object... unless [coerce_boolean(exp1), coerce_boolean(exp2)].include?(nil) exp1 = coerce_boolean(exp1) exp2 = coerce_boolean(exp2) end elsif exp1.class == Time tmp = coerce_datetime(exp2) if tmp exp2 = tmp else exp1, exp2 = exp1.to_s, exp2.to_s end elsif exp2.class == Time tmp = coerce_datetime(exp1) if tmp exp1 = tmp else exp1, exp2 = exp1.to_s, exp2.to_s end elsif [exp1.class, exp2.class].include?(Float) # if either is a float then make sure they are both floats exp1 = exp1.to_f exp2 = exp2.to_f elsif [exp1.class, exp2.class].include?(Fixnum) # otherwise... if one is an integer make them both integers exp1 = exp1.to_i exp2 = exp2.to_i end @logger.debug("coerced types #{[exp1.class, exp2.class]}") [exp1, exp2] end |
#coerce_datetime(obj) ⇒ Object
241 242 243 244 245 246 247 248 249 |
# File 'lib/sneaql_lib/expressions.rb', line 241 def coerce_datetime(obj) retobj = nil case when obj.class == Fixnum then retobj = Time.at(obj) when obj.class == String then retobj = Time.parse(obj) when obj.class == Time then retobj = obj end retobj end |
#compare_expressions(operator, exp1, exp2) ⇒ Object
provides a standardized method of comparing two expressions. note that this only works for variables and constants. current version supports float, integer, and contigious strings. as of 0.0.13 expressions need to be evaluated before being passed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/sneaql_lib/expressions.rb', line 183 def compare_expressions(operator, exp1, exp2) unless valid_operators.include?(operator) raise Sneaql::Exceptions::InvalidComparisonOperator end @logger.debug "evaluating #{exp1} #{operator} #{exp2}" # evaluate exps and coerce data types coerced = coerce_data_types( exp1, exp2 ) compare_values(operator, coerced[0], coerced[1]) end |
#compare_values(operator, exp1, exp2) ⇒ Boolean
performs the actual comparison between two values
294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/sneaql_lib/expressions.rb', line 294 def compare_values(operator, exp1, exp2) # below are all the valid comparison operators @logger.debug("comparing #{exp1} #{operator} #{exp2}") case operator when '=' then return exp1 == exp2 when '!=' then return exp1 != exp2 when '>=' then return exp1 >= exp2 when '<=' then return exp1 <= exp2 when '>' then return exp1 > exp2 when '<' then return exp1 < exp2 when 'like' then return like_operator(exp1, exp2) when 'notlike' then return !like_operator(exp1, exp2) end end |
#evaluate_all_expressions(statement) ⇒ String
evaluates all expressions in a given SQL statement.
replaces...
environment variables in the form :env_HOSTNAME
session variables in the form :variable_name
session variables in the deprecated form {variable_name}
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/sneaql_lib/expressions.rb', line 111 def evaluate_all_expressions(statement) evaluate_session_variables(statement) evaluate_environment_variables(statement) evaluate_session_variables_braces(statement) return statement rescue => e @logger.error "evaluation error #{e.}" e.backtrace.each { |b| logger.error b.to_s } raise Sneaql::Exceptions::ExpressionEvaluationError end |
#evaluate_environment_variables(statement) ⇒ String
evaluates all session variables in a given SQL statement.
replaces...
session variables in the form :variable_name
variables are sorted and reversed before performing substitutions this prevents :var from partially overwriting :var_foo
143 144 145 146 147 148 |
# File 'lib/sneaql_lib/expressions.rb', line 143 def evaluate_environment_variables(statement) # replace env vars in the form :env_HOSTNAME @environment_variables.keys.sort.reverse.each do |e| statement.gsub!(/\:env\_#{e}/i, @environment_variables[e]) end end |
#evaluate_expression(expression) ⇒ String, ...
or reference to session or environment variable
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/sneaql_lib/expressions.rb', line 58 def evaluate_expression(expression) return expression unless expression.class == String # first handle empty string if expression == "''" return '' # reference to an environment variable # :env_var_name or :ENV_var_name # env variable references are case insensitive in this case elsif expression =~ /^\:env\_\w+/i return @environment_variables[expression.gsub(/\:env\_/i, '').strip] # reference to a variable # ANSI dynamic SQL :var_name # variable names are case sensitive elsif expression =~ /^\:\w+/ return @session_variables[expression.gsub(/\:/, '').strip] # deprecated elsif expression =~ /^\{.*\}$/ @logger.warn('{var_name} deprecated. use dynamic SQL syntax :var_name') return @session_variables[expression.gsub(/\{|\}/, '').strip] # boolean elsif ['true', 'false'].include?(expression.downcase) @logger.debug "handling #{expression} as boolean" return true if expression.downcase == 'true' return false if expression.downcase == 'false' # string literal enclosed in single quotes elsif expression.match(/^\s*\'.*\'\s*$/) return expression.strip[1..expression.strip.length - 2] # else assume it is a numeric literal # need some better thinking here else return expression.strip end rescue => e @logger.error("error evaluating expression: #{e.}") e.backtrace.each { |b| @logger.error(b.to_s) } raise Sneaql::Exceptions::ExpressionEvaluationError end |
#evaluate_session_variables(statement) ⇒ String
evaluates all environment variables in a given SQL statement.
replaces...
environment variables in the form :env_HOSTNAME
variables are sorted and reversed before performing substitutions this prevents :var from partially overwriting :var_foo
129 130 131 132 133 134 |
# File 'lib/sneaql_lib/expressions.rb', line 129 def evaluate_session_variables(statement) # replaces :var_name in provided statement @session_variables.keys.sort.reverse.each do |k| statement.gsub!(/\:#{k}/, @session_variables[k].to_s) end end |
#evaluate_session_variables_braces(statement) ⇒ String
evaluates all session variables in a given SQL statement.
replaces...
session variables in the deprecated form {variable_name}
156 157 158 159 160 161 |
# File 'lib/sneaql_lib/expressions.rb', line 156 def evaluate_session_variables_braces(statement) # deprecated @session_variables.keys.each do |k| statement.gsub!(/\{#{k}\}/, @session_variables[k].to_s) end end |
#filtered_environment_variables ⇒ Object
create a hash built from supplied environment variables. if SNEAQL_AVAILABLE_ENV_VARS is provided (as a comma delimited list) only the listed values are included. return <Hash>
329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/sneaql_lib/expressions.rb', line 329 def filtered_environment_variables env_vars = {} if ENV['SNEAQL_AVAILABLE_ENV_VARS'] @logger.debug('filtering environment variables') available = ENV['SNEAQL_AVAILABLE_ENV_VARS'].split(',') ENV.keys.each { |k| env_vars[k] = ENV[k] if available.include?(k) } else @logger.debug('setting environment variables') ENV.keys.each { |k| env_vars[k] = ENV[k] } end env_vars end |
#get_environment_variable(var_name) ⇒ String
51 52 53 |
# File 'lib/sneaql_lib/expressions.rb', line 51 def get_environment_variable(var_name) @environment_variables[var_name] end |
#get_session_variable(var_name) ⇒ String, ...
40 41 42 |
# File 'lib/sneaql_lib/expressions.rb', line 40 def get_session_variable(var_name) @session_variables[var_name] end |
#like_operator(left_operand, like_right_operand) ⇒ Boolean
performs SQL style LIKE comparison between inputs
313 314 315 316 |
# File 'lib/sneaql_lib/expressions.rb', line 313 def like_operator(left_operand, like_right_operand) # converts to string before comparison left_operand.to_s.match(wildcard_to_regex(like_right_operand.to_s)) ? true : false end |
#output_all_session_variables ⇒ Object
convenience method, outputs all session variables to the logger
45 46 47 |
# File 'lib/sneaql_lib/expressions.rb', line 45 def output_all_session_variables @logger.debug("current session variables: #{@session_variables}") end |
#set_session_variable(var_name, var_value) ⇒ Object
23 24 25 26 27 |
# File 'lib/sneaql_lib/expressions.rb', line 23 def set_session_variable(var_name, var_value) @logger.info("setting session var #{var_name} to #{var_value}") raise "can't set variable #{var_name}" unless valid_session_variable_name?(var_name) @session_variables[var_name] = var_value end |
#sql_injection_filter(value) ⇒ Boolean
basic regex filtering to help combat sql injection by way of environment variables
346 347 348 349 |
# File 'lib/sneaql_lib/expressions.rb', line 346 def sql_injection_filter(value) return false if value.to_s.match(/(\'|\;|(drop|alter).*(table|user|view|column|database|schema|function|sequence|procedure))/i) true end |
#text_to_boolean(value) ⇒ Boolean
converts text representation of boolean to boolean
279 280 281 282 283 284 285 286 287 |
# File 'lib/sneaql_lib/expressions.rb', line 279 def text_to_boolean(value) case value.downcase.strip when 'f', 'false', '0' retval = false when 't', 'true', '1' retval = true end retval end |
#valid_expression_reference?(expr) ⇒ Boolean
validates that this would make a suitable reference at run time. checks to see this is single quoted string, :variable_name, {var_name) or number (1, 1.031, etc.)
166 167 168 |
# File 'lib/sneaql_lib/expressions.rb', line 166 def valid_expression_reference?(expr) expr.to_s.match(/(^\s*\'.+\'\s*$|^\:\w+$|^\{\w+\}$|^\d+$|^\d+\.\d*$|true|false)/i) ? true : false end |
#valid_operators ⇒ Array<String>
Operators valid for expression comparison
172 173 174 |
# File 'lib/sneaql_lib/expressions.rb', line 172 def valid_operators ['=', '!=', '>', '<', '>=', '<=', 'like', 'notlike'] end |
#valid_session_variable_name?(var_name) ⇒ Boolean
validates that this would make a suitable variable name
32 33 34 35 36 |
# File 'lib/sneaql_lib/expressions.rb', line 32 def valid_session_variable_name?(var_name) r = var_name.to_s.match(/^\w+$/) && !var_name.to_s.match(/env\_\w*/) && !var_name.to_s.match(/^\d+/) ? true : false @logger.debug("validating #{var_name} as valid variable identifier indicates #{r}") r end |
#validate_environment_variables ⇒ Object
insures that all environment variables pass SQL injection test
352 353 354 355 356 357 358 |
# File 'lib/sneaql_lib/expressions.rb', line 352 def validate_environment_variables @environment_variables.keys.each do |k| unless sql_injection_filter(@environment_variables[k]) raise 'SQL Injection Filter Error' end end end |
#wildcard_to_regex(wildcard) ⇒ Regexp
converts a SQL LIKE wildcard expression to a Regexp
321 322 323 |
# File 'lib/sneaql_lib/expressions.rb', line 321 def wildcard_to_regex(wildcard) Regexp.new("^#{wildcard}$".gsub('%', '.*').gsub('_', '.')) end |