activefacts-compositions 1.9.6 → 1.9.8

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.
@@ -0,0 +1,180 @@
1
+ require 'activefacts/compositions'
2
+ require 'active_support'
3
+ require 'digest/sha1'
4
+
5
+ module ActiveFacts
6
+ module Metamodel
7
+ class Component
8
+ def rails
9
+ @rails_facet ||= ACTR::Component.new(self)
10
+ end
11
+ end
12
+
13
+ class Index
14
+ def rails
15
+ @rails_facet ||= ACTR::Index.new(self)
16
+ end
17
+ end
18
+
19
+ class ForeignKey
20
+ def rails
21
+ @rails_facet ||= ACTR::ForeignKey.new(self)
22
+ end
23
+ end
24
+
25
+ class Composite
26
+ def rails
27
+ @rails_facet ||= ACTR::Composite.new(self)
28
+ end
29
+ end
30
+ end
31
+
32
+ module Composition
33
+ module Traits
34
+ module Rails
35
+ def self.name_trunc name
36
+ if name.length > 63
37
+ hash = Digest::SHA1.hexdigest name
38
+ name = name[0, 53] + '__' + hash[0, 8]
39
+ end
40
+ name
41
+ end
42
+
43
+ def self.plural_name name
44
+ # Crunch spaces and pluralise the first part, all in snake_case
45
+ name.pop if name.is_a?(Array) and name.last == []
46
+ name = name[0]*'_' if name.is_a?(Array) and name.size == 1
47
+ if name.is_a?(Array)
48
+ name = ActiveSupport::Inflector.tableize((name[0]*'_').gsub(/\s+/, '_')) +
49
+ '_' +
50
+ ActiveSupport::Inflector.underscore((name[1..-1].flatten*'_').gsub(/\s+/, '_'))
51
+ else
52
+ ActiveSupport::Inflector.tableize(name.gsub(/\s+/, '_'))
53
+ end
54
+ end
55
+
56
+ def self.singular_name name
57
+ # Crunch spaces and convert to snake_case
58
+ name = name.flatten*'_' if name.is_a?(Array)
59
+ ActiveSupport::Inflector.underscore(name.gsub(/\s+/, '_'))
60
+ end
61
+
62
+ class Facet
63
+ def initialize base
64
+ @base = base
65
+ end
66
+
67
+ def method_missing m, *a, &b
68
+ @base.send(m, *a, &b)
69
+ end
70
+ end
71
+
72
+ class Component < Facet
73
+ def name
74
+ ACTR::singular_name(@base.name)
75
+ end
76
+
77
+ def plural_name
78
+ ACTR::plural_name(@base.name)
79
+ end
80
+
81
+ def type
82
+ type_name, params, constraints = *explode.type()
83
+ rails_type = case type_name
84
+ when /^Auto ?Counter$/i
85
+ 'serial' # REVISIT: Need to detect surrogate ID fields and handle them correctly
86
+
87
+ when /^[Ug]uid$/i
88
+ 'uuid'
89
+
90
+ when /^Unsigned ?Integer$/i,
91
+ /^Integer$/i,
92
+ /^Signed ?Integer$/i,
93
+ /^Unsigned ?Small ?Integer$/i,
94
+ /^Signed ?Small ?Integer$/i,
95
+ /^Unsigned ?Tiny ?Integer$/i
96
+ length = nil
97
+ 'integer'
98
+
99
+ when /^Decimal$/i
100
+ 'decimal'
101
+
102
+ when /^Float$/i, /^Double$/i, /^Real$/i
103
+ 'float'
104
+
105
+ when /^Fixed ?Length ?Text$/i, /^Char$/i
106
+ 'string'
107
+ when /^Variable ?Length ?Text$/i, /^String$/i
108
+ 'string'
109
+ when /^Large ?Length ?Text$/i, /^Text$/i
110
+ 'text'
111
+
112
+ when /^Date ?And ?Time$/i, /^Date ?Time$/i
113
+ 'datetime'
114
+ when /^Date$/i
115
+ 'date'
116
+ when /^Time$/i
117
+ 'time'
118
+ when /^Auto ?Time ?Stamp$/i
119
+ 'timestamp'
120
+
121
+ when /^Money$/i
122
+ 'decimal'
123
+ when /^Picture ?Raw ?Data$/i, /^Image$/i, /^Variable ?Length ?Raw ?Data$/i, /^Blob$/i
124
+ 'binary'
125
+ when /^BIT$/i, /^Boolean$/i
126
+ 'boolean'
127
+ else
128
+ type_name # raise "ActiveRecord type unknown for standard type #{type}"
129
+ end
130
+ [rails_type, params[:length]]
131
+ end
132
+ end
133
+
134
+ class Index < Facet
135
+ def name
136
+ column_names = columns.map{|c| c.rails.name }
137
+ index_name = "index_#{on.rails.name+'_on_'+column_names*'_'}"
138
+ ACTR.name_trunc index_name
139
+ end
140
+ end
141
+
142
+ class ForeignKey < Facet
143
+ # A foreign key is between two Composites, but involves some Absorption that traverses
144
+ # between two object types, either or both of which may be fully absorbed into the
145
+ # respective Composites. The name of a foreign key takes this into account.
146
+
147
+ def from_association_name
148
+ absorption.column_name.snakecase
149
+ end
150
+
151
+ def to_association
152
+ if absorption && absorption.child_role.is_unique
153
+ [ "has_one", source_composite.rails.singular_name]
154
+ else
155
+ [ "has_many", source_composite.rails.plural_name]
156
+ end
157
+ end
158
+ end
159
+
160
+ class Composite < Facet
161
+ def plural_name
162
+ ACTR::plural_name(@base.mapping.name)
163
+ end
164
+
165
+ def singular_name
166
+ ACTR::singular_name(@base.mapping.name)
167
+ end
168
+
169
+ def class_name
170
+ ActiveSupport::Inflector.camelize(@base.mapping.name.gsub(/\s+/, '_'))
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+ end
177
+
178
+ private
179
+ ACTR = Composition::Traits::Rails
180
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveFacts
2
2
  module Compositions
