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.
@@ -14,14 +14,14 @@ module ActiveFacts
14
14
 
15
15
  def self.options
16
16
  {
17
- surrogates: ['Boolean', "Inject a surrogate key into each table whose primary key is not already suitable as a foreign key"]
18
- }
17
+ surrogates: ['Boolean', "Inject a surrogate key into each table whose primary key is not already suitable as a foreign key"],
18
+ }.merge(Compositor.options)
19
19
  end
20
20
 
21
- def initialize constellation, name, options = {}
21
+ def initialize constellation, name, options = {}, compositor_name = 'Relational'
22
22
  # Extract recognised options:
23
23
  @option_surrogates = options.delete('surrogates')
24
- super constellation, name, options
24
+ super constellation, name, options, compositor_name
25
25
  end
26
26
 
27
27
  def generate
@@ -183,10 +183,10 @@ module ActiveFacts
183
183
  next true if a.full_absorption && child_candidate.full_absorption.absorption != a
184
184
 
185
185
  # If our counterpart is a full absorption, don't try to reverse that!
186
- next false if (a.forward_absorption || a.reverse_absorption).full_absorption
186
+ next false if (aa = (a.forward_absorption || a.reverse_absorption)) && aa.full_absorption
187
187
 
188
188
  # Otherwise the other end must already be a table or fully absorbed into one
189
- next false unless child_candidate.is_table || child_candidate.full_absorption
189
+ next false unless child_candidate.nil? || child_candidate.is_table || child_candidate.full_absorption
190
190
 
191
191
  next false unless a.child_role.is_unique && a.parent_role.is_unique # Must be one-to-one
192
192
 
@@ -347,7 +347,7 @@ module ActiveFacts
347
347
  # * there are no other columns (that might require updating) and
348
348
  # * the object is not the target of a foreign key:
349
349
  if non_fk_surrogate
350
- trace :surrogates, "#{composite.inspect} has non-FK identifiers so requires a surrogate"
350
+ trace :surrogates, "#{composite.inspect} has non-FK identifiers (in #{key_members.inspect}) so requires a surrogate"
351
351
  return true
352
352
  end
353
353
 
@@ -389,7 +389,7 @@ module ActiveFacts
389
389
  trace :surrogates, "#{composite.inspect} already has an auto-assigned key so does NOT require a surrogate"
390
390
  return false
391
391
  end
392
- trace :surrogates, "#{composite.inspect} requires a surrogate"
392
+ trace :surrogates, "#{composite.inspect} PK is #{key_member.inspect} which requires a surrogate"
393
393
  return true
394
394
  end
395
395
 
@@ -14,7 +14,8 @@ module ActiveFacts
14
14
  def self.options
15
15
  {
16
16
  stgname: ['String', "Suffix or pattern for naming staging tables. Include a + to insert the name. Default 'STG'"],
17
- }
17
+ }.merge(Relational.options).
18
+ reject{|k,v| [:surrogates].include?(k) }
18
19
  end
19
20
 
20
21
  def initialize constellation, name, options = {}
@@ -22,7 +23,7 @@ module ActiveFacts
22
23
  @option_stg_name = options.delete('stgname') || 'STG'
23
24
  @option_stg_name.sub!(/^/,'+ ') unless @option_stg_name =~ /\+/
24
25
 
25
- super constellation, name, options
26
+ super constellation, name, options, 'Staging'
26
27
 
27
28
  end
28
29
 
@@ -1,5 +1,5 @@
1
1
  module ActiveFacts
2
2
  module Compositions
3
- VERSION = "1.9.10"
3
+ VERSION = "1.9.12"
4
4
  end
5
5
  end
@@ -21,15 +21,11 @@ module ActiveFacts
21
21
  attr_accessor :xmiid
22
22
  end
23
23
 
24
- class ActiveFacts::Metamodel::Absorption # for columns
24
+ class ActiveFacts::Metamodel::Component # for columns
25
25
  attr_accessor :xmiid
26
26
  attr_accessor :index_xmiid
27
27
  end
28
28
 
29
- class ActiveFacts::Metamodel::Indicator
30
- attr_accessor :xmiid
31
- end
32
-
33
29
  class ActiveFacts::Metamodel::Index # for primary and unique indexes
34
30
  attr_accessor :xmiid
35
31
  end
@@ -38,11 +34,6 @@ module ActiveFacts
38
34
  attr_accessor :xmiid
39
35
  end
40
36
 
