activefacts 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generation support superclass that sequences entity types to avoid forward references.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module Generate #:nodoc:
9
+ module OrderedTraits
10
+ module DumpedFlag
11
+ attr_reader :ordered_dumped
12
+
13
+ def ordered_dumped!
14
+ @ordered_dumped = true
15
+ end
16
+ end
17
+
18
+ module ObjectType
19
+ include DumpedFlag
20
+ end
21
+
22
+ module FactType
23
+ include DumpedFlag
24
+ end
25
+
26
+ module Constraint
27
+ include DumpedFlag
28
+ end
29
+
30
+ include ActiveFacts::TraitInjector # Must be last in this module, after all submodules have been defined
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,210 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate Ruby classes for the ActiveFacts API from an ActiveFacts vocabulary.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module Generate
9
+ module RubyTraits
10
+ module Vocabulary
11
+ def prelude
12
+ if @mapping == 'sql'
13
+ require 'activefacts/persistence'
14
+ @tables = self.tables
15
+ end
16
+
17
+ "require 'activefacts/api'\n" +
18
+ (@mapping == 'sql' ? "require 'activefacts/persistence'\n" : '') +
19
+ "\nmodule ::#{self.name}\n\n"
20
+ end
21
+
22
+ def finale
23
+ "end"
24
+ end
25
+ end
26
+
27
+ module ObjectType
28
+ def absorbed_roles
29
+ all_role.
30
+ select do |role|
31
+ role.fact_type.all_role.size <= 2 &&
32
+ !role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
33
+ end.
34
+ sort_by do |role|
35
+ r = role.fact_type.all_role.select{|r2| r2 != role}[0] || role
36
+ r.preferred_role_name(self) + ':' + role.preferred_role_name(r.object_type)
37
+ end
38
+ end
39
+
40
+ # Map the ObjectType name to a Ruby class name
41
+ def ruby_type_name
42
+ oo_type_name
43
+ end
44
+
45
+ # Map the Ruby class name to a default role name
46
+ def ruby_default_role_name
47
+ oo_default_role_name
48
+ end
49
+
50
+
51
+ def ruby_type_reference
52
+ if !ordered_dumped
53
+ '"'+name.gsub(/ /,'')+'"'
54
+ else
55
+ role_reference = name.gsub(/ /,'')
56
+ end
57
+ end
58
+ end
59
+
60
+ module Role
61
+ def preferred_role_name(is_for = nil, &name_builder)
62
+
63
+ if fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
64
+ # Subtype and Supertype roles default to TitleCase names, and have no role_name to worry about:
65
+ return (name_builder || proc {|names| names.titlecase}).call(object_type.name.words)
66
+ end
67
+
68
+ name_builder ||= proc {|names| names.map(&:downcase)*'_' } # Make snake_case by default
69
+
70
+ # Handle an objectified unary role:
71
+ if is_for && fact_type.entity_type == is_for && fact_type.all_role.size == 1
72
+ return name_builder.call(object_type.name.words)
73
+ end
74
+
75
+ # trace "Looking for preferred_role_name of #{describe_fact_type(fact_type, self)}"
76
+ reading = fact_type.preferred_reading
77
+ preferred_role_ref = reading.role_sequence.all_role_ref.detect{|reading_rr|
78
+ reading_rr.role == self
79
+ }
80
+
81
+ if fact_type.all_role.size == 1
82
+ return name_builder.call(
83
+ role_name ?
84
+ role_name.snakewords :
85
+ reading.text.gsub(/ *\{0\} */,' ').gsub(/[- ]+/,'_').words
86
+ )
87
+ end
88
+
89
+ if role_name && role_name != ""
90
+ role_words = [role_name]
91
+ else
92
+ role_words = []
93
+
94
+ la = preferred_role_ref.leading_adjective
95
+ role_words += la.words.snakewords if la && la != ""
96
+
97
+ role_words += object_type.name.words.snakewords
98
+
99
+ ta = preferred_role_ref.trailing_adjective
100
+ role_words += ta.words.snakewords if ta && ta != ""
101
+ end
102
+
103
+ # n = role_words.map{|w| w.gsub(/([a-z])([A-Z]+)/,'\1_\2').downcase}*"_"
104
+ n = role_words*'_'
105
+ # trace "\tresult=#{n}"
106
+ return name_builder.call(n.gsub(' ','_').split(/_/))
107
+ end
108
+
109
+ def as_binary(role_name, role_player, mandatory = nil, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
110
+ ruby_role_name = ":"+role_name.words.snakecase
111
+
112
+ # Find whether we need the name of the other role player, and whether it's defined yet:
113
+ implied_role_name = role_player.name.gsub(/ /,'').sub(/^[a-z]/) {|i| i.upcase}
114
+ if role_name.camelcase != implied_role_name
115
+ # Only use Class name if it's not implied by the rolename
116
+ role_reference = ":class => "+role_player.ruby_type_reference
117
+ end
118
+
119
+ other_role_name = ":counterpart => :"+other_role_name.gsub(/ /,'_') if other_role_name
120
+
121
+ if vr = role_value_constraint
122
+ value_restriction = ":restrict => #{vr}"
123
+ end
124
+
125
+ options = [
126
+ ruby_role_name,
127
+ role_reference,
128
+ mandatory ? ":mandatory => true" : nil,
129
+ readings,
130
+ other_role_name,
131
+ value_restriction
132
+ ].compact
133
+
134
+ debugger if ruby_role_name == 'astronomicalobject'
135
+
136
+ line = " #{one_to_one ? "one_to_one" : "has_one" } #{options*', '} "
137
+ if other_method_name
138
+ line += " "*(48-line.length) if line.length < 48
139
+ line += "\# See #{role_player.name.gsub(/ /,'')}.#{other_method_name}"
140
+ end
141
+ line+"\n"
142
+ end
143
+
144
+ def ruby_role_definition
145
+ oo_role_definition
146
+ end
147
+ end
148
+
149
+ module ValueType
150
+ def ruby_definition
151
+ return if name == "_ImplicitBooleanValueType"
152
+
153
+ ruby_length = length && length > 0 ? ":length => #{length}" : nil
154
+ ruby_scale = scale && scale > 0 ? ":scale => #{scale}" : nil
155
+ params = [ruby_length,ruby_scale].compact * ", "
156
+
157
+ base_type = supertype || self
158
+ base_type_name = base_type.ruby_type_name
159
+ ruby_name = ruby_type_name
160
+ if base_type_name == ruby_name
161
+ base_type_name = '::'+base_type_name
162
+ end
163
+
164
+ " class #{ruby_name} < #{base_type_name}\n" +
165
+ " value_type #{params}\n" +
166
+ #emit_mapping self if is_table
167
+ (value_constraint ?
168
+ " restrict #{value_constraint.all_allowed_range_sorted.map{|ar| ar.to_s}*", "}\n" :
169
+ ""
170
+ ) +
171
+ (unit ?
172
+ " \# REVISIT: #{ruby_name} is in units of #{unit.name}\n" :
173
+ ""
174
+ ) +
175
+ absorbed_roles.map do |role|
176
+ role.ruby_role_definition
177
+ end.
178
+ compact*"" +
179
+ " end\n\n"
180
+ end
181
+ end
182
+
183
+ module FactType
184
+ # An objectified fact type has internal roles that are always "has_one":
185
+ def fact_roles
186
+ raise "Fact #{describe} type is not objectified" unless entity_type
187
+ all_role.sort_by do |role|
188
+ role.preferred_role_name(entity_type)
189
+ end.
190
+ map do |role|
191
+ role_name = role.preferred_role_name(entity_type)
192
+ one_to_one = role.all_role_ref.detect{|rr|
193
+ rr.role_sequence.all_role_ref.size == 1 &&
194
+ rr.role_sequence.all_presence_constraint.detect{|pc|
195
+ pc.max_frequency == 1
196
+ }
197
+ }
198
+ counterpart_role_method = (one_to_one ? "" : "all_") +
199
+ entity_type.oo_default_role_name +
200
+ (role_name != role.object_type.oo_default_role_name ? "_as_#{role_name}" : '')
201
+ role.as_binary(role_name, role.object_type, true, one_to_one, nil, nil, counterpart_role_method)
202
+ end.
203
+ join('')
204
+ end
205
+ end
206
+
207
+ include ActiveFacts::TraitInjector # Must be last in this module, after all submodules have been defined
208
+ end
209
+ end
210
+ end
@@ -867,6 +867,152 @@ module ActiveFacts
867
867
  [subtype_step(query, ti)]
868
868
  end
869
869
 
870
+ # If there's a query, build it and return a new RoleSequence containing the projected roles:
871
+ def query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
872
+ # Skip if there's no query here (sequence join nor end-point subset join)
873
+ role_refs = role_sequence.all_role_ref_in_order
874
+ if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
875
+ # No sequence join nor end_point join here
876
+ return role_sequence
877
+ end
878
+
879
+ # A RoleSequence for the actual query end-points
880
+ replacement_rs = @constellation.RoleSequence(:new)
881
+
882
+ query = @constellation.Query(:new)
883
+ variable = nil
884
+ query_role = nil
885
+ role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
886
+
887
+ # Each role_ref is to an object joined via joined_role to variable (or which will be the variable)
888
+
889
+ # Create a variable for the actual end-point (supertype of the constrained roles)
890
+ end_point = end_points[i]
891
+ unless end_point
892
+ raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
893
+ end
894
+ trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
895
+ end_node = @constellation.Variable(query, query.all_variable.size, :object_type => end_point)
896
+
897
+ # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
898
+ role_node = end_node
899
+ end_role = role_ref.role
900
+
901
+ # Create subtyping steps at the end-point, if needed:
902
+ projecting_play = nil
903
+ constrained_play = nil
904
+ if (subtype = role_ref.role.object_type) != end_point
905
+ trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
906
+ # There may be more than one supertyping level. Make the steps:
907
+ subtyping_steps = subtype_steps(query, subtype, end_point)
908
+ step = subtyping_steps[0]
909
+ #constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
910
+ subtyping_steps.detect{|s| constrained_play = s.all_play.detect{|p| p.role.object_type == end_point}}
911
+
912
+ # Replace the constrained role and node with the supertype ones:
913
+ end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
914
+ #projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
915
+ subtyping_steps.detect{|s| projecting_play = s.all_play.detect{|p| p.role.object_type == subtype}}
916
+ end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
917
+ role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
918
+ end
919
+ end
920
+
921
+ if end_role.object_type != end_node.object_type
922
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch"
923
+ end
924
+
925
+ if join_over
926
+ if !variable # Create the Variable when processing the first role
927
+ trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
928
+ variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
929
+ end
930
+ trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
931
+ rs = @constellation.RoleSequence(:new)
932
+ # Detect the fact type over which we're stepping (may involve objectification)
933
+ 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
934
+ step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
935
+ @constellation.RoleRef(rs, 0, :role => role_ref.role)
936
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
937
+ # Make the projected RoleRef:
938
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
939
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
940
+ @constellation.RoleRef(rs, 1, :role => joined_role)
941
+ join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
942
+ trace :query, "New step #{step.describe}"
943
+ end
944
+ else
945
+ trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
946
+ if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
947
+ # Here we have an end join (step already created) but no sequence join
948
+ if variable
949
+ raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
950
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
951
+ join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
952
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
953
+ # Make the projected RoleRef:
954
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
955
+ roles -= [query_role, role_ref.role]
956
+ roles.each do |incidental_role|
957
+ jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
958
+ play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
959
+ end
960
+ else
961
+
962
+ if role_sequence.all_role_ref.size > 1
963
+ variable = role_node
964
+ query_role = role_ref.role
965
+
966
+ # Make the projected RoleRef:
967
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
968
+ else
969
+ # We enter this fact type (requiring that a role be played) but don't exit it.
970
+ # I think this can only happen where we have subtyping steps, above.
971
+
972
+ # There's no query in this role sequence, so we'd drop off the bottom without doing the right things. Why?
973
+ # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
974
+ # 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)
975
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
976
+
977
+ # p constrained_play.role.object_type.name
978
+ # p projecting_play.role.object_type.name
979
+ # debugger
980
+
981
+ role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
982
+
983
+ # Make the projected RoleRef:
984
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
985
+
986
+ # role_ref.role.fact_type.all_role.each do |role|
987
+ # next if role == role_play.role
988
+ # next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
989
+ # jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
990
+ # play = @constellation.Play(:step => step, :variable => jn, :role => role)
991
+ # if role == role_ref.role
992
+ # # Make the projected RoleRef:
993
+ # rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role, :play => play)
994
+ # end
995
+ # end
996
+
997
+ end
998
+ end
999
+ else
1000
+ # Unary fact type, make a Step from and to the constrained_play
1001
+ step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
1002
+ play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
1003
+ # Make the projected RoleRef:
1004
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => play)
1005
+ end
1006
+ end
1007
+ end
1008
+ raise "hell" if replacement_rs.all_role_ref.size != role_sequence.all_role_ref.size
1009
+
1010
+ # Thoroughly check that this is a valid query
1011
+ query.validate
1012
+ trace :query, "Query has projected nodes #{replacement_rs.describe}"
1013
+ replacement_rs
1014
+ end
1015
+
870
1016
  # Equality and subset join constraints involve two or more role sequences,
