activefacts-compositions 1.9.10 → 1.9.12
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/.gitignore +1 -0
- data/activefacts-compositions.gemspec +6 -5
- data/bin/afcomp +192 -0
- data/bin/schema_compositor +26 -11
- data/lib/activefacts/compositions/binary.rb +4 -0
- data/lib/activefacts/compositions/compositor.rb +79 -7
- data/lib/activefacts/compositions/datavault.rb +308 -110
- data/lib/activefacts/compositions/docgraph.rb +798 -0
- data/lib/activefacts/compositions/relational.rb +8 -8
- data/lib/activefacts/compositions/staging.rb +3 -2
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/cwm.rb +11 -17
- data/lib/activefacts/generator/oo.rb +2 -3
- data/lib/activefacts/generator/rails/models.rb +2 -3
- data/lib/activefacts/generator/rails/schema.rb +2 -3
- data/lib/activefacts/generator/ruby.rb +1 -2
- data/lib/activefacts/generator/sql.rb +29 -15
- data/lib/activefacts/generator/summary.rb +22 -17
- data/lib/activefacts/generator/transgen.rb +144 -0
- data/lib/activefacts/generator/validate.rb +3 -3
- metadata +59 -21
@@ -10,6 +10,12 @@ require "activefacts/compositions/relational"
|
|
10
10
|
module ActiveFacts
|
11
11
|
module Compositions
|
12
12
|
class DataVault < Relational
|
13
|
+
BDV_ANNOTATIONS = /same as link|hierarchy link|computed link|exploration link|computed satellite|point in time|bridge/
|
14
|
+
BDV_LINK_ANNOTATIONS = /same as link|hierarchy link|computed link|exploration link/
|
15
|
+
BDV_SAT_ANNOTATIONS = /computed satellite/
|
16
|
+
BDV_PIT_ANNOTATIONS = /point in time/
|
17
|
+
BDV_BRIDGE_ANNOTATIONS = /bridge/
|
18
|
+
|
13
19
|
public
|
14
20
|
def self.options
|
15
21
|
{
|
@@ -19,8 +25,11 @@ module ActiveFacts
|
|
19
25
|
hubname: ['String', "Suffix or pattern for naming hub tables. Include a + to insert the name. Default 'HUB'"],
|
20
26
|
linkname: ['String', "Suffix or pattern for naming link tables. Include a + to insert the name. Default 'LINK'"],
|
21
27
|
satname: ['String', "Suffix or pattern for naming satellite tables. Include a + to insert the name. Default 'SAT'"],
|
28
|
+
pitname: ['String', "Suffix or pattern for naming point in time tables. Include a + to insert the name. Default 'PIT'"],
|
29
|
+
bridgename: ['String', "Suffix or pattern for naming bridge tables. Include a + to insert the name. Default 'BRIDGE'"],
|
22
30
|
refname: ['String', "Suffix or pattern for naming reference tables. Include a + to insert the name. Default '+'"],
|
23
|
-
}
|
31
|
+
}.merge(Relational.options).
|
32
|
+
reject{|k,v| [:surrogates].include?(k) } # Datavault surrogates are not optional
|
24
33
|
end
|
25
34
|
|
26
35
|
def initialize constellation, name, options = {}
|
@@ -34,10 +43,14 @@ module ActiveFacts
|
|
34
43
|
@option_link_name.sub!(/^/,'+ ') unless @option_link_name =~ /\+/
|
35
44
|
@option_sat_name = options.delete('satname') || 'SAT'
|
36
45
|
@option_sat_name.sub!(/^/,'+ ') unless @option_sat_name =~ /\+/
|
46
|
+
@option_pit_name = options.delete('refname') || 'PIT'
|
47
|
+
@option_pit_name.sub!(/^/,'+ ') unless @option_ref_name =~ /\+/
|
48
|
+
@option_bridge_name = options.delete('refname') || 'BRIDGE'
|
49
|
+
@option_bridge_name.sub!(/^/,'+ ') unless @option_ref_name =~ /\+/
|
37
50
|
@option_ref_name = options.delete('refname') || 'REF'
|
38
51
|
@option_ref_name.sub!(/^/,'+ ') unless @option_ref_name =~ /\+/
|
39
52
|
|
40
|
-
super constellation, name, options
|
53
|
+
super constellation, name, options, 'DataVault'
|
41
54
|
|
42
55
|
@option_surrogates = true # Always inject surrogates regardless of superclass
|
43
56
|
end
|
@@ -51,10 +64,20 @@ module ActiveFacts
|
|
51
64
|
|
52
65
|
def composite_is_reference composite
|
53
66
|
object_type = composite.mapping.object_type
|
54
|
-
object_type.concept.all_concept_annotation
|
67
|
+
all_ca = object_type.concept.all_concept_annotation
|
68
|
+
|
69
|
+
all_ca.detect{|ca| ca.mapping_annotation == 'static'} or
|
55
70
|
!object_type.is_a?(ActiveFacts::Metamodel::EntityType)
|
56
71
|
end
|
57
72
|
|
73
|
+
def composite_is_bdv composite
|
74
|
+
object_type = composite.mapping.object_type
|
75
|
+
all_ca = object_type.concept.all_concept_annotation
|
76
|
+
|
77
|
+
trace :datavault, "composite #{composite.mapping.name} annotations #{all_ca.map{|ca| ca.mapping_annotation} *' '}"
|
78
|
+
all_ca.detect{|ca| ca.mapping_annotation =~ BDV_ANNOTATIONS}
|
79
|
+
end
|
80
|
+
|
58
81
|
# Data Vaults need a surrogate key on every Hub and Link.
|
59
82
|
# Don't add a surrogate on a Reference table!
|
60
83
|
def needs_surrogate(composite)
|
@@ -74,72 +97,151 @@ module ActiveFacts
|
|
74
97
|
def classify_composites
|
75
98
|
detect_reference_tables
|
76
99
|
|
77
|
-
|
100
|
+
@bdv_composites, @rdv_composites =
|
101
|
+
@non_reference_composites.partition { |composite| composite_is_bdv(composite) }
|
102
|
+
|
103
|
+
trace :datavault, "Classify non-reference composites into hubs, links, pits and bridges" do
|
78
104
|
# Make an initial determination, then adjust for foreign keys to links afterwards
|
105
|
+
@hub_composites = []
|
106
|
+
@link_composites = []
|
107
|
+
@sat_composites = []
|
108
|
+
@bdv_link_composites = []
|
109
|
+
@bdv_sat_composites = []
|
110
|
+
@pit_composites = []
|
111
|
+
@bridge_composites = []
|
79
112
|
@key_structure = {}
|
80
|
-
@
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
113
|
+
@links_as_hubs = {}
|
114
|
+
@pit_hub = {}
|
115
|
+
@pit_members = {}
|
116
|
+
@pit_satellite = {}
|
117
|
+
|
118
|
+
rdv_classify_composites
|
119
|
+
bdv_classify_composites
|
120
|
+
|
121
|
+
trace :datavault_classification!, "Data Vault classification of composites:" do
|
122
|
+
trace :datavault, "Reference: #{@reference_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
123
|
+
trace :datavault, "Raw: #{@rdv_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
124
|
+
trace :datavault, "Business: #{@bdv_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
125
|
+
trace :datavault, "Hub: #{@hub_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
126
|
+
trace :datavault, "Link: #{@link_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
127
|
+
trace :datavault, "BDV Link: #{@bdv_link_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
128
|
+
trace :datavault, "BDV Sat: #{@bdv_sat_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
129
|
+
trace :datavault, "PIT: #{@pit_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
130
|
+
trace :datavault, "Bridge: #{@bridge_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
93
134
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
135
|
+
def bdv_classify_composites
|
136
|
+
# Classify the business links and bridges
|
137
|
+
@bdv_composites.sort_by{|c| c.mapping.name}.each do |composite|
|
138
|
+
trace :datavault, "Decide whether #{composite.mapping.name} is a link or bridge"
|
139
|
+
object_type = composite.mapping.object_type
|
140
|
+
composite.composite_group = 'bdv'
|
141
|
+
mapped_to = object_type.fact_type.all_role.to_a
|
142
|
+
trace :datavault, "#{composite.mapping.name} encloses foreign keys to #{mapped_to.inspect}" unless mapped_to.compact.empty?
|
143
|
+
|
144
|
+
all_ca = composite.mapping.object_type.concept.all_concept_annotation
|
145
|
+
if all_ca.detect{ |ca| ca.mapping_annotation =~ BDV_LINK_ANNOTATIONS}
|
146
|
+
@bdv_link_composites << composite
|
147
|
+
elsif all_ca.detect{ |ca| ca.mapping_annotation =~ BDV_BRIDGE_ANNOTATIONS}
|
148
|
+
@bridge_composites << composite
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Classify the point in time tables
|
153
|
+
@rdv_composites.sort_by{|c| c.mapping.name}.each do |composite|
|
154
|
+
composite.composite_group = 'rdv'
|
155
|
+
trace :datavault, "Decide whether #{composite.mapping.name} has point in time table"
|
156
|
+
|
157
|
+
pit_members = composite.mapping.all_member.select do |member|
|
158
|
+
if member.is_a?(MM::Absorption)
|
159
|
+
if found_pit = check_pit(member)
|
160
|
+
trace :datavault, "Found PIT member #{member.child_role.object_type.name}"
|
107
161
|
end
|
162
|
+
found_pit
|
163
|
+
else
|
164
|
+
false
|
108
165
|
end
|
166
|
+
end
|
109
167
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
168
|
+
if pit_members.size > 0
|
169
|
+
pit_composite = create_pit(composite.mapping.name, composite)
|
170
|
+
@pit_composites << pit_composite
|
171
|
+
@pit_hub[pit_composite] = composite
|
172
|
+
@pit_members[pit_composite] = pit_members
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def check_pit component
|
178
|
+
all_pc = component.parent_role.all_role_ref.map(&:role_sequence).uniq.flat_map(&:all_presence_constraint).uniq
|
179
|
+
all_pc.detect do |pc|
|
180
|
+
pc.concept.all_concept_annotation.detect{ |ca| ca.mapping_annotation =~ BDV_PIT_ANNOTATIONS}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Create a new PIT for the same object_type as this composite
|
185
|
+
def create_pit name, composite
|
186
|
+
mapping = @constellation.Mapping(:new, name: name, object_type: composite.mapping.object_type)
|
187
|
+
@constellation.Composite(mapping, composition: @composition, composite_group: 'bdv')
|
188
|
+
end
|
189
|
+
|
190
|
+
def rdv_classify_composites
|
191
|
+
@link_composites, @hub_composites =
|
192
|
+
@rdv_composites.
|
193
|
+
sort_by{|c| c.mapping.name}.
|
194
|
+
partition do |composite|
|
195
|
+
trace :datavault, "Decide whether #{composite.mapping.name} is a link or a hub" do
|
196
|
+
@key_structure[composite] =
|
197
|
+
mapped_to =
|
198
|
+
composite_key_structure composite
|
199
|
+
|
200
|
+
# It's a Link if the preferred identifier includes more than non_reference_composite.
|
201
|
+
mapped_to.compact.size > 1
|
116
202
|
end
|
203
|
+
end
|
117
204
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
205
|
+
trace :datavault, "Checking for foreign keys that reference links" do
|
206
|
+
# Links may never be the target of a foreign key.
|
207
|
+
# Any such links must be defined as hubs instead.
|
208
|
+
|
209
|
+
fk_dependencies_by_target = {}
|
210
|
+
fk_dependencies_by_source = {}
|
211
|
+
(@hub_composites+@link_composites).each do |composite|
|
212
|
+
target_composites = enumerate_foreign_keys composite.mapping
|
213
|
+
target_composites.each do |target_composite|
|
214
|
+
next if @reference_composites.include?(target_composite)
|
215
|
+
(fk_dependencies_by_target[target_composite] ||= []) << composite
|
216
|
+
(fk_dependencies_by_source[composite] ||= []) << target_composite
|
217
|
+
end
|
218
|
+
end
|
130
219
|
|
220
|
+
fk_dependencies_by_target.keys.each do |target_composite|
|
221
|
+
if @link_composites.delete(target_composite)
|
222
|
+
trace :datavault, "Link #{target_composite.inspect} must be a hub because foreign keys reference it"
|
223
|
+
@hub_composites << target_composite
|
224
|
+
@links_as_hubs[target_composite] = true
|
225
|
+
end
|
131
226
|
end
|
132
227
|
|
228
|
+
begin
|
229
|
+
converted =
|
230
|
+
@link_composites.select do |composite|
|
231
|
+
targets = fk_dependencies_by_source[composite]
|
232
|
+
id_targets = composite_key_structure(composite).compact
|
233
|
+
next if targets.size == id_targets.size
|
234
|
+
trace :datavault, "Link #{composite.mapping.name} must be a hub because it has non-identifying FK references"
|
235
|
+
@link_composites.delete(composite)
|
236
|
+
@hub_composites << composite
|
237
|
+
@links_as_hubs[composite] = true
|
238
|
+
end
|
239
|
+
end while converted.size > 0
|
240
|
+
|
133
241
|
# Note: We may still have hubs whose identifiers contain foreign keys to one or more other hubs.
|
134
242
|
# REVISIT: These foreign keys will be deleted so these hubs stand alone,
|
135
243
|
# but have been re-instated as new links to the referenced hubs.
|
136
244
|
end
|
137
|
-
|
138
|
-
trace :datavault_classification!, "Data Vault classification of composites:" do
|
139
|
-
trace :datavault, "Reference: #{@reference_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
140
|
-
trace :datavault, "Hub: #{@hub_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
141
|
-
trace :datavault, "Link: #{@link_composites.map(&:mapping).map(&:object_type).map(&:name)*', '}"
|
142
|
-
end
|
143
245
|
end
|
144
246
|
|
145
247
|
def detect_reference_tables
|
@@ -148,19 +250,57 @@ module ActiveFacts
|
|
148
250
|
initial_composites.partition { |composite| composite_is_reference(composite) }
|
149
251
|
end
|
150
252
|
|
253
|
+
def composite_key_structure composite
|
254
|
+
# We know that composite.mapping.object_type is an EntityType because all ValueType composites are reference tables
|
255
|
+
object_type = composite.mapping.object_type
|
256
|
+
|
257
|
+
mapped_to =
|
258
|
+
object_type.preferred_identifier.role_sequence.all_role_ref_in_order.map do |role_ref|
|
259
|
+
player = role_ref.role.object_type
|
260
|
+
next nil if player == object_type && role_ref.role.fact_type.all_role.size == 1 # Unaries.
|
261
|
+
candidate = @candidates[player]
|
262
|
+
next nil unless candidate
|
263
|
+
# Take care of full absorption
|
264
|
+
while candidate.full_absorption
|
265
|
+
candidate = candidate.full_absorption.composition
|
266
|
+
end
|
267
|
+
@non_reference_composites.include?(c = candidate.mapping.composite) ? c : nil
|
268
|
+
end
|
269
|
+
|
270
|
+
trace :datavault, "Preferred identifier for #{composite.mapping.name} encloses foreign keys to #{mapped_to.inspect}" unless mapped_to.compact.empty?
|
271
|
+
|
272
|
+
number_of_keys = mapped_to.compact.size
|
273
|
+
number_of_values = mapped_to.size-number_of_keys
|
274
|
+
trace :datavault_classify,
|
275
|
+
if number_of_keys > 1
|
276
|
+
# Links have more than one FK to a hub in their key
|
277
|
+
"Link #{composite.mapping.name} links #{mapped_to.compact.inspect} with #{number_of_values} values"
|
278
|
+
elsif number_of_keys == 1 && number_of_values > 0
|
279
|
+
# This is a new hub with a composite key - but we will have to eliminate the foreign key to the base hub
|
280
|
+
"Augmented Hub #{composite.mapping.name} has a hub link to #{mapped_to.compact[0].inspect} and #{number_of_values} values"
|
281
|
+
elsif number_of_keys == 1
|
282
|
+
# This is a new hub with a single-part key that references another hub.
|
283
|
+
"Dependent Hub #{composite.mapping.name} is identified by another hub: #{mapped_to.compact[0].inspect}"
|
284
|
+
else
|
285
|
+
"Hub #{composite.mapping.name} has #{mapped_to.size} parts in its key"
|
286
|
+
end
|
287
|
+
|
288
|
+
mapped_to
|
289
|
+
end
|
290
|
+
|
151
291
|
def devolve_all
|
152
292
|
delete_reference_table_foreign_keys
|
153
293
|
|
154
294
|
# For each hub and link, move each non-identifying member
|
155
295
|
# to a new satellite or promote it to a new link.
|
156
|
-
|
157
|
-
@non_reference_composites.
|
158
|
-
each do |composite|
|
296
|
+
(@hub_composites + @link_composites).each do |composite|
|
159
297
|
devolve composite
|
160
298
|
end
|
161
299
|
|
300
|
+
# Rename parents for rdv and bdv
|
162
301
|
rename_parents
|
163
302
|
|
303
|
+
# inject datetime and record source for rdv hubs and links
|
164
304
|
inject_all_datetime_recordsource
|
165
305
|
|
166
306
|
unless @option_reference
|
@@ -180,6 +320,78 @@ module ActiveFacts
|
|
180
320
|
|
181
321
|
@constellation.loggers.pop if trace :reference_retraction
|
182
322
|
end
|
323
|
+
|
324
|
+
# Devolve point in time tables, if any
|
325
|
+
@pit_composites.each do |composite|
|
326
|
+
devolve_pit(composite)
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
def devolve_pit(pit_composite)
|
332
|
+
# inject standard PIT components: surrogate key, hub hash key and snapshot date time
|
333
|
+
inject_surrogate(pit_composite)
|
334
|
+
|
335
|
+
hub_composite = @pit_hub[pit_composite]
|
336
|
+
hub_hash_field = hub_composite.primary_index.all_index_field.single.component
|
337
|
+
pit_hub_field = hub_hash_field.fork_to_new_parent(pit_composite.mapping)
|
338
|
+
date_field = @constellation.ValidFrom(:new,
|
339
|
+
parent: pit_composite.mapping,
|
340
|
+
name: "Snapshot"+datestamp_type_name,
|
341
|
+
object_type: datestamp_type
|
342
|
+
)
|
343
|
+
|
344
|
+
natural_index =
|
345
|
+
@constellation.Index(:new, composite: pit_composite, is_unique: true,
|
346
|
+
presence_constraint: nil, composite_as_natural_index: pit_composite #, composite_as_primary_index: pit_composite
|
347
|
+
)
|
348
|
+
@constellation.IndexField(access_path: natural_index, ordinal: 0, component: pit_hub_field)
|
349
|
+
@constellation.IndexField(access_path: natural_index, ordinal: 1, component: date_field)
|
350
|
+
|
351
|
+
# Add a foreign key to the hub
|
352
|
+
fk = @constellation.ForeignKey(
|
353
|
+
:new,
|
354
|
+
source_composite: pit_composite,
|
355
|
+
composite: hub_composite,
|
356
|
+
absorption: nil # REVISIT: This is a ForeignKey without its mandatory Absorption. That's gonna hurt
|
357
|
+
)
|
358
|
+
@constellation.ForeignKeyField(foreign_key: fk, ordinal: 0, component: pit_hub_field)
|
359
|
+
# This should be filled in by complete_foreign_keys, but there is no Absorption
|
360
|
+
@constellation.IndexField(access_path: fk, ordinal: 0, component: hub_hash_field)
|
361
|
+
|
362
|
+
# inject hash and load date time for sats associated with all pit members
|
363
|
+
pit_members = @pit_members[pit_composite]
|
364
|
+
sat_composites = pit_members.map{|pm| @pit_satellite[pm]}.compact.uniq
|
365
|
+
|
366
|
+
sat_composites.each do |sat_composite|
|
367
|
+
sat_name = sat_composite.mapping.name
|
368
|
+
sat_hash_name = "#{sat_name}#{@option_id}"
|
369
|
+
|
370
|
+
src_hash_field = hub_hash_field.fork_to_new_parent(pit_composite.mapping)
|
371
|
+
src_hash_field.name = sat_hash_name
|
372
|
+
src_load_field = @constellation.ValidFrom(:new,
|
373
|
+
parent: pit_composite.mapping,
|
374
|
+
name: "#{sat_name} Load"+datestamp_type_name,
|
375
|
+
object_type: datestamp_type
|
376
|
+
)
|
377
|
+
|
378
|
+
sat_index_fields = sat_composite.primary_index.all_index_field.to_a
|
379
|
+
sat_hash_field = sat_index_fields[0].component
|
380
|
+
sat_load_field = sat_index_fields[1].component
|
381
|
+
|
382
|
+
# Add a foreign key to the satellite's primary key and load date time
|
383
|
+
fk = @constellation.ForeignKey(
|
384
|
+
:new,
|
385
|
+
source_composite: pit_composite,
|
386
|
+
composite: sat_composite,
|
387
|
+
absorption: nil # REVISIT: This is a ForeignKey without its mandatory Absorption. That's gonna hurt
|
388
|
+
)
|
389
|
+
@constellation.ForeignKeyField(foreign_key: fk, ordinal: 0, component: src_hash_field)
|
390
|
+
@constellation.IndexField(access_path: fk, ordinal: 0, component: sat_hash_field)
|
391
|
+
@constellation.ForeignKeyField(foreign_key: fk, ordinal: 1, component: src_load_field)
|
392
|
+
@constellation.IndexField(access_path: fk, ordinal: 1, component: sat_load_field)
|
393
|
+
end
|
394
|
+
pit_composite.mapping.re_rank
|
183
395
|
end
|
184
396
|
|
185
397
|
def delete_reference_table_foreign_keys
|
@@ -192,48 +404,10 @@ module ActiveFacts
|
|
192
404
|
end
|
193
405
|
|
194
406
|
def prefer_natural_key building_natural_key, source_composite, target_composite
|
195
|
-
return false if building_natural_key && @link_composites.include?(source_composite)
|
407
|
+
return false if building_natural_key && (@link_composites.include?(source_composite) || @bdv_link_composites.include?(source_composite))
|
196
408
|
building_natural_key && @hub_composites.include?(target_composite)
|
197
409
|
end
|
198
410
|
|
199
|
-
def composite_key_structure composite
|
200
|
-
# We know that composite.mapping.object_type is an EntityType because all ValueType composites are reference tables
|
201
|
-
|
202
|
-
object_type = composite.mapping.object_type
|
203
|
-
mapped_to =
|
204
|
-
object_type.preferred_identifier.role_sequence.all_role_ref_in_order.map do |role_ref|
|
205
|
-
player = role_ref.role.object_type
|
206
|
-
next nil if player == object_type && role_ref.role.fact_type.all_role.size == 1 # Unaries.
|
207
|
-
candidate = @candidates[player]
|
208
|
-
next nil unless candidate
|
209
|
-
# Take care of full absorption
|
210
|
-
while candidate.full_absorption
|
211
|
-
candidate = candidate.full_absorption.composition
|
212
|
-
end
|
213
|
-
@non_reference_composites.include?(c = candidate.mapping.composite) ? c : nil
|
214
|
-
end
|
215
|
-
|
216
|
-
trace :datavault, "Preferred identifier for #{composite.mapping.name} encloses foreign keys to #{mapped_to.inspect}" unless mapped_to.compact.empty?
|
217
|
-
|
218
|
-
number_of_keys = mapped_to.compact.size
|
219
|
-
number_of_values = mapped_to.size-number_of_keys
|
220
|
-
trace :datavault_classify,
|
221
|
-
if number_of_keys > 1
|
222
|
-
# Links have more than one FK to a hub in their key
|
223
|
-
"Link #{composite.mapping.name} links #{mapped_to.compact.inspect} with #{number_of_values} values"
|
224
|
-
elsif number_of_keys == 1 && number_of_values > 0
|
225
|
-
# This is a new hub with a composite key - but we will have to eliminate the foreign key to the base hub
|
226
|
-
"Augmented Hub #{composite.mapping.name} has a hub link to #{mapped_to.compact[0].inspect} and #{number_of_values} values"
|
227
|
-
elsif number_of_keys == 1
|
228
|
-
# This is a new hub with a single-part key that references another hub.
|
229
|
-
"Dependent Hub #{composite.mapping.name} is identified by another hub: #{mapped_to.compact[0].inspect}"
|
230
|
-
else
|
231
|
-
"Hub #{composite.mapping.name} has #{mapped_to.size} parts in its key"
|
232
|
-
end
|
233
|
-
|
234
|
-
mapped_to
|
235
|
-
end
|
236
|
-
|
237
411
|
# For each member of this composite, decide whether to devolve it to a satellite
|
238
412
|
# or to a new link. If it goes to a link that's still part of this natural key,
|
239
413
|
# we need to leave that key intact, but remove the foreign key it entails.
|
@@ -273,12 +447,13 @@ module ActiveFacts
|
|
273
447
|
# We may absorb a subtype that has no contents. There's no point moving these to a satellite.
|
274
448
|
next if is_empty_inheritance member
|
275
449
|
|
276
|
-
satellite_name = name_satellite
|
277
|
-
satellite = satellites[satellite_name]
|
278
|
-
|
279
|
-
satellite =
|
280
|
-
|
281
|
-
|
450
|
+
satellite_name, is_computed = *name_satellite(member)
|
451
|
+
if !(satellite = satellites[satellite_name])
|
452
|
+
satellite = satellites[satellite_name] = create_satellite(satellite_name, composite)
|
453
|
+
satellite.composite_group = (is_computed ? 'bdv' : 'rdv')
|
454
|
+
if member.is_a?(MM::Absorption) && check_pit(member)
|
455
|
+
@pit_satellite[member] = satellite
|
456
|
+
end
|
282
457
|
end
|
283
458
|
|
284
459
|
devolve_member_to_satellite satellite, member
|
@@ -332,20 +507,34 @@ module ActiveFacts
|
|
332
507
|
satellite.classify_constraints
|
333
508
|
satellite.all_local_constraint.map(&:local_constraint).each(&:retract)
|
334
509
|
leaf_constraints = satellite.mapping.all_leaf.flat_map(&:all_leaf_constraint).map(&:leaf_constraint).each(&:retract)
|
510
|
+
|
511
|
+
if satellite.composite_group == 'bdv'
|
512
|
+
@bdv_sat_composites << satellite
|
513
|
+
else
|
514
|
+
@sat_composites << satellite
|
515
|
+
end
|
335
516
|
end
|
336
517
|
end
|
337
518
|
|
338
519
|
# Decide what to call a new satellite that will adopt this component
|
339
|
-
def
|
520
|
+
def satellite_base_name_and_type component
|
521
|
+
computed_name = nil
|
340
522
|
satellite_name =
|
341
523
|
if component.is_a?(MM::Absorption)
|
342
524
|
pc = component.parent_role.base_role.uniqueness_constraint and
|
343
|
-
pc.concept.all_concept_annotation.map
|
525
|
+
pc.concept.all_concept_annotation.map do |ca|
|
526
|
+
computed_name = ca.mapping_annotation =~ /^computed satellite *(.*)/ && "#{$1} Computed"
|
527
|
+
ca.mapping_annotation =~ /^satellite *(.*)/ && $1 or computed_name
|
528
|
+
end.compact.uniq[0]
|
344
529
|
# REVISIT: How do we name the satellite for an Indicator? Add a Concept Annotation on the fact type?
|
345
530
|
end
|
346
531
|
satellite_name = satellite_name.words.capcase if satellite_name
|
347
|
-
satellite_name
|
348
|
-
|
532
|
+
[ satellite_name || component.root.mapping.name, computed_name ]
|
533
|
+
end
|
534
|
+
|
535
|
+
def name_satellite component
|
536
|
+
name, is_computed = *satellite_base_name_and_type(component)
|
537
|
+
[apply_name(@option_sat_name, name), is_computed != nil]
|
349
538
|
end
|
350
539
|
|
351
540
|
# Create a new satellite for the same object_type as this composite
|
@@ -502,14 +691,23 @@ module ActiveFacts
|
|
502
691
|
end
|
503
692
|
|
504
693
|
def rename_parents
|
505
|
-
@
|
506
|
-
composite.mapping.name = apply_name(@
|
694
|
+
@reference_composites.each do |composite|
|
695
|
+
composite.mapping.name = apply_name(@option_ref_name, composite.mapping.name)
|
507
696
|
end
|
508
697
|
@hub_composites.each do |composite|
|
509
698
|
composite.mapping.name = apply_name(@option_hub_name, composite.mapping.name)
|
510
699
|
end
|
511
|
-
@
|
512
|
-
composite.mapping.name = apply_name(@
|
700
|
+
@link_composites.each do |composite|
|
701
|
+
composite.mapping.name = apply_name(@option_link_name, composite.mapping.name)
|
702
|
+
end
|
703
|
+
@bdv_link_composites.each do |composite|
|
704
|
+
composite.mapping.name = apply_name(@option_link_name, composite.mapping.name)
|
705
|
+
end
|
706
|
+
@pit_composites.each do |composite|
|
707
|
+
composite.mapping.name = apply_name(@option_pit_name, composite.mapping.name)
|
708
|
+
end
|
709
|
+
@bridge_composites.each do |composite|
|
710
|
+
composite.mapping.name = apply_name(@option_bridge_name, composite.mapping.name)
|
513
711
|
end
|
514
712
|
end
|
515
713
|
|