41
- class ActiveFacts::Metamodel::ValueField
42
- attr_accessor :xmiid
43
- attr_accessor :index_xmiid
44
- end
45
-
46
37
  class CWM
47
38
  MM = ActiveFacts::Metamodel unless const_defined?(:MM)
48
39
  def self.options
@@ -51,9 +42,8 @@ module ActiveFacts
51
42
  }
52
43
  end
53
44
 
54
- def initialize compositions, options = {}
55
- raise "--cwm only processes a single composition" if compositions.size > 1
56
- @composition = compositions[0]
45
+ def initialize composition, options = {}
46
+ @composition = composition
57
47
  @options = options
58
48
  @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
59
49
 
@@ -141,12 +131,14 @@ module ActiveFacts
141
131
  def populate_table_ids(table)
142
132
  tname = table_name(table)
143
133
  nsdef(table)
144
- table.mapping.all_leaf.flat_map do |leaf|
134
+ table.mapping.all_leaf.flat_map.sort_by{|c| column_name(c)}.map do |leaf|
145
135
  # Absorbed empty subtypes appear as leaves
146
136
  next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
147
137
  nsdef(leaf)
148
138
  end
149
- table.all_index.map do |index|
139
+ table.all_index.sort_by do |idx|
140
+ idx.all_index_field.map { |ixf| ixf.component.xmiid }
141
+ end.map do |index|
150
142
  nsdef(index)
151
143
  index.all_index_field.map{|idf| idf.component.index_xmiid = index.xmiid}
152
144
  end
@@ -162,7 +154,7 @@ module ActiveFacts
162
154
  "<XMI xmlns:CWM=\"org.omg.CWM1.1\" xmlns:CWMRDB=\"org.omg.CWM1.1/Relational\" xmi.version=\"1.1\">\n" +
163
155
  " <XMI.header>\n" +
164
156
  " <XMI.documentation>\n" +
165
- " <XMI.exporter>Infinuedo APRIMO</XMI.exporter>\n" +
157
+ " <XMI.exporter>ActiveFacts</XMI.exporter>\n" +
166
158
  " <XMI.exporterVersion>0.1</XMI.exporterVersion>\n" +
167
159
  " </XMI.documentation>\n" +
168
160
  " <XMI.metamodel xmi.name=\"CWM\" xmi.version=\"1.1\" />" +
@@ -228,7 +220,9 @@ module ActiveFacts
228
220
 
229
221
  table_keys =
230
222
  indent(depth, " <CWM:Namespace.ownedElement>") +
231
- (table.all_index.map do |index|
223
+ (table.all_index.sort_by do |idx|
224
+ idx.all_index_field.map { |ixf| ixf.component.xmiid }
225
+ end.map do |index|
232
226
  generate_index(depth+2, table.xmiid, index, name, table.all_foreign_key_as_target_composite)
233
227
  end
234
228
  ) * "" +
@@ -18,9 +18,8 @@ module ActiveFacts
18
18
  }
19
19
  end
20
20
 
21
- def initialize compositions, options = {}
22
- raise "--oo only processes a single composition" if compositions.size > 1
23
- @composition = compositions[0]
21
+ def initialize composition, options = {}
22
+ @composition = composition
24
23
  @options = options
25
24
  @comments = @options.delete("comments")
26
25
  end
@@ -22,9 +22,8 @@ module ActiveFacts
22
22
  })
23
23
  end
24
24
 
25
- def initialize compositions, options = {}
26
- raise "--rails/models only processes a single composition" if compositions.size > 1
27
- @composition = compositions[0]
25
+ def initialize composition, options = {}
26
+ @composition = composition
28
27
  @options = options
29
28
  @option_output = options.delete("output")
30
29
  @option_concern = options.delete("concern")
@@ -24,9 +24,8 @@ module ActiveFacts
24
24
  })
25
25
  end
26
26
 
27
- def initialize compositions, options = {}
28
- raise "--rails/schema only processes a single composition" if compositions.size > 1
29
- @composition = compositions[0]
27
+ def initialize composition, options = {}
28
+ @composition = composition
30
29
  @options = options
31
30
  @option_exclude_fks = options.delete("exclude_fks")
32
31
  @option_include_comments = options.delete("include_comments")
@@ -20,8 +20,7 @@ module ActiveFacts
20
20
  )
21
21
  end
22
22
 
23
- def initialize compositions, options = {}
24
- raise "--ruby only processes a single composition" if compositions.size > 1
23
+ def initialize composition, options = {}
25
24
  super
