ibruby 0.5.1-i586-linux → 0.5.2-i586-linux

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ib_lib.so CHANGED
Binary file
data/lib/ibmeta.rb ADDED
@@ -0,0 +1,823 @@
1
+ #-------------------------------------------------------------------------------
2
+ # ibmeta.rb
3
+ #-------------------------------------------------------------------------------
4
+ # Copyright � Peter Wood, 2005; Richard Vowles, 2006
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved. All modifications (and all InterBaseMetaFunctions) are by Richard Vowles
20
+ # (richard@developers-inc.co.nz).
21
+
22
+ module IBRuby
23
+ class InterBaseIndex
24
+
25
+ attr_accessor :table, :name, :unique, :columns, :direction, :active
26
+
27
+ def initialize(table, name, unique, columns, direction = nil, active = nil)
28
+ @table = table
29
+ @name = name
30
+ @unique = unique
31
+ @columns = columns
32
+ @direction = direction
33
+ @active = active
34
+ end
35
+
36
+ INDEX_ACTIVE = :INDEX_ACTIVE
37
+ INDEX_INACTIVE = :INDEX_INACTIVE
38
+
39
+ def to_sql
40
+ sql = "create"
41
+ if unique == true
42
+ sql << " unique"
43
+ end
44
+ if @direction
45
+ case @direction
46
+ when InterBaseMetaFunctions::ASCENDING
47
+ sql << " asc"
48
+ when InterBaseMetaFunctions::DESCENDING
49
+ sql << " desc"
50
+ end
51
+ end
52
+
53
+ sql << " index"
54
+
55
+ sql << " #{@name}"
56
+
57
+ sql << " on #{@table} ("
58
+ columns.each() do |col|
59
+ sql << ", " unless col == columns.first
60
+ sql << ((col.instance_of? InterBaseColumn) ? col.name : col.to_s)
61
+ end
62
+
63
+ sql << ")"
64
+
65
+ #puts "sql is #{sql}"
66
+
67
+ sql
68
+ end
69
+
70
+ def create_index(conn)
71
+ #puts #{to_sql} vs #{self.to_sql}"
72
+ conn.execute_immediate(self.to_sql)
73
+ end
74
+
75
+ def rename_index(conn, new_name)
76
+ old_name = @name
77
+ @name = new_name
78
+ begin
79
+ create(conn)
80
+ @name = old_name
81
+ remove(conn)
82
+ ensure
83
+ @name = old_name
84
+ end
85
+ end
86
+
87
+ def change_activation(conn, activation)
88
+ sql = "alter index #{@name} "
89
+ sql << (activation == InterBaseIndex::INDEX_ACTIVE) ? "active" : "inactive"
90
+ conn.execute_immediate( sql )
91
+ end
92
+
93
+ def remove_index(conn)
94
+ conn.execute_immediate( "DROP INDEX #{name}" )
95
+ end
96
+ end
97
+
98
+ class InterBaseConstraint
99
+ attr_accessor :name, :type, :columns, :table, :foreign_cols, :foreign_table_name
100
+
101
+ PRIMARY_KEY = :PRIMARY_KEY
102
+ FOREIGN_KEY = :FOREIGN_KEY
103
+
104
+ # columns are strings only, not complete columns. if they are complete columns
105
+ # then we just get their name
106
+ def initialize(type, columns, foreign_cols=nil, foreign_table_name=nil, name=nil)
107
+ @name = name
108
+ @type = type
109
+ @columns = columns
110
+ @foreign_cols=foreign_cols
111
+ @foreign_table_name=foreign_table_name
112
+
113
+ #puts "constraint created #{to_sql}"
114
+ end
115
+
116
+ def to_sql
117
+ tab_name = table.nil? ? "NULL" : table.name
118
+ sql = "alter table #{tab_name} add "
119
+
120
+ if @type == :PRIMARY_KEY
121
+ sql << "primary key ("
122
+ sql << cycle_cols
123
+ sql << ")"
124
+ elsif @type == :FOREIGN_KEY
125
+ sql << "foreign key ("
126
+ sql << cycle_cols
127
+ sql << ") references #{foreign_table_name} ("
128
+ sql << cycle_cols(foreign_cols)
129
+ sql << ")"
130
+ else
131
+ raise NotImplementedError, "Other constraints #{@type.to_s} not implemented yet"
132
+ end
133
+ end
134
+
135
+ private
136
+ def cycle_cols(columns=@columns)
137
+ sql = ""
138
+ if !columns.nil?
139
+ col_count = 0
140
+ columns.each() do |col|
141
+ sql << ", " unless col_count == 0
142
+ col_count += 1 # can't use columns.first as it may not be passed first by each????
143
+ if col === InterBaseColumn
144
+ sql << col.name
145
+ else
146
+ sql << col.to_s
147
+ end
148
+ end
149
+ end
150
+
151
+ sql
152
+ end
153
+
154
+ end
155
+
156
+ class InterBaseTable
157
+ attr_accessor :name, :columns, :indices, :constraints
158
+
159
+ SQL_ALL = :SQL_ALL
160
+ SQL_TABLE = :SQL_TABLE
161
+ SQL_INDICES = :SQL_INDICES
162
+ SQL_PRIMARY_KEYS = :SQL_PRIMARY_KEYS
163
+ SQL_FOREIGN_KEYS = :SQL_FOREIGN_KEYS
164
+
165
+ def initialize(name, columns=[], indices=[], constraints=[] )
166
+ #puts "table name new table: #{name}"
167
+ @name = name.to_s.upcase
168
+ @columns = columns
169
+ @indices = indices
170
+ @constraints = constraints
171
+
172
+ if @constraints
173
+ @constraints.each() {|c| c.table = self }
174
+ end
175
+ end
176
+
177
+ def load(conn)
178
+ @columns = InterBaseMetaFunctions.table_fields(conn,@name,true)
179
+ @indices = InterBaseMetaFunctions.indices(conn,@name)
180
+ @constraints = InterBaseMetaFunctions.table_constraints(conn,@name)
181
+
182
+ #puts "#{@constraints.size} constraints found"
183
+
184
+ @constraints.each() {|c| c.table = self }
185
+
186
+ @loaded = true
187
+ end
188
+
189
+ def drop_table(conn)
190
+ #puts "DROP TABLE #{name}"
191
+ conn.execute_immediate( "DROP TABLE #{name}" )
192
+ end
193
+
194
+ def create_table(conn)
195
+ to_sql.each() do |sql|
196
+ #puts "executing: #{sql}"
197
+ conn.execute_immediate( sql )
198
+ end
199
+ end
200
+
201
+ ## returns an array of sql required to create the table and all dependents
202
+ # when reconstructing the database, create all the tables, then create the primary keys and then
203
+ # create the foreign keys and then the indices
204
+ def to_sql(sql_restriction=:SQL_ALL)
205
+ sql = []
206
+
207
+ if ( [:SQL_ALL, :SQL_TABLE].include?(sql_restriction) )
208
+ sql << to_sql_create_table
209
+ end
210
+
211
+ if ( [:SQL_ALL, :SQL_INDICES].include?(sql_restriction) && @indices )
212
+ @indices.each() {|index| sql << index.to_sql }
213
+ end
214
+
215
+ if ( [:SQL_ALL, :SQL_PRIMARY_KEYS].include?(sql_restriction) && @constraints )
216
+ @constraints.each() do |c|
217
+ if (c.type == InterBaseConstraint::PRIMARY_KEY)
218
+ sql << c.to_sql
219
+ end
220
+ end
221
+ end
222
+
223
+ if ( [:SQL_ALL, :SQL_FOREIGN_KEYS].include?(sql_restriction) && @constraints )
224
+ @constraints.each() do |c|
225
+ if (c.type == InterBaseConstraint::FOREIGN_KEY)
226
+ sql << c.to_sql
227
+ end
228
+ end
229
+ end
230
+
231
+ sql
232
+ end
233
+
234
+ def rename_table(conn, ntable_name)
235
+ new_table_name = ntable_name.to_s.upcase
236
+
237
+ if @loaded.nil? or !@loaded
238
+ load(conn)
239
+ end
240
+
241
+ old_table_name = @name
242
+ load(conn) # load the definition
243
+ @name = new_table_name
244
+ to_sql(:SQL_TABLE).each() {|sql| conn.execute_immediate(sql) }
245
+ # copy all the data across
246
+ conn.execute_immediate( "insert into #{new_table_name} select * from #{old_table_name}")
247
+ to_sql(:SQL_PRIMARY_KEYS).each() {|sql| conn.execute_immediate(sql) }
248
+ to_sql(:SQL_FOREIGN_KEYS).each() {|sql| conn.execute_immediate(sql) }
249
+ @indices.each() do |index|
250
+ index.remove_index(conn)
251
+ index.table = new_table_name
252
+ index.create_index(conn)
253
+ end
254
+ @name = old_table_name
255
+ drop_table(conn)
256
+ end
257
+
258
+ private
259
+ def to_sql_create_table
260
+ sql = "create table #{name} ("
261
+
262
+ if !columns.nil?
263
+ col_count = 0
264
+ columns.each() do |col|
265
+ sql << ", " unless col_count == 0
266
+ col_count += 1
267
+ sql << col.to_sql
268
+ end
269
+ end
270
+
271
+ sql << ")"
272
+
273
+ #puts sql
274
+ sql
275
+ end
276
+ end
277
+
278
+ # InterBaseMetaFunctions
279
+ # Rather than requiring Ruby on Rails for access to these useful functions, I decided to move
280
+ # them here instead.
281
+ #
282
+
283
+ class InterBaseMetaFunctions
284
+
285
+ ASCENDING = :ASCENDING
286
+ DESCENDING = :DESCENDING
287
+
288
+ def self.quote( value, column_meta_data )
289
+ if column_meta_data.expects_quoting
290
+ '#{value}'
291
+ elsif ((column_meta_data.type == InterBaseColumn::BLOB) && (column_meta_data.sub_type != 1 ) )
292
+ raise IBRubyException.new("'#{value}' is not a valid default for this column #{column_meta_data.name}.")
293
+ else
294
+ value
295
+ end
296
+ end
297
+
298
+
299
+ def self.table_names(conn)
300
+ tables = []
301
+
302
+ conn.execute_immediate("select rdb$relation_name from rdb$relations where rdb$relation_name not starting 'RDB$' and rdb$flags=1") do |row|
303
+ tables << row[0].to_s.rstrip
304
+ end
305
+
306
+ tables
307
+ end
308
+
309
+ def self.indices(conn, table_name)
310
+ indices = []
311
+
312
+ # we must make sure the current transaction is committed, otherwise this won't work!
313
+ indicesSQL = <<-END_SQL
314
+ select rdb$index_name, rdb$unique_flag,RDB$INDEX_INACTIVE,RDB$INDEX_TYPE from rdb$indices
315
+ where rdb$relation_name = '#{table_name.to_s.upcase}' and rdb$index_name not starting 'RDB$'
316
+ END_SQL
317
+
318
+ #~ puts "SQL"
319
+ #~ puts indicesSQL
320
+
321
+ conn.execute_immediate(indicesSQL) do |row|
322
+ #puts "index #{ib_to_ar_case(row[0].to_s.rstrip)}"
323
+ indices << InterBaseIndex.new(table_name, row[0].to_s.rstrip,
324
+ row[1].to_i == 1, [],
325
+ (row[3] == 1)?InterBaseMetaFunctions::DESCENDING : InterBaseMetaFunctions::ASCENDING,
326
+ (row[2] == 1)?InterBaseIndex::INDEX_INACTIVE : InterBaseIndex::INDEX_ACTIVE)
327
+ end
328
+
329
+ #puts "Indices size #{indices.size}"
330
+
331
+ if !indices.empty?
332
+ indices.each() do |index|
333
+ sql = "select rdb$field_name from rdb$index_segments where rdb$index_name "\
334
+ "= '#{index.name.upcase}' order by rdb$index_name, rdb$field_position"
335
+
336
+ #puts "index SQL: #{sql}"
337
+
338
+ conn.execute_immediate(sql) do |row|
339
+ index.columns << table_fields(conn, table_name, true, row[0].to_s.rstrip )
340
+ end # each row in the index and get the InterBaseColumn
341
+ end # each index
342
+ end # if we found indices
343
+
344
+ indices
345
+ end
346
+
347
+ def self.remove_index(conn, index_name)
348
+ conn.execute_immediate( "DROP INDEX #{index_name}" )
349
+ end
350
+
351
+ # This class method fetches the type details for a named table. The
352
+ # method returns a hash that links column names to InterBaseColumn objects.
353
+ #
354
+ # ==== Parameters
355
+ # table:: A string containing the name of the table.
356
+ # connection:: A reference to the connection to be used to determine
357
+ # the type information.
358
+ # extract_ordered: if true then returns an ordered array of columns otherwise returns hash with col names
359
+ # column_name: if true then returns a single column (not array or hash)
360
+ #
361
+ # ==== Exception
362
+ # IBRubyException:: Generated if an invalid table name is specified
363
+ # or an SQL error occurs.
364
+ def self.table_fields(connection, table, extract_ordered = false, column_name = nil)
365
+ # Check for naughty table names.
366
+ if /\s+/ =~ table.to_s
367
+ raise IBRubyException.new("'#{table.to_s}' is not a valid table name.")
368
+ end
369
+
370
+ extract_ordered = true if !column_name.nil?
371
+
372
+ types = extract_ordered ? [] : {}
373
+ begin
374
+ # r.rdb$field_source,
375
+ sql = "SELECT r.rdb$field_name, f.rdb$field_type, "\
376
+ "f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale * -1, "\
377
+ "f.rdb$field_sub_type, "\
378
+ "COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source, "\
379
+ "COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag, rdb$character_length "\
380
+ "FROM rdb$relation_fields r "\
381
+ "JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name "\
382
+ "WHERE r.rdb$relation_name = '#{table.to_s.upcase}'";
383
+
384
+ if !column_name.nil?
385
+ sql << " AND r.rdb$field_name = '#{column_name.to_s.upcase}'"
386
+ elsif extract_ordered
387
+ sql << " ORDER BY r.rdb$field_position"
388
+ end
389
+
390
+ #puts "sql: #{sql}"
391
+
392
+ # sql = "SELECT RF.RDB$FIELD_NAME, F.RDB$FIELD_TYPE, "\
393
+ # "F.RDB$FIELD_LENGTH, F.RDB$FIELD_PRECISION, "\
394
+ # "F.RDB$FIELD_SCALE * -1, F.RDB$FIELD_SUB_TYPE "\
395
+ # "FROM RDB$RELATION_FIELDS RF, RDB$FIELDS F "\
396
+ # "WHERE RF.RDB$RELATION_NAME = UPPER('#{table}') "\
397
+ # "AND RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME"
398
+
399
+ #connection.start_transaction do |tx|
400
+ # tx.execute(sql)
401
+ connection.execute_immediate(sql) do |row|
402
+ sql_type = InterBaseColumn.to_base_type(row[1], row[5])
403
+ type = nil
404
+ field_name = row[0].strip
405
+
406
+ #column_name, table_name, type, default_source=nil, null_flag=false, length=nil, precision=nil, scale=nil, sub_type=nil )
407
+ #row[0], row
408
+ case sql_type
409
+ when InterBaseColumn::BLOB
410
+ type = InterBaseColumn.new(field_name, table.to_s, sql_type, row[6], !row[7].nil?, nil, nil, nil, row[5] )
411
+ when InterBaseColumn::CHAR, InterBaseColumn::VARCHAR
412
+ type = InterBaseColumn.new(field_name, table.to_s, sql_type, row[6], !row[7].nil?, row[8] )
413
+ # row[8] is the real length, field_length depends on the character set being used
414
+ when InterBaseColumn::DECIMAL, InterBaseColumn::NUMERIC
415
+ type = InterBaseColumn.new(field_name, table.to_s, sql_type, row[6], !row[7].nil?, nil, row[3], row[4] )
416
+ else
417
+ type = InterBaseColumn.new(field_name, table.to_s, sql_type, row[6], !row[7].nil? )
418
+ end
419
+
420
+ if extract_ordered
421
+ types << type
422
+ else
423
+ types[field_name] = type
424
+ end
425
+
426
+ #puts "col: #{type.to_sql}"
427
+ end
428
+
429
+ #end
430
+ end
431
+ if ( types.size > 1 || column_name.nil? )
432
+ types
433
+ elsif (types.size == 1)
434
+ types[0]
435
+ else
436
+ nil
437
+ end
438
+ end # table_fields
439
+
440
+ def self.table_constraints(conn, table_name)
441
+ sql = "select rdb$constraint_name, rdb$constraint_type, rdb$index_name "\
442
+ "from rdb$relation_constraints "\
443
+ "where rdb$constraint_type in ('FOREIGN KEY', 'PRIMARY KEY' ) "\
444
+ "and rdb$relation_name = '#{table_name.to_s.upcase}'"
445
+
446
+ constraints = []
447
+
448
+ conn.execute_immediate(sql) do |constraint|
449
+ constraint_name = constraint[0].strip
450
+ constraint_type = ( constraint[1].strip == 'PRIMARY KEY' ) ?
451
+ InterBaseConstraint::PRIMARY_KEY : InterBaseConstraint::FOREIGN_KEY;
452
+ representing_index = constraint[2]
453
+ # now we need to get the columns that are being keyed on on this table, that is the same
454
+ # for PK and FK
455
+ columns = []
456
+ conn.execute_immediate( "select rdb$field_name "\
457
+ "from rdb$index_segments where rdb$index_name='#{representing_index}'") do |col|
458
+ columns << col[0].to_s.strip
459
+ end
460
+ # and now for foreign keys, we need to find out what the foreign key index name is
461
+ if constraint_type == InterBaseConstraint::FOREIGN_KEY
462
+ fk_columns = []
463
+ foreign_key_index = nil
464
+ foreign_key_table = nil
465
+ conn.execute_immediate( "select rdb$foreign_key from rdb$indices "\
466
+ "where rdb$index_name='#{representing_index}'") do |fk|
467
+ foreign_key_index = fk[0].strip
468
+ end
469
+ conn.execute_immediate( "select rdb$relation_name from rdb$indices "\
470
+ "where rdb$index_name='#{foreign_key_index}'") do |fk|
471
+ foreign_key_table = fk[0].strip
472
+ end
473
+ conn.execute_immediate( "select rdb$field_name "\
474
+ "from rdb$index_segments where rdb$index_name='#{foreign_key_index}'") do |col|
475
+ fk_columns << col[0].to_s.strip
476
+ end
477
+
478
+ constraints << InterBaseConstraint.new( constraint_type, columns, fk_columns, foreign_key_table )
479
+ else
480
+ constraints << InterBaseConstraint.new( constraint_type, columns )
481
+ end #if constraints_type
482
+ end #conn.execute_immediate
483
+
484
+ constraints
485
+ end #def table_constraints
486
+
487
+ def self.db_type_cast( conn, column_type, column_value )
488
+ sql = "SELECT CAST(#{column_value} AS #{column_type}) FROM RDB$DATABASE ROWS 1 TO 1"
489
+ #puts "db_type_cast: #{sql}"
490
+ retVal = nil
491
+ conn.execute_immediate(sql) do |row|
492
+ retVal = row[0]
493
+ end
494
+ retVal
495
+ end
496
+
497
+
498
+ # end of InterBaseMetaFunctions class
499
+ end
500
+
501
+
502
+ # This class is used to represent SQL table column tables.
503
+ class InterBaseColumn
504
+ # allow these to be overriden
505
+ @@default_precision = 10
506
+ @@default_scale = 2
507
+ @@default_length = 255
508
+
509
+ # A definition for a base SQL type.
510
+ BOOLEAN = :BOOLEAN
511
+
512
+ # A definition for a base SQL type.
513
+ BLOB = :BLOB
514
+
515
+ # A definition for a base SQL type.
516
+ CHAR = :CHAR
517
+
518
+ # A definition for a base SQL type.
519
+ DATE = :DATE
520
+
521
+ # A definition for a base SQL type.
522
+ DECIMAL = :DECIMAL
523
+
524
+ # A definition for a base SQL type.
525
+ DOUBLE = :DOUBLE
526
+
527
+ # A definition for a base SQL type.
528
+ FLOAT = :FLOAT
529
+
530
+ # A definition for a base SQL type.
531
+ INTEGER = :INTEGER
532
+
533
+ # A definition for a base SQL type.
534
+ NUMERIC = :NUMERIC
535
+
536
+ # A definition for a base SQL type.
537
+ SMALLINT = :SMALLINT
538
+
539
+ # A definition for a base SQL type.
540
+ TIME = :TIME
541
+
542
+ # A definition for a base SQL type.
543
+ TIMESTAMP = :TIMESTAMP
544
+
545
+ # A definition for a base SQL type.
546
+ VARCHAR = :VARCHAR
547
+
548
+ # data type can be returned when arithmetic occurs, e.g. SMALLINT * -1 returns a INT64
549
+ INT64 = :INT64
550
+
551
+ # Attribute accessor.
552
+ attr_accessor :type, :length, :precision, :scale, :sub_type, :name, :table_name, :default, :not_null
553
+
554
+ def self.expects_quoting(col)
555
+ case col.type
556
+ when InterBaseColumn::NUMERIC, InterBaseColumn::DECIMAL, InterBaseColumn::INTEGER,
557
+ InterBaseColumn::DOUBLE, InterBaseColumn::FLOAT
558
+ false
559
+ when InterBaseColumn::CHAR, InterBaseColumn::VARCHAR, InterBaseColumn::BLOB,
560
+ InterBaseColumn::DATE, InterBaseColumn::TIME, InterBaseColumn::TIMESTAMP
561
+ true
562
+ else
563
+ nil
564
+ end
565
+ end
566
+
567
+ def expects_quoting
568
+ InterBaseColumn.expects_quoting(self)
569
+ end
570
+
571
+ # This is the constructor for the InterBaseColumn class.
572
+ #
573
+ # ==== Parameters
574
+ # type:: The base type for the InterBaseColumn object. Must be one of the
575
+ # base types defined within the class.
576
+ # length:: The length setting for the type. Defaults to nil.
577
+ # precision:: The precision setting for the type. Defaults to nil.
578
+ # scale:: The scale setting for the type. Defaults to nil.
579
+ # sub_type:: The SQL sub-type setting. Defaults to nil.
580
+ # default_source:: the whole string "default xxx" or "default 'xxx'"
581
+ # not_null: true for NOT NULL, false for nulls allowed
582
+ # actual_default:: if specified then we don't bother processing default_source
583
+ def initialize(column_name, table_name, type, default_source=nil, not_null=false,
584
+ length=nil, precision=10, scale=0, sub_type=nil, actual_default=nil )
585
+ @name = column_name
586
+ @table_name = table_name
587
+ @not_null = not_null
588
+ @type = type
589
+ @length = length
590
+ @precision = precision
591
+ @scale = scale
592
+ @sub_type = sub_type
593
+
594
+
595
+ if !actual_default.nil?
596
+ #puts "actual default #{actual_default}"
597
+ @default = actual_default
598
+ else
599
+ validate_default_source(default_source)
600
+ end
601
+ end
602
+
603
+ def validate
604
+ # ensure sensible defaults are set
605
+ @precision = @@default_precision if @precision.nil?
606
+ @scale = @@default_scale if @scale.nil?
607
+ @length = @@default_length if @length.nil?
608
+ end
609
+
610
+ def validate_default_source(default_source)
611
+ if !default_source.nil?
612
+ #puts "checking default: #{default_source}"
613
+ match = Regexp.new( '^\s*DEFAULT\s+(.*)\s*', Regexp::IGNORECASE )
614
+ matchData = match.match(default_source.to_s)
615
+ if matchData
616
+ @default = matchData[1]
617
+
618
+ #puts "result was #{@default} type is #{@type}"
619
+
620
+ if @default
621
+ if InterBaseColumn.expects_quoting(self)
622
+ len = @default.size - 2
623
+ @default = @default[1..len]
624
+ else
625
+ case @type
626
+ when InterBaseColumn::BOOLEAN
627
+ @default = "true".casecmp( @default.to_s ) == 0
628
+ when InterBaseColumn::DECIMAL, InterBaseColumn::NUMERIC
629
+ @default = BigDecimal.new( @default.to_s )
630
+ when InterBaseColumn::DOUBLE, InterBaseColumn::FLOAT
631
+ @default = @default.to_f
632
+ when InterBaseColumn::INTEGER
633
+ @default = @default.to_i
634
+ when InterBaseColumn::DATE, InterBaseColumn::TIME, InterBaseColumn::TIMESTAMP
635
+ if @default.to_s !~ /^current/i
636
+ @default = InterBaseMetaFunctions.db_type_cast( @default, to_s )
637
+ end
638
+ end
639
+ end
640
+ end
641
+ end
642
+ else
643
+ #puts "default source passed is null"
644
+ @default = nil
645
+ end
646
+ end
647
+
648
+
649
+ # This method overloads the equivalence test operator for the InterBaseColumn
650
+ # class.
651
+ #
652
+ # ==== Parameters
653
+ # object:: A reference to the object to be compared with.
654
+ def ==(object)
655
+ result = false
656
+ if object.instance_of?(InterBaseColumn)
657
+ result = (@type == object.type &&
658
+ @length == object.length &&
659
+ @precision == object.precision &&
660
+ @scale == object.scale &&
661
+ @sub_type == object.sub_type)
662
+ end
663
+ result
664
+ end
665
+
666
+
667
+ # This method generates a textual description for a InterBaseColumn object.
668
+ def to_s
669
+ validate # ensure sensible defaults
670
+
671
+ if @type == InterBaseColumn::DECIMAL or @type == InterBaseColumn::NUMERIC
672
+ "#{@type.id2name}(#{@precision},#{@scale})"
673
+ elsif @type == InterBaseColumn::BLOB
674
+ "#{@type.id2name} SUB_TYPE #{@sub_type}"
675
+ elsif @type == InterBaseColumn::CHAR or @type == InterBaseColumn::VARCHAR
676
+ "#{@type.id2name}(#{@length})"
677
+ elsif @type == InterBaseColumn::DOUBLE
678
+ "DOUBLE PRECISION"
679
+ else
680
+ @type.id2name
681
+ end
682
+ end
683
+
684
+ def to_sql
685
+ sql = name + " " + to_s
686
+ if !@default.nil?
687
+ sql << " default "
688
+ equote = expects_quoting
689
+ sql << "'" if equote
690
+ sql << @default.to_s
691
+ sql << "'" if equote
692
+ end
693
+ if @not_null == true
694
+ sql << " not null"
695
+ end
696
+ sql
697
+ # all manner of other things, we are ignoring (e.g. check constraints)
698
+ end
699
+
700
+ def to_base_type
701
+ InterBaseColumn.to_base_type(self.type, self.sub_type)
702
+ end
703
+
704
+ # This class method converts a InterBase internal type to a InterBaseColumn base
705
+ # type.
706
+ #
707
+ # ==== Parameters
708
+ # type:: A reference to the Interbase field type value.
709
+ # sub_type:: A reference to the Interbase field subtype value.
710
+ def self.to_base_type(type, subtype)
711
+ case type
712
+ when 16 # DECIMAL, NUMERIC
713
+ if subtype
714
+ subtype == 1 ? InterBaseColumn::NUMERIC : InterBaseColumn::DECIMAL
715
+ else
716
+ InterBaseColumn::INT64 # can't actually define a column of this type
717
+ end
718
+ when 17 # BOOLEAN
719
+ InterBaseColumn::BOOLEAN
720
+
721
+ when 261 # BLOB
722
+ InterBaseColumn::BLOB
723
+
724
+ when 14 # CHAR
725
+ InterBaseColumn::CHAR
726
+
727
+ when 12 # DATE
728
+ InterBaseColumn::DATE
729
+
730
+ when 27 # DOUBLE, DECIMAL, NUMERIC
731
+ if subtype
732
+ subtype == 1 ? InterBaseColumn::NUMERIC : InterBaseColumn::DECIMAL
733
+ else
734
+ InterBaseColumn::DOUBLE
735
+ end
736
+
737
+ when 10 # FLOAT
738
+ InterBaseColumn::FLOAT
739
+
740
+ when 8 # INTEGER, DECIMAL, NUMERIC
741
+ if subtype
742
+ subtype == 1 ? InterBaseColumn::NUMERIC : InterBaseColumn::DECIMAL
743
+ else
744
+ InterBaseColumn::INTEGER
745
+ end
746
+
747
+ when 7 # SMALLINT, DECIMAL, NUMERIC
748
+ if subtype
749
+ subtype == 1 ? InterBaseColumn::NUMERIC : InterBaseColumn::DECIMAL
750
+ else
751
+ InterBaseColumn::SMALLINT
752
+ end
753
+
754
+ when 13 # TIME
755
+ InterBaseColumn::TIME
756
+
757
+ when 35 # TIMESTAMP
758
+ InterBaseColumn::TIMESTAMP
759
+
760
+ when 37 # VARCHAR
761
+ InterBaseColumn::VARCHAR
762
+ end
763
+ end
764
+
765
+ # we should also check to see if this table has indexes which need to be dropped and re-created
766
+ # but the migrations user should really do that
767
+ def rename_column( connection, new_column_name )
768
+ #puts "alter table #{@table_name} alter column #{@name} to #{new_column_name}"
769
+ connection.execute_immediate( "alter table #{@table_name} alter column #{@name} to #{new_column_name}" )
770
+ end
771
+
772
+ # this column does not exist in the database, please create it!
773
+ def add_column( connection )
774
+ validate # ensure sensible defaults
775
+ #puts "alter table #{@table_name} add #{self.to_sql}"
776
+ connection.execute_immediate( "alter table #{@table_name} add #{self.to_sql}" )
777
+ end
778
+
779
+ def change_column(conn, new_column)
780
+ new_column.validate # ensure sensible defaults
781
+
782
+ if new_column.type != self # should use == defined above
783
+ change_type_sql = "ALTER TABLE #{@table_name} alter #{@name} type #{new_column.to_s}"
784
+ #puts change_type_sql
785
+ conn.execute_immediate(change_type_sql)
786
+ end
787
+
788
+ if new_column.not_null != @not_null
789
+ # now change the NULL status, this may make the table invalid so...
790
+ nullFlag = new_column.not_null ? "1" : "NULL"
791
+ update_relations_fields_sql =
792
+ "UPDATE rdb$relation_fields set rdb$null_flags = #{nullFlag}"\
793
+ " where rdb$relation_name='#{@table_name.upcase}' and "\
794
+ "rdb$field_name='#{@name.upcase}'"
795
+ conn.execute_immediate(update_relations_fields_sql)
796
+ end
797
+
798
+ # changed default or changed type
799
+ if (new_column.default != @default) || (new_column.type != self)
800
+ # now the default change, which is complicated!
801
+ defaultSource = new_column.default.nil? ? "" : ("default " << InterBaseMetaFunctions.quote(new_column.default.to_s, new_column ) )
802
+ #puts "alter table #{@table_name} add ib$$temp type #{new_column.to_s} #{defaultSource}"
803
+ conn.execute_immediate("alter table #{@table_name} add ib$$temp #{new_column.to_s} #{defaultSource}")
804
+
805
+ # standard hack to change the default type
806
+ begin
807
+ sql = <<-END_SQL
808
+ update rdb$relation_fields set
809
+ rdb$default_source=(select rdb$default_source from rdb$relation_fields where
810
+ rdb$field_name='IB$$TEMP' and rdb$relation_name='#{@table_name.upcase}'),
811
+ rdb$default_value=(select rdb$default_value from rdb$relation_fields where
812
+ rdb$field_name='IB$$TEMP' and rdb$relation_name='#{@table_name.upcase}')
813
+ where rdb$field_name='#{@name.upcase}' and rdb$relation_name='#{@table_name.upcase}';
814
+ END_SQL
815
+ conn.execute_immediate(sql)
816
+ ensure
817
+ conn.execute_immediate("alter table #{@table_name} drop ib$$temp" )
818
+ end
819
+ end
820
+ end
821
+
822
+ end # End of the InterBaseColumn class.
823
+ end # End of the IBRuby module.
data/lib/ibruby.rb CHANGED
@@ -19,4 +19,4 @@
19
19
  # Reserved.
