lib/activefacts/input/orm.rb in activefacts-1.3.0 vs lib/activefacts/input/orm.rb in activefacts-1.5.0
- old
+ new
@@ -865,10 +865,156 @@
# Make supertype steps first:
(ti.supertype == supertype ? [] : subtype_steps(query, ti.supertype, supertype)) +
[subtype_step(query, ti)]
end
+ # If there's a query, build it and return a new RoleSequence containing the projected roles:
+ def query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
+ # Skip if there's no query here (sequence join nor end-point subset join)
+ role_refs = role_sequence.all_role_ref_in_order
+ if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
+ # No sequence join nor end_point join here
+ return role_sequence
+ end
+
+ # A RoleSequence for the actual query end-points
+ replacement_rs = @constellation.RoleSequence(:new)
+
+ query = @constellation.Query(:new)
+ variable = nil
+ query_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 variable (or which will be the variable)
+
+ # Create a variable for the actual end-point (supertype of the constrained roles)
+ end_point = end_points[i]
+ unless end_point
+ raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
+ end
+ trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
+ end_node = @constellation.Variable(query, query.all_variable.size, :object_type => 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
+
+ # Create subtyping steps at the end-point, if needed:
+ projecting_play = nil
+ constrained_play = nil
+ if (subtype = role_ref.role.object_type) != end_point
+ trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
+ # There may be more than one supertyping level. Make the steps:
+ subtyping_steps = subtype_steps(query, subtype, end_point)
+ step = subtyping_steps[0]
+ #constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
+ subtyping_steps.detect{|s| constrained_play = s.all_play.detect{|p| p.role.object_type == end_point}}
+
+ # Replace the constrained role and node with the supertype ones:
+ end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
+ #projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
+ subtyping_steps.detect{|s| projecting_play = s.all_play.detect{|p| p.role.object_type == subtype}}
+ end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
+ role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
+ end
+ end
+
+ if end_role.object_type != end_node.object_type
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch"
+ end
+
+ if join_over
+ if !variable # Create the Variable when processing the first role
+ trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
+ variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
+ end
+ trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
+ rs = @constellation.RoleSequence(:new)
+ # Detect the fact type over which we're stepping (may involve objectification)
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, object type mismatch" if role_ref.role.object_type != role_node.object_type
+ step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
+ @constellation.RoleRef(rs, 0, :role => role_ref.role)
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
+ # Make the projected RoleRef:
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
+ @constellation.RoleRef(rs, 1, :role => joined_role)
+ join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
+ trace :query, "New step #{step.describe}"
+ end
+ else
+ trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
+ 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 variable
+ raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
+ join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
+ # Make the projected RoleRef:
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
+ roles -= [query_role, role_ref.role]
+ roles.each do |incidental_role|
+ jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
+ play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
+ end
+ else
+
+ if role_sequence.all_role_ref.size > 1
+ variable = role_node
+ query_role = role_ref.role
+
+ # Make the projected RoleRef:
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
+ else
+ # We enter this fact type (requiring that a role be played) but don't exit it.
+ # I think this can only happen where we have subtyping steps, above.
+
+ # There's no query in this role sequence, so we'd drop off the 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)
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
+
+ # p constrained_play.role.object_type.name
+ # p projecting_play.role.object_type.name
+ # debugger
+
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
+
+ # Make the projected RoleRef:
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
+
+# role_ref.role.fact_type.all_role.each do |role|
+# next if role == role_play.role
+# next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
+# jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
+# play = @constellation.Play(:step => step, :variable => jn, :role => role)
+# if role == role_ref.role
+# # Make the projected RoleRef:
+# rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role, :play => play)
+# end
+# end
+
+ end
+ end
+ else
+ # Unary fact type, make a Step from and to the constrained_play
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
+ play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
+ # Make the projected RoleRef:
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => play)
+ end
+ end
+ end
+ raise "hell" if replacement_rs.all_role_ref.size != role_sequence.all_role_ref.size
+
+ # Thoroughly check that this is a valid query
+ query.validate
+ trace :query, "Query has projected nodes #{replacement_rs.describe}"
+ replacement_rs
+ end
+
# Equality and subset join constraints involve two or more role sequences,
# and the respective roles from each sequence must be compatible,
# Compatibility might involve subtyping steps but not objectification steps
# to the respective end-point (constrained object type).
# Also, all roles in each sequence constitute a join over a single
@@ -877,30 +1023,31 @@
def make_queries(constraint_type, name, role_sequences)
begin
# Get the object types constrained for each position in the role sequences.
# Supertyping steps may be needed to reach them.
end_points = [] # An array of the common supertype for matching role_refs across the sequences
- end_steps = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
+ end_step_needed = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
role_sequences[0].all_role_ref.size.times do |i|
role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
# $stderr.puts(role_sequences.map{|rs| rs.all_role_ref.map{|rr| rr.role.fact_type.describe(rr.role)}}.inspect)
raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'"
end
if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
+ # All roles in this set are played by the same object type
end_point = players[0]
- end_steps[i] = false
+ end_step_needed[i] = false
else
# Can the players be joined using subtyping steps?
common_supertypes = players[1..-1].
inject(players[0].supertypes_transitive) do |remaining, player|
remaining & player.supertypes_transitive
end
end_point = common_supertypes[0]
raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
- end_steps[i] = true
+ end_step_needed[i] = true
end
end_points[i] = end_point
end
# For each role_sequence, find the object type over which the join is implied (nil if no join)
@@ -914,19 +1061,19 @@
sequence_joined_roles << joined_roles
end
end
# If there are no queries, we can drop out here.
- if sequence_join_over.compact.empty? && !end_steps.detect{|e| true}
+ if sequence_join_over.compact.empty? && !end_step_needed.detect{|e| e}
return true
end
trace :query, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
query = nil
trace :query, "#{constraint_type} join constraint #{name} constrains #{
- end_points.zip(end_steps).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
+ end_points.zip(end_step_needed).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
}#{
if role_sequences[0].all_role_ref.size > 1
", joined over #{
sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
(o ? o.name : '(none)') +
@@ -937,126 +1084,16 @@
end
}" do
# There may be one query per role sequence:
role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
- # Skip if there's no query here (sequence join nor end-point subset join)
- role_refs = role_sequence.all_role_ref_in_order
- if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
- # No sequence join nor end_point join here
- next
- end
+ position = role_sequences.index(role_sequence)
+ replacement_rs = query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
+ if role_sequence != replacement_rs
+ role_sequences[position] = replacement_rs
+ end
+ end
- # A RoleSequence for the actual query end-points
- replacement_rs = @constellation.RoleSequence(:new)
-
- query = @constellation.Query(:new)
- variable = nil
- query_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 variable (or which will be the variable)
-
- # Create a variable for the actual end-point (supertype of the constrained roles)
- end_point = end_points[i]
- unless end_point
- raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
- end
- trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
- end_node = @constellation.Variable(query, query.all_variable.size, :object_type => 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
-
- # Create subtyping steps at the end-point, if needed:
- projecting_play = nil
- constrained_play = nil
- if (subtype = role_ref.role.object_type) != end_point
- trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
- # There may be more than one supertyping level. Make the steps:
- subtyping_steps = subtype_steps(query, subtype, end_point)
- step = subtyping_steps[0]
- constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
-
- # Replace the constrained role and node with the supertype ones:
- end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
- projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
- end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
- role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
- end
- end
-
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch" if end_role.object_type != end_node.object_type
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
- projecting_play ||= (constrained_play = @constellation.Play(:variable => end_node, :role => end_role))
- projecting_play.role_ref = rr # Project this RoleRef
- # projecting_play.variable.projection = rr.role # REVISIT: The variable should project a role, not the Play a RoleRef
-
- if join_over
- if !variable # Create the Variable when processing the first role
- trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
- variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
- end
- trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
- rs = @constellation.RoleSequence(:new)
- # Detect the fact type over which we're stepping (may involve objectification)
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, object type mismatch" if role_ref.role.object_type != role_node.object_type
- step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
- @constellation.RoleRef(rs, 0, :role => role_ref.role)
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
- @constellation.RoleRef(rs, 1, :role => joined_role)
- join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
- trace :query, "New step #{step.describe}"
- end
- else
- trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
- 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 variable
- raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
- join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
- roles -= [query_role, role_ref.role]
- roles.each do |incidental_role|
- jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
- play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
- end
- else
- if role_sequence.all_role_ref.size > 1
- variable = role_node
- query_role = role_ref.role
- else
- # There's no query in this role sequence, so we'd drop off the 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)
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
- role_ref.role.fact_type.all_role.each do |role|
- next if role == role_play.role
- next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
- jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
- play = @constellation.Play(:step => step, :variable => jn, :role => role)
- end
- end
- end
- else
- # Unary fact type, make a Step from and to the constrained_play
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
- play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
- end
- end
- end
-
- # Thoroughly check that this is a valid query
- query.validate
- trace :query, "Query has projected nodes #{replacement_rs.describe}"
-
- # Constrain the replacement role sequence, which has the attached query:
- role_sequences[role_sequences.index(role_sequence)] = replacement_rs
- end
return true
end
rescue => e
debugger if trace :debug
$stderr.puts "// #{e.to_s}: #{e.backtrace[0]}"