26
25
  @scope = options.delete('scope') || ''
27
26
  @scope = @scope.split(/::/)
@@ -14,35 +14,47 @@ module ActiveFacts
14
14
  module Generators
15
15
  # Options are comma or space separated:
16
16
  # * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
17
- # * underscore
17
+ # * underscore
18
18
  class SQL
19
19
  MM = ActiveFacts::Metamodel unless const_defined?(:MM)
20
20
  def self.options
21
21
  {
22
22
  delay_fks: ['Boolean', "Delay emitting all foreign keys until the bottom of the file"],
23
- underscore: [String, "Use 'str' instead of underscore between words in table names"],
23
+ underscore: ['String', "Use 'str' instead of underscore between words in table names"],
24
24
  unicode: ['Boolean', "Use Unicode for all text fields by default"],
25
+ # datavault: ['String', "Generate 'raw' or 'business' data vault tables"],
26
+ restrict: ['String', "Restrict generation to tables in the specified group (e.g. bdv, rdv)"]
25
27
  }
26
28
  end
27
29
 
28
- def initialize compositions, options = {}
29
- raise "--sql only processes a single composition" if compositions.size > 1
30
- @composition = compositions[0]
30
+ def initialize composition, options = {}
31
+ @composition = composition
31
32
  @options = options
32
33
  @delay_fks = options.delete "delay_fks"
33
34
  @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
34
35
  @unicode = options.delete "unicode"
36
+ @restrict = options.delete "restrict"
37
+ # Legacy option. Use restrict=bdv/rdv instead
38
+ @datavault = options.delete "datavault"
39
+ case @datavault
40
+ when "business"
41
+ @restrict = "bdv"
42
+ when "raw"
43
+ @restrict = "rdv"
44
+ end
35
45
  end
36
46
 
37
47
  def generate
38
48
  @tables_emitted = {}
39
49
  @delayed_foreign_keys = []
40
50
 
51
+ @composite_list = @composition.all_composite.sort_by{|composite| composite.mapping.name}
52
+ if @restrict
53
+ @composite_list.select!{|composite| g = composite.composite_group and g.name == @restrict}
54
+ end
55
+
41
56
  generate_schema +
42
- @composition.
43
- all_composite.
44
- sort_by{|composite| composite.mapping.name}.
45
- map{|composite| generate_table composite}*"\n" + "\n" +
57
+ @composite_list.map{|composite| generate_table composite}*"\n" + "\n" +
46
58
  @delayed_foreign_keys.sort*"\n"
47
59
  end
48
60
 
@@ -88,7 +100,9 @@ module ActiveFacts
88
100
  end.compact.sort +
89
101
  composite.all_foreign_key_as_source_composite.map do |fk|
90
102
  fk_text = generate_foreign_key fk
91
- if !@delay_fks and @tables_emitted[fk.composite]
103
+ if !@delay_fks and # We're not delaying foreign keys unnecessarily
104
+ @tables_emitted[fk.composite] || # Already done
105
+ !@composite_list.include?(fk.composite) # Not going to be done
92
106
  fk_text
93
107
  else
94
108
  @delayed_foreign_keys <<
@@ -195,12 +209,12 @@ module ActiveFacts
195
209
  MM::DataType.normalise_int_length('int', data_type_context.default_surrogate_length, value_constraint, data_type_context)[0]
196
210
  else
197
211
  v, = MM::DataType.normalise_int_length(type_name, length, value_constraint, data_type_context)
198
- v
212
+ v # The typename here has the appropriate length, don't return a length
199
213
  end
200
214
  when MM::DataType::TYPE_Real;
201
215
  ["FLOAT", data_type_context.default_length(type, type_name)]
202
- when MM::DataType::TYPE_Decimal; 'DECIMAL'
203
- when MM::DataType::TYPE_Money; 'DECIMAL'
216
+ when MM::DataType::TYPE_Decimal; ['DECIMAL', length]
217
+ when MM::DataType::TYPE_Money; ['DECIMAL', length]
204
218
  when MM::DataType::TYPE_Char; [data_type_context.default_char_type, length || data_type_context.char_default_length]
205
219
  when MM::DataType::TYPE_String; [data_type_context.default_varchar_type, length || data_type_context.varchar_default_length]
206
220
  when MM::DataType::TYPE_Text; [data_type_context.default_text_type, length || 'MAX']
@@ -213,10 +227,10 @@ module ActiveFacts
213
227
  if length
