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