dbd4 1.0.2
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.
- data/README +0 -0
- data/bin/dbd4 +152 -0
- data/lib/dbd4/dbd4_model_file.rb +531 -0
- data/lib/dbd4/ordered_hash.rb +182 -0
- data/lib/dbd4/rails_migration_file.rb +122 -0
- data/lib/dbd4/rails_model_file.rb +279 -0
- data/lib/slurp.rb +16 -0
- data/tests/unit/test_dbd4.rb +243 -0
- data/tests/unit/test_dbd4_model_file.rb +53 -0
- metadata +79 -0
data/README
ADDED
File without changes
|
data/bin/dbd4
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Daniel Shane
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#--
|
23
|
+
|
24
|
+
DBD4VERSION = '1.0.0'
|
25
|
+
|
26
|
+
require 'getoptlong'
|
27
|
+
require 'dbd4/dbd4_model_file'
|
28
|
+
|
29
|
+
############################################################################
|
30
|
+
# DBModel main application object. When invoking +dbmodel+ from the command
|
31
|
+
# line, a DBModelApp object is created and run.
|
32
|
+
#
|
33
|
+
class DBD4Import
|
34
|
+
|
35
|
+
OPTIONS = [
|
36
|
+
['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
|
37
|
+
"Do a dry run without executing actions."],
|
38
|
+
['--help', '-H', GetoptLong::NO_ARGUMENT,
|
39
|
+
"Display this help message."],
|
40
|
+
['--quiet', '-q', GetoptLong::NO_ARGUMENT,
|
41
|
+
"Do not log messages to standard output."],
|
42
|
+
['--usage', '-h', GetoptLong::NO_ARGUMENT,
|
43
|
+
"Display usage."],
|
44
|
+
['--version', '-V', GetoptLong::NO_ARGUMENT,
|
45
|
+
"Display the program version."],
|
46
|
+
]
|
47
|
+
|
48
|
+
# Create a DBModelApp object.
|
49
|
+
def initialize
|
50
|
+
end
|
51
|
+
|
52
|
+
# Display the program usage line.
|
53
|
+
def usage
|
54
|
+
puts "dbmodel {options} [dbmodelfiles ...]"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Display the dbmodel command line help.
|
58
|
+
def help
|
59
|
+
usage
|
60
|
+
puts
|
61
|
+
puts "Options are ..."
|
62
|
+
puts
|
63
|
+
OPTIONS.sort.each do |long, short, mode, desc|
|
64
|
+
if mode == GetoptLong::REQUIRED_ARGUMENT
|
65
|
+
if desc =~ /\b([A-Z]{2,})\b/
|
66
|
+
long = long + "=#{$1}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
printf " %-20s (%s)\n", long, short
|
70
|
+
printf " %s\n", desc
|
71
|
+
end
|
72
|
+
puts "\nPlease read the rdoc README for more information."
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return a list of the command line options supported by the
|
76
|
+
# program.
|
77
|
+
def command_line_options
|
78
|
+
OPTIONS.collect { |lst| lst[0..-2] }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Do the option defined by +opt+ and +value+.
|
82
|
+
def do_option(opt, value)
|
83
|
+
case opt
|
84
|
+
when '--dry-run'
|
85
|
+
$dryrun = true
|
86
|
+
when '--help'
|
87
|
+
help
|
88
|
+
exit
|
89
|
+
when '--quiet'
|
90
|
+
$silent = true
|
91
|
+
when '--usage'
|
92
|
+
usage
|
93
|
+
exit
|
94
|
+
when '--version'
|
95
|
+
puts "dbmodel, version #{DBMODELVERSION}"
|
96
|
+
exit
|
97
|
+
else
|
98
|
+
fail "Unknown option: #{opt}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Read and handle the command line options.
|
103
|
+
def handle_options
|
104
|
+
opts = GetoptLong.new(*command_line_options)
|
105
|
+
opts.each { |opt, value| do_option(opt, value) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Collect the list of dbmodel filenames on the command line.
|
109
|
+
# Default filename is "dbmodel.xml"
|
110
|
+
def get_filenames
|
111
|
+
default_filename = File.join('db', 'dbd4model.xml')
|
112
|
+
filenames = []
|
113
|
+
ARGV.each { |arg| filenames << arg }
|
114
|
+
if filenames.size == 0
|
115
|
+
if File.exist?(default_filename)
|
116
|
+
filenames.push(default_filename)
|
117
|
+
else
|
118
|
+
puts "\nERROR: Could not find any database model XML files (default : #{default_filename})."
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
filenames.each do |f|
|
123
|
+
raise "File #{f} is not located in the DB directory, make sure your XML model is in your app/db directory." if File.basename(File.dirname(File.expand_path(f))) != 'db'
|
124
|
+
end
|
125
|
+
|
126
|
+
filenames
|
127
|
+
end
|
128
|
+
|
129
|
+
# Run the dbmodel app
|
130
|
+
def run
|
131
|
+
handle_options
|
132
|
+
begin
|
133
|
+
get_filenames.each do |f|
|
134
|
+
puts "\nReading the datamodel XML (#{f}) from DBDesigner..." if not $silent
|
135
|
+
dbmf = DBD4::DBD4ModelFile.new(f)
|
136
|
+
if dbmf.messages[:warnings].size > 0
|
137
|
+
puts dbmf.messages[:warnings]
|
138
|
+
exit(1)
|
139
|
+
end
|
140
|
+
|
141
|
+
dbmf.generateModelFiles
|
142
|
+
puts "Done!"
|
143
|
+
end
|
144
|
+
rescue Exception => ex
|
145
|
+
puts "dbmodel aborted!"
|
146
|
+
puts ex
|
147
|
+
exit(1)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
DBD4Import.new.run
|
@@ -0,0 +1,531 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Daniel Shane
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#--
|
23
|
+
|
24
|
+
require 'rexml/document'
|
25
|
+
require 'rails_generator'
|
26
|
+
require 'rails_generator/scripts/generate'
|
27
|
+
require 'active_support/inflector'
|
28
|
+
require 'config/boot'
|
29
|
+
require 'dbd4/rails_model_file'
|
30
|
+
require 'dbd4/rails_migration_file'
|
31
|
+
require 'dbd4/ordered_hash'
|
32
|
+
|
33
|
+
module DBD4
|
34
|
+
class DBD4Error < StandardError
|
35
|
+
end
|
36
|
+
|
37
|
+
class DBD4ModelFile
|
38
|
+
attr_reader :tables, :generalDatatypes, :relations, :dbmodelxml, :file, :messages
|
39
|
+
|
40
|
+
def initialize(file)
|
41
|
+
@messages = { :errors => [], :warnings => [], :info => [] }
|
42
|
+
@file = file
|
43
|
+
@dbmodelxml = REXML::Document.new(File.open(file))
|
44
|
+
@allObjects = {
|
45
|
+
:tables => Tables.new,
|
46
|
+
:relations => Relations.new,
|
47
|
+
:datatypeDefinitions => DatatypeDefinitions.new
|
48
|
+
}
|
49
|
+
|
50
|
+
dbmodelxml.elements.each("//*[@ID]") do |e|
|
51
|
+
case e.name
|
52
|
+
when "DATATYPE"
|
53
|
+
@allObjects[:datatypeDefinitions] << DatatypeDefinition.new(e)
|
54
|
+
when "TABLE"
|
55
|
+
@allObjects[:tables] << Table.new(e)
|
56
|
+
when "COLUMN"
|
57
|
+
@allObjects[:tables].last.addColumnFromXml(e)
|
58
|
+
when "RELATION_START"
|
59
|
+
@allObjects[:tables].last.addStartRelation(RelationID.new(e))
|
60
|
+
when "RELATION_END"
|
61
|
+
@allObjects[:tables].last.addEndRelation(RelationID.new(e))
|
62
|
+
when "RELATION"
|
63
|
+
@allObjects[:relations] << Relation.new(e)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@allObjects[:relations].resolve(@allObjects)
|
67
|
+
@allObjects[:tables].resolve(@allObjects)
|
68
|
+
@tables = @allObjects[:tables]
|
69
|
+
validate
|
70
|
+
raise DBD4Error, messages[:errors].join("\n") if @messages[:errors].size > 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate
|
74
|
+
@tables.each_value { |t| t.validate(@messages) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def generateModelFiles
|
78
|
+
@tables.generateModelFiles
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_str
|
82
|
+
"DBModelFile(file=#{File.basename(file)})\n" + @tables.to_str + "\n"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
class Table
|
89
|
+
attr_reader :id, :name, :columns, :relation_starts, :relation_ends, :modelname, :comments, :non_standard_name, :nm_table
|
90
|
+
attr_writer :modelfile, :name, :nm_table
|
91
|
+
|
92
|
+
def initialize(table)
|
93
|
+
@id = table.attributes['ID']
|
94
|
+
@comments = table.attributes['Comments']
|
95
|
+
@name = table.attributes['Tablename']
|
96
|
+
@modelname = Inflector.singularize(@name)
|
97
|
+
@nm_table = false;
|
98
|
+
@primary_key = nil;
|
99
|
+
|
100
|
+
@relation_starts = Array.new
|
101
|
+
@relation_ends = Array.new
|
102
|
+
@columns = Columns.new(self)
|
103
|
+
@non_standard_name = false
|
104
|
+
end
|
105
|
+
|
106
|
+
def addColumnFromXml(column_xml)
|
107
|
+
column = Column.new(column_xml, self)
|
108
|
+
@primary_key = column if column.primary_key? and ! @primary_key
|
109
|
+
columns << column
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def addEndRelation(e)
|
114
|
+
@relation_ends << e
|
115
|
+
end
|
116
|
+
|
117
|
+
def addStartRelation(e)
|
118
|
+
@relation_starts << e
|
119
|
+
end
|
120
|
+
|
121
|
+
def validate(messages)
|
122
|
+
if @nm_table
|
123
|
+
t1 = @relation_ends.values[0].source_table
|
124
|
+
t2 = @relation_ends.values[1].source_table
|
125
|
+
expected_name = nil;
|
126
|
+
if t1.name < t2.name
|
127
|
+
expected_name = "#{t1.name}_#{t2.name}"
|
128
|
+
else
|
129
|
+
expected_name = "#{t2.name}_#{t1.name}"
|
130
|
+
end
|
131
|
+
if @name != expected_name then
|
132
|
+
messages[:warnings] << "Warning : join table #{name} differs from expected #{expected_name}"
|
133
|
+
@non_standard_name = true
|
134
|
+
end
|
135
|
+
else
|
136
|
+
if @modelname == @name
|
137
|
+
messages[:warnings] << "Warning : table name #{name} is not in a valid plural form, should be #{Inflector.pluralize(name)}"
|
138
|
+
@non_standard_name = true
|
139
|
+
elsif Inflector.pluralize(@modelname) != @name
|
140
|
+
messages[:warnings] << "Warning : table name #{name} is not in a valid plural formm should be #{Inflector.pluralize(@modelname)}"
|
141
|
+
@non_standard_name = true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
columns.validate(messages)
|
146
|
+
relation_starts.validate(messages)
|
147
|
+
# no need to validate relation_ends, since we would do the job twice... doh
|
148
|
+
end
|
149
|
+
|
150
|
+
def resolve(allObjects)
|
151
|
+
@columns.resolve(allObjects)
|
152
|
+
tmp = Relations.new
|
153
|
+
@relation_starts.each { |r| tmp<<allObjects[:relations][r.id] }
|
154
|
+
@relation_starts = tmp
|
155
|
+
tmp = Relations.new
|
156
|
+
@relation_ends.each { |r| tmp<<allObjects[:relations][r.id] }
|
157
|
+
@relation_ends = tmp
|
158
|
+
if @columns.size == 3
|
159
|
+
if @columns.foreign_keys.size == 2
|
160
|
+
if @relation_ends.values.size == 2
|
161
|
+
if @relation_ends.values[0].type == :one2many and @relation_ends.values[1].type == :one2many
|
162
|
+
@nm_table = true
|
163
|
+
@relation_ends.values[0].destination_table = @relation_ends.values[1].source_table
|
164
|
+
@relation_ends.values[0].join_table = self
|
165
|
+
@relation_ends.values[1].destination_table = @relation_ends.values[0].source_table
|
166
|
+
@relation_ends.values[1].join_table = self
|
167
|
+
@relation_ends.values[0].type = :many2many
|
168
|
+
@relation_ends.values[1].type = :many2many
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def generateModelFile
|
176
|
+
m = RailsModelFile.new({:modelname => @modelname})
|
177
|
+
m.update(self) unless @nm_table
|
178
|
+
m = RailsMigrationFile.new({:tablename => @name})
|
179
|
+
m.update(self)
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_str
|
183
|
+
"Table(id=#{id}, name=#{name}, non_standard_name=#{non_standard_name}, nm_table=#{nm_table})\n" +
|
184
|
+
"Start relations\n" +
|
185
|
+
@relation_starts.to_str + "\n" +
|
186
|
+
"End relations" + "\n" +
|
187
|
+
@relation_ends.to_str + "\n" +
|
188
|
+
"Columns" + "\n" +
|
189
|
+
@columns.to_str + "\n-----------------------\n"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class Column
|
194
|
+
attr_reader :id, :name, :datatype, :primary_key, :foreign_key, :table, :non_standard_name, :polymorphic
|
195
|
+
attr_writer :table, :non_standard_name, :polymorphic
|
196
|
+
|
197
|
+
def primary_key?
|
198
|
+
return @primary_key != nil && @primary_key == '1'
|
199
|
+
end
|
200
|
+
|
201
|
+
def foreign_key?
|
202
|
+
return @foreign_key != nil && @foreign_key == '1'
|
203
|
+
end
|
204
|
+
|
205
|
+
def initialize(column, table)
|
206
|
+
@id = column.attributes['ID']
|
207
|
+
@name= column.attributes['ColName']
|
208
|
+
@primary_key = column.attributes['PrimaryKey']
|
209
|
+
@foreign_key = column.attributes['IsForeignKey']
|
210
|
+
@datatype = Datatype.new(column, self)
|
211
|
+
@table = table
|
212
|
+
@non_standard_name = false
|
213
|
+
@polymorphic = nil
|
214
|
+
end
|
215
|
+
|
216
|
+
def validate(messages)
|
217
|
+
@datatype.validate(messages)
|
218
|
+
end
|
219
|
+
|
220
|
+
def resolve(allObjects)
|
221
|
+
@datatype.resolve(allObjects)
|
222
|
+
end
|
223
|
+
|
224
|
+
def to_str
|
225
|
+
"Column(id=#{id}, name=#{name}, pkey=#{primary_key?}, fkey=#{foreign_key?}, poly=#{polymorphic}, non_standard=#{non_standard_name}, " + datatype.to_str + ")"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class ForeignKeyField
|
230
|
+
attr_reader :source_field, :destination_field
|
231
|
+
|
232
|
+
def initialize(fkfield_string)
|
233
|
+
@source_field, @destination_field = fkfield_string.split(/=/)
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_str
|
237
|
+
"#{source_field} => #{destination_field}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class ForeignKeyFields < Array
|
242
|
+
def initialize(fkfields_string)
|
243
|
+
if fkfields_string
|
244
|
+
fkfields_string.split(/\\n/).each do |fk|
|
245
|
+
fkfield = ForeignKeyField.new(fk)
|
246
|
+
self<<fkfield
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def to_str
|
252
|
+
self.collect {|v| v.to_str}.join(",")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class RelationID
|
257
|
+
attr_reader :id
|
258
|
+
|
259
|
+
def initialize(relation)
|
260
|
+
@id = relation.attributes['ID']
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class Relation
|
265
|
+
attr_reader :id, :name, :type, :source_column, :destination_column, :source_table, :destination_table, :join_table, :fk_fields
|
266
|
+
attr_writer :destination_table, :source_table, :join_table, :type
|
267
|
+
|
268
|
+
def initialize(relation)
|
269
|
+
@join_table = nil
|
270
|
+
@fk_fields = ForeignKeyFields.new(relation.attributes['FKFields'])
|
271
|
+
@id = relation.attributes['ID']
|
272
|
+
@name = relation.attributes['RelationName']
|
273
|
+
@kind = relation.attributes['Kind']
|
274
|
+
@type = nil
|
275
|
+
case @kind
|
276
|
+
when '5'
|
277
|
+
@type = :one2one
|
278
|
+
when '2'
|
279
|
+
@type = :one2many
|
280
|
+
end
|
281
|
+
@source_table = relation.attributes['SrcTable']
|
282
|
+
@destination_table = relation.attributes['DestTable']
|
283
|
+
end
|
284
|
+
|
285
|
+
def resolve(allObjects)
|
286
|
+
@source_table = allObjects[:tables][@source_table]
|
287
|
+
@destination_table = allObjects[:tables][@destination_table]
|
288
|
+
@source_column = @source_table.columns.primary_keys[@fk_fields[0].source_field]
|
289
|
+
@destination_column = @destination_table.columns.foreign_keys[@fk_fields[0].destination_field]
|
290
|
+
end
|
291
|
+
|
292
|
+
def validate(messages)
|
293
|
+
if @fk_fields.size > 1
|
294
|
+
messages[:warnings] << "Warning : between table #{@destination_column.table.name} and table #{@source_column.table.name}, relation #{name} targets more than 1 foreign_key"
|
295
|
+
end
|
296
|
+
|
297
|
+
expected_fkey_name = Inflector.singularize(@source_table.name) + "_id"
|
298
|
+
if @destination_column.name != expected_fkey_name and @destination_column.polymorphic == nil then
|
299
|
+
messages[:warnings] << "Warning : table #{@destination_column.table.name}, foreign key #{@destination_column.name} does not match expected name #{expected_fkey_name}"
|
300
|
+
@destination_column.non_standard_name = true
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def to_str
|
305
|
+
"Relation(id=#{id}, name=#{name}, type=#{type}, src=#{source_table.name}, dst=#{destination_table.name}, fk=#{fk_fields.to_str})"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class Datatype
|
310
|
+
attr_reader :type, :params, :notnull, :id, :non_standard_type, :column
|
311
|
+
|
312
|
+
def initialize(datatype, column)
|
313
|
+
@column = column
|
314
|
+
@params = Hash.new()
|
315
|
+
@id = datatype.attributes['idDatatype']
|
316
|
+
@param_values = datatype.attributes['DatatypeParams'].delete("()").split(/[,]/)
|
317
|
+
@params = Hash.new()
|
318
|
+
@type = nil
|
319
|
+
@notnull = datatype.attributes['NotNull']
|
320
|
+
@non_standard_type = false
|
321
|
+
end
|
322
|
+
|
323
|
+
def resolve(allObjects)
|
324
|
+
dd = allObjects[:datatypeDefinitions][@id]
|
325
|
+
# Create a Hash from 2 arrays by zipping them together
|
326
|
+
@params = Hash[*dd.zip(@param_values).flatten] if ! @param_values.empty?
|
327
|
+
@type = dd.type
|
328
|
+
end
|
329
|
+
|
330
|
+
def validate(messages)
|
331
|
+
case @type
|
332
|
+
when 'BOOL'
|
333
|
+
if @params.size != 0
|
334
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} cannot take any parameters"
|
335
|
+
@non_standard_type = true
|
336
|
+
end
|
337
|
+
when 'TEXT'
|
338
|
+
if @params.size != 0
|
339
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} cannot take any parameters"
|
340
|
+
@non_standard_type = true
|
341
|
+
end
|
342
|
+
when 'VARCHAR'
|
343
|
+
@params.each_key do |k|
|
344
|
+
if k != 'length'
|
345
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} can only take length as parameter"
|
346
|
+
@non_standard_type = true
|
347
|
+
end
|
348
|
+
end
|
349
|
+
when 'FLOAT'
|
350
|
+
if @params.size != 0
|
351
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} cannot take any parameters"
|
352
|
+
@non_standard_type = true
|
353
|
+
end
|
354
|
+
when 'DATETIME'
|
355
|
+
if @params.size != 0
|
356
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} cannot take any parameters"
|
357
|
+
@non_standard_type = true
|
358
|
+
end
|
359
|
+
when 'DATE'
|
360
|
+
if @params.size != 0
|
361
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} cannot take any parameters"
|
362
|
+
@non_standard_type = true
|
363
|
+
end
|
364
|
+
when 'INTEGER'
|
365
|
+
@params.each_key do |k|
|
366
|
+
if k != 'length'
|
367
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} can only take length as parameter"
|
368
|
+
@non_standard_type = true
|
369
|
+
end
|
370
|
+
end
|
371
|
+
when 'DECIMAL'
|
372
|
+
if @params.size != 2 or not @params['length'] or not @params['decimals']
|
373
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} takes 2 params, length and decimals"
|
374
|
+
@non_standard_type = true
|
375
|
+
end
|
376
|
+
when 'BINARY'
|
377
|
+
if @params.size != 0
|
378
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} does not take any parameters"
|
379
|
+
@non_standard_type = true
|
380
|
+
end
|
381
|
+
when 'BLOB'
|
382
|
+
if @params.size != 0
|
383
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} does not take any parameters"
|
384
|
+
@non_standard_type = true
|
385
|
+
end
|
386
|
+
else
|
387
|
+
messages[:warnings] << "Warning : table #{column.table.name} column #{column.name}, #{type} is not a valid type"
|
388
|
+
@non_standard_type = true
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def to_str
|
393
|
+
"Datatype(type=#{type}, non_standard=#{non_standard_type}, notnull=#{notnull}" + params.keys.collect { |k| ", #{k}=#{params[k]}" }.join("") + ")"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
class DatatypeDefinition < Array
|
398
|
+
attr_reader :type, :id, :param_keys
|
399
|
+
|
400
|
+
def initialize(datatype)
|
401
|
+
@id = datatype.attributes['ID']
|
402
|
+
@type = datatype.attributes['TypeName']
|
403
|
+
datatype.elements.each("PARAMS/PARAM") do |p|
|
404
|
+
self << p.attributes['Name']
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def to_str
|
409
|
+
"GeneralDataType(id=#{id}, type=#{type}, #{params})"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
|
414
|
+
class Relations < OrderedHash
|
415
|
+
def <<(relation)
|
416
|
+
self[relation.id] = relation
|
417
|
+
end
|
418
|
+
|
419
|
+
def fix_many2many
|
420
|
+
each_value { |v| v.fix_many2many }
|
421
|
+
end
|
422
|
+
|
423
|
+
def resolve(allObjects)
|
424
|
+
each_key { |k| self[k] = allObjects[:relations][k] }
|
425
|
+
each_value { |v| v.resolve(allObjects) }
|
426
|
+
end
|
427
|
+
|
428
|
+
def validate(messages)
|
429
|
+
each_value { |v| v.validate(messages) }
|
430
|
+
end
|
431
|
+
|
432
|
+
def to_str
|
433
|
+
values.collect { |v| v.to_str + "\n" }.join("")
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
class Columns < Hash
|
438
|
+
attr_reader :primary_keys, :foreign_keys
|
439
|
+
attr_writer :primary_keys, :foreign_keys
|
440
|
+
|
441
|
+
def initialize(table, *args)
|
442
|
+
@primary_key = nil
|
443
|
+
@primary_keys = OrderedHash.new
|
444
|
+
@foreign_keys = OrderedHash.new
|
445
|
+
@table = table
|
446
|
+
super(*args)
|
447
|
+
end
|
448
|
+
|
449
|
+
def <<(column)
|
450
|
+
column.table = @table
|
451
|
+
@primary_key = column unless @primary_key
|
452
|
+
if column.primary_key?
|
453
|
+
@primary_keys[column.name] = column
|
454
|
+
end
|
455
|
+
if column.foreign_key?
|
456
|
+
@foreign_keys[column.name] = column if column.foreign_key?
|
457
|
+
if column.name =~ /^(.*)_id/ and self[$~[1] + "_type"] then
|
458
|
+
column.polymorphic = $~[1]
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
if ! column.primary_key? and ! column.foreign_key?
|
463
|
+
if column.name =~ /^(.*)_type/ and @foreign_keys[$~[1] + "_id"] then
|
464
|
+
@foreign_keys[$~[1] + "_id"].polymorphic = $~[1]
|
465
|
+
end
|
466
|
+
end
|
467
|
+
self[column.name] = column
|
468
|
+
end
|
469
|
+
|
470
|
+
def validate(messages)
|
471
|
+
if @primary_key == nil then
|
472
|
+
messages[:errors] << "Error : table #{@table.name} has no primary keys, you need at least 1"
|
473
|
+
else
|
474
|
+
if @primary_keys.size > 1 then
|
475
|
+
messages[:warnings] << "Warning : table #{@table.name} has more than 1 primary key, only the first one will be considered"
|
476
|
+
end
|
477
|
+
|
478
|
+
if @primary_key.name != 'id'
|
479
|
+
messages[:warnings] << "Warning : table #{@table.name} primary key #{@primary_key.name} differs from expected name id"
|
480
|
+
@primary_key.non_standard_name = true
|
481
|
+
end
|
482
|
+
end
|
483
|
+
each_value { |v| v.validate(messages) }
|
484
|
+
end
|
485
|
+
|
486
|
+
def resolve(allObjects)
|
487
|
+
each_value { |v| v.resolve(allObjects) }
|
488
|
+
end
|
489
|
+
|
490
|
+
def to_str
|
491
|
+
values.collect { |v| v.to_str + "\n" }.join("")
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
class Tables < Hash
|
496
|
+
def last
|
497
|
+
self[@order.last]
|
498
|
+
end
|
499
|
+
|
500
|
+
def initialize
|
501
|
+
@order = Array.new
|
502
|
+
end
|
503
|
+
|
504
|
+
def <<(table)
|
505
|
+
self[table.id] = table
|
506
|
+
@order << table.id
|
507
|
+
end
|
508
|
+
|
509
|
+
def resolve(allObjects)
|
510
|
+
each_value { |v| v.resolve(allObjects) }
|
511
|
+
end
|
512
|
+
|
513
|
+
def generateModelFiles
|
514
|
+
each_value { |v| v.generateModelFile }
|
515
|
+
end
|
516
|
+
|
517
|
+
def to_str
|
518
|
+
values.collect { |v| v.to_str + "\n" }.join("")
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
class DatatypeDefinitions < Hash
|
523
|
+
def <<(datatypeDefinition)
|
524
|
+
self[datatypeDefinition.id] = datatypeDefinition
|
525
|
+
end
|
526
|
+
|
527
|
+
def to_str
|
528
|
+
values { |v| v.to_str + "\n"}.join("")
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|