214
228
  ['BINARY', length]
215
229
  else
216
- 'VARBINARY'
230
+ ['VARBINARY', length]
217
231
  end
218
232
  else
219
- type_name
233
+ [type_name, length]
220
234
  end
221
235
  end
222
236
 
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # ActiveFacts Compositions, Metamodel aspect to create a textual summary of a composition and its Composites
3
- #
3
+ #
4
4
  # Copyright (c) 2015 Clifford Heath. Read the LICENSE file.
5
5
  #
6
6
  require "activefacts/metamodel"
@@ -13,7 +13,11 @@ module ActiveFacts
13
13
  class Composition
14
14
  def summary
15
15
  classify_constraints
16
- "Summary of #{name}\n" +
16
+
17
+ vn = (v = constellation.Vocabulary.values[0]) ? v.version_number : nil
18
+ version_str = vn ? " version #{vn}" : ''
19
+
20
+ "Summary of #{name}#{version_str}\n" +
17
21
  all_composite.
18
22
  sort_by{|composite| composite.mapping.name}.
19
23
  flat_map do |composite|
@@ -35,14 +39,15 @@ module ActiveFacts
35
39
 
36
40
  # Build a display of the names in this absorption path, with FK and optional indicators
37
41
  path_names = leaf.path.map do |component|
38
- is_mandatory = case component
39
- when Indicator
40
- false
41
- when Absorption
42
- component.parent_role.is_mandatory
43
- else
44
- true
45
- end
42
+ is_mandatory = true
43
+ is_unique = true
44
+ case component
45
+ when Absorption
46
+ is_mandatory = component.parent_role.is_mandatory
47
+ is_unique = component.parent_role.is_unique
48
+ when Indicator
49
+ is_mandatory = false
50
+ end
46
51
 
47
52
  if component.all_foreign_key_field.size > 0
48
53
  "[#{component.name}]"
@@ -51,8 +56,8 @@ module ActiveFacts
51
56
  else
52
57
  component.name
53
58
  end +
54
- (is_mandatory ? '' : '?')
55
- end*'->'
59
+ (is_mandatory ? '' : '?') + (is_unique ? '' : '*')
60
+ end*'->'
56
61
 
57
62
  # Build a symbolic representation of the index participation of this leaf
58
63
  pos = 0
@@ -63,8 +68,8 @@ module ActiveFacts
63
68
  end
64
69
  a
65
70
  end
66
- if indexing.empty?
67
- indexing = ''
71
+ if indexing.empty?
72
+ indexing = ''
68
73
  else
69
74
  indexing = "[#{indexing*','}]"
70
75
  end
@@ -92,13 +97,13 @@ module ActiveFacts
92
97
  }
93
98
  end
94
99
 
95
- def initialize compositions, options = {}
96
- @compositions = compositions
100
+ def initialize composition, options = {}
101
+ @composition = composition
97
102
  @options = options
98
103
  end
99
104
 
100
105
  def generate
101
- @compositions.map(&:summary)*"\n\n"
106
+ @composition.summary
102
107
  end
103
108
  end
104
109
  publish_generator Summary
