Class: ActiveRecord::ConnectionAdapters::FirebirdAdapter

Inherits:
AbstractAdapter show all
Defined in:
lib/active_record/connection_adapters/firebird_adapter.rb

Overview

The Firebird adapter relies on the FireRuby extension, version 0.4.0 or later (available as a gem or from RubyForge). FireRuby works with Firebird 1.5.x on Linux, OS X and Win32 platforms.

Usage Notes

Sequence (Generator) Names

The Firebird adapter supports the same approach adopted for the Oracle adapter. See ActiveRecord::Base#set_sequence_name for more details.

Note that in general there is no need to create a BEFORE INSERT trigger corresponding to a Firebird sequence generator when using ActiveRecord. In other words, you don’t have to try to make Firebird simulate an AUTO_INCREMENT or IDENTITY column. When saving a new record, ActiveRecord pre-fetches the next sequence value for the table and explicitly includes it in the INSERT statement. (Pre-fetching the next primary key value is the only reliable method for the Firebird adapter to report back the id after a successful insert.)

BOOLEAN Domain

Firebird 1.5 does not provide a native BOOLEAN type. But you can easily define a BOOLEAN domain for this purpose, e.g.:

CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));

When the Firebird adapter encounters a column that is based on a domain that includes “BOOLEAN” in the domain name, it will attempt to treat the column as a BOOLEAN.

By default, the Firebird adapter will assume that the BOOLEAN domain is defined as above. This can be modified if needed. For example, if you have a legacy schema with the following BOOLEAN domain defined:

CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));

…you can add the following line to your environment.rb file:

ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }

BLOB Elements

The Firebird adapter currently provides only limited support for BLOB columns. You cannot currently retrieve or insert a BLOB as an IO stream. When selecting a BLOB, the entire element is converted into a String. When inserting or updating a BLOB, the entire value is included in-line in the SQL statement, limiting you to values <= 32KB in size.

Column Name Case Semantics

Firebird and ActiveRecord have somewhat conflicting case semantics for column names.

Firebird

The standard practice is to use unquoted column names, which can be thought of as case-insensitive. (In fact, Firebird converts them to uppercase.) Quoted column names (not typically used) are case-sensitive.

ActiveRecord

Attribute accessors corresponding to column names are case-sensitive. The defaults for primary key and inheritance columns are lowercase, and in general, people use lowercase attribute names.

In order to map between the differing semantics in a way that conforms to common usage for both Firebird and ActiveRecord, uppercase column names in Firebird are converted to lowercase attribute names in ActiveRecord, and vice-versa. Mixed-case column names retain their case in both directions. Lowercase (quoted) Firebird column names are not supported. This is similar to the solutions adopted by other adapters.

In general, the best approach is to use unqouted (case-insensitive) column names in your Firebird DDL (or if you must quote, use uppercase column names). These will correspond to lowercase attributes in ActiveRecord.

For example, a Firebird table based on the following DDL:

CREATE TABLE products (
  id BIGINT NOT NULL PRIMARY KEY,
  "TYPE" VARCHAR(50),
  name VARCHAR(255) );

…will correspond to an ActiveRecord model class called Product with the following attributes: id, type, name.

Quoting "TYPE" and other Firebird reserved words:

In ActiveRecord, the default inheritance column name is type. The word type is a Firebird reserved word, so it must be quoted in any Firebird SQL statements. Because of the case mapping described above, you should always reference this column using quoted-uppercase syntax ("TYPE") within Firebird DDL or other SQL statements (as in the example above). This holds true for any other Firebird reserved words used as column names as well.

Migrations

The Firebird adapter does not currently support Migrations. I hope to add this feature in the near future.

Connection Options

The following options are supported by the Firebird adapter. None of the options have default values.

:database

Required option. Specifies one of: (i) a Firebird database alias; (ii) the full path of a database file; or (iii) a full Firebird connection string. Do not specify :host, :service or :port as separate options when using a full connection string.

:host

Set to "remote.host.name" for remote database connections. May be omitted for local connections if a full database path is specified for :database. Some platforms require a value of "localhost" for local connections when using a Firebird database alias.

:service

Specifies a service name for the connection. Only used if :host is provided. Required when connecting to a non-standard service.

:port

Specifies the connection port. Only used if :host is provided and :service is not. Required when connecting to a non-standard port and :service is not defined.

:username

Specifies the database user. May be omitted or set to nil (together with :password) to use the underlying operating system user credentials on supported platforms.

:password

Specifies the database password. Must be provided if :username is explicitly specified; should be omitted if OS user credentials are are being used.

:charset

Specifies the character set to be used by the connection. Refer to Firebird documentation for valid options.

Constant Summary collapse

@@boolean_domain =
{ :true => 1, :false => 0 }

Instance Method Summary collapse

Methods inherited from AbstractAdapter

#disconnect!, #raw_connection, #reset_runtime, #supports_count_distinct?, #supports_migrations?, #verify!

Methods included from Quoting

#quoted_date

Methods included from DatabaseStatements

#add_limit!, #reset_sequence!, #select_value, #select_values, #transaction

Methods included from SchemaStatements