20
20
 
21
21
  require 'ib_lib'
22
- require 'SQLType'
22
+ require 'ibmeta'
@@ -3,7 +3,7 @@
3
3
  require 'TestSetup'
4
4
  require 'test/unit'
5
5
  #require 'rubygems'
6
- require 'ibruby'
6
+ require 'ib_lib'
7
7
 
8
8
  include IBRuby
9
9
 
data/test/MetaTest.rb ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'TestSetup'
4
+ require 'test/unit'
5
+ #require 'rubygems'
6
+ require 'ibruby'
7
+
8
+ include IBRuby
9
+
10
+ class MetaTest < Test::Unit::TestCase
11
+ CURDIR = "#{Dir.getwd}"
12
+ DB_FILE = "#{CURDIR}#{File::SEPARATOR}meta_unit_test.ib"
13
+
14
+ def setup
15
+ puts "#{self.class.name} started." if TEST_LOGGING
16
+ if File::exist?(DB_FILE)
17
+ Database.new(DB_FILE).drop(DB_USER_NAME, DB_PASSWORD)
18
+ end
19
+
20
+ @database = Database.create(DB_FILE, DB_USER_NAME, DB_PASSWORD)
21
+ @connection = @database.connect(DB_USER_NAME, DB_PASSWORD)
22
+
23
+ @creation_sql = "create table mtest (id integer not null, "\
24
+ "bool1 boolean default false, bool2 boolean, bool3 boolean default true, bool4 boolean default true not null,"\
25
+ " blob1 blob sub_type 1 not null, blob2 blob sub_type 1, blob3 blob sub_type 0,"\
26
+ " char1 char(10) default 'fred', char2 char(10) default 'fred' not null, char3 char(10), char4 char(20) default 'wil''ma',"\
27
+ " date1 date default current_date, date2 date not null, date3 date default current_date not null,"\
28
+ " decimal1 decimal(18,5) default 1.345, decimal2 decimal(15,5) default 20.22 not null, decimal3 decimal(12,6) not null"\
29
+ ")"
30
+ #puts sql
31
+ @connection.execute_immediate( @creation_sql );
32
+ @connection.execute_immediate( "create table pk_table(id integer not null primary key)" )
33
+ @connection.execute_immediate( "create table fk_table(id integer not null primary key, "\
34
+ "fk_id integer references pk_table(id))" )
35
+ @pk_sql = "alter table mtest "
36
+
37
+ end
38
+
39
+
40
+ def teardown
41
+ @connection.close
42
+
43
+ # if File::exist?(DB_FILE)
44
+ # Database.new(DB_FILE).drop(DB_USER_NAME, DB_PASSWORD)
45
+ # end
46
+ puts "#{self.class.name} finished." if TEST_LOGGING
47
+ end
48
+
49
+ def test01
50
+ table = InterBaseTable.new('mtest')
51
+ assert_nothing_thrown( "failed to load table mtest" ) { table.load(@connection) }
52
+ sql = []
53
+ assert_nothing_thrown( "unable to build sql!" ) { sql = table.to_sql }
54
+ assert_not_nil sql, "sql returned nil for create table"
55
+ assert_equal sql.size > 0, true, "sql returned has nothing in it for table creation!"
56
+ #assert_equal @creation_sql.upcase, sql[0].upcase
57
+ #they ARE the same, but shows false and don't know why
58
+ puts sql
59
+ end
60
+
61
+ def test02
62
+ col = InterBaseMetaFunctions.table_fields( @connection, "mtest", true, "decimal2" )
63
+ new_col = col.dup
64
+ new_col.precision = 16
65
+ new_col.scale = 2
66
+ col.change_column(@connection,new_col)
67
+ new_col = InterBaseMetaFunctions.table_fields( @connection, "mtest", true, "decimal2" )
68
+
69
+ assert_equal false, new_col == col, "column decimal2 not changed"
70
+ assert_equal new_col.precision, 16, "column decimal2 precision not 16!"
71
+ assert_equal new_col.scale, 2, "column scale should be 2!"
72
+ end
73
+
74
+ def test03
75
+ col = InterBaseMetaFunctions.table_fields( @connection, "mtest", true, "decimal2" )
76
+ col.rename_column( @connection, "decimal_rename" )
77
+ ren_col = InterBaseMetaFunctions.table_fields( @connection, "mtest", true, "decimal2" )
78
+ assert_equal nil, ren_col, "column not renamed!"
79
+ new_col = InterBaseMetaFunctions.table_fields( @connection, "mtest", true, "decimal_rename" )
80
+ assert_equal new_col, col, "column types not identical after rename"
81
+ new_col.rename_column( @connection, "decimal2" )
82
+ end
83
+
84
+ def test04
85
+ table = InterBaseTable.new('fk_table')
86
+ table.load(@connection)
87
+ assert_nothing_thrown( "unable build sql for table fk_table! " ) do
88
+ table.to_sql.each() {|sql| puts "table creation sql: #{sql}" }
89
+ end
90
+ assert_nothing_thrown( "unable to rename table" ) do
91
+ table.rename_table( @connection, 'new_fk_table' )
92
+ end
93
+ table = InterBaseTable.new('new_fk_table')
94
+ table.load(@connection)
95
+ assert_nothing_thrown( "unable build sql for table fk_table! " ) do
96
+ table.to_sql.each() {|sql| puts "table creation sql: #{sql}" }
97
+ end
98
+ end
99
+ end
metadata CHANGED
@@ -3,12 +3,12 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ibruby
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.5.1
7
- date: 2006-12-05 00:00:00 +13:00
6
+ version: 0.5.2
7
+ date: 2006-12-23 00:00:00 +13:00
8
8
  summary: Ruby interface library for the InterBase database.
