activefacts-generators 1.8.3 → 1.9.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/activefacts-generators.gemspec +3 -3
- data/lib/activefacts/dependency_analyser.rb +80 -80
- data/lib/activefacts/generators/absorption.rb +1 -1
- data/lib/activefacts/generators/composition.rb +76 -76
- data/lib/activefacts/generators/cql.rb +73 -73
- data/lib/activefacts/generators/diagrams/json.rb +313 -313
- data/lib/activefacts/generators/help.rb +10 -10
- data/lib/activefacts/generators/helpers/inject.rb +5 -5
- data/lib/activefacts/generators/helpers/oo.rb +5 -5
- data/lib/activefacts/generators/helpers/ordered.rb +51 -51
- data/lib/activefacts/generators/html/glossary.rb +241 -241
- data/lib/activefacts/generators/metadata/json.rb +155 -155
- data/lib/activefacts/generators/ruby.rb +4 -4
- data/lib/activefacts/generators/scala.rb +48 -48
- data/lib/activefacts/generators/sql/server.rb +3 -3
- data/lib/activefacts/generators/stats.rb +37 -37
- data/lib/activefacts/generators/traits/datavault.rb +217 -217
- data/lib/activefacts/generators/traits/oo.rb +13 -13
- data/lib/activefacts/generators/traits/ordered.rb +8 -8
- data/lib/activefacts/generators/traits/ruby.rb +145 -145
- data/lib/activefacts/generators/traits/scala.rb +319 -319
- data/lib/activefacts/generators/transform/datavault.rb +282 -282
- metadata +6 -12
@@ -24,288 +24,288 @@ module ActiveFacts
|
|
24
24
|
module Generators #:nodoc:
|
25
25
|
module Transform #:nodoc:
|
26
26
|
class DataVault
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
27
|
+
def initialize(vocabulary, *options)
|
28
|
+
@vocabulary = vocabulary
|
29
|
+
@constellation = vocabulary.constellation
|
30
|
+
end
|
31
|
+
|
32
|
+
def classify_tables
|
33
|
+
initial_tables = @vocabulary.tables
|
34
|
+
non_reference_tables = initial_tables.reject do |table|
|
35
|
+
table.concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'static'} or
|
36
|
+
!table.is_a?(ActiveFacts::Metamodel::EntityType)
|
37
|
+
end
|
38
|
+
@reference_tables = initial_tables-non_reference_tables
|
39
|
+
|
40
|
+
@link_tables, @hub_tables = non_reference_tables.partition do |table|
|
41
|
+
identifying_references = table.identifier_columns.map{|c| c.references.first}.uniq
|
42
|
+
# Which identifying_references are played by other tables?
|
43
|
+
ir_tables =
|
44
|
+
identifying_references.select do |r|
|
45
|
+
table_referred_to = r.to
|
46
|
+
# I have no examples of multi-level absorption, but it's possible, so loop
|
47
|
+
while av = table_referred_to.absorbed_via
|
48
|
+
table_referred_to = av.from
|
49
|
+
end
|
50
|
+
table_referred_to.is_table
|
51
|
+
end
|
52
|
+
ir_tables.size > 1
|
53
|
+
end
|
54
|
+
trace_table_classifications
|
55
|
+
end
|
56
|
+
|
57
|
+
def trace_table_classifications
|
58
|
+
# Trace the decisions about table types:
|
59
|
+
if trace :datavault
|
60
|
+
[@reference_tables, @hub_tables, @link_tables].zip(['Reference', 'Hub', 'Link']).each do |tables, kind|
|
61
|
+
trace :datavault, kind+' tables:' do
|
62
|
+
tables.each do |table|
|
63
|
+
identifying_references = table.identifier_columns.map{|c| c.references.first}.uniq
|
64
|
+
trace :datavault, "#{table.name}(#{identifying_references.map{|r| (t = r.to) && t.name || 'self'}*', '})"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def detect_required_surrogates
|
72
|
+
trace :datavault, "Detecting required surrogates" do
|
73
|
+
@required_surrogates =
|
74
|
+
(@hub_tables+@link_tables).select do |table|
|
75
|
+
table.dv_needs_surrogate
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def inject_required_surrogates
|
81
|
+
trace :datavault, "Injecting any required surrogates" do
|
82
|
+
trace :datavault, "Need to inject surrogates into #{@required_surrogates.map(&:name)*', '}"
|
83
|
+
@required_surrogates.each do |table|
|
84
|
+
table.dv_inject_surrogate
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def classify_satellite_references table
|
90
|
+
identifying_references = table.identifier_columns.map{|c| c.references.first}.uniq
|
91
|
+
non_identifying_references = table.columns.map{|c| c.references[0]}.uniq - identifying_references
|
92
|
+
|
93
|
+
# Skip this table if no satellite data is needed
|
94
|
+
# REVISIT: Needed anyway for a link?
|
95
|
+
if non_identifying_references.size == 0
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
satellites = non_identifying_references.inject({}) do |hash, ref|
|
100
|
+
# Extract the declared satellite name, or use just "satellite"
|
101
|
+
satellite_subname =
|
102
|
+
ref.fact_type.internal_presence_constraints.map do |pc|
|
103
|
+
next if !pc.max_frequency || pc.max_frequency > 1 # Not a Uniqueness Constraint
|
104
|
+
next if pc.role_sequence.all_role_ref.size > 1 # Covers more than one role
|
105
|
+
next if pc.role_sequence.all_role_ref.single.role.object_type != table # Not a unique attribute
|
106
|
+
pc.concept.all_concept_annotation.map do |ca|
|
107
|
+
if ca.mapping_annotation =~ /^satellite */
|
108
|
+
ca.mapping_annotation.sub(/^satellite +/, '')
|
109
|
+
else
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end.flatten.compact.uniq[0] || table.name
|
114
|
+
satellite_name = satellite_subname
|
115
|
+
(hash[satellite_name] ||= []) << ref
|
116
|
+
hash
|
117
|
+
end
|
118
|
+
trace :datavault, "#{table.name} satellites are #{satellites.inspect}"
|
119
|
+
satellites
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_one_to_many(one, many, predicate_1 = 'has', predicate_2 = 'is of', one_adj = nil)
|
123
|
+
# Create a fact type
|
124
|
+
fact_type = @constellation.FactType(:concept => :new)
|
125
|
+
one_role = @constellation.Role(:concept => :new, :fact_type => fact_type, :ordinal => 0, :object_type => one)
|
126
|
+
many_role = @constellation.Role(:concept => :new, :fact_type => fact_type, :ordinal => 1, :object_type => many)
|
127
|
+
|
128
|
+
# Create two readings
|
129
|
+
reading2 = @constellation.Reading(:fact_type => fact_type, :ordinal => 0, :role_sequence => [:new], :text => "{0} #{predicate_2} {1}")
|
130
|
+
@constellation.RoleRef(:role_sequence => reading2.role_sequence, :ordinal => 0, :role => many_role)
|
131
|
+
@constellation.RoleRef(:role_sequence => reading2.role_sequence, :ordinal => 1, :role => one_role, :leading_adjective => one_adj)
|
132
|
+
|
133
|
+
reading1 = @constellation.Reading(:fact_type => fact_type, :ordinal => 1, :role_sequence => [:new], :text => "{0} #{predicate_1} {1}")
|
134
|
+
@constellation.RoleRef(:role_sequence => reading1.role_sequence, :ordinal => 0, :role => one_role, :leading_adjective => one_adj)
|
135
|
+
@constellation.RoleRef(:role_sequence => reading1.role_sequence, :ordinal => 1, :role => many_role)
|
136
|
+
|
137
|
+
one_id = @constellation.PresenceConstraint(
|
138
|
+
:concept => :new,
|
139
|
+
:vocabulary => @vocabulary,
|
140
|
+
:name => one.name+'HasOne'+many.name,
|
141
|
+
:role_sequence => [:new],
|
142
|
+
:is_mandatory => true,
|
143
|
+
:min_frequency => 1,
|
144
|
+
:max_frequency => 1,
|
145
|
+
:is_preferred_identifier => false
|
146
|
+
)
|
147
|
+
@constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => many_role)
|
148
|
+
one_role
|
149
|
+
end
|
150
|
+
|
151
|
+
def assert_value_type name, supertype = nil
|
152
|
+
@vocabulary.valid_value_type_name(name) ||
|
153
|
+
@constellation.ValueType(:vocabulary => @vocabulary, :name => name, :supertype => supertype, :concept => :new)
|
154
|
+
end
|
155
|
+
|
156
|
+
def assert_record_source
|
157
|
+
assert_value_type('Record Source', assert_value_type('String'))
|
158
|
+
end
|
159
|
+
|
160
|
+
def assert_date_time
|
161
|
+
assert_value_type('Date Time')
|
162
|
+
end
|
163
|
+
|
164
|
+
# Create a PresenceConstraint with two roles, marked as preferred_identifier
|
165
|
+
def create_two_role_identifier(r1, r2)
|
166
|
+
pc = @constellation.PresenceConstraint(
|
167
|
+
:concept => :new,
|
168
|
+
:vocabulary => @vocabulary,
|
169
|
+
:name => r1.object_type.name+' '+r1.object_type.name+'PK',
|
170
|
+
:role_sequence => [:new],
|
171
|
+
:is_mandatory => true,
|
172
|
+
:min_frequency => 1,
|
173
|
+
:max_frequency => 1,
|
174
|
+
:is_preferred_identifier => true
|
175
|
+
)
|
176
|
+
@constellation.RoleRef(:role_sequence => pc.role_sequence, :ordinal => 0, :role => r1)
|
177
|
+
@constellation.RoleRef(:role_sequence => pc.role_sequence, :ordinal => 1, :role => r2)
|
178
|
+
end
|
179
|
+
|
180
|
+
def lift_role_to_link(ref, table_role)
|
181
|
+
trace :datavault, "Broaden #{ref} into a new link"
|
182
|
+
uc = table_role.uniqueness_constraint
|
183
|
+
one_to_one_constraint = ref.fact_type.internal_presence_constraints.detect{|pc| pc != uc }
|
184
|
+
|
185
|
+
# Any query Step or Reading on this fact type should be unaffected
|
186
|
+
|
187
|
+
# Make a new RoleRef for the uniqueness constraint so it spans
|
188
|
+
uc.constellation.RoleRef(uc.role_sequence, 1, :role => ref.to_role)
|
189
|
+
one_to_one_constraint.retract if one_to_one_constraint
|
190
|
+
|
191
|
+
# Add the objectifying entity type:
|
192
|
+
et = uc.constellation.EntityType(
|
193
|
+
uc.vocabulary,
|
194
|
+
"#{ref.from.name} #{ref.to_names*' '}",
|
195
|
+
:fact_type => ref.fact_type,
|
196
|
+
:concept => :new
|
197
|
+
)
|
198
|
+
|
199
|
+
# REVISIT: Objectifying requires creation of LinkFactTypes.
|
200
|
+
|
201
|
+
@link_tables << et
|
202
|
+
end
|
203
|
+
|
204
|
+
def create_satellite(table, satellite_name, references)
|
205
|
+
satellite_name = satellite_name.words.titlewords*' '+' SAT'
|
206
|
+
|
207
|
+
# Create a new entity type with record-date fields in its identifier
|
208
|
+
trace :datavault, "Creating #{satellite_name} with #{references.size} references"
|
209
|
+
satellite = @constellation.EntityType(:vocabulary => @vocabulary, :name => "#{satellite_name}", :concept => [:new, :implication_rule => "datavault"])
|
210
|
+
satellite.definitely_table
|
211
|
+
|
212
|
+
table_role = create_one_to_many(table, satellite)
|
213
|
+
|
214
|
+
date_time = assert_date_time
|
215
|
+
date_time_role = create_one_to_many(date_time, satellite, 'is of', 'was loaded at', 'load')
|
216
|
+
create_two_role_identifier(table_role, date_time_role)
|
217
|
+
|
218
|
+
record_source = assert_record_source
|
219
|
+
record_source.length = 64
|
220
|
+
record_source_role = create_one_to_many(record_source, satellite, 'is of', 'was loaded from')
|
221
|
+
|
222
|
+
# Move all roles across to it from the parent table.
|
223
|
+
references.each do |ref|
|
224
|
+
trace :datavault, "Moving #{ref} across to #{table.name}_#{satellite_name}" do
|
225
|
+
table_role = ref.fact_type.all_role.detect{|r| r.object_type == table}
|
226
|
+
if table_role
|
227
|
+
remote_table = ref.to
|
228
|
+
while remote_table && remote_table.absorbed_via
|
229
|
+
absorbed_into = remote_table.absorbed_via.from
|
230
|
+
remote_table = absorbed_into
|
231
|
+
end
|
232
|
+
if @hub_tables.include?(remote_table)
|
233
|
+
lift_role_to_link(ref, table_role)
|
234
|
+
else
|
235
|
+
# Reassign the role player to the satellite:
|
236
|
+
table_role.object_type = satellite
|
237
|
+
end
|
238
|
+
else
|
239
|
+
#debugger # Bum, the crappy Reference object bites again.
|
240
|
+
$stderr.puts "REVISIT: Can't move the objectified role for #{ref.inspect}. This column will remain in the hub instead of moving to the satellite"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
satellite
|
245
|
+
end
|
246
|
+
|
247
|
+
def generate(out = $stdout)
|
248
|
+
@out = out
|
249
|
+
|
250
|
+
# Strategy:
|
251
|
+
# Determine list of ER tables
|
252
|
+
# Partition tables into reference tables (annotated), link tables (two+ FKs in PK), and hub tables
|
253
|
+
# For each hub and link table
|
254
|
+
# Apply a surrogate key if needed (all links, hubs lacking a simple surrogate)
|
255
|
+
# Detect references (fact types) leading to all attributes (non-identifying columns)
|
256
|
+
# Group attribute facts into satellites (use the satellite annotation if present)
|
257
|
+
# For each satellite
|
258
|
+
# Create a new entity type with a (hub-key, record-date key)
|
259
|
+
# Make new one->many fact type between hub and satellite
|
260
|
+
# Modify all attribute facts in this group to attach to the satellite
|
261
|
+
# Compute a gresh relational mapping
|
262
|
+
# Exclude reference tables and disable enforcement to them
|
263
|
+
|
264
|
+
classify_tables
|
265
|
+
|
266
|
+
detect_required_surrogates
|
267
|
+
|
268
|
+
@sat_tables = []
|
269
|
+
trace :datavault, "Creating satellites" do
|
270
|
+
(@hub_tables+@link_tables).each do |table|
|
271
|
+
satellites = classify_satellite_references table
|
272
|
+
next unless satellites
|
273
|
+
|
274
|
+
trace :datavault, "Creating #{satellites.size} satellites for #{table.name}" do
|
275
|
+
satellites.each do |satellite_name, references|
|
276
|
+
@sat_tables << create_satellite(table, satellite_name, references)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
trace :datavault, "#{@sat_tables.size} satellite tables created"
|
282
|
+
|
283
|
+
inject_required_surrogates
|
284
|
+
|
285
|
+
trace :datavault, "Adding standard fields to hubs and links" do
|
286
|
+
(@hub_tables+@link_tables).each do |table|
|
287
|
+
date_time = assert_date_time
|
288
|
+
date_time_role = create_one_to_many(date_time, table, 'is of', 'was loaded at', 'load')
|
289
|
+
|
290
|
+
record_source = assert_record_source
|
291
|
+
record_source_role = create_one_to_many(record_source, table, 'is of', 'was loaded from')
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Now, redo the E-R mapping using the revised schema:
|
296
|
+
@vocabulary.decide_tables
|
297
|
+
|
298
|
+
# Suffix Hub and Link tables with HUB and LINK
|
299
|
+
@hub_tables.each { |h| h.name = "#{h.name} HUB"}
|
300
|
+
@link_tables.each { |l| l.name = "#{l.name} LINK"}
|
301
|
+
|
302
|
+
# Before departing, ensure we don't emit the reference tables!
|
303
|
+
@reference_tables.each do |table|
|
304
|
+
table.definitely_not_table
|
305
|
+
@vocabulary.tables.delete(table)
|
306
|
+
end
|
307
|
+
|
308
|
+
end # generate
|
309
309
|
|
310
310
|
end
|
311
311
|
end
|