871
1017
  # and the respective roles from each sequence must be compatible,
872
1018
  # Compatibility might involve subtyping steps but not objectification steps
@@ -879,7 +1025,7 @@ module ActiveFacts
879
1025
  # Get the object types constrained for each position in the role sequences.
880
1026
  # Supertyping steps may be needed to reach them.
881
1027
  end_points = [] # An array of the common supertype for matching role_refs across the sequences
882
- end_steps = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
1028
+ end_step_needed = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
883
1029
  role_sequences[0].all_role_ref.size.times do |i|
884
1030
  role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
885
1031
  if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
@@ -887,8 +1033,9 @@ module ActiveFacts
887
1033
  raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'"
888
1034
  end
889
1035
  if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
1036
+ # All roles in this set are played by the same object type
890
1037
  end_point = players[0]
891
- end_steps[i] = false
1038
+ end_step_needed[i] = false
892
1039
  else
893
1040
  # Can the players be joined using subtyping steps?
894
1041
  common_supertypes = players[1..-1].
@@ -898,7 +1045,7 @@ module ActiveFacts
898
1045
  end_point = common_supertypes[0]
899
1046
 
900
1047
  raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
901
- end_steps[i] = true
1048
+ end_step_needed[i] = true
902
1049
  end
903
1050
  end_points[i] = end_point
