activefacts-compositions 1.9.17 → 1.9.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/activefacts-compositions.gemspec +2 -2
- data/lib/activefacts/compositions/binary.rb +1 -1
- data/lib/activefacts/compositions/compositor.rb +16 -12
- data/lib/activefacts/compositions/datavault.rb +110 -115
- data/lib/activefacts/compositions/relational.rb +137 -94
- data/lib/activefacts/compositions/staging.rb +89 -27
- data/lib/activefacts/compositions/traits/datavault.rb +116 -49
- data/lib/activefacts/compositions/traits/rails.rb +2 -2
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/cwm.rb +6 -18
- data/lib/activefacts/generator/doc/ldm.rb +1 -1
- data/lib/activefacts/generator/etl/unidex.rb +341 -0
- data/lib/activefacts/generator/oo.rb +31 -14
- data/lib/activefacts/generator/rails/models.rb +6 -5
- data/lib/activefacts/generator/rails/schema.rb +5 -9
- data/lib/activefacts/generator/ruby.rb +2 -2
- data/lib/activefacts/generator/sql/mysql.rb +3 -184
- data/lib/activefacts/generator/sql/oracle.rb +3 -152
- data/lib/activefacts/generator/sql/postgres.rb +3 -145
- data/lib/activefacts/generator/sql/server.rb +3 -126
- data/lib/activefacts/generator/sql.rb +54 -422
- data/lib/activefacts/generator/summary.rb +15 -6
- data/lib/activefacts/generator/traits/expr.rb +41 -0
- data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
- data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
- data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
- data/lib/activefacts/generator/traits/sql/server.rb +262 -0
- data/lib/activefacts/generator/traits/sql.rb +538 -0
- metadata +13 -8
- data/lib/activefacts/compositions/docgraph.rb +0 -798
- data/lib/activefacts/compositions/staging/persistent.rb +0 -107
@@ -17,44 +17,80 @@ module ActiveFacts
|
|
17
17
|
def self.options
|
18
18
|
datavault_options.
|
19
19
|
merge({
|
20
|
+
cdc: [%w{record satellite all}, "Add computed hash fields for change detection"],
|
21
|
+
persistent: ['Boolean', "Allow multiple batches to be loaded into the same tables"],
|
20
22
|
stgname: ['String', "Suffix or pattern for naming staging tables. Include a + to insert the name. Default 'STG'"],
|
21
|
-
fk: ['Boolean', "Retain foreign keys in the output (by default they are deleted)"]
|
22
23
|
}).
|
23
|
-
merge(Relational.options)
|
24
|
-
reject{|k,v| [:surrogates].include?(k) }
|
24
|
+
merge(Relational.options)
|
25
25
|
end
|
26
26
|
|
27
27
|
def initialize constellation, name, options = {}
|
28
28
|
# Extract recognised options:
|
29
|
-
|
29
|
+
@option_cdc = options.delete('cdc')
|
30
|
+
|
31
|
+
@option_persistent = options.delete('persistent')
|
32
|
+
options = {'surrogates'=>'Record GUID'}. # You must have surrogates, but can call them what you wish
|
33
|
+
merge(options).merge({'fk'=>'natural', 'audit'=>'batch'}) if @option_persistent # Must be batch auditing
|
34
|
+
|
30
35
|
@option_stg_name = options.delete('stgname') || 'STG'
|
31
36
|
@option_stg_name.sub!(/^/,'+ ') unless @option_stg_name =~ /\+/
|
32
37
|
|
33
|
-
|
34
|
-
@option_keep_fks = ['', true, 'true', 'yes'].include?(@option_keep_fks)
|
38
|
+
datavault_initialize options
|
35
39
|
|
40
|
+
@option_fk = :natural # Default value
|
36
41
|
super constellation, name, options, 'Staging'
|
37
42
|
end
|
38
43
|
|
39
|
-
def
|
40
|
-
create_loadbatch if @option_loadbatch
|
44
|
+
def complete_foreign_keys
|
41
45
|
super
|
46
|
+
|
47
|
+
augment_keys if @option_persistent
|
42
48
|
end
|
43
49
|
|
44
|
-
def
|
50
|
+
def needs_surrogate(composite)
|
51
|
+
@option_surrogates && composite.mapping.object_type != @loadbatch_entity_type
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate
|
55
|
+
create_loadbatch if @option_audit == 'batch'
|
45
56
|
super
|
46
|
-
inject_loadbatch_relationships if @option_loadbatch
|
47
57
|
end
|
48
58
|
|
49
|
-
|
59
|
+
# Find the leaf absorption of the LoadBatchID in composite
|
60
|
+
def load_batch_field composite
|
61
|
+
load_batch_role =
|
62
|
+
composite.mapping.object_type.all_role.detect do |role|
|
63
|
+
c = role.counterpart and c.object_type == @loadbatch_entity_type
|
64
|
+
end
|
65
|
+
trace :index, "Found LoadBatch role in #{load_batch_role.fact_type.default_reading}" if load_batch_role
|
66
|
+
# There can only be one absorption of LoadBatch, because we added it,
|
67
|
+
# but if you have separate subtypes, we need to select the one for the right composite:
|
68
|
+
absorptions = load_batch_role.
|
69
|
+
counterpart.
|
70
|
+
all_absorption_as_child_role.
|
71
|
+
select{|a| a.root == composite}
|
72
|
+
# There should now always be exactly one.
|
73
|
+
raise "Missing or ambiguous FK to LoadBatch from #{composite.inspect}" if absorptions.size != 1
|
74
|
+
absorptions[0].all_leaf[0]
|
75
|
+
# This is the absorption of LoadBatchID
|
76
|
+
end
|
77
|
+
|
78
|
+
def inject_all_audit_fields
|
79
|
+
inject_loadbatch_relationships if @option_audit == 'batch'
|
80
|
+
end
|
81
|
+
|
82
|
+
def apply_all_audit_transformations
|
50
83
|
composites = @composition.all_composite.to_a
|
51
84
|
return if composites.empty?
|
52
85
|
|
53
86
|
trace :staging, "Injecting load datetime and record source" do
|
54
87
|
@composition.all_composite.each do |composite|
|
55
|
-
|
56
|
-
|
57
|
-
|
88
|
+
is_loadbatch_composite = composite.mapping.object_type == @loadbatch_entity_type
|
89
|
+
composite.mapping.injection_annotation = 'loadbatch' if is_loadbatch_composite
|
90
|
+
if @option_audit == 'record' || is_loadbatch_composite
|
91
|
+
inject_audit_fields composite
|
92
|
+
composite.mapping.re_rank
|
93
|
+
end
|
58
94
|
end
|
59
95
|
end
|
60
96
|
end
|
@@ -63,25 +99,51 @@ module ActiveFacts
|
|
63
99
|
# Rename composites with STG prefix
|
64
100
|
apply_composite_name_pattern
|
65
101
|
|
66
|
-
|
67
|
-
end
|
102
|
+
apply_all_audit_transformations
|
68
103
|
|
69
|
-
|
70
|
-
if @option_keep_fks
|
71
|
-
super
|
72
|
-
else
|
73
|
-
retract_foreign_keys
|
74
|
-
end
|
104
|
+
super
|
75
105
|
end
|
76
106
|
|
77
|
-
def
|
78
|
-
trace :
|
107
|
+
def augment_keys
|
108
|
+
trace :index, "Augmenting keys" do
|
79
109
|
@composition.all_composite.each do |composite|
|
110
|
+
next if composite.mapping.object_type == @loadbatch_entity_type
|
111
|
+
target_batch_id = load_batch_field(composite)
|
80
112
|
composite.all_access_path.each do |path|
|
81
|
-
|
82
|
-
|
83
|
-
|
113
|
+
# Ignore foreign keys and non-unique indices:
|
114
|
+
next unless MM::Index === path and path.is_unique
|
115
|
+
|
116
|
+
# Don't meddle with the surrogate
|
117
|
+
next if path.all_index_field.size == 1 && MM::SurrogateKey === path.all_index_field.single.component
|
118
|
+
|
119
|
+
trace :index, "Add LoadBatchID to #{path.inspect}" do
|
120
|
+
@constellation.IndexField(access_path: path, ordinal: path.all_index_field.size, component: target_batch_id)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Note that the RecordGUID will have become the primary key.
|
124
|
+
# Foreign keys would be enforced onto the natural key, but we
|
125
|
+
# want the natural key to be clustering, so we switch them around
|
126
|
+
if composite.natural_index == path
|
127
|
+
pk = composite.primary_index
|
128
|
+
composite.primary_index = composite.natural_index
|
129
|
+
composite.natural_index = pk
|
130
|
+
|
131
|
+
# Fix the foreign keys that use this changed natural key:
|
132
|
+
trace :index, "Appending LoadBatch to foreign keys to #{composite.mapping.name}" do
|
133
|
+
composite.
|
134
|
+
all_foreign_key_as_target_composite.
|
135
|
+
each do |fk|
|
136
|
+
trace :index, "Appending LoadBatch to #{fk.inspect}" do
|
137
|
+
source_batch_id = load_batch_field(fk.source_composite)
|
138
|
+
trace :index, "ForeignKeyField is #{source_batch_id.root.mapping.name}.#{source_batch_id.inspect}"
|
139
|
+
trace :index, "IndexField is #{target_batch_id.root.mapping.name}.#{target_batch_id.inspect}"
|
140
|
+
@constellation.ForeignKeyField(foreign_key: fk, ordinal: fk.all_foreign_key_field.size, component: source_batch_id)
|
141
|
+
@constellation.IndexField(access_path: fk, ordinal: fk.all_index_field.size, component: target_batch_id)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
84
145
|
end
|
146
|
+
|
85
147
|
end
|
86
148
|
end
|
87
149
|
end
|
@@ -4,66 +4,133 @@ module ActiveFacts
|
|
4
4
|
module DataVault
|
5
5
|
def datavault_options
|
6
6
|
{
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# Structural options:
|
8
|
+
audit: [%w{record batch}, "Add date/source auditing fields to each record (hub/link/staging) or via a LoadBatch table"],
|
9
|
+
# Variation options:
|
10
|
+
loadbatch: ['String', "Change the name of the load batch table from LoadBatch"],
|
11
|
+
datestamp: ['String', "Data type name to use for audit date stamps (default: DateTime)"],
|
12
|
+
source: ['String', "Data type name to use for audit source (default: String)"],
|
10
13
|
}
|
11
14
|
end
|
12
15
|
|
13
16
|
def datavault_initialize options
|
14
|
-
@
|
15
|
-
|
16
|
-
@
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
@option_audit = options.delete('audit')
|
18
|
+
|
19
|
+
case @option_loadbatch = options.delete('loadbatch')
|
20
|
+
when true, 'true', 'yes'
|
21
|
+
@option_loadbatch = 'LoadBatch'
|
22
|
+
when false, 'false'
|
23
|
+
@option_loadbatch = nil
|
24
|
+
end
|
25
|
+
@option_loadbatch ||= 'LoadBatch' if @option_audit == 'batch'
|
26
|
+
|
27
|
+
case @option_datestamp = options.delete('datestamp')
|
28
|
+
when true, '', 'true', 'yes', nil
|
29
|
+
@option_datestamp = 'DateTime'
|
30
|
+
when false, 'false'
|
31
|
+
@option_datestamp = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
case @option_source = options.delete('source')
|
35
|
+
when true, '', 'true', 'yes', nil
|
36
|
+
@option_source = 'String'
|
37
|
+
when false, 'false'
|
38
|
+
@option_source = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def compile text
|
44
|
+
@vocabulary = @constellation.Vocabulary.values[0]
|
45
|
+
@compiler ||= ActiveFacts::CQL::Compiler.new(@vocabulary, constellation: @constellation)
|
46
|
+
@compiler.compile("schema #{@vocabulary.name};\n"+text)
|
21
47
|
end
|
22
48
|
|
23
49
|
def create_loadbatch
|
24
|
-
|
50
|
+
return unless @option_audit == 'batch' && @option_loadbatch
|
25
51
|
|
26
52
|
schema_text = %Q{
|
27
|
-
schema #{vocabulary.name};
|
28
|
-
|
29
53
|
each #{@option_loadbatch} ID is written as an Auto Counter auto-assigned at commit;
|
30
54
|
each #{@option_loadbatch} is independent identified by its ID;
|
31
|
-
each #{@option_datestamp} is written as a #{@option_datestamp};
|
32
|
-
#{@option_loadbatch} began at one start-#{@option_datestamp};
|
33
55
|
}
|
34
|
-
|
35
|
-
@
|
36
|
-
|
56
|
+
compile(schema_text)
|
57
|
+
@loadbatch_entity_type = @constellation.EntityType[[@vocabulary.identifying_role_values, @option_loadbatch]]
|
58
|
+
end
|
59
|
+
|
60
|
+
# This method only works after the LoadBatch composite has been asserted, of course
|
61
|
+
def loadbatch_composite
|
62
|
+
@loadbatch_composite ||= @composition.
|
63
|
+
all_composite.detect{|c| c.mapping.object_type == @loadbatch_entity_type}
|
37
64
|
end
|
38
65
|
|
39
66
|
def inject_loadbatch_relationships
|
40
|
-
return unless @
|
41
|
-
|
42
|
-
|
43
|
-
|
67
|
+
return unless @option_audit == 'batch'
|
68
|
+
trace :batch, "Injecting LoadBatch relationships" do
|
69
|
+
@composition.all_composite.each do |composite|
|
70
|
+
inject_loadbatch_relationship composite
|
71
|
+
end
|
44
72
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
def inject_loadbatch_relationship composite
|
76
|
+
return if composite == loadbatch_composite
|
77
|
+
trace :batch, "Injecting LoadBatch relationship into #{composite.mapping.name}" do
|
78
|
+
compile("#{composite.mapping.name} was loaded in one #{@option_loadbatch};")
|
79
|
+
|
80
|
+
role = composite.mapping.object_type.all_role.detect{|r| c = r.counterpart and c.object_type == @loadbatch_entity_type}
|
81
|
+
version_field = populate_reference(role)
|
82
|
+
version_field.injection_annotation = 'loadbatch'
|
83
|
+
composite.mapping.re_rank
|
84
|
+
loadbatch_composite.mapping.re_rank
|
85
|
+
version_field
|
48
86
|
end
|
49
87
|
end
|
50
88
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
89
|
+
def inject_audit_fields composite, copy_from = nil
|
90
|
+
version_field = begin
|
91
|
+
if @option_audit == 'batch' && composite != @loadbatch_composite
|
92
|
+
if copy_from
|
93
|
+
# Our hub or link has an FK to LoadBatch already. Fork a copy to use on the satellite
|
94
|
+
trace :batch, "Copying LoadBatchID from #{copy_from.mapping.name} into #{composite.mapping.name}" do
|
95
|
+
parent_version_field = @version_fields[copy_from]
|
96
|
+
load_batch = parent_version_field.fork_to_new_parent composite.mapping
|
97
|
+
leaves = parent_version_field.all_leaf
|
98
|
+
raise "WARNING: unexpected audit structure" unless leaves.size == 1
|
99
|
+
version_field = leaves[0].fork_to_new_parent load_batch
|
100
|
+
load_batch.injection_annotation =
|
101
|
+
version_field.injection_annotation =
|
102
|
+
parent_version_field.injection_annotation
|
103
|
+
version_field
|
104
|
+
end
|
105
|
+
else
|
106
|
+
inject_loadbatch_relationship composite
|
107
|
+
end
|
108
|
+
else
|
109
|
+
if datestamp_type
|
110
|
+
# Add a load DateTime value
|
111
|
+
version_field = @constellation.ValidFrom(:new,
|
112
|
+
parent: composite.mapping,
|
113
|
+
name: "LoadTime",
|
114
|
+
object_type: datestamp_type,
|
115
|
+
injection_annotation: "datavault"
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
if recordsource_type
|
120
|
+
# Add a load DateTime value
|
121
|
+
recsrc_field = @constellation.Mapping(:new,
|
122
|
+
parent: composite.mapping,
|
123
|
+
name: "RecordSource",
|
124
|
+
object_type: recordsource_type,
|
125
|
+
injection_annotation: "datavault"
|
126
|
+
)
|
127
|
+
end
|
128
|
+
version_field
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
composite.mapping.re_rank
|
133
|
+
(@version_fields ||= {})[composite] = version_field
|
67
134
|
end
|
68
135
|
|
69
136
|
def datestamp_type_name
|
@@ -72,10 +139,10 @@ module ActiveFacts
|
|
72
139
|
|
73
140
|
def datestamp_type
|
74
141
|
@datestamp_type ||= begin
|
75
|
-
vocabulary
|
76
|
-
@constellation.ObjectType[[[vocabulary.name], datestamp_type_name]] or
|
142
|
+
@vocabulary ||= @composition.all_composite.to_a[0].mapping.object_type.vocabulary
|
143
|
+
@constellation.ObjectType[[[@vocabulary.name], datestamp_type_name]] or
|
77
144
|
@constellation.ValueType(
|
78
|
-
vocabulary: vocabulary,
|
145
|
+
vocabulary: @vocabulary,
|
79
146
|
name: datestamp_type_name,
|
80
147
|
concept: [:new, :implication_rule => "datestamp injection"]
|
81
148
|
)
|
@@ -83,15 +150,15 @@ module ActiveFacts
|
|
83
150
|
end
|
84
151
|
|
85
152
|
def recordsource_type_name
|
86
|
-
@
|
153
|
+
@option_source
|
87
154
|
end
|
88
155
|
|
89
156
|
def recordsource_type
|
90
157
|
@recordsource_type ||= begin
|
91
|
-
vocabulary
|
92
|
-
@constellation.ObjectType[[[vocabulary.name], recordsource_type_name]] or
|
158
|
+
@vocabulary ||= @composition.all_composite.to_a[0].mapping.object_type.vocabulary
|
159
|
+
@constellation.ObjectType[[[@vocabulary.name], recordsource_type_name]] or
|
93
160
|
@constellation.ValueType(
|
94
|
-
vocabulary: vocabulary,
|
161
|
+
vocabulary: @vocabulary,
|
95
162
|
name: recordsource_type_name,
|
96
163
|
concept: [:new]
|
97
164
|
)
|
@@ -107,7 +174,7 @@ module ActiveFacts
|
|
107
174
|
|
108
175
|
def apply_composite_name_pattern
|
109
176
|
@composites.each do |key, composite|
|
110
|
-
next if composite.mapping.
|
177
|
+
next if composite.mapping.name == @option_loadbatch
|
111
178
|
composite.mapping.name = apply_name_pattern(@option_stg_name, composite.mapping.name)
|
112
179
|
end
|
113
180
|
end
|
@@ -145,11 +145,11 @@ module ActiveFacts
|
|
145
145
|
# respective Composites. The name of a foreign key takes this into account.
|
146
146
|
|
147
147
|
def from_association_name
|
148
|
-
|
148
|
+
mapping.column_name.snakecase
|
149
149
|
end
|
150
150
|
|
151
151
|
def to_association
|
152
|
-
if
|
152
|
+
if mapping && mapping.child_role.is_unique
|
153
153
|
[ "has_one", source_composite.rails.singular_name]
|
154
154
|
else
|
155
155
|
[ "has_many", source_composite.rails.plural_name]
|
@@ -142,7 +142,7 @@ module ActiveFacts
|
|
142
142
|
nsdef(index)
|
143
143
|
index.all_index_field.map{|idf| idf.component.index_xmiid = index.xmiid}
|
144
144
|
end
|
145
|
-
table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.
|
145
|
+
table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect] }.map do |fk|
|
146
146
|
nsdef(fk)
|
147
147
|
end
|
148
148
|
end
|
@@ -226,7 +226,7 @@ module ActiveFacts
|
|
226
226
|
generate_index(depth+2, table.xmiid, index, name, table.all_foreign_key_as_target_composite)
|
227
227
|
end
|
228
228
|
) * "" +
|
229
|
-
(table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.
|
229
|
+
(table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect] }.map do |fk|
|
230
230
|
generate_foreign_key(depth+2, table.xmiid, fk)
|
231
231
|
end
|
232
232
|
) * "" +
|
@@ -386,7 +386,7 @@ module ActiveFacts
|
|
386
386
|
|
387
387
|
# Return CWM type, typenum for the passed base type
|
388
388
|
def normalise_type_cwm(type_name, length)
|
389
|
-
type = MM::DataType.
|
389
|
+
type = MM::DataType.intrinsic_type(type_name)
|
390
390
|
|
391
391
|
case type
|
392
392
|
when MM::DataType::TYPE_Boolean; ['boolean', 16]
|
@@ -408,8 +408,8 @@ module ActiveFacts
|
|
408
408
|
['real', 7, data_type_context.default_length(type, type_name)]
|
409
409
|
when MM::DataType::TYPE_Decimal; ['decimal', 3]
|
410
410
|
when MM::DataType::TYPE_Money; ['decimal', 3]
|
411
|
-
when MM::DataType::TYPE_Char; ['char', 12, length
|
412
|
-
when MM::DataType::TYPE_String; ['varchar', 12, length
|
411
|
+
when MM::DataType::TYPE_Char; ['char', 12, length]
|
412
|
+
when MM::DataType::TYPE_String; ['varchar', 12, length]
|
413
413
|
when MM::DataType::TYPE_Text; ['text', 2005, length || 'MAX']
|
414
414
|
when MM::DataType::TYPE_Date; ['date', 91]
|
415
415
|
when MM::DataType::TYPE_Time; ['time', 92]
|
@@ -490,14 +490,10 @@ module ActiveFacts
|
|
490
490
|
end
|
491
491
|
|
492
492
|
def surrogate_type
|
493
|
-
type_name, =
|
493
|
+
type_name, = choose_integer_range(0, 2**(default_surrogate_length-1)-1)
|
494
494
|
type_name
|
495
495
|
end
|
496
496
|
|
497
|
-
def valid_from_type
|
498
|
-
'TIMESTAMP'
|
499
|
-
end
|
500
|
-
|
501
497
|
def date_time_type
|
502
498
|
'TIMESTAMP'
|
503
499
|
end
|
@@ -512,14 +508,6 @@ module ActiveFacts
|
|
512
508
|
'VARCHAR'
|
513
509
|
end
|
514
510
|
|
515
|
-
def char_default_length
|
516
|
-
nil
|
517
|
-
end
|
518
|
-
|
519
|
-
def varchar_default_length
|
520
|
-
nil
|
521
|
-
end
|
522
|
-
|
523
511
|
def default_surrogate_length
|
524
512
|
64
|
525
513
|
end
|