3
- VERSION = "1.9.6"
3
+ VERSION = "1.9.8"
4
4
  end
5
5
  end
@@ -0,0 +1,45 @@
1
+ a:link, a:visited {
2
+ color: #8A0092; text-decoration: underline;
3
+ }
4
+
5
+ span {
6
+ font-family: "Frutiger", sans-serif;
7
+ }
8
+
9
+ .keyword {
10
+ color: #0000CC;
11
+ }
12
+
13
+ .term, .concept {
14
+ /* Xcolor: #6A0072; The color used by NORMA */
15
+ color: #8A0092;
16
+ }
17
+
18
+ .vocabulary, .object_type {
19
+ /* Xcolor: #6A0072; The color used by NORMA */
20
+ color: #8A0092;
21
+ }
22
+
23
+ .linking, .reading {
24
+ color: #0E5400;
25
+ }
26
+
27
+ .literal, .value {
28
+ color: #FF990E;
29
+ }
30
+
31
+ .glossary-objectification .glossary-reading {
32
+ margin-left: 30px;
33
+ }
34
+
35
+ .definition {
36
+ font-weight: bold;
37
+ }
38
+
39
+ .details {
40
+ margin-left: 60px;
41
+ }
42
+
43
+ .row-bordered {
44
+ border: 1px solid #ddd;
45
+ }
@@ -0,0 +1,764 @@
1
+ #
2
+ # ActiveFacts Common Warehouse Metamodel Generator
3
+ #
4
+ # This generator produces an CWM XMI-formated model of a Composition.
5
+ #
6
+ # Copyright (c) 2016 Infinuendo. Read the LICENSE file.
7
+ #
8
+ require 'digest/sha1'
9
+ require 'activefacts/metamodel'
10
+ require 'activefacts/metamodel/datatypes'
11
+ require 'activefacts/registry'
12
+ require 'activefacts/compositions'
13
+ require 'activefacts/generator'
14
+ require 'activefacts/support'
15
+
16
+ module ActiveFacts
17
+ module Generators
18
+ module Doc
19
+
20
+ # Add namespace id to metamodel forward referencing
21
+ class ActiveFacts::Metamodel::Composite # for tables
22
+ attr_accessor :xmiid
23
+ end
24
+
25
+ class ActiveFacts::Metamodel::Absorption # for columns
26
+ attr_accessor :xmiid
27
+ attr_accessor :index_xmiid
28
+ end
29
+
30
+ class ActiveFacts::Metamodel::Indicator
31
+ attr_accessor :xmiid
32
+ end
33
+
34
+ class ActiveFacts::Metamodel::Index # for primary and unique indexes
35
+ attr_accessor :xmiid
36
+ end
37
+
38
+ class ActiveFacts::Metamodel::ForeignKey # for foreign keys
39
+ attr_accessor :xmiid
40
+ end
41
+
42
+ class CWM
43
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
44
+ def self.options
45
+ {
46
+ underscore: [String, "Use 'str' instead of underscore between words in table names"]
47
+ }
48
+ end
49
+
50
+ def initialize composition, options = {}
51
+ @composition = composition
52
+ @options = options
53
+ @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
54
+
55
+ @vocabulary = composition.constellation.Vocabulary.values[0] # REVISIT when importing from other vocabularies
56
+ end
57
+
58
+ def data_type_context
59
+ @data_type_context ||= CWMDataTypeContext.new
60
+ end
61
+
62
+ def generate
63
+ # @tables_emitted = {}
64
+ @namespace = Array.new
65
+ @datatypes = Array.new
66
+
67
+ trace.enable 'cwm'
68
+
69
+ model_ns, schema_ns = populate_namespace_ids
70
+
71
+ generate_header +
72
+ generate_content(model_ns, schema_ns) +
73
+ generate_footer
74
+ end
75
+
76
+ def table_name_max
77
+ 60
78
+ end
79
+
80
+ def column_name_max
81
+ 40
82
+ end
83
+
84
+ def index_name_max
85
+ 60
86
+ end
87
+
88
+ def schema_name_max
89
+ 60
90
+ end
91
+
92
+ def safe_table_name composite
93
+ escape(table_name(composite), table_name_max)
94
+ end
95
+
96
+ def safe_column_name component
97
+ escape(column_name(component), column_name_max)
98
+ end
99
+
100
+ def table_name composite
101
+ composite.mapping.name.gsub(' ', @underscore)
102
+ end
103
+
104
+ def column_name component
105
+ component.column_name.capcase
106
+ end
107
+
108
+ def indent depth, str
109
+ " " * depth + str + "\n"
110
+ end
111
+
112
+ def nsdef name
113
+ ns = "_#{@namespace.size + 1}"
114
+ @namespace << [ns, name]
115
+ ns
116
+ end
117
+
118
+ def populate_namespace_ids
119
+ model_ns = nsdef("Model")
120
+ schema_ns = nsdef("Schema")
121
+
122
+ @composition.
123
+ all_composite.
124
+ sort_by{|composite| composite.mapping.name}.
125
+ map{|composite| populate_table_ids(composite)}
126
+
127
+ [model_ns, schema_ns]
128
+ end
129
+
130
+ def populate_table_ids(table)
131
+ tname = table_name(table)
132
+ table.xmiid = nsdef(tname)
133
+ table.mapping.all_leaf.flat_map do |leaf|
134
+ # Absorbed empty subtypes appear as leaves
135
+ next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
136
+ leaf.xmiid = nsdef(safe_column_name(leaf))
137
+ end
138
+ table.all_index.map do |index|
139
+ index.xmiid = nsdef("PK#{tname}")
140
+ # for index to single column, save the index id with the column
141
+ if index.all_index_field.size == 1
142
+ index.all_index_field[0].component.index_xmiid = index.xmiid
143
+ end
144
+ end
145
+ table.all_foreign_key_as_source_composite.map do |fk|
146
+ fk.xmiid = nsdef("R_#{@namespace.size+1}")
147
+ end
148
+ end
149
+
150
+ def generate_header
151
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
152
+ "<!DOCTYPE XMI SYSTEM \"CWM-1.1.dtd\">\n" +
153
+ "\n" +
154
+ "<XMI xmlns:CWM=\"org.omg.CWM1.1\" xmlns:CWMRDB=\"org.omg.CWM1.1/Relational\" xmi.version=\"1.1\">\n" +
155
+ " <XMI.header>\n" +
156
+ " <XMI.documentation>\n" +
157
+ " <XMI.exporter>Infinuedo APRIMO</XMI.exporter>\n" +
158
+ " <XMI.exporterVersion>0.1</XMI.exporterVersion>\n" +
159
+ " </XMI.documentation>\n" +
160
+ " <XMI.metamodel xmi.name=\"CWM\" xmi.version=\"1.1\" />" +
161
+ " </XMI.header>\n"
162
+ end
163
+
164
+ def generate_content(model_ns, schema_ns)
165
+ " <XMI.content>\n" +
166
+ generate_catalog(2, model_ns, schema_ns) +
167
+ generate_data_types(2) +
168
+ " </XMI.content>\n"
169
+ end
170
+
171
+ def generate_footer
172
+ "</XMI>\n"
173
+ end
174
+
175
+ def generate_catalog(depth, model_ns, schema_ns)
176
+ catalog_start =
177
+ indent(depth, "<CWMRDB:Catalog xmi.id=\"#{model_ns}\" name=\"Model\" visibility=\"public\">") +
178
+ indent(depth, " <CWM:Namespace.ownedElement>") +
179
+ indent(depth, " <CWMRDB:Schema xmi.id=\"#{schema_ns}\" name=\"Schema\" visibility=\"public\" namespace=\"#{model_ns}\">") +
180
+ indent(depth, " <CWM:Namespace.ownedElement>")
181
+
182
+ catalog_body =
183
+ @composition.
184
+ all_composite.
185
+ sort_by{|composite| composite.mapping.name}.
186
+ map{|composite| generate_table(depth+4, schema_ns, composite)}*"\n" + "\n"
187
+
188
+ catalog_end =
189
+ indent(depth, " </CWM:Namespace.ownedElement>") +
190
+ indent(depth, " </CWMRDB:Schema>") +
191
+ indent(depth, " </CWM:Namespace.ownedElement>") +
192
+ indent(depth, "</CWMRDB:Catalog>")
193
+
194
+ catalog_start + catalog_body + catalog_end
195
+ end
196
+
197
+ def generate_data_types(depth)
198
+ @datatypes.map do | dt |
199
+ indent(depth, dt)
200
+ end * ""
201
+ end
202
+
203
+ def generate_table(depth, schema_ns, table)
204
+ name = table_name(table)
205
+ delayed_indices = []
206
+
207
+ table_start =
208
+ indent(depth, "<CWMRDB:Table xmi.id=\"#{table.xmiid}\" name=\"#{name}\" isSystem=\"false\" isTemporary=\"false\" visibility=\"public\" namespace=\"#{schema_ns}\">")
209
+
210
+ table_columns =
211
+ indent(depth, " <CWM:Classifier.feature>") +
212
+ (table.mapping.all_leaf.flat_map do |leaf|
213
+ # Absorbed empty subtypes appear as leaves
214
+ next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
215
+
216
+ generate_column(depth+2, table.xmiid, leaf)
217
+ end
218
+ ).compact.flat_map{|f| "#{f}" } * "" +
219
+ indent(depth, " </CWM:Classifier.feature>")
220
+
221
+ table_keys =
222
+ indent(depth, " <CWM:Namespace.ownedElement>") +
223
+ (table.all_index.map do |index|
224
+ generate_index(depth+2, table.xmiid, index, name)
225
+ end
226
+ ) * "" +
227
+ (table.all_foreign_key_as_source_composite.map do |fk|
228
+ generate_foreign_key(depth+2, table.xmiid, fk)
229
+ end
230
+ ) * "" +
231
+ indent(depth, " </CWM:Namespace.ownedElement>")
232
+
233
+ table_end =
234
+ indent(depth, "</CWMRDB:Table>")
235
+
236
+ table_start + table_columns + table_keys + table_end
237
+ end
238
+
239
+ def generate_column(depth, table_ns, column)
240
+ name = safe_column_name(column)
241
+
242
+ is_nullable = column.path_mandatory ? "columnNoNulls" : "columnNullable"
243
+ constraints = column.all_leaf_constraint
244
+
245
+ type_name, options = column.data_type(data_type_context)
246
+ options ||= {}
247
+ length = options[:length]
248
+
249
+ type_name, type_num = normalise_type_cwm(type_name, length)
250
+ column_params = ""
251
+ type_params = ""
252
+
253
+ type_ns = create_data_type(type_name, type_num, type_params)
254
+
255
+ indent(depth, "<CWMRDB:Column xmi.id=\"#{column.xmiid}\" name=\"#{name}\" isNullable=\"#{is_nullable}\" visibility=\"public\" type=\"#{type_ns}\" owner=\"#{table_ns}\" #{column_params}/>")
256
+ end
257
+
258
+ def create_data_type(type_name, type_num, type_params)
259
+ type_ns = nsdef(type_name)
260
+
261
+ cwm_data_type =
262
+ "<CWMRDB:SQLSimpleType xmi.id=\"#{type_ns}\" name=\"#{type_name}\" visibility=\"public\" typeNumber=\"#{type_num}\" #{type_params}/>"
263
+
264
+ @datatypes << cwm_data_type
265
+ type_ns
266
+ end
267
+
268
+ def generate_index(depth, table_ns, index, table_name)
269
+ key_ns = index.xmiid
270
+
271
+ nullable_columns =
272
+ index.all_index_field.select do |ixf|
273
+ !ixf.component.path_mandatory
274
+ end
275
+ contains_nullable_columns = nullable_columns.size > 0
276
+
277
+ primary = index.composite_as_primary_index && !contains_nullable_columns
278
+ column_ids =
279
+ index.all_index_field.map do |ixf|
280
+ ixf.component.xmiid
281
+ end
282
+ clustering =
283
+ (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')
284
+
285
+ key_type = primary ? 'PrimaryKey' : 'UniqueKey'
286
+
287
+ if column_ids.count == 1
288
+ colid = column_ids[0]
289
+ indent(depth, "<CWMRDB:#{key_type} xmi.id=\"#{key_ns}\" name=\"XPK#{table_name}\" visibility=\"public\" namespace=\"#{table_ns}\" feature=\"#{colid}\"/>")
290
+ else
291
+ indent(depth, "<CWMRDB:#{key_type} xmi.id=\"#{key_ns}\" name=\"XPK#{table_name}\" visibility=\"public\" namespace=\"#{table_ns}\">") +
292
+ indent(depth, " <CWM:UniqueKey.feature>") +
293
+ column_ids.map do |id|
294
+ indent(depth, " <CWM:StructuralFeature xmi.idref=\"#{id}\"/>")
295
+ end * "" +
296
+ indent(depth, " </CWM:UniqueKey.feature>") +
297
+ indent(depth, "</CWMRDB:#{key_type}>")
298
+ end
299
+ end
300
+
301
+ def generate_foreign_key(depth, table_ns, fk)
302
+ key_ns = fk.xmiid
303
+
304
+ if fk.all_foreign_key_field.size == 1
305
+ fkf = fk.all_foreign_key_field[0]
306
+ ixf = fk.all_index_field[0]
307
+ indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{table_ns}\" feature=\"#{fkf.component.xmiid}\" uniqueKey=\"#{ixf.component.index_xmiid}\"/>")
308
+ else
309
+ indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{table_ns}\">") +
310
+ indent(depth, " <CWM:KeyRelationship.feature>") +
311
+ fk.all_foreign_key_field.map do |fkf|
312
+ indent(depth, " <CWM:StructuralFeature xmi.idref=\"#{fkf.component.xmiid}\"/>")
313
+ end * "" +
314
+ indent(depth, " </CWM:KeyRelationship.feature>") +
315
+ indent(depth, "</CWMRDB:ForeignKey>")
316
+ end
317
+ # fk.all_foreign_key_field.map{|fkf| safe_column_name fkf.component}*", " +
318
+ # ") REFERENCES #{safe_table_name fk.composite} (" +
319
+ # fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +
320
+
321
+ # indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{ns}\" feature=\"_41\" uniqueKey=\"_48\" deleteRule=\"importedKeyRestrict\" updateRule=\"importedKeyRestrict\"/>")
322
+
323
+ end
324
+
325
+
326
+
327
+
328
+ ########################
329
+
330
+
331
+
332
+
333
+ #
334
+ # Dump functions
335
+ #
336
+ # def entity_type_dump(o, level)
337
+ # pi = o.preferred_identifier
338
+ # supers = o.supertypes
339
+ # if (supers.size > 0) # Ignore identification by a supertype:
340
+ # pi = nil if pi && pi.role_sequence.all_role_ref.detect{ |rr|
341
+ # rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
342
+ # }
343
+ # end
344
+ #
345
+ # cn_array = o.concept.all_context_note_as_relevant_concept.map{|cn| [cn.context_note_kind, cn.discussion] }
346
+ # cn_hash = cn_array.inject({}) do |hash, value|
347
+ # hash[value.first] = value.last
348
+ # hash
349
+ # end
350
+ #
351
+ # informal_defn = cn_hash["because"]
352
+ # defn_term =
353
+ # " <div class=\"row\">\n" +
354
+ # " <div class=\"col-md-12 definition\">\n" +
355
+ # " A #{termdef(o.name)} #{informal_defn ? 'is ' + informal_defn : ''}\n" +
356
+ # " </div>\n" +
357
+ # " </div>\n"
358
+ #
359
+ # defn_detail =
360
+ # " <div class=\"row\">\n" +
361
+ # " <div class=\"col-md-12 details\">\n" +
362
+ # (supers.size > 0 ?
363
+ # "#{span('Each', 'keyword')} #{termref(o.name, nil, o)} #{span('is a kind of', 'keyword')} #{supers.map{|s| termref(s.name, nil, s)}*', '}\n" :
364
+ # ''
365
+ # ) +
366
+ # if pi
367
+ # "#{span('Each', 'keyword')} #{termref(o.name, nil, o)} #{span('is identified by', 'keyword')} " +
368
+ # pi.role_sequence.all_role_ref_in_order.map do |rr|
369
+ # termref(
370
+ # rr.role.object_type.name,
371
+ # [ rr.leading_adjective,
372
+ # rr.role.role_name || rr.role.object_type.name,
373
+ # rr.trailing_adjective
374
+ # ].compact * '-',
375
+ # rr.role.object_type
376
+ # )
377
+ # end * ", " + "\n"
378
+ # else
379
+ # ''
380
+ # end +
381
+ # fact_types_dump(o, relevant_fact_types(o)) + "\n" +
382
+ # " </div>\n" +
383
+ # " </div>\n"
384
+ #
385
+ # defn_term + defn_detail
386
+ # end
387
+ #
388
+ # def relevant_fact_types(o)
389
+ # o.
390
+ # all_role.
391
+ # map{|r| [r, r.fact_type]}.
392
+ # reject { |r, ft| ft.is_a?(ActiveFacts::Metamodel::LinkFactType) }.
393
+ # select { |r, ft| ft.entity_type || has_another_nonstatic_role(ft, r) }
394
+ # end
395
+ #
396
+ # def has_another_nonstatic_role(ft, r)
397
+ # ft.all_role.detect do |rr|
398
+ # rr != r &&
399
+ # rr.object_type.is_a?(ActiveFacts::Metamodel::EntityType) &&
400
+ # !rr.object_type.is_static
401
+ # end
402
+ # end
403
+ #
404
+ # def fact_types_dump(o, ftm)
405
+ # ftm.
406
+ # map { |r, ft| [ft, " #{fact_type_dump(ft, o)}"] }.
407
+ # sort_by{|ft, text| [ ft.is_a?(ActiveFacts::Metamodel::TypeInheritance) ? 0 : 1, text]}.
408
+ # map{|ft, text| text} * "\n"
409
+ # end
410
+ #
411
+ # def fact_type_dump(ft, wrt = nil)
412
+ # if ft.entity_type
413
+ # div(
414
+ # div(span('Each ', 'keyword') + termref(ft.entity_type.name, nil, ft.entity_type) + span(' is where ', 'keyword')) +
415
+ # div(expand_fact_type(ft, wrt, true, 'some')),
416
+ # 'glossary-objectification'
417
+ # )
418
+ # else
419
+ # fact_type_block(ft, wrt)
420
+ # end
421
+ # end
422
+ #
423
+ # def fact_type_block(ft, wrt = nil, include_rolenames = true)
424
+ # div(expand_fact_type(ft, wrt, include_rolenames, ''), 'glossary-facttype')
425
+ # end
426
+ #
427
+ # def expand_fact_type(ft, wrt = nil, include_rolenames = true, wrt_qualifier = '')
428
+ # role = ft.all_role.detect{|r| r.object_type == wrt}
429
+ # preferred_reading = ft.reading_preferably_starting_with_role(role)
430
+ # alternate_readings = ft.all_reading.reject{|r| r == preferred_reading}
431
+ #
432
+ # div(
433
+ # expand_reading(preferred_reading, include_rolenames, wrt, wrt_qualifier),
434
+ # 'glossary-reading'
435
+ # )
436
+ # end
437
+ #
438
+ # def role_ref(rr, freq_con, l_adj, name, t_adj, role_name_def, literal)
439
+ # term_parts = [l_adj, termref(name, nil, rr.role.object_type), t_adj].compact
440
+ # [
441
+ # freq_con ? element(freq_con, :class=>:keyword) : nil,
442
+ # term_parts.size > 1 ? term([l_adj, termref(name, nil, rr.role.object_type), t_adj].compact*' ') : term_parts[0],
443
+ # role_name_def,
444
+ # literal
445
+ # ]
446
+ # end
447
+ #
448
+ # def expand_reading(reading, include_rolenames = true, wrt = nil, wrt_qualifier = '')
449
+ # role_refs = reading.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
450
+ # lrr = role_refs[role_refs.size - 1]
451
+ # element(
452
+ # # element(rr.role.is_unique ? "one" : "some", :class=>:keyword) +
453
+ # reading.expand([], include_rolenames) do |rr, freq_con, l_adj, name, t_adj, role_name_def, literal|
454
+ # if role_name_def
455
+ # role_name_def = role_name_def.gsub(/\(as ([^)]+)\)/) {
456
+ # span("(as #{ termref(rr.role.object_type.name, $1, rr.role.object_type) })", 'keyword')
457
+ # }
458
+ # end
459
+ # # qualify the last role of the reading
460
+ # quantifier = ''
461
+ # if rr == lrr
462
+ # uniq = true
463
+ # (0 ... role_refs.size - 2).each{|i| uniq = uniq && role_refs[i].role.is_unique }
464
+ # quantifier = uniq ? "one" : "at least one"
465
+ # end
466
+ # role_ref(rr, quantifier, l_adj, name, t_adj, role_name_def, literal)
467
+ # end,
468
+ # {:class => 'reading'}
469
+ # )
470
+ # end
471
+
472
+
473
+ def boolean_type
474
+ 'boolean'
475
+ end
476
+
477
+ def surrogate_type
478
+ 'bigint'
479
+ end
480
+
481
+ # def component_type component, column_name
482
+ # case component
483
+ # when MM::Indicator
484
+ # boolean_type
485
+ # when MM::SurrogateKey
486
+ # surrogate_type
487
+ # when MM::ValueField, MM::Absorption
488
+ # object_type = component.object_type
489
+ # while object_type.is_a?(MM::EntityType)
490
+ # rr = object_type.preferred_identifier.role_sequence.all_role_ref.single
491
+ # raise "Can't produce a column for composite #{component.inspect}" unless rr
492
+ # object_type = rr.role.object_type
493
+ # end
494
+ # raise "A column can only be produced from a ValueType" unless object_type.is_a?(MM::ValueType)
495
+ #
496
+ # if component.is_a?(MM::Absorption)
497
+ # value_constraint ||= component.child_role.role_value_constraint
498
+ # end
499
+ #
500
+ # supertype = object_type
501
+ # begin
502
+ # object_type = supertype
503
+ # length ||= object_type.length
504
+ # scale ||= object_type.scale
505
+ # unless component.parent.parent and component.parent.foreign_key
506
+ # # No need to enforce value constraints that are already enforced by a foreign key
507
+ # value_constraint ||= object_type.value_constraint
508
+ # end
509
+ # end while supertype = object_type.supertype
510
+ # type, length = normalise_type(object_type.name, length)
511
+ # sql_type = "#{type}#{
512
+ # if !length
513
+ # ''
514
+ # else
515
+ # '(' + length.to_s + (scale ? ", #{scale}" : '') + ')'
516
+ # end
517
+ # # }#{
518
+ # # (component.path_mandatory ? '' : ' NOT') + ' NULL'
519
+ # # }#{
520
+ # # # REVISIT: This is an SQL Server-ism. Replace with a standard SQL SEQUENCE/
521
+ # # # Emit IDENTITY for columns auto-assigned on commit (except FKs)
522
+ # # if a = object_type.is_auto_assigned and a != 'assert' and
523
+ # # !component.all_foreign_key_field.detect{|fkf| fkf.foreign_key.source_composite == component.root}
524
+ # # ' IDENTITY'
525
+ # # else
526
+ # # ''
527
+ # # end
528
+ # }#{
529
+ # value_constraint ? check_clause(column_name, value_constraint) : ''
530
+ # }"
531
+ # when MM::Injection
532
+ # component.object_type.name
533
+ # else
534
+ # raise "Can't make a column from #{component}"
535
+ # end
536
+ # end
537
+
538
+ # def generate_index index, delayed_indices, indent
539
+ # nullable_columns =
540
+ # index.all_index_field.select do |ixf|
541
+ # !ixf.component.path_mandatory
542
+ # end
543
+ # contains_nullable_columns = nullable_columns.size > 0
544
+ #
545
+ # primary = index.composite_as_primary_index && !contains_nullable_columns
546
+ # column_names =
547
+ # index.all_index_field.map do |ixf|
548
+ # column_name(ixf.component)
549
+ # end
550
+ # clustering =
551
+ # (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')
552
+ #
553
+ # if contains_nullable_columns
554
+ # table_name = safe_table_name(index.composite)
555
+ # delayed_indices <<
556
+ # 'CREATE UNIQUE'+clustering+' INDEX '+
557
+ # escape("#{table_name(index.composite)}By#{column_names*''}", index_name_max) +
558
+ # " ON #{table_name}("+column_names.map{|n| escape(n, column_name_max)}*', ' +
559
+ # ") WHERE #{
560
+ # nullable_columns.
561
+ # map{|ixf| safe_column_name ixf.component}.
562
+ # map{|column_name| column_name + ' IS NOT NULL'} *
563
+ # ' AND '
564
+ # }"
565
+ # nil
566
+ # else
567
+ # # '-- '+index.inspect
568
+ # " " * indent + (primary ? 'PRIMARY KEY' : 'UNIQUE') +
569
+ # clustering +
570
+ # "(#{column_names.map{|n| escape(n, column_name_max)}*', '})"
571
+ # end
572
+ # end
573
+
574
+ # def generate_foreign_key fk, indent
575
+ # # '-- '+fk.inspect
576
+ # " " * indent + "FOREIGN KEY (" +
577
+ # fk.all_foreign_key_field.map{|fkf| safe_column_name fkf.component}*", " +
578
+ # ") REFERENCES <a href=\"#LDMD_#{table_name fk.composite}\">#{table_name fk.composite}</a> (" +
579
+ # fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +
580
+ # ")"
581
+ # end
582
+
583
+ def reserved_words
584
+ @reserved_words ||= %w{ }
585
+ end
586
+
587
+ def is_reserved_word w
588
+ @reserved_word_hash ||=
589
+ reserved_words.inject({}) do |h,w|
590
+ h[w] = true
591
+ h
592
+ end
593
+ @reserved_word_hash[w.upcase]
594
+ end
595
+
596
+ # def go s = ''
597
+ # "#{s}\nGO\n" # REVISIT: This is an SQL-Serverism. Move it to a subclass.
598
+ # end
599
+
600
+ def escape s, max = table_name_max
601
+ # Escape SQL keywords and non-identifiers
602
+ if s.size > max
603
+ excess = s[max..-1]
604
+ s = s[0...max-(excess.size/8)] +
605
+ Digest::SHA1.hexdigest(excess)[0...excess.size/8]
606
+ end
607
+
608
+ if s =~ /[^A-Za-z0-9_]/ || is_reserved_word(s)
609
+ "[#{s}]"
610
+ else
611
+ s
612
+ end
613
+ end
614
+
615
+ # Return CWM type, typenum for the passed base type
616
+ def normalise_type_cwm(type_name, length)
617
+ type = MM::DataType.normalise(type_name)
618
+
619
+ case type
620
+ when MM::DataType::TYPE_Boolean; ['boolean', 16]
621
+ when MM::DataType::TYPE_Integer
622
+ case type_name
623
+ when /^Auto ?Counter$/i
624
+ ['int', 4]
625
+ when /([a-z ]|\b)Tiny([a-z ]|\b)/i
626
+ ['tinyint', -6]
627
+ when /([a-z ]|\b)Small([a-z ]|\b)/i,
628
+ /([a-z ]|\b)Short([a-z ]|\b)/i
629
+ ['smallint', 5]
630
+ when /([a-z ]|\b)Big(INT)?([a-z ]|\b)/i
631
+ ['bigint', -5]
632
+ else
633
+ ['int', 4]
634
+ end
635
+ when MM::DataType::TYPE_Real;
636
+ ['real', 7, data_type_context.default_length(type, type_name)]
637
+ when MM::DataType::TYPE_Decimal; ['decimal', 3]
638
+ when MM::DataType::TYPE_Money; ['decimal', 3]
639
+ when MM::DataType::TYPE_Char; ['char', 12, length || data_type_context.char_default_length]
640
+ when MM::DataType::TYPE_String; ['varchar', 12, length || data_type_context.varchar_default_length]
641
+ when MM::DataType::TYPE_Text; ['text', 2005, length || 'MAX']
642
+ when MM::DataType::TYPE_Date; ['date', 91]
643
+ when MM::DataType::TYPE_Time; ['time', 92]
644
+ when MM::DataType::TYPE_DateTime; ['datetime', 93]
645
+ when MM::DataType::TYPE_Timestamp;['timestamp', -3]
646
+ when MM::DataType::TYPE_Binary;
647
+ length ||= 16 if type_name =~ /^(guid|uuid)$/i
648
+ if length
649
+ ['BINARY', -2]
650
+ else
651
+ ['VARBINARY', -2]
652
+ end
653
+ else
654
+ ['int', 4]
655
+ end
656
+ end
657
+
658
+
659
+ def sql_value(value)
660
+ value.is_literal_string ? sql_string(value.literal) : value.literal
661
+ end
662
+
663
+ def sql_string(str)
664
+ "'" + str.gsub(/'/,"''") + "'"
665
+ end
666
+
667
+ def check_clause column_name, value_constraint
668
+ " CHECK(" +
669
+ value_constraint.all_allowed_range_sorted.map do |ar|
670
+ vr = ar.value_range
671
+ min = vr.minimum_bound
672
+ max = vr.maximum_bound
673
+ if (min && max && max.value.literal == min.value.literal)
674
+ "#{column_name} = #{sql_value(min.value)}"
675
+ else
676
+ inequalities = [
677
+ min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
678
+ max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
679
+ ].compact
680
+ inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
681
+ end
682
+ end*" OR " +
683
+ ")"
684
+ end
685
+
686
+ class CWMDataTypeContext < MM::DataType::Context
687
+ def integer_ranges
688
+ [
689
+ ['SMALLINT', -2**15, 2**15-1], # The standard says -10^5..10^5 (less than 16 bits)
690
+ ['INTEGER', -2**31, 2**31-1], # The standard says -10^10..10^10 (more than 32 bits!)
691
+ ['BIGINT', -2**63, 2**63-1], # The standard says -10^19..10^19 (less than 64 bits)
692
+ ]
693
+ end
694
+
695
+ def default_length data_type, type_name
696
+ case data_type
697
+ when MM::DataType::TYPE_Real
698
+ 53 # IEEE Double precision floating point
699
+ when MM::DataType::TYPE_Integer
700
+ case type_name
701
+ when /([a-z ]|\b)Tiny([a-z ]|\b)/i
702
+ 8
703
+ when /([a-z ]|\b)Small([a-z ]|\b)/i,
704
+ /([a-z ]|\b)Short([a-z ]|\b)/i
705
+ 16
706
+ when /([a-z ]|\b)Big(INT)?([a-z ]|\b)/i
707
+ 64
708
+ else
709
+ 32
710
+ end
711
+ else
712
+ nil
713
+ end
714
+ end
715
+
716
+ def boolean_type
717
+ 'BOOLEAN'
718
+ end
719
+
720
+ def surrogate_type
721
+ type_name, = choose_integer_type(0, 2**(default_surrogate_length-1)-1)
722
+ type_name
723
+ end
724
+
725
+ def valid_from_type
726
+ 'TIMESTAMP'
727
+ end
728
+
729
+ def date_time_type
730
+ 'TIMESTAMP'
731
+ end
732
+
733
+ def default_char_type
734
+ (@unicode ? 'NATIONAL ' : '') +
735
+ 'CHARACTER'
736
+ end
737
+
738
+ def default_varchar_type
739
+ (@unicode ? 'NATIONAL ' : '') +
740
+ 'VARCHAR'
741
+ end
742
+
743
+ def char_default_length
744
+ nil
745
+ end
746
+
747
+ def varchar_default_length
748
+ nil
749
+ end
750
+
751
+ def default_surrogate_length
752
+ 64
753
+ end
754
+
755
+ def default_text_type
756
+ default_varchar_type
757
+ end
758
+ end
759
+
760
+ end
761
+ end
762
+ publish_generator Doc::CWM
763
+ end
764
+ end