activefacts-compositions 1.9.6 → 1.9.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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