aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2005-11-16 08:18:13 +0000
committerJeremy Kemper <jeremy@bitsweat.net>2005-11-16 08:18:13 +0000
commit9cb02c5317759ab4741fb2786173287879534c1c (patch)
treebc674616bfe0099bc7eb169b0a134a6aa32bc5b9 /activerecord
parent2076dca63fde71693e4b8e23c4b1ace0a35b964f (diff)
downloadrails-9cb02c5317759ab4741fb2786173287879534c1c.tar.gz
rails-9cb02c5317759ab4741fb2786173287879534c1c.tar.bz2
rails-9cb02c5317759ab4741fb2786173287879534c1c.zip
r3116@asus: jeremy | 2005-11-16 00:17:06 -0800
Introducing the Firebird adapter. Closes #1874. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3052 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG2
-rwxr-xr-xactiverecord/Rakefile2
-rwxr-xr-xactiverecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/firebird_adapter.rb425
-rw-r--r--activerecord/test/binary_test.rb6
-rw-r--r--activerecord/test/connections/native_firebird/connection.rb24
-rw-r--r--activerecord/test/default_test_firebird.rb16
-rw-r--r--activerecord/test/fixtures/db_definitions/firebird.drop.sql54
-rw-r--r--activerecord/test/fixtures/db_definitions/firebird.sql259
-rw-r--r--activerecord/test/fixtures/db_definitions/firebird2.drop.sql2
-rw-r--r--activerecord/test/fixtures/db_definitions/firebird2.sql6
11 files changed, 793 insertions, 5 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 2557eeb98f..3ebe6b5e3e 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz <kennethkunz@gmail.com>]
+
* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward <tom@popdog.net>]
* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index ad38139eba..ac6bfb36c3 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -27,7 +27,7 @@ task :default => [ :test_mysql, :test_sqlite, :test_postgresql ]
# Run the unit tests
-for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci )
+for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oci )
Rake::TestTask.new("test_#{adapter}") { |t|
t.libs << "test" << "test/connections/native_#{adapter}"
t.pattern = "test/*_test{,_#{adapter}}.rb"
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 27ec3a6791..9aad0f028d 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -66,7 +66,7 @@ ActiveRecord::Base.class_eval do
end
unless defined?(RAILS_CONNECTION_ADAPTERS)
- RAILS_CONNECTION_ADAPTERS = %w(mysql postgresql sqlite sqlserver db2 oci)
+ RAILS_CONNECTION_ADAPTERS = %w(mysql postgresql sqlite firebird sqlserver db2 oci)
end
RAILS_CONNECTION_ADAPTERS.each do |adapter|
diff --git a/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb b/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb
new file mode 100644
index 0000000000..6f151131b4
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb
@@ -0,0 +1,425 @@
+# Author: Ken Kunz <kennethkunz@gmail.com>
+
+require 'active_record/connection_adapters/abstract_adapter'
+
+module FireRuby # :nodoc: all
+ class ResultSet
+ include Enumerable
+ end
+
+ class Database
+ def self.new_from_params(database, host, port, service)
+ db_string = ""
+ if host
+ db_string << host
+ db_string << "/#{service || port}" if service || port
+ db_string << ":"
+ end
+ db_string << database
+ new(db_string)
+ end
+ end
+end
+
+module ActiveRecord
+ class << Base
+ def firebird_connection(config) # :nodoc:
+ require_library_or_gem 'fireruby'
+ config = config.symbolize_keys
+ unless config.has_key?(:database)
+ raise ArgumentError, "No database specified. Missing argument: database."
+ end
+ options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {}
+ db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service))
+ connection = db.connect(config[:username], config[:password], options)
+ ConnectionAdapters::FirebirdAdapter.new(connection, logger)
+ end
+ end
+
+ module ConnectionAdapters
+ class FirebirdColumn < Column # :nodoc:
+ VARCHAR_MAX_LENGTH = 32_765
+ BLOB_MAX_LENGTH = 32_767
+
+ def initialize(name, domain_name, type, sub_type, length, precision, scale, default_source, null_flag)
+ column_type = metadata_to_column_type(type, sub_type)
+ sql_type = domain_name =~ /BOOLEAN/ ? 'BOOLEAN' : column_type
+ super(name.downcase, nil, sql_type, !null_flag)
+ if default_source
+ @default = parse_default(default_source)
+ @cast_type = firebird_cast_type(column_type, length, precision, scale)
+ end
+ @limit = type == 'BLOB' ? BLOB_MAX_LENGTH : length
+ end
+
+ # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
+ # This enables Firebird to provide an actual value when context variables are used as column
+ # defaults (such as CURRENT_TIMESTAMP).
+ def default
+ if @default
+ sql = "SELECT CAST(#{@default} AS #{@cast_type}) FROM RDB$DATABASE"
+ connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
+ if connection
+ type_cast connection.execute(sql).to_a.first['CAST']
+ else
+ raise ConnectionNotEstablished, "No Firebird connections established."
+ end
+ end
+ end
+
+ def type_cast(value)
+ if type == :date and value.instance_of?(Time)
+ value.to_date
+ elsif type == :boolean
+ value == true or value == ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain[:true]
+ else
+ super
+ end
+ end
+
+ private
+ # Maps the internal type returned by Firebird metadata tables to a
+ # SQL type that can be passed to #firebird_cast_type and Column#new
+ def metadata_to_column_type(type, sub_type)
+ case type
+ when 'TEXT' then 'CHAR'
+ when 'VARYING' then 'VARCHAR'
+ when 'DOUBLE' then 'DOUBLE PRECISION'
+ when 'BLOB' then sub_type == 1 ? 'CLOB' : 'BLOB'
+ when 'SHORT', 'LONG', 'INT64'
+ case sub_type
+ when 1 then 'NUMERIC'
+ when 2 then 'DECIMAL'
+ else 'BIGINT'
+ end
+ else type
+ end
+ end
+
+ def parse_default(default_source)
+ default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
+ return $1 unless $1.upcase == "NULL"
+ end
+
+ # Returns a column definition that can be used in a Firebird CAST statement
+ def firebird_cast_type(column_type, length, precision, scale)
+ case column_type
+ when 'BLOB', 'CLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
+ when 'CHAR', 'VARCHAR' then "#{column_type}(#{length})"
+ when 'NUMERIC', 'DECIMAL' then "#{column_type}(#{precision},#{scale.abs})"
+ else column_type
+ end
+ end
+
+ def simplified_type(field_type)
+ if field_type == 'TIMESTAMP'
+ :datetime
+ else
+ super
+ end
+ end
+ end
+
+ # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
+ # extension, version 0.3.2 or later (available as a gem or from
+ # RubyForge[http://rubyforge.org/projects/fireruby/]). 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 <tt>BEFORE INSERT</tt>
+ # trigger corresponding to a Firebird sequence generator when using
+ # ActiveRecord. In other words, you don't have to try to make Firebird
+ # simulate an <tt>AUTO_INCREMENT</tt> 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 <tt>environment.rb</tt> 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 <tt>"TYPE"</tt> 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
+ # (<tt>"TYPE"</tt>) 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.
+ #
+ # <tt>:database</tt>::
+ # <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
+ # (ii) the full path of a database file; _or_ (iii) a full Firebird
+ # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
+ # or <tt>:port</tt> as separate options when using a full connection
+ # string.</i>
+ # <tt>:host</tt>::
+ # Set to <tt>"remote.host.name"</tt> for remote database connections.
+ # May be omitted for local connections if a full database path is
+ # specified for <tt>:database</tt>. Some platforms require a value of
+ # <tt>"localhost"</tt> for local connections when using a Firebird
+ # database _alias_.
+ # <tt>:service</tt>::
+ # Specifies a service name for the connection. Only used if <tt>:host</tt>
+ # is provided. Required when connecting to a non-standard service.
+ # <tt>:port</tt>::
+ # Specifies the connection port. Only used if <tt>:host</tt> is provided
+ # and <tt>:service</tt> is not. Required when connecting to a non-standard
+ # port and <tt>:service</tt> is not defined.
+ # <tt>:username</tt>::
+ # Specifies the database user. May be omitted or set to +nil+ (together
+ # with <tt>:password</tt>) to use the underlying operating system user
+ # credentials on supported platforms.
+ # <tt>:password</tt>::
+ # Specifies the database password. Must be provided if <tt>:username</tt>
+ # is explicitly specified; should be omitted if OS user credentials are
+ # are being used.
+ # <tt>:charset</tt>::
+ # Specifies the character set to be used by the connection. Refer to
+ # Firebird documentation for valid options.
+ class FirebirdAdapter < AbstractAdapter
+ @@boolean_domain = { :true => 1, :false => 0 }
+ cattr_accessor :boolean_domain
+
+ def adapter_name # :nodoc:
+ 'Firebird'
+ end
+
+ # Returns true for Firebird adapter (since Firebird requires primary key
+ # values to be pre-fetched before insert). See also #next_sequence_value.
+ def prefetch_primary_key?
+ true
+ end
+
+ def default_sequence_name(table_name, primary_key) # :nodoc:
+ "#{table_name}_seq"
+ end
+
+ # QUOTING ==================================================
+
+ 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
+
+ def quote_string(string) # :nodoc:
+ string.gsub(/'/, "''")
+ end
+
+ def quote_column_name(column_name) # :nodoc:
+ %Q("#{ar_to_fb_case(column_name)}")
+ end
+
+ def quoted_true # :nodoc:
+ quote(boolean_domain[:true])
+ end
+
+ def quoted_false # :nodoc:
+ quote(boolean_domain[:false])
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ def select_all(sql, name = nil) # :nodoc:
+ select(sql, name)
+ end
+
+ def select_one(sql, name = nil) # :nodoc:
+ result = select(sql, name)
+ result.nil? ? nil : result.first
+ end
+
+ 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
+
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
+ execute(sql, name)
+ id_value
+ end
+
+ alias_method :update, :execute
+ alias_method :delete, :execute
+
+ def begin_db_transaction() # :nodoc:
+ @transaction = @connection.start_transaction
+ end
+
+ def commit_db_transaction() # :nodoc:
+ @transaction.commit
+ ensure
+ @transaction = nil
+ end
+
+ def rollback_db_transaction() # :nodoc:
+ @transaction.rollback
+ ensure
+ @transaction = nil
+ end
+
+ 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
+
+ # 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?).
+ def next_sequence_value(sequence_name)
+ FireRuby::Generator.new(sequence_name, @connection).next(1)
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def columns(table_name, name = nil) # :nodoc:
+ sql = <<-END_SQL
+ SELECT r.rdb$field_name, r.rdb$field_source, t.rdb$type_name, 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
+ JOIN rdb$types t ON f.rdb$field_type = t.rdb$type
+ WHERE r.rdb$relation_name = '#{table_name.upcase}'
+ AND t.rdb$field_name = 'RDB$FIELD_TYPE'
+ 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
+
+ private
+ def select(sql, name = nil)
+ execute(sql, name).collect do |row|
+ hashed_row = {}
+ # TODO: zip is slow.
+ row.aliases.zip(row.values) do |column_alias, value|
+ value = case value
+ when Time then guess_date_or_time(value)
+ when FireRuby::Blob then value.to_s
+ else value
+ end
+ hashed_row[fb_to_ar_case(column_alias)] = value
+ end
+ hashed_row
+ end
+ end
+
+ # FireRuby (as of 0.3.2) returns a Time object for TIME, TIMESTAMP and
+ # DATE columns. This method guesses whether time is really a date, and
+ # returns a string representing the date if it is. This date string gets
+ # properly type-cast later (as a Time or Date object) based on the
+ # column type.
+ def guess_date_or_time(time)
+ if (time.hour + time.min + time.sec + time.usec).zero?
+ time.strftime("%Y-%m-%d")
+ else
+ time
+ end
+ end
+
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
+ # mixed-case columns retain their original case.
+ def fb_to_ar_case(column_name)
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
+ end
+
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
+ # mixed-case columns retain their original case.
+ def ar_to_fb_case(column_name)
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
+ end
+ end
+ end
+end
diff --git a/activerecord/test/binary_test.rb b/activerecord/test/binary_test.rb
index 27f4881e50..8ba758a892 100644
--- a/activerecord/test/binary_test.rb
+++ b/activerecord/test/binary_test.rb
@@ -18,9 +18,9 @@ class BinaryTest < Test::Unit::TestCase
# limited to 8KB.
#
# Without using prepared statements, it makes no sense to test
- # BLOB data with DB2, because the length of a statement is
- # limited to 32KB.
- unless %w(SQLServer DB2 OCI).include? ActiveRecord::Base.connection.adapter_name
+ # BLOB data with DB2 or Firebird, because the length of a statement
+ # is limited to 32KB.
+ unless %w(SQLServer DB2 OCI Firebird).include? ActiveRecord::Base.connection.adapter_name
def test_load_save
bin = Binary.new
bin.data = @data
diff --git a/activerecord/test/connections/native_firebird/connection.rb b/activerecord/test/connections/native_firebird/connection.rb
new file mode 100644
index 0000000000..96baf38376
--- /dev/null
+++ b/activerecord/test/connections/native_firebird/connection.rb
@@ -0,0 +1,24 @@
+print "Using native Firebird\n"
+require 'fixtures/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+db1 = 'activerecord_unittest'
+db2 = 'activerecord_unittest2'
+
+ActiveRecord::Base.establish_connection(
+ :adapter => "firebird",
+ :host => "localhost",
+ :username => "rails",
+ :password => "rails",
+ :database => db1
+)
+
+Course.establish_connection(
+ :adapter => "firebird",
+ :host => "localhost",
+ :username => "rails",
+ :password => "rails",
+ :database => db2
+)
diff --git a/activerecord/test/default_test_firebird.rb b/activerecord/test/default_test_firebird.rb
new file mode 100644
index 0000000000..4f3d14ce8e
--- /dev/null
+++ b/activerecord/test/default_test_firebird.rb
@@ -0,0 +1,16 @@
+require 'abstract_unit'
+require 'fixtures/default'
+
+class DefaultTest < Test::Unit::TestCase
+ def test_default_timestamp
+ default = Default.new
+ assert_instance_of(Time, default.default_timestamp)
+ assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type)
+
+ # Variance should be small; increase if required -- e.g., if test db is on
+ # remote host and clocks aren't synchronized.
+ t1 = Time.new
+ accepted_variance = 1.0
+ assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance)
+ end
+end
diff --git a/activerecord/test/fixtures/db_definitions/firebird.drop.sql b/activerecord/test/fixtures/db_definitions/firebird.drop.sql
new file mode 100644
index 0000000000..44b3be419d
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/firebird.drop.sql
@@ -0,0 +1,54 @@
+DROP TABLE accounts;
+DROP TABLE companies;
+DROP TABLE topics;
+DROP TABLE developers;
+DROP TABLE projects;
+DROP TABLE developers_projects;
+DROP TABLE orders;
+DROP TABLE customers;
+DROP TABLE movies;
+DROP TABLE subscribers;
+DROP TABLE booleantests;
+DROP TABLE auto_id_tests;
+DROP TABLE entrants;
+DROP TABLE colnametests;
+DROP TABLE mixins;
+DROP TABLE people;
+DROP TABLE binaries;
+DROP TABLE computers;
+DROP TABLE posts;
+DROP TABLE comments;
+DROP TABLE authors;
+DROP TABLE tasks;
+DROP TABLE categories;
+DROP TABLE categories_posts;
+DROP TABLE fk_test_has_fk;
+DROP TABLE fk_test_has_pk;
+DROP TABLE keyboards;
+DROP TABLE defaults;
+
+DROP DOMAIN D_BOOLEAN;
+
+DROP GENERATOR accounts_seq;
+DROP GENERATOR companies_nonstd_seq;
+DROP GENERATOR topics_seq;
+DROP GENERATOR developers_seq;
+DROP GENERATOR projects_seq;
+DROP GENERATOR orders_seq;
+DROP GENERATOR customers_seq;
+DROP GENERATOR movies_seq;
+DROP GENERATOR booleantests_seq;
+DROP GENERATOR auto_id_tests_seq;
+DROP GENERATOR entrants_seq;
+DROP GENERATOR colnametests_seq;
+DROP GENERATOR mixins_seq;
+DROP GENERATOR people_seq;
+DROP GENERATOR binaries_seq;
+DROP GENERATOR computers_seq;
+DROP GENERATOR posts_seq;
+DROP GENERATOR comments_seq;
+DROP GENERATOR authors_seq;
+DROP GENERATOR tasks_seq;
+DROP GENERATOR categories_seq;
+DROP GENERATOR keyboards_seq;
+DROP GENERATOR defaults_seq;
diff --git a/activerecord/test/fixtures/db_definitions/firebird.sql b/activerecord/test/fixtures/db_definitions/firebird.sql
new file mode 100644
index 0000000000..8d7316d40a
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/firebird.sql
@@ -0,0 +1,259 @@
+CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
+
+CREATE TABLE accounts (
+ id BIGINT NOT NULL,
+ firm_id BIGINT,
+ credit_limit INTEGER,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR accounts_seq;
+SET GENERATOR accounts_seq TO 10000;
+
+CREATE TABLE companies (
+ id BIGINT NOT NULL,
+ "TYPE" VARCHAR(50),
+ ruby_type VARCHAR(50),
+ firm_id BIGINT,
+ name VARCHAR(50),
+ client_of INTEGER,
+ rating INTEGER DEFAULT 1,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR companies_nonstd_seq;
+SET GENERATOR companies_nonstd_seq TO 10000;
+
+CREATE TABLE topics (
+ id BIGINT NOT NULL,
+ title VARCHAR(255),
+ author_name VARCHAR(255),
+ author_email_address VARCHAR(255),
+ written_on TIMESTAMP,
+ bonus_time TIME,
+ last_read DATE,
+ content VARCHAR(4000),
+ approved D_BOOLEAN DEFAULT 1,
+ replies_count INTEGER DEFAULT 0,
+ parent_id BIGINT,
+ "TYPE" VARCHAR(50),
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR topics_seq;
+SET GENERATOR topics_seq TO 10000;
+
+CREATE TABLE developers (
+ id BIGINT NOT NULL,
+ name VARCHAR(100),
+ salary INTEGER DEFAULT 70000,
+ created_at TIMESTAMP,
+ updated_at TIMESTAMP,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR developers_seq;
+SET GENERATOR developers_seq TO 10000;
+
+CREATE TABLE projects (
+ id BIGINT NOT NULL,
+ name VARCHAR(100),
+ "TYPE" VARCHAR(255),
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR projects_seq;
+SET GENERATOR projects_seq TO 10000;
+
+CREATE TABLE developers_projects (
+ developer_id BIGINT NOT NULL,
+ project_id BIGINT NOT NULL,
+ joined_on DATE,
+ access_level SMALLINT DEFAULT 1
+);
+
+CREATE TABLE orders (
+ id BIGINT NOT NULL,
+ name VARCHAR(100),
+ billing_customer_id BIGINT,
+ shipping_customer_id BIGINT,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR orders_seq;
+SET GENERATOR orders_seq TO 10000;
+
+CREATE TABLE customers (
+ id BIGINT NOT NULL,
+ name VARCHAR(100),
+ balance INTEGER DEFAULT 0,
+ address_street VARCHAR(100),
+ address_city VARCHAR(100),
+ address_country VARCHAR(100),
+ gps_location VARCHAR(100),
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR customers_seq;
+SET GENERATOR customers_seq TO 10000;
+
+CREATE TABLE movies (
+ movieid BIGINT NOT NULL,
+ name varchar(100),
+ PRIMARY KEY (movieid)
+);
+CREATE GENERATOR movies_seq;
+SET GENERATOR movies_seq TO 10000;
+
+CREATE TABLE subscribers (
+ nick VARCHAR(100) NOT NULL,
+ name VARCHAR(100),
+ PRIMARY KEY (nick)
+);
+
+CREATE TABLE booleantests (
+ id BIGINT NOT NULL,
+ "VALUE" D_BOOLEAN,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR booleantests_seq;
+SET GENERATOR booleantests_seq TO 10000;
+
+CREATE TABLE auto_id_tests (
+ auto_id BIGINT NOT NULL,
+ "VALUE" INTEGER,
+ PRIMARY KEY (auto_id)
+);
+CREATE GENERATOR auto_id_tests_seq;
+SET GENERATOR auto_id_tests_seq TO 10000;
+
+CREATE TABLE entrants (
+ id BIGINT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ course_id INTEGER NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR entrants_seq;
+SET GENERATOR entrants_seq TO 10000;
+
+CREATE TABLE colnametests (
+ id BIGINT NOT NULL,
+ "REFERENCES" INTEGER NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR colnametests_seq;
+SET GENERATOR colnametests_seq TO 10000;
+
+CREATE TABLE mixins (
+ id BIGINT NOT NULL,
+ parent_id BIGINT,
+ pos INTEGER,
+ created_at TIMESTAMP,
+ updated_at TIMESTAMP,
+ lft INTEGER,
+ rgt INTEGER,
+ root_id BIGINT,
+ "TYPE" VARCHAR(40),
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR mixins_seq;
+SET GENERATOR mixins_seq TO 10000;
+
+CREATE TABLE people (
+ id BIGINT NOT NULL,
+ first_name VARCHAR(40),
+ lock_version INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR people_seq;
+SET GENERATOR people_seq TO 10000;
+
+CREATE TABLE binaries (
+ id BIGINT NOT NULL,
+ data BLOB,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR binaries_seq;
+SET GENERATOR binaries_seq TO 10000;
+
+CREATE TABLE computers (
+ id BIGINT NOT NULL,
+ developer INTEGER NOT NULL,
+ "extendedWarranty" INTEGER NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR computers_seq;
+SET GENERATOR computers_seq TO 10000;
+
+CREATE TABLE posts (
+ id BIGINT NOT NULL,
+ author_id BIGINT,
+ title VARCHAR(255) NOT NULL,
+ "TYPE" VARCHAR(255) NOT NULL,
+ body VARCHAR(3000) NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR posts_seq;
+SET GENERATOR posts_seq TO 10000;
+
+CREATE TABLE comments (
+ id BIGINT NOT NULL,
+ post_id BIGINT NOT NULL,
+ "TYPE" VARCHAR(255) NOT NULL,
+ body VARCHAR(3000) NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR comments_seq;
+SET GENERATOR comments_seq TO 10000;
+
+CREATE TABLE authors (
+ id BIGINT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR authors_seq;
+SET GENERATOR authors_seq TO 10000;
+
+CREATE TABLE tasks (
+ id BIGINT NOT NULL,
+ "STARTING" TIMESTAMP,
+ ending TIMESTAMP,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR tasks_seq;
+SET GENERATOR tasks_seq TO 10000;
+
+CREATE TABLE categories (
+ id BIGINT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ "TYPE" VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR categories_seq;
+SET GENERATOR categories_seq TO 10000;
+
+CREATE TABLE categories_posts (
+ category_id BIGINT NOT NULL,
+ post_id BIGINT NOT NULL,
+ PRIMARY KEY (category_id, post_id)
+);
+
+CREATE TABLE fk_test_has_pk (
+ id BIGINT NOT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE fk_test_has_fk (
+ id BIGINT NOT NULL,
+ fk_id BIGINT NOT NULL,
+ PRIMARY KEY (id),
+ FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id)
+);
+
+CREATE TABLE keyboards (
+ key_number BIGINT NOT NULL,
+ name VARCHAR(50),
+ PRIMARY KEY (key_number)
+);
+CREATE GENERATOR keyboards_seq;
+SET GENERATOR keyboards_seq TO 10000;
+
+CREATE TABLE defaults (
+ id BIGINT NOT NULL,
+ default_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+CREATE GENERATOR defaults_seq;
+SET GENERATOR defaults_seq TO 10000;
diff --git a/activerecord/test/fixtures/db_definitions/firebird2.drop.sql b/activerecord/test/fixtures/db_definitions/firebird2.drop.sql
new file mode 100644
index 0000000000..c59fb1f2ff
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/firebird2.drop.sql
@@ -0,0 +1,2 @@
+DROP TABLE courses;
+DROP GENERATOR courses_seq;
diff --git a/activerecord/test/fixtures/db_definitions/firebird2.sql b/activerecord/test/fixtures/db_definitions/firebird2.sql
new file mode 100644
index 0000000000..c1bc251fbc
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/firebird2.sql
@@ -0,0 +1,6 @@
+CREATE TABLE courses (
+ id BIGINT NOT NULL PRIMARY KEY,
+ name VARCHAR(255) NOT NULL
+);
+CREATE GENERATOR courses_seq;
+SET GENERATOR courses_seq TO 10000;