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.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/examples/CQL/Metamodel.cql +5 -0
- data/lib/activefacts/cql/compiler.rb +25 -2
- data/lib/activefacts/cql/compiler/constraint.rb +9 -1
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -0
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generate/composition.rb +118 -0
- data/lib/activefacts/generate/cql.rb +3 -1
- data/lib/activefacts/generate/helpers/inject.rb +16 -0
- data/lib/activefacts/generate/rails/models.rb +1 -1
- data/lib/activefacts/generate/stats.rb +69 -0
- data/lib/activefacts/generate/topics.rb +265 -0
- data/lib/activefacts/generate/traits/oo.rb +73 -0
- data/lib/activefacts/generate/traits/ordered.rb +33 -0
- data/lib/activefacts/generate/traits/ruby.rb +210 -0
- data/lib/activefacts/input/orm.rb +158 -121
- data/lib/activefacts/persistence/columns.rb +1 -11
- data/lib/activefacts/persistence/foreignkey.rb +5 -6
- data/lib/activefacts/persistence/reference.rb +21 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +236 -8
- data/lib/activefacts/vocabulary/metamodel.rb +11 -0
- data/lib/activefacts/vocabulary/query_evaluator.rb +304 -0
- metadata +11 -2
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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? && !
|
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(
|
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
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
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
|