@@ -0,0 +1,144 @@
1
+ #
2
+ # ActiveFacts Tranformation Rule Stub Generator
3
+ #
4
+ # Copyright (c) 2017 Factil Pty Ltd. Read the LICENSE file.
5
+ #
6
+ require 'digest/sha1'
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/metamodel/datatypes'
9
+ require 'activefacts/compositions'
10
+ require 'activefacts/compositions/names'
11
+ require 'activefacts/generator'
12
+
13
+ module ActiveFacts
14
+ module Generators
15
+ class TransGen
16
+ INDENT = " "
17
+ def self.options
18
+ {
19
+ }
20
+ end
21
+
22
+ def initialize composition, options = {}
23
+ @composition = composition
24
+ @options = options
25
+ end
26
+
27
+ def generate
28
+ @constellation = @composition.constellation
29
+ @transform_topic = @constellation.Topic.values.select{|t| t.all_import_as_precursor_topic.size == 0}.first
30
+ if !@constellation.Vocabulary.values.to_a[0].is_transform
31
+ raise "Expected input file to be transform"
32
+ end
33
+
34
+ source_imports = @constellation.Import.values.select{|imp| imp.topic == @transform_topic && imp.import_role == "source"}
35
+ target_imports = @constellation.Import.values.select{|imp| imp.topic == @transform_topic && imp.import_role == "target"}
36
+
37
+ composite_list = @composition.all_composite.select do |composite|
38
+ target_imports.detect do |imp|
39
+ imp.precursor_topic.all_concept.detect{|c| c.object_type == composite.mapping.object_type}
40
+ end
41
+ end.sort_by{|composite| composite.mapping.name}
42
+
43
+ generate_header(source_imports, target_imports) +
44
+ composite_list.map{|composite| generate_transform_rule composite}*"\n" + "\n"
45
+ end
46
+
47
+ def generate_header source_imports, target_imports
48
+ "transform #{@transform_topic.topic_name};\n" +
49
+ "\n" +
50
+ source_imports.map{|imp| "import source #{imp.precursor_topic.topic_name};"} * "\n" + "\n" +
51
+ target_imports.map{|imp| "import target #{imp.precursor_topic.topic_name};"} * "\n" + "\n" +
52
+ "\n"
53
+ end
54
+
55
+ def generate_transform_rule composite
56
+ existing_rules = @transform_topic.all_concept.select do |concept|
57
+ tr = concept.transform_rule and
58
+ tr.compound_matching.all_transform_target_ref.to_a[0].object_type == composite.mapping.object_type
59
+ end.map {|concept| concept.transform_rule}
60
+
61
+ if existing_rules.size > 0
62
+ existing_rules.map{|tr| regenerate_transform_rule(composite, tr)} * "\n\n"
63
+ else
64
+ generate_new_transform_rule(composite)
65
+ end
66
+ end
67
+
68
+ def indent text, level
69
+ INDENT * level + text
70
+ end
71
+
72
+ def leading_hyphenate leading_adjective
73
+ words = leading_adjective.words
74
+ if words.size == 1
75
+ leading_adjective + '-'
76
+ else
77
+ words[0] + '- ' + words[1..-1].join(' ')
78
+ end
79
+ end
80
+
81
+ def trailing_hyphenate trailing_adjective
82
+ words = trailing_adjective.words
83
+ if words.size == 1
84
+ '-' + trailing_adjective
85
+ else
86
+ words[0...-1].join(' ') + ' -' + words[-1]
87
+ end
88
+ end
89
+
90
+ def full_role_name mapping
91
+ if mapping.is_a?(ActiveFacts::Metamodel::Absorption)
92
+ la = mapping.child_role.all_role_ref.to_a[0].leading_adjective
93
+ ta = mapping.child_role.all_role_ref.to_a[0].trailing_adjective
94
+ (la ? leading_hyphenate(la) + ' ' : '') + mapping.object_type.name + (ta ? ' ' + trailing_hyphenate(ta) : '')
95
+ else
96
+ mapping.object_type.name
97
+ end
98
+ end
99
+
100
+ #
101
+ # New transform rule
102
+ #
103
+ def generate_new_transform_rule composite
104
+ generate_compound_matching(composite.mapping, 0, '') + ";\n"
105
+ end
106
+
107
+ def generate_transform_matching mapping, level, prefix
108
+ if mapping.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
109
+ generate_simple_matching(mapping, level, prefix)
110
+ else
111
+ generate_compound_matching(mapping, level, prefix)
112
+ end
113
+ end
114
+
115
+ def generate_simple_matching mapping, level, prefix
116
+ full_name = full_role_name(mapping)
117
+ prefixed_name = (prefix.size > 0 ? prefix + ' . ' : '') + full_name
118
+ indent("#{prefixed_name} <-- /* EXPR */", level)
119
+ end
120
+
121
+ def generate_compound_matching mapping, level, prefix
122
+ members = mapping.all_member.sort_by(&:ordinal).flatten
123
+ prefixed_name = (prefix.size > 0 ? prefix + ' . ' : '') + full_role_name(mapping)
124
+ if members.size == 1
125
+ generate_transform_matching(members[0], level, prefixed_name)
126
+ else
127
+ indent("#{prefixed_name} <== /* OBJECT TYPE or QUERY */ {\n", level) +
128
+ members.map do |m|
129
+ generate_transform_matching(m, level + 1, '')
130
+ end * ",\n" + "\n" +
131
+ indent("}", level)
132
+ end
133
+ end
134
+
135
+ #
136
+ # Regenerate existing transform rule
137
+ #
138
+ def regenerate_transform_rule(composite, transform_rule)
139
+ # regenerate_compound_matching(composite.mapping, 0, '') + ";\n"
140
+ end
141
+ end
142
+ publish_generator TransGen
143
+ end
144
+ end