activerecord-interbase-adapter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+
2
+ ## replace the validates_uniqueness_of method because we have to use UPPER and it uses LOWER.
3
+
4
+ ActiveRecord::Validations::ClassMethods.class_eval do
5
+ alias_method :old_validates_uniqueness_of, :validates_uniqueness_of
6
+
7
+ def validates_uniqueness_of(*attr_names)
8
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true }
9
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
10
+
11
+ validates_each(attr_names,configuration) do |record, attr_name, value|
12
+ if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?)
13
+ condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}"
14
+ condition_params = [value]
15
+ else
16
+ condition_sql = "UPPER(#{record.class.table_name}.#{attr_name}) #{attribute_condition(value)}"
17
+ condition_params = [value.upcase]
18
+ end
19
+ if scope = configuration[:scope]
20
+ Array(scope).map do |scope_item|
21
+ scope_value = record.send(scope_item)
22
+ condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}"
23
+ condition_params << scope_value
24
+ end
25
+ end
26
+ unless record.new_record?
27
+ condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?"
28
+ condition_params << record.send(:id)
29
+ end
30
+ if record.class.find(:first, :conditions => [condition_sql, *condition_params])
31
+ record.errors.add(attr_name, configuration[:message])
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,857 @@
1
+ # Original Firebird code by Ken Kunz <kennethkunz@gmail.com>,
2
+ # Extensive InterBase modifications by Richard Vowles - Blue Train Software, www.bluetrainsoftware.com
3
+ # roles functionality added - tested and works
4
+ #
5
+ # Updating for Rails 2.0.2
6
+
7
+ require 'active_record/connection_adapters/abstract_adapter'
8
+ require 'active_record/connection_adapters/ibrails_case_fix'
9
+ require_library_or_gem 'ibruby'
10
+
11
+ include IBRuby
12
+
13
+ module IBRuby # :nodoc: all
14
+ class Database
15
+
16
+ def self.db_string_for(config)
17
+ unless config.has_key?(:database)
18
+ raise ArgumentError, "No database specified. Missing argument: database."
19
+ end
20
+ host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host]
21
+ [host_string, config[:database]].join(":")
22
+ end
23
+
24
+ def self.new_from_config(config)
25
+ db = new db_string_for(config)
26
+ db.character_set = config[:charset]
27
+ return db
28
+ end
29
+
30
+ def self.new_from_params(database, host, port, service)
31
+ db_string = ""
32
+ if host
33
+ db_string << host
34
+ db_string << "/#{service || port}" if service || port
35
+ db_string << ":"
36
+ end
37
+ db_string << database
38
+ new(db_string)
39
+ end
40
+ end
41
+ end
42
+
43
+ module ActiveRecord
44
+ class << Base
45
+ def interbase_connection(config) # :nodoc:
46
+ unless defined? IBRuby::InterBaseColumn
47
+ raise AdapterNotFound,
48
+ 'The InterBase adapter requires IBRuby version 0.5.4 or greater; you appear ' <<
49
+ 'to be running an older version -- please update IBRuby (gem install ibruby).'
50
+ end
51
+
52
+ config = config.symbolize_keys
53
+
54
+ unless config.has_key?(:database)
55
+ raise ArgumentError, "No database specified. Missing argument: database."
56
+ end
57
+
58
+ options = config[:charset] ? { IBRuby::Connection::CHARACTER_SET => config[:charset] } : {}
59
+ options[IBRuby::Connection::ROLE] = config[:role] if config[:role]
60
+
61
+ # need to add :role to options
62
+ connection_params = [config[:username], config[:password], options]
63
+ db = IBRuby::Database.new( config[:database] )
64
+ begin
65
+ connection = db.connect(*connection_params)
66
+ rescue IBRuby::IBRubyException
67
+ if config.has_key?(:create_when_missing)
68
+ IBRuby::Database.create( config[:database], config[:username], config[:password], 4096, config[:charset] )
69
+ connection = db.connect(*connection_params)
70
+ else
71
+ raise
72
+ end
73
+ end
74
+
75
+ ConnectionAdapters::InterBaseAdapter.new(connection, logger, connection_params)
76
+ end
77
+ end
78
+
79
+ module ConnectionAdapters
80
+ class InterBaseTableDefinition < TableDefinition
81
+ attr_reader :pk_field
82
+
83
+ def primary_key(name)
84
+ col_count = @columns ? @columns.size : 0
85
+
86
+ column(name, :integer, {:null => false} )
87
+
88
+ if col_count != @columns.size # a column got added
89
+ @pk_field = @columns.last
90
+ end
91
+ end
92
+ end
93
+
94
+ class InterBaseColumn < Column # :nodoc:
95
+ VARCHAR_MAX_LENGTH = 32_765
96
+ BLOB_MAX_LENGTH = 32_767 # because activerecord has no parameterized inserts/updates!!!!!
97
+ DEFAULT_STRING_LENGTH = 252 # to allow :string fields to be indexes
98
+ attr_reader :interBaseColumn
99
+
100
+ def initialize(interBaseColumn)
101
+ @interBaseColumn = interBaseColumn
102
+
103
+ @name = InterBaseAdapter.ib_to_ar_case(interBaseColumn.name.downcase)
104
+ @sql_type = interBaseColumn.to_s
105
+ @null = !interBaseColumn.not_null
106
+ @limit = !interBaseColumn.type == IBRuby::InterBaseColumn::BLOB ? BLOB_MAX_LENGTH : interBaseColumn.length
107
+ @precision = interBaseColumn.precision
108
+ @scale = interBaseColumn.scale
109
+ @type = simplified_type(@sql_type)
110
+
111
+ @default = typecast_default
112
+
113
+ @primary = nil
114
+ end #def initialize
115
+
116
+ def default
117
+ if !@interBaseColumn.default.nil?
118
+ if ( [:timestamp, :time, :date].detect() {|val| @type == val } ) &&
119
+ ( @interBaseColumn.default.to_s =~ /^current_/i )
120
+ request_db_value( @interBaseColumn.default )
121
+ else
122
+ @default
123
+ end
124
+ else
125
+ nil
126
+ end
127
+ end # def default
128
+
129
+ def type_cast(value)
130
+ retVal = super
131
+ #puts "type_cast #{@name}:#{@interbase_type} = #{value} ==> #{retVal}"
132
+ retVal
133
+ end
134
+
135
+ private
136
+ # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
137
+ # This enables InterBase to provide an actual value when context variables are used as column
138
+ # defaults (such as CURRENT_TIMESTAMP).
139
+ def request_db_value( value )
140
+ connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'InterBase' }
141
+
142
+ if connection
143
+ type_cast InterBaseMetaFunctions.db_type_cast( conn, value, @sql_type )
144
+ else
145
+ raise ConnectionNotEstablished, "No InterBase connections established."
146
+ end
147
+ end
148
+
149
+ def typecast_default
150
+ def_val = nil
151
+ #puts "interbase default: #{@interBaseColumn.default} name is #{@name}:#{@type}"
152
+
153
+ if !@interBaseColumn.default.nil?
154
+ case @type
155
+ when :boolean
156
+ def_val = ( @interBaseColumn.default.to_s.casecmp( 'true' ) == 0)
157
+ when :string, :text
158
+ def_val = @interBaseColumn.default.to_s
159
+ when :integer
160
+ def_val = @interBaseColumn.default.to_s.to_i
161
+ when :float
162
+ def_val = @interBaseColumn.default.to_s.to_f
163
+ when :text
164
+ def_val = @interBaseColumn.default.to_s
165
+ when :timestamp, :time, :date
166
+ if @interBaseColumn.default =~ /^current_/i
167
+ def_val = nil
168
+ else
169
+ def_val = type_cast(@interBaseColumn.default)
170
+ end
171
+ else
172
+ if ((@interBaseColumn.type == IBRuby::InterBaseColumn::BLOB) && (@interBaseColumn.sub_type == 1 ) )
173
+ def_val = @interBaseColumn.default.to_s
174
+ end
175
+ end
176
+ end
177
+
178
+ #puts "default is #{def_val.class}:#{def_val}"
179
+ def_val
180
+ end
181
+
182
+ def simplified_type(field_type)
183
+ case interBaseColumn.type
184
+ when IBRuby::InterBaseColumn::INTEGER, IBRuby::InterBaseColumn::INT64, IBRuby::InterBaseColumn::SMALLINT
185
+ :integer
186
+ when IBRuby::InterBaseColumn::FLOAT, IBRuby::InterBaseColumn::DOUBLE
187
+ :float
188
+ when IBRuby::InterBaseColumn::DECIMAL, IBRuby::InterBaseColumn::NUMERIC
189
+ interBaseColumn.scale == 0 ? :integer : :decimal
190
+ when IBRuby::InterBaseColumn::TIMESTAMP
191
+ :timestamp
192
+ when IBRuby::InterBaseColumn::TIME
193
+ :time
194
+ when IBRuby::InterBaseColumn::DATE
195
+ :date
196
+ when IBRuby::InterBaseColumn::BLOB
197
+ interBaseColumn.sub_type == 1 ? :text : :binary
198
+ when IBRuby::InterBaseColumn::CHAR, IBRuby::InterBaseColumn::VARCHAR
199
+ :string
200
+ when IBRuby::InterBaseColumn::BOOLEAN
201
+ :boolean
202
+ end
203
+ end
204
+ end
205
+
206
+ # The InterBase adapter relies on the IBRuby[http://rubyforge.org/projects/ibruby/]
207
+ # extension, version 0.5.1 or later (available as a gem or from
208
+ # RubyForge[http://rubyforge.org/projects/ibruby/]). IBRuby works with
209
+ # InterBase 7.1, 7.5 and 2007 on Linux, Solaris and Win32 platforms.
210
+ #
211
+ # == Usage Notes
212
+ #
213
+ # === Sequence (Generator) Names
214
+ # The InterBase adapter supports the same approach adopted for the Oracle & Firebird
215
+ # adapters. See ActiveRecord::Base#set_sequence_name for more details.
216
+ #
217
+ # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
218
+ # trigger corresponding to a InterBase sequence generator when using
219
+ # ActiveRecord. In other words, you don't have to try to make InterBase
220
+ # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
221
+ # new record, ActiveRecord pre-fetches the next sequence value for the table
222
+ # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
223
+ # next primary key value is the only reliable method for the InterBase
224
+ # adapter to report back the +id+ after a successful insert.)
225
+ #
226
+ # === BLOB Elements
227
+ # The InterBase adapter currently provides only limited support for +BLOB+
228
+ # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
229
+ # When selecting a +BLOB+, the entire element is converted into a String.
230
+ # When inserting or updating a +BLOB+, the entire value is included in-line
231
+ # in the SQL statement, limiting you to values <= 32KB in size.
232
+ #
233
+ # === Column Name Case Semantics
234
+ # InterBase and ActiveRecord have somewhat conflicting case semantics for
235
+ # column names.
236
+ #
237
+ # [*InterBase*]
238
+ # The standard practice is to use unquoted column names, which can be
239
+ # thought of as case-insensitive. (In fact, InterBase converts them to
240
+ # uppercase.) Quoted column names (not typically used) are case-sensitive.
241
+ # [*ActiveRecord*]
242
+ # Attribute accessors corresponding to column names are case-sensitive.
243
+ # The defaults for primary key and inheritance columns are lowercase, and
244
+ # in general, people use lowercase attribute names.
245
+ #
246
+ # In order to map between the differing semantics in a way that conforms
247
+ # to common usage for both InterBase and ActiveRecord, uppercase column names
248
+ # in InterBase are converted to lowercase attribute names in ActiveRecord,
249
+ # and vice-versa. Mixed-case column names retain their case in both
250
+ # directions. Lowercase (quoted) InterBase column names are not supported.
251
+ # This is similar to the solutions adopted by other adapters.
252
+ #
253
+ # In general, the best approach is to use unqouted (case-insensitive) column
254
+ # names in your InterBase DDL (or if you must quote, use uppercase column
255
+ # names). These will correspond to lowercase attributes in ActiveRecord.
256
+ #
257
+ # For example, a InterBase table based on the following DDL:
258
+ #
259
+ # CREATE TABLE products (
260
+ # id INTEGER NOT NULL PRIMARY KEY,
261
+ # "type" VARCHAR(50),
262
+ # name VARCHAR(255) );
263
+ #
264
+ # ...will correspond to an ActiveRecord model class called +Product+ with
265
+ # the following attributes: +id+, +type+, +name+.
266
+ #
267
+ # ==== Quoting <tt>"TYPE"</tt> and other InterBase reserved words:
268
+ # In ActiveRecord, the default inheritance column name is +type+. The word
269
+ # _type_ is a InterBase reserved word, so it must be quoted in any InterBase
270
+ # SQL statements. Because of the case mapping described above, you should
271
+ # always reference this column using quoted-lowercase syntax
272
+ # (<tt>"type"</tt>) within InterBase DDL or other SQL statements (as in the
273
+ # example above). This holds true for any other InterBase reserved words used
274
+ # as column names as well as this driver keeps a list of all reserved words and will
275
+ # ensure they are not mapped to upper case and they are always used quoted in the database.
276
+ #
277
+ # === Migrations
278
+ # The InterBase adapter does support Migrations.
279
+ #
280
+ #
281
+ # === SQL
282
+ # ActiveRecord has as part of its test suite syntax which is not strictly compliant with the SQL standard
283
+ # i.e. field IN (NULL). NULL is not valid in the IN clause, and where this particular part of SQL has appeared
284
+ # I change it to field IS NULL instead using Ruby's pattern matching
285
+ #
286
+ # The same is true of = NULL in the WHERE clause, this is also not SQL standard and in those cases, I break the
287
+ # SQL statement up into the before WHERE and after WHERE and replace all = NULL after the WHERE with IS NULL. This
288
+ # is clearly time consuming on the SQL so I really wish they wouldn't do it.
289
+ #
290
+ # == Connection Options
291
+ # The following options are supported by the InterBase adapter. None of the
292
+ # options have default values.
293
+ #
294
+ # <tt>:database</tt>::
295
+ # <i>Required option.</i> Specifies one of: (i) a InterBase database alias;
296
+ # (ii) the full path of a database file; _or_ (iii) a full InterBase
297
+ # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
298
+ # or <tt>:port</tt> as separate options when using a full connection
299
+ # string.</i>
300
+ # <tt>:host</tt>::
301
+ # Set to <tt>"remote.host.name"</tt> for remote database connections.
302
+ # May be omitted for local connections if a full database path is
303
+ # specified for <tt>:database</tt>. Some platforms require a value of
304
+ # <tt>"localhost"</tt> for local connections when using a InterBase
305
+ # database _alias_.
306
+ # <tt>:service</tt>::
307
+ # Specifies a service name for the connection. Only used if <tt>:host</tt>
308
+ # is provided. Required when connecting to a non-standard service.
309
+ # <tt>:port</tt>::
310
+ # Specifies the connection port. Only used if <tt>:host</tt> is provided
311
+ # and <tt>:service</tt> is not. Required when connecting to a non-standard
312
+ # port and <tt>:service</tt> is not defined.
313
+ # <tt>:username</tt>::
314
+ # Specifies the database user. May be omitted or set to +nil+ (together
315
+ # with <tt>:password</tt>) to use the underlying operating system user
316
+ # credentials on supported platforms.
317
+ # <tt>:password</tt>::
318
+ # Specifies the database password. Must be provided if <tt>:username</tt>
319
+ # is explicitly specified; should be omitted if OS user credentials are
320
+ # are being used.
321
+ # <tt>:charset</tt>::
322
+ # Specifies the character set to be used by the connection. Refer to
323
+ # InterBase documentation for valid options.
324
+ # <tt>:role</tt>:
325
+ # Specifies the role that the username is using when connecting to the database (if any)
326
+
327
+ class InterBaseAdapter < AbstractAdapter
328
+
329
+ # class property
330
+ @@reserved_words_array = [ "ACTION", "AFTER", "ANY", "AT", "BASED", "BEGIN", "BOOLEAN",
331
+ "CASCADE", "CHARACTER", "ACTIVE", "ALL", "AS", "AUTO", "BASENAME", "BETWEEN", "BUFFER", "CAST",
332
+ "CHAR_LENGTH", "ADD", "ALTER", "ASC", "AUTODDL", "BASE_NAME", "BLOB", "BY", "CHAR", "CHECK",
333
+ "ADMIN", "AND", "ASCENDING", "AVG", "BEFORE", "BLOBEDIT", "CACHE", "CHARACTER", "CHECK_POINT_LEN",
334
+ "CHECK_POINT_LENGTH", "COLLATE", "COLLATION", "COLUMN", "COMMIT", "COMMITTED", "COMPILETIME",
335
+ "COMPUTED", "CLOSE", "CONDITIONAL", "CONNECT", "CONSTRAINT", "CONTAINING", "CONTINUE", "COUNT",
336
+ "CREATE", "CSTRING", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURSOR", "DATABASE",
337
+ "DATE", "DAY", "DB_KEY", "DEBUG", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DESC", "DESCENDING",
338
+ "DESCRIBE", "DESCRIPTOR", "DISCONNECT", "DISPLAY", "DISTINCT", "DO", "DOMAIN", "DOUBLE", "DROP", "ECHO",
339
+ "EDIT", "ELSE", "END", "ENTRY_POINT", "ESCAPE", "EVENT", "EXCEPTION", "EXECUTE", "EXISTS", "EXIT", "EXTERN",
340
+ "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FILE", "FILTER", "FLOAT", "FOR", "FOREIGN", "FOUND", "FREE_IT",
341
+ "FROM", "FULL", "FUNCTION", "GDSCODE", "GENERATOR", "GEN_ID", "GLOBAL", "GOTO", "GRANT", "GROUP", "GROUP_COMMIT_WAIT",
342
+ "GROUP_COMMIT_WAIT_TIME", "HAVING", "HELP", "HOUR", "IF", "IMMEDIATE", "IN", "INACTIVE", "INDEX", "INDICATOR",
343
+ "INIT", "INNER", "INPUT", "INPUT_TYPE", "INSERT", "INT", "INTEGER", "INTO", "IS", "ISOLATION", "ISQL", "JOIN",
344
+ "KEY", "LC_MESSAGES", "LC_TYPE", "LEFT", "LENGTH", "LEV", "LEVEL", "LIKE", "LOGFILE", "LOG_BUFFER_SIZE",
345
+ "LOG_BUF_SIZE", "LONG", "MANUAL", "MAX", "MAXIMUM", "MAXIMUM_SEGMENT", "MAX_SEGMENT", "MERGE", "MESSAGE", "MIN",
346
+ "MINIMUM", "MINUTE", "MODULE_NAME", "MONTH", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NO", "NOAUTO", "NOT", "NULL",
347
+ "NUMERIC", "NUM_LOG_BUFS", "NUM_LOG_BUFFERS", "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR", "ORDER",
348
+ "OUTER", "OUTPUT", "OUTPUT_TYPE", "OVERFLOW", "PAGE", "PAGELENGTH", "PAGES", "PAGE_SIZE", "PARAMETER", "PASSWORD",
349
+ "PERCENT", "PLAN", "POSITION", "POST_EVENT", "PRECISION", "PREPARE", "PRESERVE", "PROCEDURE", "PROTECTED", "PRIMARY",
350
+ "PRIVILEGES", "PUBLIC", "QUIT", "RAW_PARTITIONS", "READ", "REAL", "RECORD_VERSION", "REFERENCES", "RELEASE", "RESERV",
351
+ "RESERVING", "RESTRICT", "RETAIN", "RETURN", "RETURNING_VALUES", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROWS",
352
+ "RUNTIME", "SCHEMA", "SECOND", "SEGMENT", "SELECT", "SET", "SHADOW", "SHARED", "SHELL", "SHOW", "SINGULAR", "SIZE",
353
+ "SMALLINT", "SNAPSHOT", "SOME", "SORT", "SQLCODE", "SQLERROR", "SQLWARNING", "STABILITY", "STARTING", "STARTS", "STATEMENT",
354
+ "STATIC", "STATISTICS", "SUB_TYPE", "SUM", "SUSPEND", "TABLE", "TEMPORARY", "TERMINATOR", "THEN", "TIES", "TIME", "TIMESTAMP",
355
+ "TO", "TRANSACTION", "TRANSLATE", "TRANSLATION", "TRIGGER", "TRIM", "TRUE", "TYPE", "UNCOMMITTED", "UNION", "UNIQUE", "UNKNOWN",
356
+ "UPDATE", "UPPER", "USER", "USING", "VALUE", "VALUES", "VARCHAR", "VARIABLE", "VARYING", "VERSION", "VIEW", "WAIT", "WEEKDAY",
357
+ "WHEN", "WHENEVER", "WHERE", "WHILE", "WITH", "WORK", "WRITE", "YEAR", "YEARDAY"
358
+ ]
359
+
360
+ ## will turn it into a hash for faster access
361
+ @@reserved_words_hash = {}
362
+
363
+ # You can make a column case sensitive by adding it to the list of reserved words. This method builds the fast hash when first accessed.
364
+ def self.reserved_words
365
+ if @@reserved_words_hash.size == 0
366
+ @@reserved_words_array.each() do |value|
367
+ @@reserved_words_hash[ value ] = true
368
+ end
369
+ end
370
+
371
+ @@reserved_words_hash
372
+ end
373
+
374
+ def initialize(connection, logger, connection_params=nil)
375
+ super(connection, logger)
376
+ @connection_params = connection_params
377
+ end
378
+
379
+ def case_insignificant_uses_lower
380
+ false
381
+ end
382
+
383
+
384
+ def adapter_name # :nodoc:
385
+ 'InterBase'
386
+ end
387
+
388
+ # Returns true for InterBase adapter (since InterBase requires primary key
389
+ # values to be pre-fetched before insert). See also #next_sequence_value.
390
+ def prefetch_primary_key?(table_name = nil)
391
+ true
392
+ end
393
+
394
+ def default_sequence_name(table_name, primary_key) # :nodoc:
395
+ "#{table_name}_seq"
396
+ end
397
+
398
+
399
+ # QUOTING ==================================================
400
+
401
+ def quote(value, column = nil) # :nodoc:
402
+ if [Time, DateTime].include?(value.class)
403
+ "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
404
+ else
405
+ super
406
+ end
407
+ end
408
+
409
+ def quote_string(string) # :nodoc:
410
+ string.gsub(/'/, "''")
411
+ end
412
+
413
+ def quote_column_name(column_name) # :nodoc:
414
+ if InterBaseAdapter.reserved_words[column_name.to_s.upcase]
415
+ %Q("#{column_name.to_s.downcase}")
416
+ else
417
+ InterBaseAdapter.ar_to_ib_case(column_name)
418
+ end
419
+ end
420
+
421
+ def quoted_true # :nodoc:
422
+ 'true'
423
+ #true
424
+ end
425
+
426
+ def quoted_false # :nodoc:
427
+ 'false'
428
+ #false
429
+ end
430
+
431
+
432
+ # CONNECTION MANAGEMENT ====================================
433
+
434
+ def active?
435
+ not @connection.closed?
436
+ end
437
+
438
+ def reconnect!
439
+ @connection.close
440
+ @connection = @connection.database.connect(*@connection_params)
441
+ end
442
+
443
+ def disconnect!
444
+ @connection.close
445
+ end
446
+
447
+ # DATABASE STATEMENTS ======================================
448
+
449
+ def interbaseSQL( sql )
450
+ sql = sql.sub( / (IN|in) \(NULL\)/, ' IS NULL' )
451
+ # anything after the WHERE clause where we find = NULL, we need replace this with IS NULL
452
+ if ((sql !=~ /~INSERT /) && (sql =~ /= NULL/) )
453
+ sqlMatch = / WHERE /
454
+ matched = sqlMatch.match( sql )
455
+ if !matched.nil? # this of course will break if the = NULL is in a bit of text
456
+ sql = matched.pre_match << ' WHERE ' << matched.post_match.gsub( /= NULL/, 'IS NULL' )
457
+ end
458
+ end
459
+ #puts "SQL = #{sql}"
460
+ sql
461
+ end
462
+
463
+ def select_all(sql, name = nil) # :nodoc:
464
+ select(sql, name)
465
+ end
466
+
467
+ def select_one(sql, name = nil) # :nodoc:
468
+ if sql !=~ / ROWS /
469
+ sql << " ROWS 1 TO 1"
470
+ end
471
+ result = select(sql, name)
472
+ result.nil? ? nil : result.first #first element in array
473
+ end
474
+
475
+ def select_value(sql, name = nil)
476
+ result = select_one(sql, name)
477
+
478
+ result.nil? ? nil : result.values.first
479
+ end
480
+
481
+ def execute(sql, name = nil, &block) # :nodoc:
482
+ sql = interbaseSQL(sql)
483
+ #~ begin
484
+ log(sql, name) do
485
+ if @transaction
486
+ #puts "within trans: #{sql}"
487
+ @connection.execute(sql, @transaction, &block)
488
+ else
489
+ #puts "immediate: #{sql}"
490
+ @connection.execute_immediate(sql, &block)
491
+ end
492
+ end
493
+ #~ rescue Exception => boom
494
+ #~ puts "sql went boom #{boom.message}"
495
+ #~ nil
496
+ #~ end
497
+ end
498
+
499
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
500
+ execute(sql, name)
501
+ id_value
502
+ end
503
+
504
+ alias_method :update, :execute
505
+ alias_method :delete, :execute
506
+
507
+ def begin_db_transaction() # :nodoc:
508
+ #puts "begin_transaction"
509
+ #~ if @transaction.nil? or !transaction.active?
510
+ #~ if raw_connection.transactions.size > 0
511
+ #~ @transaction = raw_connection.transactions[0]
512
+ #~ else
513
+ @transaction = @connection.start_transaction
514
+ #~ end
515
+ #~ end
516
+ end
517
+
518
+ def commit_db_transaction() # :nodoc:
519
+ #puts "commit transaction"
520
+ if @total_count
521
+ @total_count += 1
522
+ else
523
+ @total_count = 0
524
+ end
525
+ #raise Exception, 'someone called commit?' if @total_count == 1
526
+ @transaction.commit unless @transaction.nil? or !@transaction.active?
527
+ ensure
528
+ @transaction = nil
529
+ end
530
+
531
+ def rollback_db_transaction() # :nodoc:
532
+ #puts "rollback transaction"
533
+ @transaction.rollback unless @transaction.nil? or !@transaction.active?
534
+ ensure
535
+ @transaction = nil
536
+ end
537
+
538
+ def add_limit_offset!(sql, options) # :nodoc:
539
+ if options[:limit]
540
+ limit_string = " ROWS "
541
+ offset = options[:offset] ? (options[:offset] + 1) : 1
542
+ if offset.to_i < 1
543
+ offset = 1
544
+ end
545
+ limit_string << offset.to_s
546
+ limit = offset.to_i + options[:limit].to_i - 1
547
+ limit_string << " TO #{limit}"
548
+ #sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
549
+ sql << limit_string
550
+ end
551
+ end
552
+
553
+ # Returns the next sequence value from a sequence generator. Not generally
554
+ # called directly; used by ActiveRecord to get the next primary key value
555
+ # when inserting a new database record (see #prefetch_primary_key?).
556
+ def next_sequence_value(sequence_name)
557
+ IBRuby::Generator.new(sequence_name, @connection).next(1)
558
+ end
559
+
560
+
561
+ # SCHEMA STATEMENTS ========================================
562
+ def supports_migrations?
563
+ true
564
+ end
565
+
566
+ # should really put this in IBRuby but don't want to change it for the release of 3rdRail
567
+ def recreate_database!
568
+ sql = "SELECT rdb$character_set_name FROM rdb$database"
569
+ charset = execute(sql).to_a.first[0]
570
+ filename = @connection.database.file
571
+ disconnect!
572
+ db = IBRuby::Database.new( filename )
573
+ db.drop(@connection_params[0], @connection_params[1])
574
+ db = nil
575
+ if charset.nil?
576
+ IBRuby::Database.create(@connection.database.file,
577
+ @connection_params[0], @connection_params[1], 4096)
578
+ else
579
+ IBRuby::Database.create(@connection.database.file,
580
+ @connection_params[0], @connection_params[1], 4096, charset.rstrip)
581
+ end
582
+ db = IBRuby::Database.new( filename )
583
+ @connection = db.connect( @connection_params[0], @connection_params[1], @connection_params[2] )
584
+ end
585
+
586
+
587
+ # have to create a sequence for each table because of the "prefetching of the primary key"
588
+ def create_table(name, options = {})
589
+ # this from super
590
+ table_definition = InterBaseTableDefinition.new(self)
591
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
592
+
593
+ yield table_definition
594
+
595
+ if options[:force]
596
+ drop_table(name) rescue nil
597
+ end
598
+ # to here
599
+ #
600
+ # now build the proper IB meta structures
601
+ columns = []
602
+
603
+ table_definition.columns.each() do |col|
604
+ new_col = IBRuby::InterBaseColumn.new( quote_column_name(col.name), name, rails_type_to_native_type[col.type],
605
+ nil, !col.null.nil? && !col.null, col.limit, col.precision, col.scale, rails_type_to_native_sub_type[col.type],
606
+ col.default )
607
+
608
+ columns << new_col
609
+ end
610
+
611
+ if ( table_definition.pk_field )
612
+ pk_constraints = [InterBaseConstraint.new(IBRuby::InterBaseConstraint::PRIMARY_KEY,
613
+ [table_definition.pk_field.name] ) ]
614
+ end
615
+
616
+ table = InterBaseTable.new(name, columns, nil, pk_constraints )
617
+
618
+ table.create_table( raw_connection )
619
+
620
+ seq_name = name.to_s + "_SEQ"
621
+ if !IBRuby::Generator.exists?( seq_name, raw_connection )
622
+ IBRuby::Generator::create(seq_name, @connection)
623
+ end
624
+ end
625
+
626
+ # should drop the indexes as well as any other meta data associated with this table!
627
+ def drop_table(name)
628
+ #puts "transactions? #{@transaction.nil?}"
629
+ InterBaseTable.new(name).drop_table(raw_connection)
630
+
631
+ seq_name = name.to_s + "_SEQ"
632
+ if IBRuby::Generator.exists?( seq_name, raw_connection )
633
+ IBRuby::Generator.new(seq_name, @connection).drop
634
+ end
635
+ end
636
+
637
+ def rename_column(table_name, column_name, new_column_name)
638
+ # will break if there is an index based on this column!
639
+ #
640
+ col = InterBaseMetaFunctions.table_fields( raw_connection, table_name.to_s, true, quote_column_name(column_name) )
641
+ col.rename_column( raw_connection, quote_column_name(new_column_name) )
642
+ end
643
+
644
+ def change_column(table_name, column_name, type, options = {})
645
+ col = InterBaseMetaFunctions.table_fields( raw_connection, table_name.to_s, true, column_name )
646
+
647
+ new_col = col.dup
648
+
649
+ if !type.nil?
650
+ new_col.type = rails_type_to_native_type[type]
651
+ new_col.sub_type = rails_type_to_native_sub_type[type]
652
+ end
653
+
654
+ push_options_into_ib_column( new_col, options )
655
+
656
+ if !options[:default].nil?
657
+ new_col.default = options[:default]
658
+ end
659
+
660
+ col.change_column( raw_connection, new_col )
661
+ end
662
+
663
+ def change_column_default(table_name, column_name, default)
664
+ change_column( table_name.to_s, column_name, nil, { :default => default } )
665
+ end
666
+
667
+ def native_database_types #:nodoc
668
+ {
669
+ :primary_key => "integer not null PRIMARY KEY",
670
+ :string => { :name => "varchar", :limit => 252 }, # allows default string to be index (index limit 252 in InterBase)
671
+ :text => { :name => "blob sub_type 1" },
672
+ :integer => { :name => "integer" },
673
+ :float => { :name => "double precision" },
674
+ :decimal => { :name => "decimal" },
675
+ :datetime => { :name => "timestamp" },
676
+ :timestamp => { :name => "timestamp" },
677
+ :time => { :name => "time" },
678
+ :date => { :name => "date" },
679
+ :binary => { :name => "blob" },
680
+ :boolean => { :name => "boolean" }
681
+ }
682
+ end
683
+
684
+ def rails_type_to_native_type
685
+ {
686
+ :string => IBRuby::InterBaseColumn::VARCHAR,
687
+ :text => IBRuby::InterBaseColumn::BLOB,
688
+ :integer => IBRuby::InterBaseColumn::INTEGER,
689
+ :float => IBRuby::InterBaseColumn::DOUBLE,
690
+ :decimal => IBRuby::InterBaseColumn::DECIMAL,
691
+ :datetime => IBRuby::InterBaseColumn::TIMESTAMP,
692
+ :timestamp => IBRuby::InterBaseColumn::TIMESTAMP,
693
+ :time => IBRuby::InterBaseColumn::TIME,
694
+ :date => IBRuby::InterBaseColumn::DATE,
695
+ :binary => IBRuby::InterBaseColumn::BLOB,
696
+ :boolean => IBRuby::InterBaseColumn::BOOLEAN
697
+ }
698
+ end
699
+
700
+ def rails_type_to_native_sub_type
701
+ {
702
+ :string => 0,
703
+ :text => 1,
704
+ :integer => 0,
705
+ :float => 0,
706
+ :decimal => 0, # 0 is decimal, 1 is numeric
707
+ :datetime => 0,
708
+ :timestamp => 0,
709
+ :time => 0,
710
+ :date => 0,
711
+ :binary => 0,
712
+ :boolean => 0
713
+ }
714
+ end
715
+
716
+
717
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
718
+ return super unless type.to_s == 'integer'
719
+
720
+ if limit.nil? || limit == 4
721
+ 'integer'
722
+ elsif limit < 4
723
+ 'smallint'
724
+ else
725
+ 'decimal(#{limit},0)'
726
+ end
727
+ end
728
+
729
+ def add_index(table_name, column_name, options = {})
730
+ column_names = Array(column_name)
731
+ index_name = index_name(table_name.to_s, :column => column_names)
732
+
733
+ if Hash === options # legacy support, since this param was a string
734
+ index_type = options[:unique] ? "UNIQUE" : ""
735
+ index_name = options[:name] || index_name
736
+ else
737
+ index_type = options
738
+ end
739
+
740
+ columns = column_names.collect() { |col| quote_column_name(col) }
741
+
742
+ begin
743
+ index = IBRuby::InterBaseIndex.new( table_name.to_s, index_name, "UNIQUE".casecmp(index_type) == 0, columns )
744
+ index.create_index( raw_connection )
745
+ rescue Exception => indexFailed
746
+ puts "Warning: Index creation failed #{indexFailed.message} - please read up on InterBase Index size limitations in Operations Guide"
747
+ end
748
+ end
749
+
750
+ def add_column(table_name, column_name, type, options = {})
751
+ new_col = IBRuby::InterBaseColumn.new( quote_column_name(column_name), table_name.to_s,
752
+ rails_type_to_native_type[type] )
753
+
754
+ new_col.sub_type = rails_type_to_native_sub_type[type]
755
+
756
+ push_options_into_ib_column( new_col, options )
757
+
758
+ if (new_col.type == IBRuby::InterBaseColumn::VARCHAR) && new_col.length.nil?
759
+ new_col.length = 255;
760
+ end
761
+
762
+ new_col.default = options[:default]
763
+
764
+ #puts "#{new_col.name} precision:#{new_col.precision}:#{new_col.scale}"
765
+
766
+ # if ( type == :decimal )
767
+ # new_col.precision = 10 unless new_col.precision?
768
+ # new_col.scale = 0 unless new_col.scale?
769
+ # end
770
+
771
+ new_col.add_column(raw_connection)
772
+ end
773
+
774
+ # returns an array of IndexDefinitions which include a string array of columns
775
+ def indexes(table_name, name = nil) #:nodoc:
776
+ indices = []
777
+
778
+ ibIndices = InterBaseMetaFunctions.indices(raw_connection,table_name.to_s)
779
+
780
+ # returns array of InterBaseIndex's
781
+ ibIndices.each() do |ibIndex|
782
+ index = IndexDefinition.new(InterBaseAdapter.ib_to_ar_case(ibIndex.table),
783
+ InterBaseAdapter.ib_to_ar_case(ibIndex.name), ibIndex.unique, [])
784
+
785
+ ibIndex.columns.each() do |col|
786
+ index.columns << InterBaseAdapter.ib_to_ar_case(col.name)
787
+ end
788
+
789
+ indices << index
790
+ end
791
+
792
+ indices
793
+ end
794
+
795
+ # returns a string array of tables!
796
+ def tables
797
+ table_names = InterBaseMetaFunctions.table_names(raw_connection)
798
+ table_names.each() { |table_name| table_name.downcase! }
799
+ end
800
+
801
+ def rename_table(name, new_name)
802
+ table = IBRuby::InterBaseTable.new(name)
803
+ table.rename_table(raw_connection, new_name )
804
+ end
805
+
806
+ # no table name specified for InterBase
807
+ def remove_index(table_name, options = {})
808
+ InterBaseMetaFunctions.remove_index(raw_connection, index_name(table_name.to_s, options) )
809
+ end
810
+
811
+ def columns(table_name, name = nil) # :nodoc:
812
+ columns = InterBaseMetaFunctions.table_fields(raw_connection,table_name.to_s,true)
813
+
814
+ cols = columns.collect() do |field|
815
+ ConnectionAdapters::InterBaseColumn.new( field )
816
+ end
817
+
818
+ cols
819
+ end
820
+
821
+ protected
822
+ ## returns an array of hashes
823
+ def select(sql, name = nil)
824
+ rs = execute(sql, name)
825
+ results = rs.collect do |row|
826
+ hashed_row = {}
827
+ row.each do |column, value|
828
+ value = value.to_s if IBRuby::Blob === value
829
+ hashed_row[InterBaseAdapter.ib_to_ar_case(column)] = value
830
+ end
831
+ hashed_row
832
+ end
833
+ rs.close # otherwise we temporarily and unnecessarily leak result sets, which lock meta-data
834
+ results
835
+ end
836
+
837
+ # Maps uppercase InterBase column names to lowercase for ActiveRecord;
838
+ # mixed-case columns retain their original case.
839
+ def self.ib_to_ar_case(column_name)
840
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.to_s.downcase
841
+ end
842
+
843
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
844
+ # mixed-case columns retain their original case.
845
+ def self.ar_to_ib_case(column_name)
846
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.to_s.upcase
847
+ end
848
+
849
+ def push_options_into_ib_column( new_col, options )
850
+ new_col.length = options[:limit].to_i unless options[:limit].nil?
851
+ new_col.precision = options[:precision].to_i unless options[:precision].nil?
852
+ new_col.not_null = options[:null] == false unless options[:null].nil?
853
+ new_col.scale = options[:scale].to_i unless options[:scale].nil?
854
+ end
855
+ end
856
+ end
857
+ end
@@ -0,0 +1,86 @@
1
+ # Richard Vowles - Developers Inc Ltd, www.developers-inc.co.nz
2
+ # Thank you Matthew Bass!
3
+ # Basic code taken from http://matthewbass.com/2007/03/07/overriding-existing-rake-tasks/
4
+ #
5
+
6
+ Rake::TaskManager.class_eval do
7
+ def remove_task(task_name)
8
+ t_name = task_name.to_s
9
+ @tasks.delete(t_name)
10
+ end
11
+ end
12
+
13
+ def remove_task(task_name)
14
+ Rake.application.remove_task(task_name)
15
+ end
16
+
17
+ remove_task( "db:structure:dump" )
18
+ remove_task( "db:test:clone_structure" )
19
+ remove_task( "db:test:purge" )
20
+ remove_task( "db:sessions:create" )
21
+
22
+ # now replace the tasks from database.rake
23
+ namespace :db do
24
+ namespace :structure do
25
+ desc "Dump the database structure to a SQL file"
26
+ task :dump => :environment do
27
+ abcs = ActiveRecord::Base.configurations
28
+ case abcs[RAILS_ENV]["adapter"]
29
+ when "interbase"
30
+ set_firebird_env(abcs[RAILS_ENV])
31
+ db_string = IBRuby::Database.db_string_for(abcs[RAILS_ENV].symbolize_keys)
32
+ sh "isql -a #{db_string} > db/#{RAILS_ENV}_structure.sql"
33
+ else
34
+ raise "Task not supported by '#{abcs["test"]["adapter"]}'"
35
+ end
36
+
37
+ if ActiveRecord::Base.connection.supports_migrations?
38
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
39
+ end
40
+ end
41
+ end #namespace :structure
42
+
43
+ namespace :test do
44
+ desc "Recreate the test databases from the development structure"
45
+ task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
46
+ abcs = ActiveRecord::Base.configurations
47
+ case abcs["test"]["adapter"]
48
+ when "interbase"
49
+ set_firebird_env(abcs["test"]) # actually InterBase ones, Firebird being a fork!
50
+ db_string = IBRuby::Database.db_string_for(abcs["test"].symbolize_keys)
51
+ begin
52
+ ## outputs an EOF but don't know why
53
+ sh "isql -i db/#{RAILS_ENV}_structure.sql #{db_string}"
54
+ rescue
55
+
56
+ end
57
+ else
58
+ raise "Task not supported by '#{abcs["test"]["adapter"]}'"
59
+ end
60
+ end
61
+
62
+ desc "Empty the test database"
63
+ task :purge => :environment do
64
+ abcs = ActiveRecord::Base.configurations
65
+ case abcs["test"]["adapter"]
66
+ when "interbase"
67
+ ActiveRecord::Base.establish_connection(:test)
68
+ ActiveRecord::Base.connection.recreate_database!
69
+ else
70
+ raise "Task not supported by '#{abcs["test"]["adapter"]}'"
71
+ end
72
+ end
73
+ end #namespace :test
74
+
75
+ namespace :sessions do
76
+ desc "Creates a sessions table for use with CGI::Session::ActiveRecordStore"
77
+ task :create => :environment do
78
+ raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations?
79
+ require 'rails_generator'
80
+ require 'rails_generator/scripts/generate'
81
+ Rails::Generator::Scripts::Generate.new.run(["session_migration", ENV["MIGRATION"] || "AddSessions"],
82
+ :source => "#{RAILS_ROOT}/vendor/plugins/ibrails_plugin/db")
83
+ end
84
+ end #namespace :sessions
85
+ end #namespace :database
86
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: activerecord-interbase-adapter
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2008-01-30 00:00:00 +13:00
8
+ summary: Rails interface library for the InterBase database.
9
+ require_paths:
10
+ - lib
11
+ email: richard@bluetrainsoftware.com
12
+ homepage: http://rubyforge.org/projects/activerecord-interbase-adapter/
13
+ rubyforge_project:
14
+ description: requires IBRuby
15
+ autorequire: ibruby
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform:
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Richard Vowles
31
+ files:
32
+ - lib/active_record
33
+ - lib/active_record/connection_adapters
34
+ - lib/active_record/connection_adapters/ibrails_case_fix.rb
35
+ - lib/active_record/connection_adapters/interbase_adapter.rb
36
+ - lib/tasks
37
+ - lib/tasks/interbase.rake
38
+ test_files: []
39
+
40
+ rdoc_options:
41
+ - --main
42
+ extra_rdoc_files: []
43
+
44
+ executables: []
45
+
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies: []
51
+