Module: OpenEHR::AQL::PathEvaluator
- Defined in:
- lib/openehr/aql/engine/path_evaluator.rb
Overview
Evaluates a columnExpr/terminal Model node against a Binding.
SELECT paths ("o/data/.../value/magnitude", "c/name/value")
walk one segment at a time: as long as the current value is
Pathable AND the segment names one of its declared
path_attributes (the content-structure attributes PATHABLE
navigation was built for - see lib/openehr/rm/common/archetyped.rb),
the hop goes through PATHABLE#items_at_path (reusing its predicate
matching for node/archetype predicates). Everything else - RM
metadata that isn't part of that DSL (name, archetype_details,
composer, ...) and genuinely non-Pathable values (a DV_QUANTITY's
magnitude, a DV_TEXT's value) - is a plain attribute read via a
whitelisted public_send, never an arbitrary send driven by query
text. A predicate directly on an identifiedPath's own variable
(e.g. "c/..."), parameters and function calls are added by
later engine milestones.
Defined Under Namespace
Classes: EhrIdValue
Constant Summary collapse
- ALLOWED_TERMINAL_HOPS =
Attribute hops not reachable through the path_attribute DSL. Expand only when a real query needs another one - see the project's "no arbitrary send" rule in the class comment above.
%w[magnitude name value].freeze
Class Method Summary collapse
- .declared_path_attribute?(current, segment) ⇒ Boolean
- .evaluate(expression, binding) ⇒ Object
- .evaluate_identified_path(path, binding) ⇒ Object
- .navigate(current, segment) ⇒ Object
-
.navigate_ehr_record(record, segment) ⇒ Object
"e/ehr_id/value" and "e/ehr_status/..." always resolve (from Dataset's own record shape); any other "e/..." path falls through to whatever full RM::EHR::EHR the record carries, if any, else resolves to nil rather than erroring - AQL's path-absent-means-null semantics again, not a missing feature.
-
.navigate_pathable(current, segment) ⇒ Object
A path segment matching nothing is a legitimate "no value here" (AQL's path-absent-means-null semantics, same convention as PATHABLE#item_at_path itself), not an error - only an ambiguous match (more than one item) is.
- .navigate_terminal(current, segment) ⇒ Object
- .node_predicate_path_text(predicate) ⇒ Object
- .predicate_path_text(predicate) ⇒ Object
- .rm_path_for(segment) ⇒ Object
Class Method Details
.declared_path_attribute?(current, segment) ⇒ Boolean
81 82 83 84 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 81 def declared_path_attribute?(current, segment) current.is_a?(OpenEHR::RM::Common::Archetyped::Pathable) && current.class.path_attributes.map(&:to_s).include?(segment.attribute) end |
.evaluate(expression, binding) ⇒ Object
33 34 35 36 37 38 39 40 41 42 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 33 def evaluate(expression, binding) case expression when Model::IdentifiedPath evaluate_identified_path(expression, binding) when Model::Literal expression.value else raise ExecutionError, "cannot evaluate a #{expression.class} yet" end end |
.evaluate_identified_path(path, binding) ⇒ Object
44 45 46 47 48 49 50 51 52 53 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 44 def evaluate_identified_path(path, binding) raise ExecutionError, 'predicates on a SELECT variable are not yet supported' if path.predicate value = binding[path.variable] return value unless path.path path.path.segments.reduce(value) do |current, segment| current.nil? ? nil : navigate(current, segment) end end |
.navigate(current, segment) ⇒ Object
55 56 57 58 59 60 61 62 63 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 55 def navigate(current, segment) return navigate_ehr_record(current, segment) if current.is_a?(Dataset::EHRRecord) if declared_path_attribute?(current, segment) navigate_pathable(current, segment) else navigate_terminal(current, segment) end end |
.navigate_ehr_record(record, segment) ⇒ Object
"e/ehr_id/value" and "e/ehr_status/..." always resolve (from Dataset's own record shape); any other "e/..." path falls through to whatever full RM::EHR::EHR the record carries, if any, else resolves to nil rather than erroring - AQL's path-absent-means-null semantics again, not a missing feature.
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 70 def navigate_ehr_record(record, segment) raise ExecutionError, "path predicates on an EHR root are not supported (#{segment.attribute})" if segment.predicate case segment.attribute when 'ehr_id' then EhrIdValue.new(record.ehr_id) when 'ehr_status' then record.ehr_status else record.ehr.nil? ? nil : navigate(record.ehr, segment) end end |
.navigate_pathable(current, segment) ⇒ Object
A path segment matching nothing is a legitimate "no value here" (AQL's path-absent-means-null semantics, same convention as PATHABLE#item_at_path itself), not an error - only an ambiguous match (more than one item) is.
90 91 92 93 94 95 96 97 98 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 90 def navigate_pathable(current, segment) matches = current.items_at_path(rm_path_for(segment)) case matches.size when 0 then nil when 1 then matches.first else raise ExecutionError, "path segment #{segment.to_s.inspect} matched #{matches.size} items " \ '(fan-out SELECT paths are not yet supported)' end end |
.navigate_terminal(current, segment) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 100 def navigate_terminal(current, segment) raise ExecutionError, "path predicates on a non-Pathable value are not supported (#{segment.attribute})" if segment.predicate unless ALLOWED_TERMINAL_HOPS.include?(segment.attribute) raise ExecutionError, "unsupported path attribute #{segment.attribute.inspect} on a #{current.class}" end current.public_send(segment.attribute) end |
.node_predicate_path_text(predicate) ⇒ Object
126 127 128 129 130 131 132 133 134 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 126 def node_predicate_path_text(predicate) return predicate.code unless predicate.value unless predicate.value.is_a?(String) raise ExecutionError, "cannot evaluate a #{predicate.value.class} node predicate value yet" end "#{predicate.code}, '#{predicate.value}'" end |
.predicate_path_text(predicate) ⇒ Object
115 116 117 118 119 120 121 122 123 124 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 115 def predicate_path_text(predicate) case predicate when Model::ArchetypePredicate predicate.archetype_id when Model::NodePredicate node_predicate_path_text(predicate) else raise ExecutionError, "cannot evaluate a #{predicate.class} path predicate yet" end end |
.rm_path_for(segment) ⇒ Object
109 110 111 112 113 |
# File 'lib/openehr/aql/engine/path_evaluator.rb', line 109 def rm_path_for(segment) return "/#{segment.attribute}" unless segment.predicate "/#{segment.attribute}[#{predicate_path_text(segment.predicate)}]" end |