sneaql 0.0.8-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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