sneaql 0.0.8-java

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.
@@ -0,0 +1,78 @@
1
+ module Sneaql
2
+ # Exceptions for SneaQL
3
+ module Exceptions
4
+ # Base error class for Sneaql
5
+ class BaseError < StandardError; end
6
+
7
+ # Exception used to to gracefully exit test
8
+ class SQLTestExitCondition < BaseError
9
+ def initialize(msg = "Exit condition met by test, this is not an error")
10
+ super
11
+ end
12
+ end
13
+
14
+ # Exception used to gracefully exit test step
15
+ class SQLTestStepExitCondition < BaseError
16
+ def initialize(msg = "Exit condition for this step has been met, this is not an error")
17
+ super
18
+ end
19
+ end
20
+
21
+ # Transform is locked by another process. This is a
22
+ # possibility when using the LockManager
23
+ class TransformIsLocked < BaseError
24
+ def initialize(msg = "This transform is locked by another process")
25
+ super
26
+ end
27
+ end
28
+
29
+ # Recordset check failure indicator
30
+ class RecordsetContainsInconsistentOrInvalidTypes < BaseError
31
+ def initialize(msg = "Recordsets must have identical keys in every record")
32
+ super
33
+ end
34
+ end
35
+
36
+ # Recordset check failure indicator
37
+ class RecordsetIsNotAnArray < BaseError
38
+ def initialize(msg = "Recordset must be an array of hashes with identical keys")
39
+ super
40
+ end
41
+ end
42
+
43
+ # General error evaluating expression.
44
+ class ExpressionEvaluationError < BaseError
45
+ def initialize(msg = "Error evaluating expression")
46
+ super
47
+ end
48
+ end
49
+
50
+ # Comparison operator must be explicitly supported.
51
+ class InvalidComparisonOperator < BaseError
52
+ def initialize(msg = "Invalid or no comparison operator provided")
53
+ super
54
+ end
55
+ end
56
+
57
+ # Error raised during parser validation process
58
+ class StatementParsingError < BaseError
59
+ def initialize(msg = "General error parsing Sneaql tag and statement")
60
+ super
61
+ end
62
+ end
63
+
64
+ # Sneaql step files must not be empty
65
+ class NoStatementsFoundInFile < BaseError
66
+ def initialize(msg = "No statements found in step file.")
67
+ super
68
+ end
69
+ end
70
+
71
+ # Sneaql command tags must be formed correctly
72
+ class MalformedSneaqlCommandsInStep < BaseError
73
+ def initialize(msg = "Sneaql command tag is malformed.")
74
+ super
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,238 @@
1
+ require 'logger'
2
+
3
+ module Sneaql
4
+ module Core
5
+ # Handles variables, expression evaluation, and comparisons.
6
+ # A single ExpressionHandler is created per transform. This
7
+ # object will get passed around to the various commands as well
8
+ # as other manager objects attached to the transform class.
9
+ class ExpressionHandler
10
+ # @param [Hash] environment_variables pass in a set of ENV
11
+ # @param [Logger] logger object otherwise will default to new Logger
12
+ def initialize(environment_variables, logger = nil)
13
+ @logger = logger ? logger : Logger.new(STDOUT)
14
+ @environment_variables = environment_variables
15
+ @session_variables = {}
16
+ end
17
+
18
+ # @param [String] var_name identifier for variable
19
+ # @param [String, Fixnum, Float] var_value value to store, expressions here will not be evaluated
20
+ def set_session_variable(var_name, var_value)
21
+ @logger.info("setting session var #{var_name} to #{var_value}")
22
+ raise "can't set environment variable #{var_name}" unless valid_session_variable_name?(var_name)
23
+ @session_variables[var_name] = var_value
24
+ end
25
+
26
+ # validates that this would make a suitable variable name
27
+ # @param [String] var_name
28
+ # @return [Boolean]
29
+ def valid_session_variable_name?(var_name)
30
+ r = (var_name.to_s.match(/^\w+$/) && !var_name.to_s.match(/env\_\w*/) && !var_name.to_s.match(/^\d+/)) ? true : false
31
+ @logger.debug "validating #{var_name} as valid variable identifier indicates #{r}"
32
+ r
33
+ end
34
+
35
+ # @param [String] var_name identifier for variable
36
+ # @return [String, Fixnum, Float]
37
+ def get_session_variable(var_name)
38
+ @session_variables[var_name]
39
+ end
40
+
41
+ # convenience method, outputs all session variables to the logger
42
+ def output_all_session_variables
43
+ @logger.debug("current session variables: #{@session_variables}")
44
+ end
45
+
46
+ # @param [String] var_name identifier for environment variable as defined in ENV
47
+ # @return [String]
48
+ def get_environment_variable(var_name)
49
+ @environment_variables[var_name]
50
+ end
51
+
52
+ # @param [String] expression either a numeric constant, string constant in '',
53
+ # or reference to session or environment variable
54
+ # @return [String, Fixnum, Float]
55
+ def evaluate_expression(expression)
56
+ return expression unless expression.class == String
57
+
58
+ # reference to an environment variable
59
+ # :env_var_name or :ENV_var_name
60
+ # env variable references are case insensitive in this case
61
+ if expression =~ /\:env\_\w+/i
62
+ return @environment_variables[expression.gsub(/\:env\_/i, '').strip]
63
+
64
+ # reference to a variable
65
+ # ANSI dynamic SQL :var_name
66
+ # variable names are case sensitive
67
+ elsif expression =~ /\:\w+/
68
+ return @session_variables[expression.gsub(/\:/, '').strip]
69
+
70
+ # deprecated
71
+ elsif expression =~ /\{.*\}/
72
+ @logger.warn '{var_name} deprecated. use dynamic SQL syntax :var_name'
73
+ return @session_variables[expression.gsub(/\{|\}/, '').strip]
74
+
75
+ # string literal enclosed in single quotes
76
+ # only works for a single word... no whitespace allowed at this time
77
+ elsif expression =~ /\'.*\'/
78
+ return expression.delete("'").strip
79
+
80
+ # else assume it is a numeric literal
81
+ # need some better thinking here
82
+ else
83
+ return expression.strip
84
+ end
85
+ rescue => e
86
+ @logger.error("error evaluating expression: #{e.message}")
87
+ e.backtrace.each { |b| logger.error(b.to_s) }
88
+ raise Sneaql::Exceptions::ExpressionEvaluationError
89
+ end
90
+
91
+ # evaluates all expressions in a given SQL statement.
92
+ # replaces...
93
+ # environment variables in the form :env_HOSTNAME
94
+ # session variables in the form :variable_name
95
+ # session variables in the deprecated form {variable_name}
96
+ # @param [String] statement SQL statement to have all expressions evaluated
97
+ # @return [String] SQL statement with all variable references resolved
98
+ def evaluate_all_expressions(statement)
99
+ evaluate_session_variables(statement)
100
+ evaluate_environment_variables(statement)
101
+ evaluate_session_variables_braces(statement)
102
+ return statement
103
+ rescue => e
104
+ @logger.error "evaluation error #{e.message}"
105
+ e.backtrace.each { |b| logger.error b.to_s }
106
+ raise Sneaql::Exceptions::ExpressionEvaluationError
107
+ end
108
+
109
+ # evaluates all environment variables in a given SQL statement.
110
+ # replaces...
111
+ # environment variables in the form :env_HOSTNAME
112
+ # @param [String] statement SQL statement to have all environment variables evaluated
113
+ # @return [String] SQL statement with all variable references resolved
114
+ def evaluate_session_variables(statement)
115
+ # replaces :var_name in provided statement
116
+ @session_variables.keys.each do |k|
117
+ statement.gsub!(/\:#{k}/, @session_variables[k].to_s)
118
+ end
119
+ end
120
+
121
+ # evaluates all session variables in a given SQL statement.
122
+ # replaces...
123
+ # session variables in the form :variable_name
124
+ # @param [String] statement SQL statement to have all session variables evaluated
125
+ # @return [String] SQL statement with all variable references resolved
126
+ def evaluate_environment_variables(statement)
127
+ # replace env vars in the form :env_HOSTNAME
128
+ @environment_variables.keys.each do |e|
129
+ statement.gsub!(/\:env\_#{e}/i, @environment_variables[e])
130
+ end
131
+ end
132
+
133
+ # evaluates all session variables in a given SQL statement.
134
+ # replaces...
135
+ # session variables in the deprecated form {variable_name}
136
+ # @param [String] statement SQL statement to have all deprecated form variable references evaluated
137
+ # @return [String] SQL statement with all variable references resolved
138
+ # @deprecated
139
+ def evaluate_session_variables_braces(statement)
140
+ # deprecated
141
+ @session_variables.keys.each do |k|
142
+ statement.gsub!(/\{#{k}\}/, @session_variables[k].to_s)
143
+ end
144
+ end
145
+
146
+ # validates that this would make a suitable reference at run time.
147
+ # checks to see this is single quoted string, :variable_name, {var_name) or number (1, 1.031, etc.)
148
+ # @param [String] expr value to check
149
+ def valid_expression_reference?(expr)
150
+ return expr.to_s.match(/(^\'.+\'$|^\:\w+$|^\{\w+\}$|^\d+$|^\d+\.\d*$)/) ? true : false
151
+ end
152
+
153
+ # Operators valid for expression comparison
154
+ # @return [Array<String>]
155
+ def valid_operators
156
+ ['=', '!=', '>', '<', '>=', '<=', 'like', 'notlike']
157
+ end
158
+
159
+ # provides a standardized method of comparing two expressions.
160
+ # note that this only works for variables and constants.
161
+ # current version supports float, integer, and contigious strings.
162
+ # @param [String] operator comparison operator @see valid_operators
163
+ # @param [String] exp1 expression for left operand
164
+ # @param [String] exp2 expression for right operand
165
+ def compare_expressions(operator, exp1, exp2)
166
+ unless valid_operators.include?(operator)
167
+ raise Sneaql::Exceptions::InvalidComparisonOperator
168
+ end
169
+
170
+ @logger.debug "evaluating #{exp1} #{operator} #{exp2}"
171
+
172
+ # evaluate exps and coerce data types
173
+ coerced = coerce_data_types(
174
+ evaluate_expression(exp1),
175
+ evaluate_expression(exp2)
176
+ )
177
+
178
+ compare_values(operator, coerced[0], coerced[1])
179
+ end
180
+
181
+ # coerces the data types for both expressions to match for valid comparison
182
+ # @param [String, Float, Fixnum] exp1 expression for left operand
183
+ # @param [String, Float, Fixnum] exp2 expression for right operand
184
+ # @return [Array<Float, Fixnum, String>] returns array with both input expressions coerced to the same data type
185
+ def coerce_data_types(exp1, exp2)
186
+ # coerce data types to make for a good comparison
187
+ if exp1.class == exp2.class
188
+ nil # nothing to do... continue with comparison
189
+ elsif [exp1.class, exp2.class].include? Float
190
+ # if either is a float then make sure they are both floats
191
+ exp1 = exp1.to_f
192
+ exp2 = exp2.to_f
193
+ elsif [exp1.class, exp2.class].include? Fixnum
194
+ # otherwise... if one is an integer make them both integers
195
+ exp1 = exp1.to_i
196
+ exp2 = exp2.to_i
197
+ end
198
+ [exp1, exp2]
199
+ end
200
+
201
+ # performs the actual comparison between two values
202
+ # @param [String] operator comparison operator @see valid_operators
203
+ # @param [String] exp1 expression for left operand
204
+ # @param [String] exp2 expression for right operand
205
+ # @return [Boolean]
206
+ def compare_values(operator, exp1, exp2)
207
+ # below are all the valid comparison operators
208
+ @logger.debug("comparing #{exp1} #{operator} #{exp2}")
209
+ case operator
210
+ when '=' then return exp1 == exp2
211
+ when '!=' then return exp1 != exp2
212
+ when '>=' then return exp1 >= exp2
213
+ when '<=' then return exp1 <= exp2
214
+ when '>' then return exp1 > exp2
215
+ when '<' then return exp1 < exp2
216
+ when 'like' then return like_operator(exp1, exp2)
217
+ when 'notlike' then return !like_operator(exp1, exp2)
218
+ end
219
+ end
220
+
221
+ # performs SQL style LIKE comparison between inputs
222
+ # @param [String] left_operand
223
+ # @param [String] like_right_operand this will be the like expression
224
+ # @return [Boolean]
225
+ def like_operator(left_operand, like_right_operand)
226
+ #converts to string before comparison
227
+ return left_operand.to_s.match(wildcard_to_regex(like_right_operand.to_s)) ? true : false
228
+ end
229
+
230
+ # converts a SQL LIKE wildcard expression to a Regexp
231
+ # @param [String] wildcard like expression
232
+ # @return [Regexp] returns regexp object for use in match comparison
233
+ def wildcard_to_regex(wildcard)
234
+ Regexp.new("^#{wildcard}$".gsub('%','.*').gsub('_','.'))
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,176 @@
1
+ require 'jdbc_helpers'
2
+
3
+ module Sneaql
4
+ module Core
5
+ # manages transform locking operations using a standardized
6
+ # table for storing the locks.
7
+ class TransformLockManager
8
+ # set instance variables that will be used to manage the locks
9
+ def initialize(params, logger = nil)
10
+ @logger = logger ? logger : Logger.new(STDOUT)
11
+ @transform_name = params[:transform_name]
12
+ @transform_lock_id = params[:transform_lock_id]
13
+ @transform_lock_table = params[:transform_lock_table]
14
+ @jdbc_url = params[:jdbc_url]
15
+ @db_user = params[:db_user]
16
+ @db_pass = params[:db_pass]
17
+ @database_manager = Sneaql::Core.find_class(
18
+ :database,
19
+ params[:database]
20
+ ).new
21
+ rescue => e
22
+ @logger.error e.message
23
+ e.backtrace.each { |b| @logger.error b}
24
+ end
25
+
26
+ # Creates a connection in the current JDBC context
27
+ def create_jdbc_connection
28
+ JDBCHelpers::ConnectionFactory.new(
29
+ @jdbc_url,
30
+ @db_user,
31
+ @db_pass,
32
+ @logger
33
+ ).connection
34
+ end
35
+
36
+ # Checks to see if the current transform is locked
37
+ # @return [Boolean]
38
+ def acquire_lock
39
+ # check to see if this transform is locked by
40
+ # another transform returns true if locked
41
+ jdbc_connection = create_jdbc_connection
42
+
43
+ # initialize lock value
44
+ lock_value = false
45
+
46
+ if @database_manager.supports_transactions == true
47
+ l = JDBCHelpers::Execute.new(
48
+ jdbc_connection,
49
+ @database_manager.begin_statement,
50
+ @logger
51
+ )
52
+ end
53
+
54
+ if @database_manager.supports_table_locking == true
55
+ l = JDBCHelpers::Execute.new(
56
+ jdbc_connection,
57
+ @database_manager.lock_table_statement(@transform_lock_table),
58
+ @logger
59
+ )
60
+ end
61
+
62
+ # query the number of rows which match the condition...
63
+ # should be 1 or 0... 1 indicating a lock
64
+ r = JDBCHelpers::SingleValueFromQuery.new(
65
+ jdbc_connection,
66
+ %(select
67
+ count(*)
68
+ from
69
+ #{@transform_lock_table}
70
+ where
71
+ transform_name='#{@transform_name}'
72
+ and
73
+ transform_lock_id!=#{@transform_lock_id};),
74
+ @logger
75
+ ).result
76
+
77
+ # table is unlocked
78
+ if r == 0
79
+ l = JDBCHelpers::Execute.new(
80
+ jdbc_connection,
81
+ %{insert into #{@transform_lock_table}
82
+ (
83
+ transform_lock_id,
84
+ transform_name,
85
+ transform_lock_time
86
+ )
87
+ values
88
+ (
89
+ #{@transform_lock_id},
90
+ '#{@transform_name}',
91
+ current_timestamp
92
+ );},
93
+ @logger
94
+ )
95
+
96
+ if @database_manager.supports_transactions == true
97
+ l = JDBCHelpers::Execute.new(
98
+ jdbc_connection,
99
+ @database_manager.commit_statement,
100
+ @logger
101
+ )
102
+ end
103
+
104
+ lock_value = true
105
+ else
106
+ if @database_manager.supports_transactions == true
107
+ l = JDBCHelpers::Execute.new(
108
+ jdbc_connection,
109
+ @database_manager.rollback_statement,
110
+ @logger
111
+ )
112
+ end
113
+ lock_value = false
114
+ end
115
+
116
+ if lock_value == true
117
+ @logger.info("#{@transform_name} transform lock acquired;")
118
+ else
119
+ @logger.info("#{@transform_name} is locked by another process")
120
+ end
121
+ ensure
122
+ # close this connection
123
+ jdbc_connection.close
124
+
125
+ lock_value
126
+ end
127
+
128
+ # Removes transform lock if it's present.
129
+ def remove_lock
130
+ # get a fresh jdbc connection...
131
+ # to avoid committing the main transform unnecessarily
132
+ jdbc_connection = create_jdbc_connection
133
+
134
+ if @database_manager.supports_transactions == true
135
+ l = JDBCHelpers::Execute.new(
136
+ jdbc_connection,
137
+ @database_manager.begin_statement,
138
+ @logger
139
+ )
140
+ end
141
+
142
+ if @database_manager.supports_table_locking == true
143
+ l = JDBCHelpers::Execute.new(
144
+ jdbc_connection,
145
+ @database_manager.lock_table_statement(@transform_lock_table),
146
+ @logger
147
+ )
148
+ end
149
+
150
+ # delete the lock record and commit
151
+ JDBCHelpers::Execute.new(
152
+ jdbc_connection,
153
+ %(delete from #{@transform_lock_table}
154
+ where transform_name='#{@transform_name}'
155
+ and transform_lock_id=#{@transform_lock_id};),
156
+ @logger
157
+ )
158
+
159
+ c = JDBCHelpers::Execute.new(
160
+ jdbc_connection,
161
+ @database_manager.commit_statement,
162
+ @logger
163
+ )
164
+ ensure
165
+ jdbc_connection.close
166
+
167
+ return true
168
+ end
169
+
170
+ # TBD
171
+ def lock_all_available_transforms
172
+ # undefined at this time
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,89 @@
1
+ module Sneaql
2
+ module Core
3
+ # Parses a step file into discrete statements.
4
+ # Also performs validation of all Sneaql tags.
5
+ class StepParser
6
+ # array of raw statement text
7
+ attr_reader :statements
8
+ attr_reader :expression_handler
9
+
10
+ # @param [String] file_path pathname to step file
11
+ # @param [Sneaql::ExpressionHandler] expression_handler
12
+ # @param [Sneaql::RecordsetManager] recordset_manager
13
+ # @param [Logger] logger optional logger, if omitted default logger will be used
14
+ def initialize(file_path, expression_handler, recordset_manager, logger = nil)
15
+ @logger = logger ? logger : Logger.new(STDOUT)
16
+ @expression_handler = expression_handler
17
+ @recordset_manager = recordset_manager
18
+
19
+ # parse the statements from the file and store them in an array
20
+ # this is a simple text parsing based upon the /*- delimiter
21
+ @statements = parse_statements_from_file(file_path)
22
+
23
+ raise Sneaql::Exceptions::NoStatementsFoundInFile if @statements == []
24
+ end
25
+
26
+ # Performs the actual parsing from file
27
+ # @param [String] file_path
28
+ def parse_statements_from_file(file_path)
29
+ @logger.info("parsing statements from step file #{file_path}")
30
+ stmt = []
31
+ File.read(file_path).split('/*-').each { |s| stmt << "/*-#{s.strip}" }
32
+ # delete the first element because of the way it splits
33
+ stmt.delete_at(0)
34
+ @logger.info("#{stmt.length} statements found")
35
+ stmt
36
+ rescue => e
37
+ @logger.error("file parsing error :#{e.message}")
38
+ e.backtrace.each { |b| @logger.error b.to_s }
39
+ raise Sneaql::Exceptions::StatementParsingError
40
+ end
41
+
42
+ # Extracts tag and splits into an array
43
+ # @param [String] statement_text_with_command
44
+ # @return [Array]
45
+ def tag_splitter(statement_text_with_command)
46
+ # splits out all the tag elements into an array
47
+ statement_text_with_command.split('-*/')[0].gsub('/*-', '').strip.split
48
+ end
49
+
50
+ # Returns command tag from statement at specified index. Allows for
51
+ # @param [Fixnum] indx index of statement in statements array
52
+ # @return [Hash]
53
+ def command_at_index(indx)
54
+ parsed_tag = tag_splitter(@statements[indx])
55
+ { command: parsed_tag[0], arguments: parsed_tag[1..parsed_tag.length - 1] }
56
+ end
57
+
58
+ # Validates the Sneaql command tag and arguments
59
+ # @return [Boolean]
60
+ def valid_arguments_in_all_statements?
61
+ all_statements_valid = true
62
+ @statements.each_with_index do |_s, i|
63
+ cmd = command_at_index(i)
64
+ @logger.debug("validating #{cmd}")
65
+ unless statement_args_are_valid?(cmd)
66
+ all_statements_valid = false
67
+ @logger.info "argument validation error: #{cmd}"
68
+ end
69
+ end
70
+ return all_statements_valid
71
+ end
72
+
73
+ # Checks to see if the arguments for a given command are valid.
74
+ # This is done by calling the validate_args method of the command class.
75
+ # @param [Hash] this_cmd parsed command tag
76
+ # @return [Boolean]
77
+ def statement_args_are_valid?(this_cmd)
78
+ c = Sneaql::Core.find_class(:command, this_cmd[:command]).new(
79
+ nil,
80
+ @expression_handler,
81
+ @recordset_manager,
82
+ nil,
83
+ @logger
84
+ )
85
+ c.validate_args(this_cmd[:arguments])
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,97 @@
1
+ require 'logger'
2
+
3
+ module Sneaql
4
+ module Core
5
+ #manages stored recordsets in sneaql transforms
6
+ class RecordsetManager
7
+ attr_reader :recordset
8
+
9
+ def initialize(expression_manager, logger = nil)
10
+ @logger = logger ? logger : Logger.new(STDOUT)
11
+ @expression_manager = expression_manager
12
+ @recordset = {}
13
+ end
14
+
15
+ # Stores a recordset if it is in a valid format.
16
+ # @param [String] name name for recordset
17
+ # @param [Array<Hash>] rs recordset to store
18
+ def store_recordset(name, rs)
19
+ raise Sneaql::RecordsetIsNotAnArray unless rs.class == Array
20
+ raise Sneaql::RecordsetContainsInconsistentOrInvalidTypes unless recordset_valid?(rs)
21
+ recordset[name] = rs
22
+ end
23
+
24
+ # Validates recordset. Must be an array of hashes with identical keys.
25
+ # @param [Array<Hash>] rs recordset to validate
26
+ def recordset_valid?(rs)
27
+ return false unless rs.class == Array
28
+ r1 = rs[0].keys
29
+
30
+ rs.each do |record|
31
+ return false unless record.class == Hash
32
+ return false unless r1 == record.keys
33
+ record.keys {|k| puts k; return false unless valid_element_data_types.include?(record[k].class)}
34
+ end
35
+ true
36
+ end
37
+
38
+ # Ruby data types that are valid as recordset fields.
39
+ # @return [Array<Class>]
40
+ def valid_element_data_types
41
+ [Fixnum, String, Float]
42
+ end
43
+
44
+ # Validates that the string will make a valid recordset name.
45
+ # @param [String] name
46
+ # @return [Boolean]
47
+ def valid_recordset_name?(name)
48
+ return false unless name.match(/^\w+/)
49
+ h = {}
50
+ h[name] == 1
51
+ rescue
52
+ return false
53
+ else
54
+ return true
55
+ end
56
+
57
+ # Validates that the recordset name doesn't conflict with session var names
58
+ # @param [String] name
59
+ # @return [Boolean]
60
+ def recordset_name_conflicts_with_variables?(name)
61
+ @expression_manager.session_variables.key?(name)
62
+ end
63
+
64
+ # Parses a recordset expression.
65
+ # @param [Array<Hash>] args
66
+ # @return [Array<Hash>]
67
+ def parse_recordset_expression(args)
68
+ # takes in argument array as an argument
69
+ # returns array of expressions to be checked at run time
70
+ args.delete_at(0) # get rid of the first element, recordset ref
71
+ args.each_slice(4).to_a.map{ |x| { condition: x[0].downcase, field: x[1], operator: x[2], expression: x[3]}}
72
+ end
73
+
74
+ # applies a conditional expression set against a record.
75
+ # @param [Hash] record
76
+ # @param [Array<Hash>] expressions
77
+ # @return [Boolean]
78
+ def evaluate_expression_against_record(record, expressions)
79
+ conditions = []
80
+ expressions.each do |exp|
81
+ @logger.debug("applying #{exp} to #{record}")
82
+ raw_result = @expression_manager.compare_expressions(
83
+ exp[:operator],
84
+ record[exp[:field]],
85
+ exp[:expression]
86
+ )
87
+ if exp[:condition] == 'include'
88
+ conditions << raw_result
89
+ elsif exp[:condition] == 'exclude'
90
+ conditions << !raw_result
91
+ end
92
+ end
93
+ return !conditions.include?(false)
94
+ end
95
+ end
96
+ end
97
+ end