dataMetaDom 1.0.0
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 +7 -0
- data/.yardopts +1 -0
- data/History.md +6 -0
- data/PostInstall.txt +1 -0
- data/README.md +137 -0
- data/Rakefile +13 -0
- data/bin/dataMetaGvExport.rb +97 -0
- data/bin/dataMetaMySqlDdl.rb +27 -0
- data/bin/dataMetaOracleDdl.rb +26 -0
- data/bin/dataMetaPojo.rb +27 -0
- data/bin/dataMetaReVersion.rb +66 -0
- data/bin/dataMetaSameFullJ.rb +13 -0
- data/bin/dataMetaSameIdJ.rb +12 -0
- data/lib/dataMetaDom/converter.rb +70 -0
- data/lib/dataMetaDom/dataType.rb +112 -0
- data/lib/dataMetaDom/docs.rb +122 -0
- data/lib/dataMetaDom/enum.rb +125 -0
- data/lib/dataMetaDom/field.rb +182 -0
- data/lib/dataMetaDom/help.rb +41 -0
- data/lib/dataMetaDom/model.rb +274 -0
- data/lib/dataMetaDom/mySql.rb +256 -0
- data/lib/dataMetaDom/ora.rb +295 -0
- data/lib/dataMetaDom/pojo.rb +1056 -0
- data/lib/dataMetaDom/python.rb +168 -0
- data/lib/dataMetaDom/recAttr.rb +271 -0
- data/lib/dataMetaDom/record.rb +397 -0
- data/lib/dataMetaDom/ref.rb +127 -0
- data/lib/dataMetaDom/sourceFile.rb +150 -0
- data/lib/dataMetaDom/sources.rb +68 -0
- data/lib/dataMetaDom/util.rb +279 -0
- data/lib/dataMetaDom/ver.rb +244 -0
- data/lib/dataMetaDom.rb +141 -0
- data/test/test_dataMetaDom.rb +130 -0
- data/test/test_helper.rb +6 -0
- data/tmpl/java/migrationEntityEnums.erb +172 -0
- data/tmpl/python/entity.erb +50 -0
- metadata +126 -0
@@ -0,0 +1,295 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
%w(fileutils set).each { |r| require r }
|
4
|
+
|
5
|
+
module DataMetaDom
|
6
|
+
|
7
|
+
=begin rdoc
|
8
|
+
Definition for generating Oracle 11 and later artifacts such as schemas, select statements,
|
9
|
+
ORM input files etc etc
|
10
|
+
|
11
|
+
TODO this isn't a bad way, but beter use templating next time such as {ERB}[http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html].
|
12
|
+
|
13
|
+
For command line details either check the new method's source or the README.rdoc file, the usage section.
|
14
|
+
=end
|
15
|
+
module OraLexer
|
16
|
+
|
17
|
+
=begin rdoc
|
18
|
+
Integer types
|
19
|
+
=end
|
20
|
+
INT_TYPES = {2 => 'number(6)', 4 => 'number(9)', 8 => 'number(38)'}
|
21
|
+
|
22
|
+
=begin rdoc
|
23
|
+
Not null (required) wording per Oracle DDL syntax
|
24
|
+
=end
|
25
|
+
NOT_NULL=' not null'
|
26
|
+
|
27
|
+
=begin rdoc
|
28
|
+
\Mapping from DataMeta DOM standard types to correspondent Oracle types renderer lambdas.
|
29
|
+
=end
|
30
|
+
SQL_TYPES={
|
31
|
+
INT => lambda { |len, scale, isReq|
|
32
|
+
concreteType = INT_TYPES[len]
|
33
|
+
raise "Invalid integer type length #{len} " unless concreteType
|
34
|
+
"#{concreteType}#{isReq ? NOT_NULL : ''}"
|
35
|
+
},
|
36
|
+
STRING => lambda { |len, scale, isReq| "nvarchar2(#{len})#{isReq ? NOT_NULL : ''}" },
|
37
|
+
DATETIME => lambda { |len, scale, isReq| "timestamp#{isReq ? NOT_NULL : ''}" },
|
38
|
+
# Boolean implementation in Oracle is not optimal, see the doc:
|
39
|
+
# http://docs.oracle.com/cd/B19306_01/olap.102/b14346/dml_datatypes004.htm
|
40
|
+
BOOL => lambda { |len, scale, isReq| "boolean#{isReq ? NOT_NULL : ''}" },
|
41
|
+
CHAR => ->(len, scale, isReq) { "nchar(#{len})#{isReq ? NOT_NULL : ''}" },
|
42
|
+
NUMERIC => ->(len, scale, isReq) { "number(#{len}, #{scale})#{isReq ? NOT_NULL : ''}" }
|
43
|
+
|
44
|
+
}
|
45
|
+
|
46
|
+
=begin rdoc
|
47
|
+
Encapsulates 4 parts of DDL related SQL output:
|
48
|
+
* Creates
|
49
|
+
* Drops
|
50
|
+
* Linking aka Coupling aka creating Foreign Keys
|
51
|
+
* Unlinking aka Uncoupling aka dropping Foreign Keys
|
52
|
+
=end
|
53
|
+
class SqlOutput
|
54
|
+
|
55
|
+
=begin rdoc
|
56
|
+
Open output file into create SQL DDL statements (CREATE TABLE)
|
57
|
+
=end
|
58
|
+
attr_reader :create
|
59
|
+
|
60
|
+
=begin rdoc
|
61
|
+
Open output file into drop SQL DDL statements (DROP TABLE)
|
62
|
+
=end
|
63
|
+
attr_reader :drop
|
64
|
+
|
65
|
+
=begin rdoc
|
66
|
+
Open output file into the \couple SQL DDL statements, creating foreign keys
|
67
|
+
=end
|
68
|
+
attr_reader :couple
|
69
|
+
=begin rdoc
|
70
|
+
Open output file into the \uncouple SQL DDL statements, dropping foreign keys
|
71
|
+
=end
|
72
|
+
attr_reader :uncouple
|
73
|
+
=begin rdoc
|
74
|
+
Sequences and triggers - create
|
75
|
+
=end
|
76
|
+
attr_reader :crSeqs
|
77
|
+
=begin rdoc
|
78
|
+
Sequences and triggers - drop
|
79
|
+
=end
|
80
|
+
attr_reader :drSeqs
|
81
|
+
=begin rdoc
|
82
|
+
Indexes
|
83
|
+
=end
|
84
|
+
attr_reader :ixes
|
85
|
+
|
86
|
+
|
87
|
+
=begin rdoc
|
88
|
+
Creates an instance into the given target directory in which all 4 parts of the SQL DDL
|
89
|
+
process will be created.
|
90
|
+
=end
|
91
|
+
def initialize(sqlTargetDir)
|
92
|
+
@selTargetDir = sqlTargetDir
|
93
|
+
@create = File.new("#{sqlTargetDir}/DDL-createTables.sql", 'wb')
|
94
|
+
@crSeqs = File.new("#{sqlTargetDir}/DDL-createSeqs.sql", 'wb')
|
95
|
+
@drSeqs = File.new("#{sqlTargetDir}/DDL-dropSeqs.sql", 'wb')
|
96
|
+
@ixes = File.new("#{sqlTargetDir}/DDL-indexes.sql", 'wb')
|
97
|
+
@drop = File.new("#{sqlTargetDir}/DDL-drop.sql", 'wb')
|
98
|
+
@couple = File.new("#{sqlTargetDir}/DDL-couple.sql", 'wb')
|
99
|
+
@uncouple = File.new("#{sqlTargetDir}/DDL-uncouple.sql", 'wb')
|
100
|
+
@allScriptFiles = [@create, @drop, @couple, @uncouple, @drSeqs, @crSeqs, @ixes]
|
101
|
+
@dropScripts = [@uncouple, @drop]
|
102
|
+
@allScriptFiles.each { |f|
|
103
|
+
f.puts %q</* Generated by DataMeta DOM Oracle utility
|
104
|
+
DO NOT EDIT MANUALLY, update the DataMeta DOM source and regen.
|
105
|
+
*/
|
106
|
+
>
|
107
|
+
}
|
108
|
+
@dropScripts.each { |ds|
|
109
|
+
ds.puts %q<
|
110
|
+
/* Oracle does not have this feature: Disable all checks for safe dropping without any errors */
|
111
|
+
|
112
|
+
>
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
=begin rdoc
|
117
|
+
Safely closes all the output files.
|
118
|
+
=end
|
119
|
+
def close
|
120
|
+
@dropScripts.each { |ds|
|
121
|
+
ds.puts %q<
|
122
|
+
|
123
|
+
/* Placeholder for a drop footer */
|
124
|
+
>
|
125
|
+
}
|
126
|
+
@allScriptFiles.each { |f|
|
127
|
+
begin
|
128
|
+
f.close
|
129
|
+
rescue Exception => x;
|
130
|
+
$stderr.puts x.message
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
=begin rdoc
|
137
|
+
Renders autoincrement the very special Oracle way, via a sequence
|
138
|
+
FIXME: need to check the auto hint
|
139
|
+
|
140
|
+
@return empty string
|
141
|
+
=end
|
142
|
+
def autoGenClauseIfAny(out, record, field)
|
143
|
+
if record.identity && record.identity.length == 1 && field.name == record.identity[0] &&
|
144
|
+
field.dataType.type == DataMetaDom::INT
|
145
|
+
ns, entityName = DataMetaDom.splitNameSpace record.name
|
146
|
+
seqName = "#{entityName}_#{field.name}_sq"
|
147
|
+
# Transaction separators are important for triggers and sequences
|
148
|
+
out.crSeqs.puts %|
|
149
|
+
CREATE SEQUENCE #{seqName};
|
150
|
+
|
151
|
+
/
|
152
|
+
|
|
153
|
+
=begin rdoc
|
154
|
+
To make the sequence used automatically:
|
155
|
+
CREATE OR REPLACE TRIGGER #{entityName}_#{field.name}_trg
|
156
|
+
BEFORE INSERT ON #{entityName}
|
157
|
+
FOR EACH ROW
|
158
|
+
|
159
|
+
BEGIN
|
160
|
+
SELECT #{seqName}.NEXTVAL
|
161
|
+
INTO :new.#{field.name}
|
162
|
+
FROM dual;
|
163
|
+
END;
|
164
|
+
=end
|
165
|
+
out.drSeqs.puts %|
|
166
|
+
|
167
|
+
drop SEQUENCE #{seqName};
|
168
|
+
/
|
169
|
+
|
|
170
|
+
end
|
171
|
+
|
172
|
+
''
|
173
|
+
end
|
174
|
+
|
175
|
+
=begin rdoc
|
176
|
+
Renders the given field into create statement.
|
177
|
+
* Parameters:
|
178
|
+
* +createStatement+ - the create statement to append the field definition to.
|
179
|
+
* +parser+ - the instance of the Model
|
180
|
+
* +record+ - the instance of the Record to which the field belongs
|
181
|
+
* +fieldKey+ - the full name of the field to render turned into a symbol.
|
182
|
+
* +isFirstField+ - the boolean, true if the field is first in the create statement.
|
183
|
+
=end
|
184
|
+
def renderField(out, createStatement, parser, record, fieldKey, isFirstField)
|
185
|
+
field = record[fieldKey]
|
186
|
+
ty = field.dataType
|
187
|
+
stdRenderer = SQL_TYPES[ty.type]
|
188
|
+
typeEnum = parser.enums[ty.type]
|
189
|
+
typeRec = parser.records[ty.type]
|
190
|
+
|
191
|
+
typeDef = if stdRenderer
|
192
|
+
stdRenderer.call ty.length, ty.scale, field.isRequired
|
193
|
+
elsif typeEnum
|
194
|
+
"enum('#{typeEnum.values.join("','")}')"
|
195
|
+
elsif typeRec
|
196
|
+
raise "Invalid ref to #{typeRec} - it has no singular ID" unless typeRec.identity.length == 1
|
197
|
+
idField = typeRec[typeRec.identity[0]]
|
198
|
+
idRenderer = SQL_TYPES[idField.dataType.type]
|
199
|
+
raise 'Only one-level prim type references only allowed in this version' unless idRenderer
|
200
|
+
idRenderer.call idField.dataType.length, idField.dataType.scale, field.isRequired
|
201
|
+
end
|
202
|
+
createStatement << ",\n" unless isFirstField
|
203
|
+
createStatement << "\t#{field.name} #{typeDef}#{autoGenClauseIfAny(out, record, field)}"
|
204
|
+
end
|
205
|
+
|
206
|
+
=begin rdoc
|
207
|
+
Builds and returns the foreign key name for the given entity (Record) name and the counting number of these.
|
208
|
+
* Parameters:
|
209
|
+
* +bareEntityName+ - the entity name without the namespace
|
210
|
+
* +index+ - an integer, an enumerated counting number, starting from one. For each subsequent FK this number is
|
211
|
+
incremented.
|
212
|
+
=end
|
213
|
+
def fkName(bareEntityName, index)
|
214
|
+
"fk_#{bareEntityName}_#{index}"
|
215
|
+
end
|
216
|
+
|
217
|
+
=begin rdoc
|
218
|
+
Render SQL record with for the given model into the given output.
|
219
|
+
* Parameters
|
220
|
+
* +out+ - an instance of SqlOutput
|
221
|
+
* +parser+ - an instance of Model
|
222
|
+
* +recordKey+ - full name of the record datatype including namespeace if any turned into a symbol.
|
223
|
+
=end
|
224
|
+
def renderRecord(out, parser, recordKey)
|
225
|
+
record = parser.records[recordKey]
|
226
|
+
ns, entityName = DataMetaDom.splitNameSpace record.name
|
227
|
+
isFirstField = true
|
228
|
+
# Oracle does not have neatly defined feature of dropping table if it exists
|
229
|
+
# https://community.oracle.com/thread/2421779?tstart=0
|
230
|
+
out.drop.puts %|\ndrop table #{entityName};
|
231
|
+
/
|
232
|
+
|
|
233
|
+
fkNumber = 1 # to generate unique names that fit in 64 characters of identifier max length for Oracle
|
234
|
+
record.refs.select { |r| r.type == Reference::RECORD }.each { |ref|
|
235
|
+
ns, fromEntityBareName = DataMetaDom.splitNameSpace ref.fromEntity.name
|
236
|
+
ns, toEntityBareName = DataMetaDom.splitNameSpace ref.toEntity.name
|
237
|
+
out.couple.puts "alter table #{fromEntityBareName} add constraint #{fkName(fromEntityBareName, fkNumber)} "\
|
238
|
+
" foreign key (#{ref.fromField.name}) references #{toEntityBareName}(#{ref.toFields.name});"
|
239
|
+
out.uncouple.puts "alter table #{fromEntityBareName} drop foreign key #{fkName(fromEntityBareName, fkNumber)};"
|
240
|
+
fkNumber += 1
|
241
|
+
}
|
242
|
+
ids = record.identity ? record.identity.args : []
|
243
|
+
createStatement = "create table #{entityName} (\n"
|
244
|
+
fieldKeys = [] << ids.map { |i| i.to_s }.sort.map { |i| i.to_sym } \
|
245
|
+
<< record.fields.keys.select { |k| !ids.include?(k) }.map { |k| k.to_s }.sort.map { |k| k.to_sym }
|
246
|
+
|
247
|
+
fieldKeys.flatten.each { |f|
|
248
|
+
renderField(out, createStatement, parser, record, f, isFirstField)
|
249
|
+
isFirstField = false
|
250
|
+
}
|
251
|
+
if record.identity && record.identity.length > 0
|
252
|
+
createStatement << ",\n\tprimary key(#{ids.sort.join(', ')})"
|
253
|
+
end
|
254
|
+
unless record.uniques.empty?
|
255
|
+
uqNumber = 1
|
256
|
+
record.uniques.each_value { |uq|
|
257
|
+
createStatement << ",\n\tconstraint uq_#{entityName}_#{uqNumber} unique(#{uq.args.join(', ')})"
|
258
|
+
uqNumber += 1 # to generate unique names that fit in 30 characters of identifier max length for Oracle
|
259
|
+
}
|
260
|
+
end
|
261
|
+
unless record.indexes.empty?
|
262
|
+
ixNumber = 1
|
263
|
+
record.indexes.each_value { |ix|
|
264
|
+
out.ixes.puts %|
|
265
|
+
CREATE INDEX #{entityName}_#{ixNumber} ON #{entityName}(#{ix.args.join(', ')});
|
266
|
+
/
|
267
|
+
|
|
268
|
+
# createStatement << ",\n\tindex ix_#{entityName}_#{ixNumber}(#{ix.args.join(', ')})"
|
269
|
+
ixNumber += 1 # to generate unique names that fit in 64 characters of identifier max length for Oracle
|
270
|
+
}
|
271
|
+
end
|
272
|
+
createStatement << "\n);\n/\n"
|
273
|
+
|
274
|
+
out.create.puts createStatement
|
275
|
+
end
|
276
|
+
|
277
|
+
=begin rdoc
|
278
|
+
Generate the Oracle DDL from the given Model into the given output directory.
|
279
|
+
* Parameters
|
280
|
+
* +parser+ - an instance of a Model
|
281
|
+
* +outDir+ - a String, the directory to generate the DDL into.
|
282
|
+
=end
|
283
|
+
def genDdl(parser, outDir)
|
284
|
+
out = SqlOutput.new(outDir)
|
285
|
+
begin
|
286
|
+
parser.records.each_key { |r|
|
287
|
+
renderRecord(out, parser, r)
|
288
|
+
}
|
289
|
+
ensure
|
290
|
+
out.close
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
end
|