Module: OpenEHR::AQL::PredicateEvaluator
- Defined in:
- lib/openehr/aql/engine/predicate_evaluator.rb
Overview
Evaluates a WHERE clause's boolean expression tree against a Binding and the query's runtime params. E5 scope: comparisons (reusing PathEvaluator for both sides), AND/OR/NOT and EXISTS. E12 adds LIKE (glob match, not SQL's %/_); E13 adds MATCHES against a literal value list (a URI/TERMINOLOGY(...) operand names an external value-set lookup this engine has no terminology service wired for, so those raise a clear ExecutionError rather than silently matching everything). Generic functionCall operands are added by a later engine milestone.
Constant Summary collapse
- COMPARATORS =
{ '=' => :==, '!=' => :!=, '<' => :<, '<=' => :<=, '>' => :>, '>=' => :>= }.freeze
- GLOB_TO_REGEXP =
LIKE's glob syntax (AQL spec, not SQL's %/_): '?' matches exactly one character, '*' matches zero or more, anything else is a literal character - and the whole value must match, not a substring.
{ '*' => '.*', '?' => '.' }.freeze
Class Method Summary collapse
- .compare(left, operator, right) ⇒ Object
-
.evaluate_comparison(comparison, binding, params) ⇒ Object
A comparison against an absent (nil) value is neither true nor false in AQL/SQL terms - it simply fails to select the row, the same as SQL's NULL-comparison-is-UNKNOWN convention.
- .evaluate_like(like_expr, binding, params) ⇒ Object
- .evaluate_matches(matches_expr, binding, params) ⇒ Object
- .like_regexp(pattern) ⇒ Object
- .lookup_param(parameter, params) ⇒ Object
-
.matches?(expression, binding, params) ⇒ Boolean
expressionis nil when there is no WHERE clause at all. - .matches_list_item_value(item, params) ⇒ Object
- .matches_value_list?(value, value_list, params) ⇒ Boolean
- .resolve_operand(node, binding, params) ⇒ Object
Class Method Details
.compare(left, operator, right) ⇒ Object
126 127 128 129 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 126 def compare(left, operator, right) method = COMPARATORS.fetch(operator) { raise ExecutionError, "unknown comparison operator #{operator.inspect}" } left.public_send(method, right) end |
.evaluate_comparison(comparison, binding, params) ⇒ Object
A comparison against an absent (nil) value is neither true nor false in AQL/SQL terms - it simply fails to select the row, the same as SQL's NULL-comparison-is-UNKNOWN convention.
105 106 107 108 109 110 111 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 105 def evaluate_comparison(comparison, binding, params) left = resolve_operand(comparison.left, binding, params) right = resolve_operand(comparison.right, binding, params) return false if left.nil? || right.nil? compare(left, comparison.operator, right) end |
.evaluate_like(like_expr, binding, params) ⇒ Object
49 50 51 52 53 54 55 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 49 def evaluate_like(like_expr, binding, params) value = PathEvaluator.evaluate(like_expr.path, binding) pattern = resolve_operand(like_expr.operand, binding, params) return false if value.nil? || pattern.nil? like_regexp(pattern).match?(value.to_s) end |
.evaluate_matches(matches_expr, binding, params) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 62 def evaluate_matches(matches_expr, binding, params) value = PathEvaluator.evaluate(matches_expr.path, binding) return false if value.nil? case matches_expr.operand when Model::MatchesValueList matches_value_list?(value, matches_expr.operand, params) when Model::UriRef, Model::TerminologyFunctionCall raise ExecutionError, "MATCHES against a #{matches_expr.operand.class} names an external terminology service lookup " \ '(a value-set expansion), which this engine has none wired in for - ' \ 'OpenEHR::TerminologyService only validates a single known code, it cannot expand a value set' else raise ExecutionError, "cannot evaluate a #{matches_expr.operand.class} MATCHES operand yet" end end |
.like_regexp(pattern) ⇒ Object
57 58 59 60 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 57 def like_regexp(pattern) body = pattern.chars.map { |char| GLOB_TO_REGEXP[char] || Regexp.escape(char) }.join Regexp.new("\\A#{body}\\z", Regexp::MULTILINE) end |
.lookup_param(parameter, params) ⇒ Object
119 120 121 122 123 124 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 119 def lookup_param(parameter, params) return params[parameter.name.to_sym] if params.key?(parameter.name.to_sym) return params[parameter.name] if params.key?(parameter.name) raise UnboundParameterError, "unbound parameter: $#{parameter.name}" end |
.matches?(expression, binding, params) ⇒ Boolean
expression is nil when there is no WHERE clause at all.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 26 def matches?(expression, binding, params) case expression when nil true when Model::Comparison evaluate_comparison(expression, binding, params) when Model::AndExpr matches?(expression.left, binding, params) && matches?(expression.right, binding, params) when Model::OrExpr matches?(expression.left, binding, params) || matches?(expression.right, binding, params) when Model::NotExpr !matches?(expression.operand, binding, params) when Model::ExistsExpr !PathEvaluator.evaluate(expression.path, binding).nil? when Model::LikeExpr evaluate_like(expression, binding, params) when Model::MatchesExpr evaluate_matches(expression, binding, params) else raise ExecutionError, "cannot evaluate a #{expression.class} WHERE expression yet" end end |
.matches_list_item_value(item, params) ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 86 def matches_list_item_value(item, params) case item when Model::Parameter lookup_param(item, params) when Model::Literal item.value when Model::TerminologyFunctionCall raise ExecutionError, "MATCHES against a #{item.class} names an external terminology service lookup " \ '(a value-set expansion), which this engine has none wired in for - ' \ 'OpenEHR::TerminologyService only validates a single known code, it cannot expand a value set' else raise ExecutionError, "cannot evaluate a #{item.class} MATCHES value-list item yet" end end |
.matches_value_list?(value, value_list, params) ⇒ Boolean
79 80 81 82 83 84 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 79 def matches_value_list?(value, value_list, params) value_list.items.any? do |item| candidate = matches_list_item_value(item, params) !candidate.nil? && value == candidate end end |
.resolve_operand(node, binding, params) ⇒ Object
113 114 115 116 117 |
# File 'lib/openehr/aql/engine/predicate_evaluator.rb', line 113 def resolve_operand(node, binding, params) return lookup_param(node, params) if node.is_a?(Model::Parameter) PathEvaluator.evaluate(node, binding) end |