lib/activefacts/input/orm.rb in activefacts-0.8.8 vs lib/activefacts/input/orm.rb in activefacts-0.8.9
- old
+ new
@@ -415,11 +415,11 @@
throw "Role is played by #{concept.class} not Concept" if !(@constellation.vocabulary.concept(:Concept) === concept)
debug :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :concept => concept)
- role.role_name = name if name
+ role.role_name = name if name && name != concept.name
debug :orm, "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x['Name']} is played by #{concept.name}, role is #{role.object_id}"
x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
x_vr.each{|vr|
x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
@@ -698,16 +698,23 @@
ib['IsImplicitBooleanValue'])
unary_identifier = true
end
mc_id = nil
+
if (mc = @mandatory_constraints_by_rs[role_sequence])
# Remove absorbed mandatory constraints, leaving residual ones.
debug :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
@mandatory_constraints_by_rs.delete(role_sequence)
mc_id = mc['id']
@mandatory_constraint_rs_by_id.delete(mc['id'])
+ elsif (fts = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq).size == 1 and
+ fts[0].entity_type
+ # this uniqueness constraint is an internal UC on an objectified fact type,
+ # so the covered roles are always mandatory (wrt the OFT)
+ # That is, the phantom roles are mandatory, even if the visible roles are not.
+ mc = true
else
debug :orm, "No MC to absorb over #{role_sequence.describe}"
end
# A TypeInheritance fact type has a uniqueness constraint on each role.
@@ -737,10 +744,43 @@
@by_id[mc_id] = pc if mc_id
}
end
end
+ def subtype_join_step(join, ti)
+ subtype_node = join.all_join_node.detect{|jn| jn.concept == ti.subtype } ||
+ @constellation.JoinNode(join, join.all_join_node.size, :concept => ti.subtype)
+ supertype_node = join.all_join_node.detect{|jn| jn.concept == ti.supertype } ||
+ @constellation.JoinNode(join, join.all_join_node.size, :concept => ti.supertype)
+ rs = @constellation.RoleSequence(:new)
+ @constellation.RoleRef(rs, 0, :role => ti.subtype_role)
+ sub_jr = @constellation.JoinRole(subtype_node, ti.subtype_role)
+ @constellation.RoleRef(rs, 1, :role => ti.supertype_role)
+ sup_jr = @constellation.JoinRole(supertype_node, ti.supertype_role)
+ js = @constellation.JoinStep(sub_jr, sup_jr, :fact_type => ti)
+ debug :join, "New subtyping join step #{js.describe}"
+ js
+ end
+
+ # Make as many join steps as it takes to get from subtype to supertype
+ def subtype_join_steps(join, subtype, supertype)
+ primary_ti = nil
+ other_ti = nil
+ subtype.all_type_inheritance_as_subtype.each do |ti|
+ next unless ti.supertype.supertypes_transitive.include? supertype
+ if ti.provides_identification
+ primary_ti ||= ti
+ else
+ other_ti ||= ti
+ end
+ end
+ ti = primary_ti || other_ti
+ # Make supertype join steps first:
+ (ti.supertype == supertype ? [] : subtype_join_steps(join, ti.supertype, supertype)) +
+ [subtype_join_step(join, ti)]
+ end
+
# Equality and subset joins involve two or more role sequences,
# and the respective roles from each sequence must be compatible,
# Compatibility might involve subtyping joins but not objectification joins
# to the respective end-point (constrained object type).
# Also, all roles in each sequence constitute a join over a single
@@ -772,13 +812,16 @@
end
# For each role_sequence, find the object type over which the join is implied (nil if no join)
sequence_join_over = []
if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
- sequence_join_over = role_sequences.map do |rs|
+ sequence_join_over = []
+ sequence_joined_roles = []
+ role_sequences.map do |rs|
join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
- join_over
+ sequence_join_over << join_over
+ sequence_joined_roles << joined_roles
end
end
# If there are no joins, we can drop out here.
if sequence_join_over.compact.empty? && !end_joins.detect{|e| true}
@@ -790,100 +833,136 @@
join = nil
debug :join, "#{constraint_type} join constraint #{name} constrains #{
end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', '
}#{
if role_sequences[0].all_role_ref.size > 1
- ", joined over #{sequence_join_over.map{|o| o ? o.name : '(none)'}*', '}"
+ ", joined over #{
+ sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
+ (o ? o.name : '(none)') +
+ (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
+ }*', '}"
else
''
end
}" do
- end
- return # REVISIT: Disabled until we get the join verbaliser working.
- while false
-
# There may be one join per role sequence:
- role_sequences.zip(sequence_join_over).map do |role_sequence, join_over|
+ role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
# Skip if there's no join here (sequence join nor end-point subset join)
- role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
- next unless join_over or role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
+ role_refs = role_sequence.all_role_ref_in_order
+ if !join_over and !role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
+ # No sequence join nor end_point join here
+ next
+ end
+ # A RoleSequence for the actual join end-points
replacement_rs = @constellation.RoleSequence(:new)
+
join = @constellation.Join(:new)
join_node = nil
- role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.each_with_index do |role_ref, i|
+ join_role = nil
+ role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
+
+ # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node)
+
+ # Create a join node for the actual end-point (supertype of the constrained roles)
end_point = end_points[i]
debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
end_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => end_point)
+
+ # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
role_node = end_node
+ end_role = role_ref.role
- if role_ref.role.concept != end_point
- subtype = role_ref.role.concept
- debug :join, "Making subtyping join step #{join.all_join_node.size} from #{subtype.name} to #{end_point.name}" do
- role_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => role_ref.role.concept)
- ti = role_ref.role.concept.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == end_point}
- raise "REVISIT: Can't yet handle multiple subtyping levels in subtyping join" unless ti
+ # Create subtyping join steps at the end-point, if needed:
+ projecting_jr = nil
+ constrained_join_role = nil
+ if (subtype = role_ref.role.concept) != end_point
+ debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do
+ # There may be more than one supertyping level. Make the steps:
+ subtyping_steps = subtype_join_steps(join, subtype, end_point)
+ js = subtyping_steps[0]
+ constrained_join_role = subtyping_steps[-1].input_join_role
- # Make a new role sequence over the subtyping fact type:
- rs = @constellation.RoleSequence(:new)
- @constellation.RoleRef(rs, 0, :role => ti.all_role.detect{|r| r.concept == role_ref.role.concept}, :join_node => role_node)
- @constellation.RoleRef(rs, 1, :role => ti.all_role.detect{|r| r.concept == end_point}, :join_node => end_node)
-
- js = @constellation.JoinStep(role_node, end_node, :fact_type => ti)
- debug :join, "New subtyping join step #{js.describe}"
+ # Replace the constrained role and node with the supertype ones:
+ end_node = join.all_join_node.detect{|jn| jn.concept == end_point }
+ projecting_jr = js.output_join_role
+ end_role = js.fact_type.all_role.detect{|r| r.concept == end_point }
+ role_node = join.all_join_node.detect{|jn| jn.concept == role_ref.role.concept }
end
end
- @constellation.RoleRef(replacement_rs, 0, :role => role_ref.role, :join_node => end_node)
+ raise "Internal error: making illegal reference to join node" if end_role.concept != end_node.concept
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
+ projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role))
+ projecting_jr.role_ref = rr # Project this RoleRef
- # REVISIT: Something here needs to handle unaries, which have a join step over two copies of the same JoinNode
-
if join_over
- if !join_node
+ if !join_node # Create the JoinNode when processing the first role
debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
join_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => join_over)
end
debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
- role = role_ref.role
- # Detect an objectification step. Here, the role is in an objectified type, but the end_point isn't another role player of that fact type... or is there a better test?
- if join_over == role.fact_type.entity_type
- p join_over.name
- p role.fact_type.default_reading
- p end_point.name
- raise "REVISIT: Incomplete"
- elsif end_point == role.fact_type.entity_type
- fact_type = role.implicit_fact_type
- # REVISIT: Might these be reversed... sometimes?
- end_role = fact_type.all_role.single
- join_role = role
- else
- fact_type = role.fact_type
- end_role = role
- # This is unambiguous, since it's come from NORMA which only supports (unambiguous) implicit joins:
- join_role = role.fact_type.all_role.detect{|r| r.concept == join_over}
- # debugger unless join_role && end_role
- end
-
rs = @constellation.RoleSequence(:new)
# Detect the fact type over which we're joining (may involve objectification)
- @constellation.RoleRef(rs, 0, :role => end_role, :join_node => end_node)
- @constellation.RoleRef(rs, 1, :role => join_role, :join_node => join_node)
- js = @constellation.JoinStep(end_node, join_node, :fact_type => fact_type)
+ raise "Internal error: making illegal reference to join node" if role_ref.role.concept != role_node.concept
+ @constellation.RoleRef(rs, 0, :role => role_ref.role)
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
+ raise "Internal error: making illegal reference to join node" if joined_role.concept != join_node.concept
+ @constellation.RoleRef(rs, 1, :role => joined_role)
+ join_jr = @constellation.JoinRole(join_node, joined_role)
+
+ js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type)
debug :join, "New join step #{js.describe}"
end
else
debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
- if role_ref.role.fact_type.all_role.size > 1
- raise unimplemented
+ if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
+ # Here we have an end join (step already created) but no sequence join
+ if join_node
+ raise "Internal error: making illegal join step" if role_ref.role.concept != role_node.concept
+ join_jr = @constellation.JoinRole(join_node, join_role)
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
+ js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type)
+ roles -= [join_role, role_ref.role]
+ roles.each do |incidental_role|
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => incidental_role.concept)
+ jr = @constellation.JoinRole(jn, incidental_role, :join_step => js)
+ end
+ else
+ if role_sequence.all_role_ref.size > 1
+ join_node = role_node
+ join_role = role_ref.role
+ else
+ # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why?
+ # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
+ # I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example)
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
+ js = nil
+ role_ref.role.fact_type.all_role.each do |role|
+ next if role == role_jr.role
+ next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => role.concept)
+ jr = @constellation.JoinRole(jn, role)
+ if js
+ jr.join_step = js # Incidental role
+ else
+ js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type)
+ end
+ end
+ end
+ end
else
- js = @constellation.JoinStep(role_node, role_node, :fact_type => role_ref.role.fact_type)
+ # Unary fact type, make a Join Step from and to the constrained_join_role
+ jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role)
+ js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type)
end
end
end
# Thoroughly check that this is a valid join
join.validate
+ debug :join, "Join has projected nodes #{replacement_rs.describe}"
# Constrain the replacement role sequence, which has the attached join:
role_sequences[role_sequences.index(role_sequence)] = replacement_rs
end
end