9
9
  require_paths:
10
10
  - lib
11
- email: paw220470@yahoo.ie, rvowles@codegear.com
11
+ email: paw220470@yahoo.ie, richard@developers-inc.co.nz
12
12
  homepage: http://rubyforge.org/projects/ibruby/
13
13
  rubyforge_project:
14
14
  description: IBRuby is an extension to the Ruby programming language that provides access to the InterBase RDBMS. IBRuby is based in the InterBase C API and has no additional dependencies. The IBRuby library wraps the API calls in a Ruby OO interface.
@@ -30,9 +30,9 @@ authors:
30
30
  - Peter Wood, Richard Vowles
31
31
  files:
32
32
  - lib/mkdoc
33
- - lib/SQLType.rb
34
33
  - lib/ibruby.rb
35
34
  - lib/src.rb
35
+ - lib/ibmeta.rb
36
36
  - lib/ib_lib.so
37
37
  - test/SQLTest.rb
38
38
  - test/DatabaseTest.rb
@@ -47,6 +47,7 @@ files:
47
47
  - test/BooleanTest.rb
48
48
  - test/KeyTest.rb
49
49
  - test/TestSetup.rb
50
+ - test/MetaTest.rb
50
51
  - test/BackupRestoreTest.rb
51
52
  - test/RowCountTest.rb
