activefacts 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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