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