#add_column, #add_column_options!, #add_index, #change_column, #change_column_default, #create_table, #drop_table, #dump_schema_information, #index_name, #initialize_schema_information, #native_database_types, #remove_column, #remove_index, #rename_column, #rename_table, #structure_dump, #table_alias_for, #table_alias_length, #type_to_sql

Constructor Details

#initialize(connection, logger, connection_params = nil) ⇒ FirebirdAdapter

Returns a new instance of FirebirdAdapter.



244
245
246
247
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 244

def initialize(connection, logger, connection_params=nil)
  super(connection, logger)
  @connection_params = connection_params
end

Instance Method Details

#active?Boolean

CONNECTION MANAGEMENT ====================================

Returns:

  • (Boolean)


293
294
295
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 293

def active?
  not @connection.closed?
end

#adapter_nameObject

:nodoc:



249
250
251
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 249

def adapter_name # :nodoc:
  'Firebird'
end

#add_limit_offset!(sql, options) ⇒ Object

:nodoc:



348
349
350
351
352
353
354
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 348

def add_limit_offset!(sql, options) # :nodoc:
  if options[:limit]
    limit_string = "FIRST #{options[:limit]}"
    limit_string << " SKIP #{options[:offset]}" if options[:offset]
    sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
  end
end

#begin_db_transactionObject

:nodoc:



332
333
334
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 332

def begin_db_transaction() # :nodoc:
  @transaction = @connection.start_transaction
end

#columns(table_name, name = nil) ⇒ Object

SCHEMA STATEMENTS ========================================



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 366

def columns(table_name, name = nil) # :nodoc:
  sql = <<-END_SQL
    SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
           f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
           COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
           COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
    FROM rdb$relation_fields r
    JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
    WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
    ORDER BY r.rdb$field_position
  END_SQL
  execute(sql, name).collect do |field|
    field_values = field.values.collect do |value|
      case value
        when String         then value.rstrip
        when FireRuby::Blob then value.to_s
        else value
      end
    end
    FirebirdColumn.new(*field_values)
  end
end

#commit_db_transactionObject

:nodoc:



336
337
338
339
340
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 336

def commit_db_transaction() # :nodoc:
  @transaction.commit
ensure
  @transaction = nil
end

#default_sequence_name(table_name, primary_key) ⇒ Object

:nodoc:



259
260
261
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 259

def default_sequence_name(table_name, primary_key) # :nodoc:
  "#{table_name}_seq"
end

#execute(sql, name = nil, &block) ⇒ Object Also known as: update, delete

:nodoc:



314
315
316
317
318
319
320
321
322
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 314

def execute(sql, name = nil, &block) # :nodoc:
  log(sql, name) do
    if @transaction
      @connection.execute(sql, @transaction, &block)
    else
      @connection.execute_immediate(sql, &block)
    end
  end
end

#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object

:nodoc:



324
325
326
327
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 324

def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
  execute(sql, name)
  id_value
end

#next_sequence_value(sequence_name) ⇒ Object

Returns the next sequence value from a sequence generator. Not generally called directly; used by ActiveRecord to get the next primary key value when inserting a new database record (see #prefetch_primary_key?).



359
360
361
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 359

def next_sequence_value(sequence_name)
  FireRuby::Generator.new(sequence_name, @connection).next(1)
end

#prefetch_primary_key?(table_name = nil) ⇒ Boolean

Returns true for Firebird adapter (since Firebird requires primary key values to be pre-fetched before insert). See also #next_sequence_value.

Returns:

  • (Boolean)


255
256
257
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 255

def prefetch_primary_key?(table_name = nil)
  true
end

#quote(value, column = nil) ⇒ Object

QUOTING ==================================================



266
267
268
269
270
271
272
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 266

def quote(value, column = nil) # :nodoc:
  if [Time, DateTime].include?(value.class)
    "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
  else
    super
  end
end

#quote_column_name(column_name) ⇒ Object

:nodoc:



278
279
280
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 278

def quote_column_name(column_name) # :nodoc:
  %Q("#{ar_to_fb_case(column_name)}")
end

#quote_string(string) ⇒ Object

:nodoc:



274
275
276
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 274

def quote_string(string) # :nodoc:
  string.gsub(/'/, "''")
end

#quoted_falseObject

:nodoc:



286
287
288
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 286

def quoted_false # :nodoc:
  quote(boolean_domain[:false])
end

#quoted_trueObject

:nodoc:



282
283
284
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 282

def quoted_true # :nodoc:
  quote(boolean_domain[:true])
end

#reconnect!Object



297
298
299
300
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 297

def reconnect!
  @connection.close
  @connection = @connection.database.connect(*@connection_params)
end

#rollback_db_transactionObject

:nodoc:



342
343
344
345
346
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 342

def rollback_db_transaction() # :nodoc:
  @transaction.rollback
ensure
  @transaction = nil
end

#select_all(sql, name = nil) ⇒ Object

DATABASE STATEMENTS ======================================



305
306
307
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 305

def select_all(sql, name = nil) # :nodoc:
  select(sql, name)
end

#select_one(sql, name = nil) ⇒ Object

:nodoc:



309
310
311
312
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 309

def select_one(sql, name = nil) # :nodoc:
  result = select(sql, name)
  result.nil? ? nil : result.first
end