Class: ActiveRecord::ConnectionAdapters::FirebirdAdapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- ActiveRecord::ConnectionAdapters::FirebirdAdapter
- 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) OR VALUE IS NULL);
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 now supports Migrations.
Create/Drop Table and Sequence Generators
Creating or dropping a table will automatically create/drop a correpsonding sequence generator, using the default naming convension. You can specify a different name using the :sequence
option; no generator is created if :sequence
is set to false
.
Rename Table
The Firebird #rename_table Migration should be used with caution. Firebird 1.5 lacks built-in support for this feature, so it is implemented by making a copy of the original table (including column definitions, indexes and data records), and then dropping the original table. Constraints and Triggers are not properly copied, so avoid this method if your original table includes constraints (other than the primary key) or triggers. (Consider manually copying your table or using a view instead.)
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
- TEMP_COLUMN_NAME =
'AR$TEMP_COLUMN'
- @@boolean_domain =
{ :name => "d_boolean", :type => "smallint", :true => 1, :false => 0 }
Instance Method Summary collapse
-
#active? ⇒ Boolean
CONNECTION MANAGEMENT ====================================.
-
#adapter_name ⇒ Object
:nodoc:.
-
#add_column(table_name, column_name, type, options = {}) ⇒ Object
:nodoc:.
-
#add_limit_offset!(sql, options) ⇒ Object
:nodoc:.
-
#begin_db_transaction ⇒ Object
:nodoc:.
-
#change_column(table_name, column_name, type, options = {}) ⇒ Object
:nodoc:.
-
#change_column_default(table_name, column_name, default) ⇒ Object
:nodoc:.
-
#columns(table_name, name = nil) ⇒ Object
:nodoc:.
-
#commit_db_transaction ⇒ Object
:nodoc:.
-
#create_table(name, options = {}) ⇒ Object
:nodoc:.
-
#current_database ⇒ Object
SCHEMA STATEMENTS ========================================.
-
#default_sequence_name(table_name, primary_key = nil) ⇒ Object
:nodoc:.
-
#disconnect! ⇒ Object
:nodoc:.
-
#drop_table(name, options = {}) ⇒ Object
:nodoc:.
-
#dump_schema_information ⇒ Object
:nodoc:.
-
#execute(sql, name = nil, &block) ⇒ Object
(also: #update, #delete)
:nodoc:.
-
#indexes(table_name, name = nil) ⇒ Object
:nodoc:.
-
#initialize(connection, logger, connection_params = nil) ⇒ FirebirdAdapter
constructor
A new instance of FirebirdAdapter.
-
#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object
:nodoc:.
-
#native_database_types ⇒ Object
:nodoc:.
-
#next_sequence_value(sequence_name) ⇒ Object
Returns the next sequence value from a sequence generator.
-
#prefetch_primary_key?(table_name = nil) ⇒ Boolean
Returns true for Firebird adapter (since Firebird requires primary key values to be pre-fetched before insert).
-
#quote(value, column = nil) ⇒ Object
QUOTING ==================================================.
-
#quote_column_name(column_name) ⇒ Object
:nodoc:.
-
#quote_string(string) ⇒ Object
:nodoc:.
-
#quoted_false ⇒ Object
:nodoc:.
-
#quoted_true ⇒ Object
:nodoc:.
-
#reconnect! ⇒ Object
:nodoc:.
-
#recreate_database! ⇒ Object
:nodoc:.
-
#remove_index(table_name, options) ⇒ Object
:nodoc:.
-
#rename_column(table_name, column_name, new_column_name) ⇒ Object
:nodoc:.
-
#rename_table(name, new_name) ⇒ Object
:nodoc:.
-
#rollback_db_transaction ⇒ Object
:nodoc:.
-
#select_all(sql, name = nil) ⇒ Object
DATABASE STATEMENTS ======================================.
-
#select_one(sql, name = nil) ⇒ Object
:nodoc:.
-
#supports_migrations? ⇒ Boolean
:nodoc:.
-
#tables(name = nil) ⇒ Object
:nodoc:.
-
#type_to_sql(type, limit = nil, precision = nil, scale = nil) ⇒ Object
:nodoc:.
Methods inherited from AbstractAdapter
#raw_connection, #requires_reloading?, #reset_runtime, #supports_count_distinct?, #verify!
Methods included from Quoting
Methods included from SchemaStatements
#add_column_options!, #add_index, #add_order_by_for_association_limiting!, #distinct, #index_name, #initialize_schema_information, #remove_column, #structure_dump, #table_alias_for, #table_alias_length
Constructor Details
#initialize(connection, logger, connection_params = nil) ⇒ FirebirdAdapter
Returns a new instance of FirebirdAdapter.
277 278 279 280 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 277 def initialize(connection, logger, connection_params = nil) super(connection, logger) @connection_params = connection_params end |
Instance Method Details
#active? ⇒ Boolean
CONNECTION MANAGEMENT ====================================
348 349 350 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 348 def active? # :nodoc: not @connection.closed? end |
#adapter_name ⇒ Object
:nodoc:
282 283 284 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 282 def adapter_name # :nodoc: 'Firebird' end |
#add_column(table_name, column_name, type, options = {}) ⇒ Object
:nodoc:
498 499 500 501 502 503 504 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 498 def add_column(table_name, column_name, type, = {}) # :nodoc: super rescue StatementInvalid raise unless non_existent_domain_error? create_boolean_domain super end |
#add_limit_offset!(sql, options) ⇒ Object
:nodoc:
406 407 408 409 410 411 412 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 406 def add_limit_offset!(sql, ) # :nodoc: if [:limit] limit_string = "FIRST #{[:limit]}" limit_string << " SKIP #{[:offset]}" if [:offset] sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ') end end |
#begin_db_transaction ⇒ Object
:nodoc:
390 391 392 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 390 def begin_db_transaction() # :nodoc: @transaction = @connection.start_transaction end |
#change_column(table_name, column_name, type, options = {}) ⇒ Object
:nodoc:
506 507 508 509 510 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 506 def change_column(table_name, column_name, type, = {}) # :nodoc: change_column_type(table_name, column_name, type, ) change_column_position(table_name, column_name, [:position]) if .include?(:position) change_column_default(table_name, column_name, [:default]) if () end |
#change_column_default(table_name, column_name, default) ⇒ Object
:nodoc:
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 512 def change_column_default(table_name, column_name, default) # :nodoc: table_name = table_name.to_s.upcase sql = <<-end_sql UPDATE rdb$relation_fields f1 SET f1.rdb$default_source = (SELECT f2.rdb$default_source FROM rdb$relation_fields f2 WHERE f2.rdb$relation_name = '#{table_name}' AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'), f1.rdb$default_value = (SELECT f2.rdb$default_value FROM rdb$relation_fields f2 WHERE f2.rdb$relation_name = '#{table_name}' AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}') WHERE f1.rdb$relation_name = '#{table_name}' AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}' end_sql transaction do add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default) execute sql remove_column(table_name, TEMP_COLUMN_NAME) end end |
#columns(table_name, name = nil) ⇒ Object
:nodoc:
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 453 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_transaction ⇒ Object
:nodoc:
394 395 396 397 398 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 394 def commit_db_transaction() # :nodoc: @transaction.commit ensure @transaction = nil end |
#create_table(name, options = {}) ⇒ Object
:nodoc:
476 477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 476 def create_table(name, = {}) # :nodoc: begin super rescue StatementInvalid raise unless non_existent_domain_error? create_boolean_domain super end unless [:id] == false or [:sequence] == false sequence_name = [:sequence] || default_sequence_name(name) create_sequence(sequence_name) end end |
#current_database ⇒ Object
SCHEMA STATEMENTS ========================================
424 425 426 427 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 424 def current_database # :nodoc: file = @connection.database.file.split(':').last File.basename(file, '.*') end |
#default_sequence_name(table_name, primary_key = nil) ⇒ Object
:nodoc:
314 315 316 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 314 def default_sequence_name(table_name, primary_key = nil) # :nodoc: "#{table_name}_seq" end |
#disconnect! ⇒ Object
:nodoc:
352 353 354 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 352 def disconnect! # :nodoc: @connection.close rescue nil end |
#drop_table(name, options = {}) ⇒ Object
:nodoc:
490 491 492 493 494 495 496 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 490 def drop_table(name, = {}) # :nodoc: super(name) unless [:sequence] == false sequence_name = [:sequence] || default_sequence_name(name) drop_sequence(sequence_name) if sequence_exists?(sequence_name) end end |
#dump_schema_information ⇒ Object
:nodoc:
564 565 566 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 564 def dump_schema_information # :nodoc: super << ";\n" end |
#execute(sql, name = nil, &block) ⇒ Object Also known as: update, delete
:nodoc:
372 373 374 375 376 377 378 379 380 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 372 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 |
#indexes(table_name, name = nil) ⇒ Object
:nodoc:
443 444 445 446 447 448 449 450 451 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 443 def indexes(table_name, name = nil) # :nodoc: (table_name, false, name).inject([]) do |indexes, row| if indexes.empty? or indexes.last.name != row[0] indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, []) end indexes.last.columns << row[2].rstrip.downcase indexes end end |
#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object
:nodoc:
382 383 384 385 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 382 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc: execute(sql, name) id_value end |
#native_database_types ⇒ Object
:nodoc:
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 290 def native_database_types # :nodoc: { :primary_key => "BIGINT NOT NULL PRIMARY KEY", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "blob sub_type text" }, :integer => { :name => "bigint" }, :decimal => { :name => "decimal" }, :numeric => { :name => "numeric" }, :float => { :name => "float" }, :datetime => { :name => "timestamp" }, :timestamp => { :name => "timestamp" }, :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob sub_type 0" }, :boolean => boolean_domain } 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?).
417 418 419 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 417 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.
310 311 312 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 310 def prefetch_primary_key?(table_name = nil) true end |
#quote(value, column = nil) ⇒ Object
QUOTING ==================================================
321 322 323 324 325 326 327 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 321 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:
333 334 335 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 333 def quote_column_name(column_name) # :nodoc: %Q("#{ar_to_fb_case(column_name.to_s)}") end |
#quote_string(string) ⇒ Object
:nodoc:
329 330 331 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 329 def quote_string(string) # :nodoc: string.gsub(/'/, "''") end |
#quoted_false ⇒ Object
:nodoc:
341 342 343 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 341 def quoted_false # :nodoc: quote(boolean_domain[:false]) end |
#quoted_true ⇒ Object
:nodoc:
337 338 339 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 337 def quoted_true # :nodoc: quote(boolean_domain[:true]) end |
#reconnect! ⇒ Object
:nodoc:
356 357 358 359 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 356 def reconnect! # :nodoc: disconnect! @connection = @connection.database.connect(*@connection_params) end |
#recreate_database! ⇒ Object
:nodoc:
429 430 431 432 433 434 435 436 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 429 def recreate_database! # :nodoc: sql = "SELECT rdb$character_set_name FROM rdb$database" charset = execute(sql).to_a.first[0].rstrip disconnect! @connection.database.drop(*@connection_params) FireRuby::Database.create(@connection.database.file, @connection_params[0], @connection_params[1], 4096, charset) end |
#remove_index(table_name, options) ⇒ Object
:nodoc:
538 539 540 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 538 def remove_index(table_name, ) #:nodoc: execute "DROP INDEX #{quote_column_name(index_name(table_name, ))}" end |
#rename_column(table_name, column_name, new_column_name) ⇒ Object
:nodoc:
534 535 536 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 534 def rename_column(table_name, column_name, new_column_name) # :nodoc: execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}" end |
#rename_table(name, new_name) ⇒ Object
:nodoc:
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 542 def rename_table(name, new_name) # :nodoc: if table_has_constraints_or_dependencies?(name) raise ActiveRecordError, "Table #{name} includes constraints or dependencies that are not supported by " << "the Firebird rename_table migration. Try explicitly removing the constraints/" << "dependencies first, or manually renaming the table." end transaction do copy_table(name, new_name) copy_table_indexes(name, new_name) end begin copy_table_data(name, new_name) copy_sequence_value(name, new_name) rescue drop_table(new_name) raise end drop_table(name) end |
#rollback_db_transaction ⇒ Object
:nodoc:
400 401 402 403 404 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 400 def rollback_db_transaction() # :nodoc: @transaction.rollback ensure @transaction = nil end |
#select_all(sql, name = nil) ⇒ Object
DATABASE STATEMENTS ======================================
364 365 366 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 364 def select_all(sql, name = nil) # :nodoc: select(sql, name) end |
#select_one(sql, name = nil) ⇒ Object
:nodoc:
368 369 370 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 368 def select_one(sql, name = nil) # :nodoc: select(sql, name).first end |
#supports_migrations? ⇒ Boolean
:nodoc:
286 287 288 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 286 def supports_migrations? # :nodoc: true end |
#tables(name = nil) ⇒ Object
:nodoc:
438 439 440 441 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 438 def tables(name = nil) # :nodoc: sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0" execute(sql, name).collect { |row| row[0].rstrip.downcase } end |
#type_to_sql(type, limit = nil, precision = nil, scale = nil) ⇒ Object
:nodoc:
568 569 570 571 572 573 574 575 |
# File 'lib/active_record/connection_adapters/firebird_adapter.rb', line 568 def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc: case type when :integer then integer_sql_type(limit) when :float then float_sql_type(limit) when :string then super(type, limit, precision, scale) else super(type, limit, precision, scale) end end |