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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/activefacts-compositions.gemspec +2 -2
  3. data/lib/activefacts/compositions/binary.rb +1 -1
  4. data/lib/activefacts/compositions/compositor.rb +16 -12
  5. data/lib/activefacts/compositions/datavault.rb +110 -115
  6. data/lib/activefacts/compositions/relational.rb +137 -94
  7. data/lib/activefacts/compositions/staging.rb +89 -27
  8. data/lib/activefacts/compositions/traits/datavault.rb +116 -49
  9. data/lib/activefacts/compositions/traits/rails.rb +2 -2
  10. data/lib/activefacts/compositions/version.rb +1 -1
  11. data/lib/activefacts/generator/doc/cwm.rb +6 -18
  12. data/lib/activefacts/generator/doc/ldm.rb +1 -1
  13. data/lib/activefacts/generator/etl/unidex.rb +341 -0
  14. data/lib/activefacts/generator/oo.rb +31 -14
  15. data/lib/activefacts/generator/rails/models.rb +6 -5
  16. data/lib/activefacts/generator/rails/schema.rb +5 -9
  17. data/lib/activefacts/generator/ruby.rb +2 -2
  18. data/lib/activefacts/generator/sql/mysql.rb +3 -184
  19. data/lib/activefacts/generator/sql/oracle.rb +3 -152
  20. data/lib/activefacts/generator/sql/postgres.rb +3 -145
  21. data/lib/activefacts/generator/sql/server.rb +3 -126
  22. data/lib/activefacts/generator/sql.rb +54 -422
  23. data/lib/activefacts/generator/summary.rb +15 -6
  24. data/lib/activefacts/generator/traits/expr.rb +41 -0
  25. data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
  26. data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
  27. data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
  28. data/lib/activefacts/generator/traits/sql/server.rb +262 -0
  29. data/lib/activefacts/generator/traits/sql.rb +538 -0
  30. metadata +13 -8
  31. data/lib/activefacts/compositions/docgraph.rb +0 -798
  32. 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
- datavault_initialize options
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
- @option_keep_fks = options.delete('fk') || false
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 generate
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 inject_value_fields
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
- def inject_all_datetime_recordsource
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
- next if composite.mapping.object_type.name == @option_loadbatch
56
- inject_datetime_recordsource composite.mapping
57
- composite.mapping.re_rank
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
- inject_all_datetime_recordsource
67
- end
102
+ apply_all_audit_transformations
68
103
 
69
- def complete_foreign_keys
70
- if @option_keep_fks
71
- super
72
- else
73
- retract_foreign_keys
74
- end
104
+ super
75
105
  end
76
106
 
77
- def retract_foreign_keys
78
- trace :relational_paths, "Retracting foreign keys" do
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
- next if MM::Index === path
82
- trace :relational_paths, "Retracting #{path.inspect}" do
83
- path.retract
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
- datestamp: ['String', "Data type name to use for data vault date stamps (default: DateTime)"],
8
- recordsource: ['String', "Data type name to use for data vault record source (default: String)"],
9
- loadbatch: ['String', "Create a load batch table using this name, default LoadBatch"],
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
- @option_datestamp = options.delete('datestamp')
15
- @option_datestamp = 'DateTime' if [true, '', 'true', 'yes', nil].include?(@option_datestamp)
16
- @option_recordsource = options.delete('recordsource')
17
- @option_recordsource = 'String' if [true, '', 'true', 'yes', nil].include?(@option_recordsource)
18
- @option_loadbatch = options.delete('loadbatch')
19
- @option_loadbatch = 'LoadBatch' if [true, 'true', 'yes'].include?(@option_loadbatch)
20
- @option_loadbatch = nil if [false, 'false', ''].include?(@option_loadbatch)
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
- vocabulary = @constellation.Vocabulary.values[0]
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
- @compiler = ActiveFacts::CQL::Compiler.new(@vocabulary, constellation: @constellation)
35
- @compiler.compile(schema_text)
36
- @loadbatch_entity_type = @constellation.EntityType[[vocabulary.identifying_role_values, @option_loadbatch]]
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 @option_loadbatch
41
- @composition.all_composite.each do |composite|
42
- next if composite.mapping.object_type.name == @option_loadbatch
43
- @compiler.compile("#{composite.mapping.name} was loaded in one #{@option_loadbatch};")
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
- @loadbatch_entity_type.all_role.each do |role|
46
- populate_reference role
47
- populate_reference role.counterpart
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 inject_datetime_recordsource mapping
52
- # Add a load DateTime value
53
- date_field = @constellation.ValidFrom(:new,
54
- parent: mapping,
55
- name: "LoadTime",
56
- object_type: datestamp_type
57
- )
58
-
59
- # Add a load DateTime value
60
- recsrc_field = @constellation.ValueField(:new,
61
- parent: mapping,
62
- name: "RecordSource",
63
- object_type: recordsource_type
64
- )
65
- mapping.re_rank
66
- date_field
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 = @composition.all_composite.to_a[0].mapping.object_type.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
- @option_recordsource
153
+ @option_source
87
154
  end
88
155
 
89
156
  def recordsource_type
90
157
  @recordsource_type ||= begin
91
- vocabulary = @composition.all_composite.to_a[0].mapping.object_type.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.object_type.name == @option_loadbatch
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
- absorption.column_name.snakecase
148
+ mapping.column_name.snakecase
149
149
  end
150
150
 
151
151
  def to_association
152
- if absorption && absorption.child_role.is_unique
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]
@@ -1,5 +1,5 @@
1
1
  module ActiveFacts
2
2
  module Compositions
3
- VERSION = "1.9.17"
3
+ VERSION = "1.9.18"
4
4
  end
5
5
  end
@@ -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.absorption.inspect] }.map do |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.absorption.inspect] }.map do |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.normalise(type_name)
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 || data_type_context.char_default_length]
412
- when MM::DataType::TYPE_String; ['varchar', 12, length || data_type_context.varchar_default_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, = choose_integer_type(0, 2**(default_surrogate_length-1)-1)
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
@@ -517,7 +517,7 @@ module ActiveFacts
517
517
  }#{
518
518
  value_constraint ? check_clause(column_name, value_constraint) : ''
519
519
  }"
520
- when MM::Injection
520
+ when MM::ValidFrom
521
521
  component.object_type.name
522
522
  else
523
523
  raise "Can't make a column from #{component}"