activefacts-compositions 1.9.10 → 1.9.12

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.
@@ -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