Class: ActiveFacts::CQL::Compiler::FactType
- Inherits:
-
Concept
- Object
- Definition
- Concept
- ActiveFacts::CQL::Compiler::FactType
- Defined in:
- lib/activefacts/cql/compiler/fact_type.rb
Instance Attribute Summary collapse
-
#fact_type ⇒ Object
readonly
Returns the value of attribute fact_type.
-
#name ⇒ Object
writeonly
Sets the attribute name.
Attributes inherited from Concept
Attributes inherited from Definition
#constellation, #source, #vocabulary
Instance Method Summary collapse
- #check_compatibility_of_matched_readings ⇒ Object
- #compile ⇒ Object
- #has_more_adjectives(less, more) ⇒ Object
-
#initialize(name, readings, conditions = nil, returning = nil) ⇒ FactType
constructor
A new instance of FactType.
- #make_default_identifier_for_fact_type(prefer = true) ⇒ Object
- #verify_matching_roles ⇒ Object
Constructor Details
#initialize(name, readings, conditions = nil, returning = nil) ⇒ FactType
Returns a new instance of FactType.
9 10 11 12 13 14 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 9 def initialize name, readings, conditions = nil, returning = nil super name @readings = readings @conditions = conditions @returning = returning end |
Instance Attribute Details
#fact_type ⇒ Object (readonly)
Returns the value of attribute fact_type.
6 7 8 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 6 def fact_type @fact_type end |
#name=(value) ⇒ Object (writeonly)
Sets the attribute name
7 8 9 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 7 def name=(value) @name = value end |
Instance Method Details
#check_compatibility_of_matched_readings ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 90 def check_compatibility_of_matched_readings @existing_readings = @readings. select{ |reading| reading.match_existing_fact_type @context }. sort_by{ |reading| reading.side_effects.cost } fact_types = @existing_readings.map{ |reading| reading.fact_type }.uniq.compact return nil if fact_types.empty? || @existing_readings[0].side_effects.cost != 0 if (fact_types.size > 1) # There must be only one fact type with exact matches: if @existing_readings[0].side_effects.cost != 0 or @existing_readings.detect{|r| r.fact_type != fact_types[0] && r.side_effects.cost == 0 } raise "Clauses match different existing fact types '#{fact_types.map{|ft| ft.preferred_reading.}*"', '"}'" end # Try to make false-matched readings match the chosen one instead @existing_readings.reject!{|r| r.fact_type != fact_types[0] } end fact_types[0] end |
#compile ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 16 def compile raise "Queries not yet handled: #{@source}" unless @conditions.empty? and !@returning # # Process: # * Identify all role players # * Match up the players in all @readings # - Be aware of multiple roles with the same player, and bind tight/loose using subscripts/role_names/adjectives # - Reject the fact type unless all @readings match # * Find any existing fact type that matches any reading, or make a new one # * Add each reading that doesn't already exist in the fact type # * Create any ring constraint(s) # * Create embedded presence constraints # * If fact type has no identifier, arrange to create the implicit one (before first use?) # * Objectify the fact type if @name # @context = CompilationContext.new(@vocabulary) @readings.each{ |reading| reading.identify_players_with_role_name(@context) } @readings.each{ |reading| reading.identify_other_players(@context) } @readings.each{ |reading| reading.bind_roles @context } # Create the Compiler::Bindings verify_matching_roles # All readings of a fact type must have the same roles # Ignore any useless readings: @readings.reject!{|reading| reading.is_existential_type } return true unless @readings.size > 0 # Nothing interesting was said. # See if any existing fact type is being invoked (presumably to objectify or extend it) @fact_type = check_compatibility_of_matched_readings if !@fact_type # Make a new fact type: first_reading = @readings[0] @fact_type = first_reading.make_fact_type(@vocabulary) first_reading.make_reading(@vocabulary, @fact_type) first_reading. vocabulary @fact_type.create_implicit_fact_type_for_unary if @fact_type.all_role.size == 1 && !@name @existing_readings = [first_reading] elsif (n = @readings.size - @existing_readings.size) > 0 debug :binding, "Extending existing fact type with #{n} new readings" end # Now make any new readings: new_readings = @readings - @existing_readings new_readings.each do |reading| reading.make_reading(@vocabulary, @fact_type) reading. vocabulary end # If a reading matched but the match left extra adjectives, we need to make a new RoleSequence for them: @existing_readings.each do |reading| reading.adjust_for_match end # Objectify the fact type if necessary: if @name if @fact_type.entity_type and @name != @fact_type.entity_type.name raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}" end @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type).create_implicit_fact_types end # REVISIT: This isn't the thing to do long term; it needs to be added later only if we find no other constraint make_default_identifier_for_fact_type @readings.each do |reading| next unless reading.context_note reading.context_note.compile(@constellation, @fact_type) end @fact_type end |
#has_more_adjectives(less, more) ⇒ Object
150 151 152 153 154 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 150 def has_more_adjectives(less, more) return false if less.leading_adjective && less.leading_adjective != more.leading_adjective return false if less.trailing_adjective && less.trailing_adjective != more.trailing_adjective return true end |
#make_default_identifier_for_fact_type(prefer = true) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 108 def make_default_identifier_for_fact_type(prefer = true) # Non-objectified unaries don't need a PI: return if @fact_type.all_role.size == 1 && !@fact_type.entity_type # It's possible that this fact type is objectified and inherits identification through a supertype. return if @fact_type.entity_type and @fact_type.entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification} # If it's a non-objectified binary and there's an alethic uniqueness constraint over the fact type already, we're done return if !@fact_type.entity_type && @fact_type.all_role.size == 2 && @fact_type.all_role. detect do |r| r.all_role_ref.detect do |rr| rr.role_sequence.all_presence_constraint.detect do |pc| pc.max_frequency == 1 && !pc.enforcement end end end # If there's an existing presence constraint that can be converted into a PC, do that: @readings.each do |reading| rr = reading.role_refs[-1] or next epc = rr. or next epc.max_frequency == 1 or next next if epc.enforcement epc.is_preferred_identifier = true return end # REVISIT: We need to check uniqueness constraints after processing the whole vocabulary # raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1 @constellation.PresenceConstraint( :new, :vocabulary => @vocabulary, :name => @fact_type.entity_type ? @fact_type.entity_type.name+"PK" : '', :role_sequence => @fact_type.preferred_reading.role_sequence, :max_frequency => 1, :is_preferred_identifier => prefer ) end |
#verify_matching_roles ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/activefacts/cql/compiler/fact_type.rb', line 156 def verify_matching_roles role_refs_by_reading_and_key = {} readings_by_role_refs = @readings.inject({}) do |hash, reading| keys = reading.role_refs.map do |rr| key = rr.key.compact role_refs_by_reading_and_key[[reading, key]] = rr key end.sort_by{|a| a.map{|k|k.to_s}} raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size (hash[keys] ||= []) << reading hash end if readings_by_role_refs.size != 1 # Attempt loose binding here; it might merge some Compiler::RoleRefs to share the same Bindings variants = readings_by_role_refs.keys (readings_by_role_refs.size-1).downto(1) do |m| # Start with the last one 0.upto(m-1) do |l| # Try to rebind onto any lower one common = variants[m]&variants[l] readings_l = readings_by_role_refs[variants[l]] readings_m = readings_by_role_refs[variants[m]] l_keys = variants[l]-common m_keys = variants[m]-common debug :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}" rebindings = 0 l_keys.each_with_index do |l_key, i| # Find possible rebinding candidates; there must be exactly one. candidates = [] (0...m_keys.size).each do |j| m_key = m_keys[j] l_role_ref = role_refs_by_reading_and_key[[readings_l[0], l_key]] m_role_ref = role_refs_by_reading_and_key[[readings_m[0], m_key]] debug :binding, "Can we match #{l_role_ref.inspect} (#{i}) with #{m_role_ref.inspect} (#{j})?" next if m_role_ref.player != l_role_ref.player if has_more_adjectives(m_role_ref, l_role_ref) debug :binding, "can rebind #{m_role_ref.inspect} to #{l_role_ref.inspect}" candidates << [m_role_ref, l_role_ref] elsif has_more_adjectives(l_role_ref, m_role_ref) debug :binding, "can rebind #{l_role_ref.inspect} to #{m_role_ref.inspect}" candidates << [l_role_ref, m_role_ref] end end # debug :binding, "found #{candidates.size} rebinding candidates for this role" debug :binding, "rebinding is ambiguous so not attempted" if candidates.size > 1 if (candidates.size == 1) candidates[0][0].rebind_to(@context, candidates[0][1]) rebindings += 1 end end if (rebindings == l_keys.size) # Successfully rebound this fact type debug :binding, "Successfully rebound readings #{readings_l.map{|r|r.inspect}*'; '} on to #{readings_m.map{|r|r.inspect}*'; '}" break else # No point continuing, we failed on this one. raise "All readings in a fact type definition must have matching role players, compare (#{ readings_by_role_refs.keys.map do |keys| keys.map{|key| key*'-' }*", " end*") with (" })" end end end # else all readings already matched end end |