52
53
  - test/UnitTest.rb
data/lib/SQLType.rb DELETED
@@ -1,230 +0,0 @@
1
- #-------------------------------------------------------------------------------
2
- # SQLType.rb
3
- #-------------------------------------------------------------------------------
4
- # Copyright � Peter Wood, 2005; Richard Vowles, 2006
5
- #
6
- # The contents of this file are subject to the Mozilla Public License Version
7
- # 1.1 (the "License"); you may not use this file except in compliance with the
8
- # License. You may obtain a copy of the License at
9
- #
10
- # http://www.mozilla.org/MPL/
11
- #
12
- # Software distributed under the License is distributed on an "AS IS" basis,
13
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
- # the specificlanguage governing rights and limitations under the License.
15
- #
16
- # The Original Code is the FireRuby extension for the Ruby language.
17
- #
18
- # The Initial Developer of the Original Code is Peter Wood. All Rights
19
- # Reserved.
20
-
21
- module IBRuby
22
- # This class is used to represent SQL table column tables.
23
- class SQLType
24
- # A definition for a base SQL type.
25
- BOOLEAN = :BOOLEAN
26
-
27
- # A definition for a base SQL type.
28
- BLOB = :BLOB
29
-
30
- # A definition for a base SQL type.
31
- CHAR = :CHAR
32
-
33
- # A definition for a base SQL type.
34
- DATE = :DATE
35
-
36
- # A definition for a base SQL type.
37
- DECIMAL = :DECIMAL
38
-
39
- # A definition for a base SQL type.
40
- DOUBLE = :DOUBLE
41
-
42
- # A definition for a base SQL type.
43
- FLOAT = :FLOAT
44
-
45
- # A definition for a base SQL type.
46
- INTEGER = :INTEGER
47
-
48
- # A definition for a base SQL type.
49
- NUMERIC = :NUMERIC
50
-
51
- # A definition for a base SQL type.
52
- SMALLINT = :SMALLINT
53
-
54
- # A definition for a base SQL type.
55
- TIME = :TIME
56
-
57
- # A definition for a base SQL type.
58
- TIMESTAMP = :TIMESTAMP
59
-
60
- # A definition for a base SQL type.
61
- VARCHAR = :VARCHAR
62
-
63
- # Attribute accessor.
64
- attr_reader :type, :length, :precision, :scale, :subtype
65
-
66
-
67
- # This is the constructor for the SQLType class.
68
- #
69
- # ==== Parameters
70
- # type:: The base type for the SQLType object. Must be one of the
71
- # base types defined within the class.
72
- # length:: The length setting for the type. Defaults to nil.
73
- # precision:: The precision setting for the type. Defaults to nil.
74
- # scale:: The scale setting for the type. Defaults to nil.
75
- # subtype:: The SQL sub-type setting. Defaults to nil.
76
- def initialize(type, length=nil, precision=nil, scale=nil, subtype=nil)
77
- @type = type
78
- @length = length
79
- @precision = precision
80
- @scale = scale
81
- @subtype = subtype
82
- end
83
-
84
-
85
- # This class method fetches the type details for a named table. The
86
- # method returns a hash that links column names to SQLType objects.
87
- #
88
- # ==== Parameters
89
- # table:: A string containing the name of the table.
90
- # connection:: A reference to the connection to be used to determine
91
- # the type information.
92
- #
93
- # ==== Exception
94
- # IBRubyException:: Generated if an invalid table name is specified
95
- # or an SQL error occurs.
96
- def SQLType.for_table(table, connection)
97
- # Check for naughty table names.
98
- if /\s+/ =~ table
99
- raise IBRubyException.new("'#{table}' is not a valid table name.")
100
- end
101
-
102
- types = {}
103
- begin
104
- sql = "SELECT RF.RDB$FIELD_NAME, F.RDB$FIELD_TYPE, "\
105
- "F.RDB$FIELD_LENGTH, F.RDB$FIELD_PRECISION, "\
106
- "F.RDB$FIELD_SCALE * -1, F.RDB$FIELD_SUB_TYPE "\
107
- "FROM RDB$RELATION_FIELDS RF, RDB$FIELDS F "\
108
- "WHERE RF.RDB$RELATION_NAME = UPPER('#{table}') "\
109
- "AND RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME"
110
-
111
- connection.start_transaction do |tx|
112
- tx.execute(sql) do |row|
113
- sql_type = SQLType.to_base_type(row[1], row[5])
114
- type = nil
115
- case sql_type
116
- when BLOB
117
- type = SQLType.new(sql_type, nil, nil, nil, row[5])
118
-
119
- when CHAR, VARCHAR
120
- type = SQLType.new(sql_type, row[2])
121
-
122
- when DECIMAL, NUMERIC
123
- type = SQLType.new(sql_type, nil, row[3], row[4])
124
-
125
- else
126
- type = SQLType.new(sql_type)
127
- end
128
- types[row[0].strip] = type
129
- end
130
-
131
- end
132
- end
133
- types
134
- end
135
-
136
-
137
- # This method overloads the equivalence test operator for the SQLType
138
- # class.
139
- #
140
- # ==== Parameters
141
- # object:: A reference to the object to be compared with.
142
- def ==(object)
143
- result = false
144
- if object.instance_of?(SQLType)
145
- result = (@type == object.type &&
146
- @length == object.length &&
147
- @precision == object.precision &&
148
- @scale == object.scale &&
149
- @subtype == object.subtype)
150
- end
151
- result
152
- end
153
-
154
-
155
- # This method generates a textual description for a SQLType object.
156
- def to_s
157
- if @type == SQLType::DECIMAL or @type == SQLType::NUMERIC
158
- "#{@type.id2name}(#{@precision},#{@scale})"
159
- elsif @type == SQLType::BLOB
160
- "#{@type.id2name} SUB TYPE #{@subtype}"
161
- elsif @type == SQLType::CHAR or @type == SQLType::VARCHAR
162
- "#{@type.id2name}(#{@length})"
163
- else
164
- @type.id2name
165
- end
166
- end
167
-
168
-
169
- # This class method converts a InterBase internal type to a SQLType base
170
- # type.
171
- #
172
- # ==== Parameters
173
- # type:: A reference to the Interbase field type value.
174
- # subtype:: A reference to the Interbase field subtype value.
175
- def SQLType.to_base_type(type, subtype)
176
- case type
177
- when 16 # DECIMAL, NUMERIC
178
- if subtype
179
- subtype == 1 ? SQLType::NUMERIC : SQLType::DECIMAL
180
- else
181
- SQLType::BIGINT
182
- end
183
- when 17 # BOOLEAN
184
- SQLType::BOOLEAN
185
-
186
- when 261 # BLOB
187
- SQLType::BLOB
188
-
189
- when 14 # CHAR
190
- SQLType::CHAR
191
-
192
- when 12 # DATE
193
- SQLType::DATE
194
-
195
- when 27 # DOUBLE, DECIMAL, NUMERIC
196
- if subtype
197
- subtype == 1 ? SQLType::NUMERIC : SQLType::DECIMAL
198
- else
199
- SQLType::DOUBLE
200
- end
201
-
202
- when 10 # FLOAT
203
- SQLType::FLOAT
204
-
205
- when 8 # INTEGER, DECIMAL, NUMERIC
206
- if subtype
207
- subtype == 1 ? SQLType::NUMERIC : SQLType::DECIMAL
208
- else
209
- SQLType::INTEGER
210
- end
211
-
212
- when 7 # SMALLINT, DECIMAL, NUMERIC
213
- if subtype
214
- subtype == 1 ? SQLType::NUMERIC : SQLType::DECIMAL
215
- else
216
- SQLType::SMALLINT
217
- end
218
-
219
- when 13 # TIME
220
- SQLType::TIME
221
-
222
- when 35 # TIMESTAMP
223
- SQLType::TIMESTAMP
224
-
225
- when 37 # VARCHAR
226
- SQLType::VARCHAR
227
- end
228
- end
229
- end # End of the SQLType class.
230
- end # End of the FireRuby module.