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