ibruby 0.5.1-i586-linux → 0.5.2-i586-linux
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/lib/ib_lib.so +0 -0
- data/lib/ibmeta.rb +823 -0
- data/lib/ibruby.rb +1 -1
- data/test/ConnectionTest.rb +1 -1
- data/test/MetaTest.rb +99 -0
- metadata +5 -4
- data/lib/SQLType.rb +0 -230
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
data/test/ConnectionTest.rb
CHANGED
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.
|
7
|
-
date: 2006-12-
|
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,
|
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.
|