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