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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +33 -0
- data/activefacts-compositions.gemspec +3 -3
- data/bin/schema_compositor +142 -85
- data/lib/activefacts/compositions/binary.rb +19 -15
- data/lib/activefacts/compositions/compositor.rb +126 -125
- data/lib/activefacts/compositions/constraints.rb +74 -54
- data/lib/activefacts/compositions/datavault.rb +545 -0
- data/lib/activefacts/compositions/names.rb +58 -58
- data/lib/activefacts/compositions/relational.rb +801 -692
- data/lib/activefacts/compositions/traits/rails.rb +180 -0
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/css/ldm.css +45 -0
- data/lib/activefacts/generator/doc/cwm.rb +764 -0
- data/lib/activefacts/generator/doc/glossary.rb +473 -0
- data/lib/activefacts/generator/doc/graphviz.rb +134 -0
- data/lib/activefacts/generator/doc/ldm.rb +698 -0
- data/lib/activefacts/generator/oo.rb +130 -124
- data/lib/activefacts/generator/rails/models.rb +237 -0
- data/lib/activefacts/generator/rails/schema.rb +273 -0
- data/lib/activefacts/generator/ruby.rb +75 -67
- data/lib/activefacts/generator/sql.rb +333 -351
- data/lib/activefacts/generator/sql/server.rb +100 -39
- data/lib/activefacts/generator/summary.rb +67 -59
- data/lib/activefacts/generator/validate.rb +19 -134
- metadata +18 -15
@@ -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
|
@@ -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
|