904
1051
  end
@@ -916,7 +1063,7 @@ module ActiveFacts
916
1063
  end
917
1064
 
918
1065
  # If there are no queries, we can drop out here.
919
- if sequence_join_over.compact.empty? && !end_steps.detect{|e| true}
1066
+ if sequence_join_over.compact.empty? && !end_step_needed.detect{|e| e}
920
1067
  return true
921
1068
  end
922
1069
 
@@ -924,7 +1071,7 @@ module ActiveFacts
924
1071
 
925
1072
  query = nil
926
1073
  trace :query, "#{constraint_type} join constraint #{name} constrains #{
927
- end_points.zip(end_steps).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
1074
+ end_points.zip(end_step_needed).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
928
1075
  }#{
929
1076
  if role_sequences[0].all_role_ref.size > 1
930
1077
  ", joined over #{
@@ -939,123 +1086,13 @@ module ActiveFacts
939
1086
 
940
1087
  # There may be one query per role sequence:
941
1088
  role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
942
- # Skip if there's no query here (sequence join nor end-point subset join)
943
- role_refs = role_sequence.all_role_ref_in_order
944
- if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
945
- # No sequence join nor end_point join here
946
- next
947
- end
948
-
949
- # A RoleSequence for the actual query end-points
950
- replacement_rs = @constellation.RoleSequence(:new)
951
-
952
- query = @constellation.Query(:new)
953
- variable = nil
954
- query_role = nil
955
- role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
956
-
957
- # Each role_ref is to an object joined via joined_role to variable (or which will be the variable)
958
-
959
- # Create a variable for the actual end-point (supertype of the constrained roles)
960
- end_point = end_points[i]
961
- unless end_point
962
- raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
963
- end
964
- trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
965
- end_node = @constellation.Variable(query, query.all_variable.size, :object_type => end_point)
966
-
967
- # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
968
- role_node = end_node
969
- end_role = role_ref.role
970
-
971
- # Create subtyping steps at the end-point, if needed:
972
- projecting_play = nil
973
- constrained_play = nil
974
- if (subtype = role_ref.role.object_type) != end_point
975
- trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
976
- # There may be more than one supertyping level. Make the steps:
977
- subtyping_steps = subtype_steps(query, subtype, end_point)
978
- step = subtyping_steps[0]
979
- constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
980
-
981
- # Replace the constrained role and node with the supertype ones:
982
- end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
983
- projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
984
- end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
985
- role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
986
- end
987
- end
988
-
989
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch" if end_role.object_type != end_node.object_type
990
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
991
- projecting_play ||= (constrained_play = @constellation.Play(:variable => end_node, :role => end_role))
992
- projecting_play.role_ref = rr # Project this RoleRef
993
- # projecting_play.variable.projection = rr.role # REVISIT: The variable should project a role, not the Play a RoleRef
994
-
995
- if join_over
996
- if !variable # Create the Variable when processing the first role
997
- trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
998
- variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
999
- end
1000
- trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
1001
- rs = @constellation.RoleSequence(:new)
1002
- # Detect the fact type over which we're stepping (may involve objectification)
1003
- 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
1004
- step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
1005
- @constellation.RoleRef(rs, 0, :role => role_ref.role)
1006
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
1007
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
1008
- @constellation.RoleRef(rs, 1, :role => joined_role)
1009
- join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
1010
- trace :query, "New step #{step.describe}"
1011
- end
1012
- else
1013
- trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
1014
- if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
1015
- # Here we have an end join (step already created) but no sequence join
1016
- if variable
1017
- raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
1018
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
1019
- join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
1020
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
1021
- roles -= [query_role, role_ref.role]
1022
- roles.each do |incidental_role|
1023
- jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
1024
- play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
1025
- end
1026
- else
1027
- if role_sequence.all_role_ref.size > 1
1028
- variable = role_node
1029
- query_role = role_ref.role
1030
- else
1031
- # There's no query in this role sequence, so we'd drop off the bottom without doing the right things. Why?
1032
- # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
1033
- # 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)
1034
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
1035
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
1036
- role_ref.role.fact_type.all_role.each do |role|
1037
- next if role == role_play.role
1038
- next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
1039
- jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
1040
- play = @constellation.Play(:step => step, :variable => jn, :role => role)
1041
- end
1042
- end
1043
- end
1044
- else
1045
- # Unary fact type, make a Step from and to the constrained_play
1046
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
1047
- play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
1048
- end
1049
- end
1050
- end
1051
-
1052
- # Thoroughly check that this is a valid query
1053
- query.validate
1054
- trace :query, "Query has projected nodes #{replacement_rs.describe}"
1089
+ position = role_sequences.index(role_sequence)
1090
+ replacement_rs = query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
1091
+ if role_sequence != replacement_rs
1092
+ role_sequences[position] = replacement_rs
1093
+ end
1094
+ end
1055
1095
 
1056
- # Constrain the replacement role sequence, which has the attached query:
1057
- role_sequences[role_sequences.index(role_sequence)] = replacement_rs
1058
- end
1059
1096
  return true
1060
1097
  end
1061
1098
  rescue => e