activerecord-interbase-adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+