Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #1874: firebird_adapter_r2032.diff

File firebird_adapter_r2032.diff, 40.5 kB (added by Ken Kunz <kennethkunz@gmail.com>, 3 years ago)

Firebird Adapter patch based on trunk rev. 2032

  • test/deprecated_associations_test.rb

    old new  
    311311  end 
    312312 
    313313  def test_has_many_find_all 
    314     assert_equal 2, Firm.find_first.find_all_in_clients("type = 'Client'").length 
     314    assert_equal 2, Firm.find_first.find_all_in_clients("#{QUOTED_TYPE} = 'Client'").length 
    315315    assert_equal 1, Firm.find_first.find_all_in_clients("name = 'Summit'").length 
    316316  end 
    317317 
  • test/connections/native_firebird/connection.rb

    old new  
     1print "Using native Firebird\n" 
     2require 'fixtures/course' 
     3require 'logger' 
     4 
     5ActiveRecord::Base.logger = Logger.new("debug.log") 
     6 
     7db1 = 'activerecord_unittest' 
     8db2 = 'activerecord_unittest2' 
     9 
     10ActiveRecord::Base.establish_connection( 
     11  :adapter  => "firebird", 
     12  :host     => "localhost", 
     13  :username => "rails", 
     14  :password => "rails", 
     15  :database => db1 
     16) 
     17 
     18Course.establish_connection( 
     19  :adapter  => "firebird", 
     20  :host     => "localhost", 
     21  :username => "rails", 
     22  :password => "rails", 
     23  :database => db2 
     24) 
  • test/associations_test.rb

    old new  
    338338  def test_find_all 
    339339    firm = Firm.find_first 
    340340    assert_equal firm.clients, firm.clients.find_all 
    341     assert_equal 2, firm.clients.find(:all, :conditions => "type = 'Client'").length 
     341    assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length 
    342342    assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length 
    343343  end 
    344344 
     
    354354    firm = Firm.find_first 
    355355    client2 = Client.find(2) 
    356356    assert_equal firm.clients.first, firm.clients.find_first 
    357     assert_equal client2, firm.clients.find_first("type = 'Client'") 
    358     assert_equal client2, firm.clients.find(:first, :conditions => "type = 'Client'") 
     357    assert_equal client2, firm.clients.find_first("#{QUOTED_TYPE} = 'Client'") 
     358    assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") 
    359359  end 
    360360 
    361361  def test_find_first_sanitized 
    362362    firm = Firm.find_first 
    363363    client2 = Client.find(2) 
    364     assert_equal client2, firm.clients.find_first(["type = ?", "Client"]) 
    365     assert_equal client2, firm.clients.find(:first, :conditions => ['type = ?', 'Client']) 
    366     assert_equal client2, firm.clients.find(:first, :conditions => ['type = :type', { :type => 'Client' }]) 
     364    assert_equal client2, firm.clients.find_first(["#{QUOTED_TYPE} = ?", "Client"]) 
     365    assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) 
     366    assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) 
    367367  end 
    368368 
    369369  def test_find_in_collection 
  • test/base_test.rb

    old new  
    859859  end 
    860860 
    861861  def test_count_with_join 
    862     res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.type = 'Post'" 
     862    res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" 
    863863    res2 = res + 1 
    864864    assert_nothing_raised do 
    865       res2 = Post.count("posts.type = 'Post'", 
     865      res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'", 
    866866                        "LEFT JOIN comments ON posts.id=comments.post_id") 
    867867    end 
    868868    assert_equal res, res2 
  • test/binary_test.rb

    old new  
    2424    if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter 
    2525      return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter) 
    2626    end 
     27 
     28    # For Firebird, length of SQL statement is limited to 32KB 
     29    # let's be honest about it and explicitly flunk the test :) 
     30    if ActiveRecord::Base.connection.adapter_name == 'Firebird' 
     31      return flunk("Firebird adapter doesn't support large BLOB values inline in SQL statement.") 
     32    end 
     33 
    2734    bin = Binary.new 
    2835    bin.data = @data 
    2936 
  • test/inheritance_test.rb

    old new  
    1111    if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) 
    1212      Company.connection.execute "SET IDENTITY_INSERT companies ON" 
    1313    end 
    14     Company.connection.insert "INSERT INTO companies (id, type, name) VALUES(100, 'bad_class!', 'Not happening')" 
     14    Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')" 
    1515    #We then need to turn it back Off before continuing. 
    1616    if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) 
    1717      Company.connection.execute "SET IDENTITY_INSERT companies OFF" 
  • test/default_test_firebird.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/default' 
     3 
     4class DefaultTest < Test::Unit::TestCase 
     5  def test_default_timestamp 
     6    default = Default.new 
     7    assert_instance_of(Time, default.default_timestamp) 
     8    assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type) 
     9 
     10    # Variance should be small; increase if required -- e.g., if test db is on 
     11    # remote host and clocks aren't synchronized. 
     12    t1 = Time.new 
     13    accepted_variance = 1.0 
     14    assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance) 
     15  end 
     16end 
  • test/abstract_unit.rb

    old new  
    88require 'active_support/breakpoint' 
    99require 'connection' 
    1010 
     11QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') 
     12 
    1113class Test::Unit::TestCase #:nodoc: 
    1214  def create_fixtures(*table_names) 
    1315    if block_given? 
  • test/fixtures/comment.rb

    old new  
    66  end 
    77   
    88  def self.search_by_type(q) 
    9     self.find(:all, :conditions => ['type = ?', q]) 
     9    self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q]) 
    1010  end 
    1111end 
    1212 
  • test/fixtures/db_definitions/firebird.sql

    old new  
     1CREATE DOMAIN D_BOOLEAN AS CHAR(1) CHECK (VALUE IN ('t', 'f')); 
     2 
     3CREATE TABLE accounts ( 
     4  id BIGINT NOT NULL, 
     5  firm_id BIGINT, 
     6  credit_limit INTEGER, 
     7  PRIMARY KEY (id) 
     8); 
     9CREATE GENERATOR accounts_seq; 
     10SET GENERATOR accounts_seq TO 10000; 
     11 
     12CREATE TABLE companies ( 
     13  id BIGINT NOT NULL, 
     14  "TYPE" VARCHAR(50), 
     15  ruby_type VARCHAR(50), 
     16  firm_id BIGINT, 
     17  name VARCHAR(50), 
     18  client_of INTEGER, 
     19  rating INTEGER DEFAULT 1, 
     20  PRIMARY KEY (id) 
     21); 
     22CREATE GENERATOR companies_nonstd_seq; 
     23SET GENERATOR companies_nonstd_seq TO 10000; 
     24 
     25CREATE TABLE topics ( 
     26  id BIGINT NOT NULL, 
     27  title VARCHAR(255), 
     28  author_name VARCHAR(255), 
     29  author_email_address VARCHAR(255), 
     30  written_on TIMESTAMP, 
     31  bonus_time TIME, 
     32  last_read DATE, 
     33  content VARCHAR(4000), 
     34  approved SMALLINT DEFAULT 1, 
     35  replies_count INTEGER DEFAULT 0, 
     36  parent_id BIGINT, 
     37  "TYPE" VARCHAR(50), 
     38  PRIMARY KEY (id) 
     39); 
     40CREATE GENERATOR topics_seq; 
     41SET GENERATOR topics_seq TO 10000; 
     42 
     43CREATE TABLE developers ( 
     44  id BIGINT NOT NULL, 
     45  name VARCHAR(100), 
     46  salary INTEGER DEFAULT 70000, 
     47  created_at TIMESTAMP, 
     48  updated_at TIMESTAMP, 
     49  PRIMARY KEY (id) 
     50); 
     51CREATE GENERATOR developers_seq; 
     52SET GENERATOR developers_seq TO 10000; 
     53 
     54CREATE TABLE projects ( 
     55  id BIGINT NOT NULL, 
     56  name VARCHAR(100), 
     57  "TYPE" VARCHAR(255), 
     58  PRIMARY KEY (id) 
     59); 
     60CREATE GENERATOR projects_seq; 
     61SET GENERATOR projects_seq TO 10000; 
     62 
     63CREATE TABLE developers_projects ( 
     64  developer_id BIGINT NOT NULL, 
     65  project_id BIGINT NOT NULL, 
     66  joined_on DATE, 
     67  access_level SMALLINT DEFAULT 1 
     68); 
     69 
     70CREATE TABLE customers ( 
     71  id BIGINT NOT NULL, 
     72  name VARCHAR(100), 
     73  balance INTEGER DEFAULT 0, 
     74  address_street VARCHAR(100), 
     75  address_city VARCHAR(100), 
     76  address_country VARCHAR(100), 
     77  gps_location VARCHAR(100), 
     78  PRIMARY KEY (id) 
     79); 
     80CREATE GENERATOR customers_seq; 
     81SET GENERATOR customers_seq TO 10000; 
     82 
     83CREATE TABLE movies ( 
     84  movieid BIGINT NOT NULL, 
     85  name varchar(100), 
     86  PRIMARY KEY (movieid) 
     87); 
     88CREATE GENERATOR movies_seq; 
     89SET GENERATOR movies_seq TO 10000; 
     90 
     91CREATE TABLE subscribers ( 
     92  nick VARCHAR(100) NOT NULL, 
     93  name VARCHAR(100), 
     94  PRIMARY KEY (nick) 
     95); 
     96 
     97CREATE TABLE booleantests ( 
     98  id BIGINT NOT NULL, 
     99  "VALUE" D_BOOLEAN, 
     100  PRIMARY KEY (id) 
     101); 
     102CREATE GENERATOR booleantests_seq; 
     103SET GENERATOR booleantests_seq TO 10000; 
     104 
     105CREATE TABLE auto_id_tests ( 
     106  auto_id BIGINT NOT NULL, 
     107  "VALUE" INTEGER, 
     108  PRIMARY KEY (auto_id) 
     109); 
     110CREATE GENERATOR auto_id_tests_seq; 
     111SET GENERATOR auto_id_tests_seq TO 10000; 
     112 
     113CREATE TABLE entrants ( 
     114  id BIGINT NOT NULL, 
     115  name VARCHAR(255) NOT NULL, 
     116  course_id INTEGER NOT NULL, 
     117  PRIMARY KEY (id) 
     118); 
     119CREATE GENERATOR entrants_seq; 
     120SET GENERATOR entrants_seq TO 10000; 
     121 
     122CREATE TABLE colnametests ( 
     123  id BIGINT NOT NULL, 
     124  "REFERENCES" INTEGER NOT NULL, 
     125  PRIMARY KEY (id) 
     126); 
     127CREATE GENERATOR colnametests_seq; 
     128SET GENERATOR colnametests_seq TO 10000; 
     129 
     130CREATE TABLE mixins ( 
     131  id BIGINT NOT NULL, 
     132  parent_id BIGINT, 
     133  pos INTEGER, 
     134  created_at TIMESTAMP, 
     135  updated_at TIMESTAMP, 
     136  lft INTEGER, 
     137  rgt INTEGER, 
     138  root_id BIGINT, 
     139  "TYPE" VARCHAR(40), 
     140  PRIMARY KEY (id) 
     141); 
     142CREATE GENERATOR mixins_seq; 
     143SET GENERATOR mixins_seq TO 10000; 
     144 
     145CREATE TABLE people ( 
     146  id BIGINT NOT NULL, 
     147  first_name VARCHAR(40) NOT NULL, 
     148  lock_version INTEGER DEFAULT 0 NOT NULL, 
     149  PRIMARY KEY (id) 
     150); 
     151CREATE GENERATOR people_seq; 
     152SET GENERATOR people_seq TO 10000; 
     153 
     154CREATE TABLE binaries ( 
     155  id BIGINT NOT NULL, 
     156  data BLOB, 
     157  PRIMARY KEY (id) 
     158); 
     159CREATE GENERATOR binaries_seq; 
     160SET GENERATOR binaries_seq TO 10000; 
     161 
     162CREATE TABLE computers ( 
     163  id BIGINT NOT NULL, 
     164  developer INTEGER NOT NULL, 
     165  "extendedWarranty" INTEGER NOT NULL, 
     166  PRIMARY KEY (id) 
     167); 
     168CREATE GENERATOR computers_seq; 
     169SET GENERATOR computers_seq TO 10000; 
     170 
     171CREATE TABLE posts ( 
     172  id BIGINT NOT NULL, 
     173  author_id BIGINT, 
     174  title VARCHAR(255) NOT NULL, 
     175  "TYPE" VARCHAR(255) NOT NULL, 
     176  body VARCHAR(3000) NOT NULL, 
     177  PRIMARY KEY (id) 
     178); 
     179CREATE GENERATOR posts_seq; 
     180SET GENERATOR posts_seq TO 10000; 
     181 
     182CREATE TABLE comments ( 
     183  id BIGINT NOT NULL, 
     184  post_id BIGINT NOT NULL, 
     185  "TYPE" VARCHAR(255) NOT NULL, 
     186  body VARCHAR(3000) NOT NULL, 
     187  PRIMARY KEY (id) 
     188); 
     189CREATE GENERATOR comments_seq; 
     190SET GENERATOR comments_seq TO 10000; 
     191 
     192CREATE TABLE authors ( 
     193  id BIGINT NOT NULL, 
     194  name VARCHAR(255) NOT NULL, 
     195  PRIMARY KEY (id) 
     196); 
     197CREATE GENERATOR authors_seq; 
     198SET GENERATOR authors_seq TO 10000; 
     199 
     200CREATE TABLE tasks ( 
     201  id BIGINT NOT NULL, 
     202  "STARTING" TIMESTAMP, 
     203  ending TIMESTAMP, 
     204  PRIMARY KEY (id) 
     205); 
     206CREATE GENERATOR tasks_seq; 
     207SET GENERATOR tasks_seq TO 10000; 
     208 
     209CREATE TABLE categories ( 
     210  id BIGINT NOT NULL, 
     211  name VARCHAR(255) NOT NULL, 
     212  "TYPE" VARCHAR(255) NOT NULL, 
     213  PRIMARY KEY (id) 
     214); 
     215CREATE GENERATOR categories_seq; 
     216SET GENERATOR categories_seq TO 10000; 
     217 
     218CREATE TABLE categories_posts ( 
     219  category_id BIGINT NOT NULL, 
     220  post_id BIGINT NOT NULL, 
     221  PRIMARY KEY (category_id, post_id) 
     222); 
     223 
     224CREATE TABLE fk_test_has_pk ( 
     225  id BIGINT NOT NULL, 
     226  PRIMARY KEY (id) 
     227); 
     228 
     229CREATE TABLE fk_test_has_fk ( 
     230  id BIGINT NOT NULL, 
     231  fk_id BIGINT NOT NULL, 
     232  PRIMARY KEY (id), 
     233  FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id) 
     234); 
     235 
     236CREATE TABLE defaults ( 
     237  id BIGINT NOT NULL, 
     238  default_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP 
     239); 
     240CREATE GENERATOR defaults_seq; 
     241SET GENERATOR defaults_seq TO 10000; 
  • test/fixtures/db_definitions/firebird.drop.sql

    old new  
     1DROP TABLE accounts; 
     2DROP TABLE companies; 
     3DROP TABLE topics; 
     4DROP TABLE developers; 
     5DROP TABLE projects; 
     6DROP TABLE developers_projects; 
     7DROP TABLE customers; 
     8DROP TABLE movies; 
     9DROP TABLE subscribers; 
     10DROP TABLE booleantests; 
     11DROP TABLE auto_id_tests; 
     12DROP TABLE entrants; 
     13DROP TABLE colnametests; 
     14DROP TABLE mixins; 
     15DROP TABLE people; 
     16DROP TABLE binaries; 
     17DROP TABLE computers; 
     18DROP TABLE posts; 
     19DROP TABLE comments; 
     20DROP TABLE authors; 
     21DROP TABLE tasks; 
     22DROP TABLE categories; 
     23DROP TABLE categories_posts; 
     24DROP TABLE fk_test_has_fk; 
     25DROP TABLE fk_test_has_pk; 
     26DROP TABLE defaults; 
     27 
     28DROP DOMAIN D_BOOLEAN; 
     29 
     30DROP GENERATOR accounts_seq; 
     31DROP GENERATOR companies_nonstd_seq; 
     32DROP GENERATOR topics_seq; 
     33DROP GENERATOR developers_seq; 
     34DROP GENERATOR projects_seq; 
     35DROP GENERATOR customers_seq; 
     36DROP GENERATOR movies_seq; 
     37DROP GENERATOR booleantests_seq; 
     38DROP GENERATOR auto_id_tests_seq; 
     39DROP GENERATOR entrants_seq; 
     40DROP GENERATOR colnametests_seq; 
     41DROP GENERATOR mixins_seq; 
     42DROP GENERATOR people_seq; 
     43DROP GENERATOR binaries_seq; 
     44DROP GENERATOR computers_seq; 
     45DROP GENERATOR posts_seq; 
     46DROP GENERATOR comments_seq; 
     47DROP GENERATOR authors_seq; 
     48DROP GENERATOR tasks_seq; 
     49DROP GENERATOR categories_seq; 
     50DROP GENERATOR defaults_seq; 
  • test/fixtures/db_definitions/firebird2.sql

    old new  
     1CREATE TABLE courses ( 
     2  id BIGINT NOT NULL PRIMARY KEY, 
     3  name VARCHAR(255) NOT NULL 
     4); 
     5CREATE GENERATOR courses_seq; 
     6SET GENERATOR courses_seq TO 10000; 
  • test/fixtures/db_definitions/firebird2.drop.sql

    old new  
     1DROP TABLE courses; 
     2DROP GENERATOR courses_seq; 
  • test/fixtures/company.rb

    old new  
    77 
    88 
    99class Firm < Company 
    10   has_many :clients, :order => "id", :dependent => true, :counter_sql => "SELECT COUNT(*) FROM companies WHERE firm_id = 1 AND (type = 'Client' OR type = 'SpecialClient' OR type = 'VerySpecialClient' )" 
     10  has_many :clients, :order => "id", :dependent => true, :counter_sql => 
     11      "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " + 
     12      "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )" 
    1113  has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC" 
    1214  has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id" 
    1315  has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" 
  • Rakefile

    old new  
    2626 
    2727# Run the unit tests 
    2828 
    29 for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci
     29for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci firebird
    3030  Rake::TestTask.new("test_#{adapter}") { |t| 
    3131    t.libs << "test" << "test/connections/native_#{adapter}" 
    3232    t.pattern = "test/*_test{,_#{adapter}}.rb" 
  • lib/active_record/validations.rb

    old new  
    464464        configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 
    465465        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) 
    466466 
    467         if scope = configuration[:scope] 
    468           validates_each(attr_names,configuration) do |record, attr_name, value| 
    469             record.errors.add(attr_name, configuration[:message]) if record.class.find(:first, :conditions => (record.new_record? ? ["#{attr_name} = ? AND #{scope} = ?", record.send(attr_name), record.send(scope)] : ["#{attr_name} = ? AND #{record.class.primary_key} <> ? AND #{scope} = ?", record.send(attr_name), record.send(:id), record.send(scope)])) 
     467        validates_each(attr_names,configuration) do |record, attr_name, value| 
     468          condition_sql = "#{attr_name} #{attribute_condition(value)}" 
     469          condition_params = [value] 
     470          if scope = configuration[:scope] 
     471            scope_value = record.send(scope) 
     472            condition_sql << " AND #{scope} #{attribute_condition(scope_value)}" 
     473            condition_params << scope_value 
    470474          end 
    471         else 
    472           validates_each(attr_names,configuration) do |record, attr_name, value| 
    473             record.errors.add(attr_name, configuration[:message]) if record.class.find(:first, :conditions => (record.new_record? ? ["#{attr_name} = ?", record.send(attr_name)] : ["#{attr_name} = ? AND #{record.class.primary_key} <> ?", record.send(attr_name), record.send(:id) ] )
     475          unless record.new_record? 
     476            condition_sql << " AND #{record.class.primary_key} <> ?" 
     477            condition_params << record.send(:id
    474478          end 
     479          if record.class.find(:first, :conditions => [condition_sql, *condition_params]) 
     480            record.errors.add(attr_name, configuration[:message]) 
     481          end 
    475482        end 
    476483      end 
    477484       
  • lib/active_record/connection_adapters/sqlite_adapter.rb

    old new  
    171171      end 
    172172 
    173173      def quote_column_name(name) 
    174         "'#{name}'" 
     174        %Q("#{name}") 
    175175      end 
    176176 
    177177      def adapter_name() 
  • lib/active_record/connection_adapters/abstract_adapter.rb

    old new  
    155155      attr_reader :name, :default, :type, :limit 
    156156      # The name should contain the name of the column, such as "name" in "name varchar(250)" 
    157157      # The default should contain the type-casted default of the column, such as 1 in "count int(11) DEFAULT 1" 
    158       # The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string 
    159       # The sql_type is just used for extracting the limit, such as 10 in "varchar(10)" 
    160       def initialize(name, default, sql_type = nil) 
    161         @name, @default, @type = name, type_cast(default), simplified_type(sql_type) 
    162         @limit = extract_limit(sql_type) unless sql_type.nil? 
     158      # The sql_type should contain the database (sql) type, which gets mapped to a simplified type, 
     159      # such as :integer, :float, :datetime, :date, :text, :string 
     160      # The limit can be passed as a separate argument, or may be extracted out of the sql_type, 
     161      # such as 10 in "varchar(10)" 
     162      def initialize(name, default, sql_type = nil, limit = nil) 
     163        @name    = name 
     164        @default = default 
     165        @type    = simplified_type(sql_type) 
     166        if sql_type 
     167          @limit = limit || extract_limit(sql_type) 
     168        end 
    163169      end 
    164170 
    165171      def klass 
     
    365371        sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil? 
    366372      end 
    367373 
     374      # A concrete adapter should return true here if it requires primary key values to be pre-fetched before 
     375      # performing an insert.  If the adapter returns true, it must also implement next_sequence_value (see 
     376      # Firebird adapter for a reference implementation). 
     377      def prefetch_primary_key? 
     378        false 
     379      end 
    368380 
    369381      def initialize_schema_information 
    370382        begin 
  • lib/active_record/connection_adapters/firebird_adapter.rb

    old new  
     1# Author: Ken Kunz <kennethkunz@gmail.com> 
     2 
     3require 'active_record/connection_adapters/abstract_adapter' 
     4 
     5module FireRuby # :nodoc: all 
     6  class ResultSet 
     7    include Enumerable 
     8  end 
     9 
     10  class Database 
     11    def self.new_from_params(database, host, port, service) 
     12      db_string = "" 
     13      if host 
     14        db_string << host 
     15        db_string << "/#{service || port}" if service || port 
     16        db_string << ":" 
     17      end 
     18      db_string << database 
     19      new(db_string) 
     20    end 
     21  end 
     22end 
     23 
     24module ActiveRecord 
     25  class << Base 
     26    def firebird_connection(config) # :nodoc: 
     27      require_library_or_gem 'fireruby' 
     28      symbolize_strings_in_hash(config) 
     29      unless config.has_key?(:database) 
     30        raise ArgumentError, "No database specified. Missing argument: database." 
     31      end 
     32      options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {} 
     33      db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service)) 
     34      connection = db.connect(config[:username], config[:password], options) 
     35      ConnectionAdapters::FirebirdAdapter.new(connection, logger) 
     36    end 
     37  end 
     38 
     39  module ConnectionAdapters 
     40    class FirebirdColumn < Column # :nodoc: 
     41      # Submits a _CAST_ query to the database, casting the default value to the specified SQL type. 
     42      # This enables Firebird to provide an actual value when context variables are used as column 
     43      # defaults (such as CURRENT_TIMESTAMP). 
     44      def default 
     45        if @default 
     46          sql = "SELECT CAST(#{@default[:value]} AS #{@default[:cast_type]}) FROM RDB$DATABASE" 
     47          connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' } 
     48          if connection 
     49            type_cast connection.execute(sql).to_a.first['CAST'] 
     50          else 
     51            raise ConnectionNotEstablished, "No Firebird connections established." 
     52          end 
     53        end 
     54      end 
     55 
     56      def type_cast(value) 
     57        if type == :date and value.instance_of?(Time) 
     58          value.to_date 
     59        else 
     60          super 
     61        end 
     62      end 
     63 
     64      private 
     65        def simplified_type(field_type) 
     66          if field_type == 'TIMESTAMP' 
     67            :datetime 
     68          else 
     69            super 
     70          end 
     71        end 
     72    end 
     73 
     74    # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/] 
     75    # extension, version 0.3.2 or later (available as a gem or from 
     76    # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with 
     77    # Firebird 1.5.x on Linux, OS X and Win32 platforms. 
     78    # 
     79    # == Usage Notes 
     80    # 
     81    # === Sequence (Generator) Names 
     82    # The Firebird adapter supports the same approach adopted for the Oracle 
     83    # adapter. See ActiveRecord::Base#set_sequence_name for more details. 
     84    # 
     85    # Note that in general there is no need to create a <tt>BEFORE INSERT</tt> 
     86    # trigger corresponding to a Firebird sequence generator when using 
     87    # ActiveRecord. In other words, you don't have to try to make Firebird 
     88    # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a 
     89    # new record, ActiveRecord pre-fetches the next sequence value for the table 
     90    # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the 
     91    # next primary key value is the only reliable method for the Firebird 
     92    # adapter to report back the +id+ after a successful insert.) 
     93    # 
     94    # === +BOOLEAN+ Domain 
     95    # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily 
     96    # define a +BOOLEAN+ _domain_ for this purpose, e.g.: 
     97    # 
     98    #  CREATE DOMAIN D_BOOLEAN AS CHAR(1) CHECK (VALUE IN ('t', 'f')); 
     99    # 
     100    # When the Firebird adapter encounters a column that is based on a domain 
     101    # that includes "BOOLEAN" in the domain name, it will attempt to treat 
     102    # the column as a +BOOLEAN+. 
     103    # 
     104    # === +BLOB+ Elements 
     105    # The Firebird adapter currently provides only limited support for +BLOB+ 
     106    # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream. 
     107    # When selecting a +BLOB+, the entire element is converted into a String. 
     108    # When inserting or updating a +BLOB+, the entire value is included in-line 
     109    # in the SQL statement, limiting you to values <= 32KB in size. 
     110    # 
     111    # === Column Name Case Semantics 
     112    # Firebird and ActiveRecord have somewhat conflicting case semantics for 
     113    # column names. 
     114    # 
     115    # [*Firebird*] 
     116    #   The standard practice is to use unquoted column names, which can be 
     117    #   thought of as case-insensitive. (In fact, Firebird converts them to 
     118    #   uppercase.) Quoted column names (not typically used) are case-sensitive. 
     119    # [*ActiveRecord*] 
     120    #   Attribute accessors corresponding to column names are case-sensitive. 
     121    #   The defaults for primary key and inheritance columns are lowercase, and 
     122    #   in general, people use lowercase attribute names. 
     123    # 
     124    # In order to map between the differing semantics in a way that conforms 
     125    # to common usage for both Firebird and ActiveRecord, uppercase column names 
     126    # in Firebird are converted to lowercase attribute names in ActiveRecord, 
     127    # and vice-versa. Mixed-case column names retain their case in both 
     128    # directions. Lowercase (quoted) Firebird column names are not supported. 
     129    # This is similar to the solutions adopted by other adapters. 
     130    # 
     131    # In general, the best approach is to use unqouted (case-insensitive) column 
     132    # names in your Firebird DDL (or if you must quote, use uppercase column 
     133    # names). These will correspond to lowercase attributes in ActiveRecord. 
     134    # 
     135    # For example, a Firebird table based on the following DDL: 
     136    # 
     137    #  CREATE TABLE products ( 
     138    #    id BIGINT NOT NULL PRIMARY KEY, 
     139    #    "TYPE" VARCHAR(50), 
     140    #    name VARCHAR(255) ); 
     141    # 
     142    # ...will correspond to an ActiveRecord model class called +Product+ with 
     143    # the following attributes: +id+, +type+, +name+. 
     144    # 
     145    # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words: 
     146    # In ActiveRecord, the default inheritance column name is +type+. The word 
     147    # _type_ is a Firebird reserved word, so it must be quoted in any Firebird 
     148    # SQL statements. Because of the case mapping described above, you should 
     149    # always reference this column using quoted-uppercase syntax 
     150    # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the 
     151    # example above). This holds true for any other Firebird reserved words used 
     152    # as column names as well. 
     153    # 
     154    # === Migrations 
     155    # The Firebird adapter does not currently support Migrations.  I hope to 
     156    # add this feature in the near future. 
     157    # 
     158    # == Connection Options 
     159    # 
     160    # The following options are supported by the Firebird adapter. None of the 
     161    # options have default values. 
     162    # 
     163    # <tt>:database</tt>:: 
     164    #   <i>Required option.</i> Specifies one of: (i) a Firebird database alias; 
     165    #   (ii) the full path of a database file; _or_ (iii) a full Firebird 
     166    #   connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt> 
     167    #   or <tt>:port</tt> as separate options when using a full connection 
     168    #   string.</i> 
     169    # <tt>:host</tt>:: 
     170    #   Set to <tt>"remote.host.name"</tt> for remote database connections. 
     171    #   May be omitted for local connections if a full database path is 
     172    #   specified for <tt>:database</tt>. Some platforms require a value of 
     173    #   <tt>"localhost"</tt> for local connections when using a Firebird 
     174    #   database _alias_. 
     175    # <tt>:service</tt>:: 
     176    #   Specifies a service name for the connection. Only used if <tt>:host</tt> 
     177    #   is provided. Required when connecting to a non-standard service. 
     178    # <tt>:port</tt>:: 
     179    #   Specifies the connection port. Only used if <tt>:host</tt> is provided 
     180    #   and <tt>:service</tt> is not. Required when connecting to a non-standard 
     181    #   port and <tt>:service</tt> is not defined. 
     182    # <tt>:username</tt>:: 
     183    #   Specifies the database user. May be omitted or set to +nil+ (together 
     184    #   with <tt>:password</tt>) to use the underlying operating system user 
     185    #   credentials on supported platforms. 
     186    # <tt>:password</tt>:: 
     187    #   Specifies the database password. Must be provided if <tt>:username</tt> 
     188    #   is explicitly specified; should be omitted if OS user credentials are 
     189    #   are being used. 
     190    # <tt>:charset</tt>:: 
     191    #   Specifies the character set to be used by the connection. Refer to 
     192    #   Firebird documentation for valid options. 
     193    class FirebirdAdapter < AbstractAdapter 
     194      BLOB_MAX_LENGTH = 32_767 
     195 
     196      def select_all(sql, name = nil) 
     197        select(sql, name) 
     198      end 
     199 
     200      def select_one(sql, name = nil) 
     201        result = select(sql, name) 
     202        result.nil? ? nil : result.first 
     203      end 
     204 
     205      def columns(table_name, name = nil) 
     206        sql = <<-END_OF_SQL 
     207          SELECT R.RDB$FIELD_NAME, R.RDB$FIELD_SOURCE, T.RDB$TYPE_NAME, F.RDB$FIELD_SUB_TYPE, 
     208                 F.RDB$FIELD_LENGTH, F.RDB$FIELD_PRECISION, F.RDB$FIELD_SCALE, 
     209                 COALESCE(R.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) RDB$DEFAULT_SOURCE 
     210          FROM RDB$RELATION_FIELDS R, RDB$FIELDS F, RDB$TYPES T 
     211          WHERE R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME 
     212          AND R.RDB$RELATION_NAME = '#{table_name.upcase}' 
     213          AND F.RDB$FIELD_TYPE = T.RDB$TYPE 
     214          AND T.RDB$FIELD_NAME = 'RDB$FIELD_TYPE' 
     215          ORDER BY R.RDB$FIELD_POSITION 
     216        END_OF_SQL 
     217        execute(sql, name).collect do |field| 
     218          field_values = field.values.collect do |value| 
     219            case value 
     220              when String         then value.rstrip 
     221              when FireRuby::Blob then value.to_s 
     222              else value 
     223            end 
     224          end 
     225          create_column(*field_values) 
     226        end 
     227      end 
     228 
     229      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 
     230        execute(sql, name) 
     231        id_value 
     232      end 
     233 
     234      def execute(sql, name = nil, &block) 
     235        log(sql, name) do 
     236          if @transaction 
     237            @connection.execute(sql, @transaction, &block) 
     238          else 
     239            @connection.execute_immediate(sql, &block) 
     240          end 
     241        end 
     242      end 
     243 
     244      alias_method :update, :execute 
     245      alias_method :delete, :execute 
     246 
     247      def begin_db_transaction() 
     248        @transaction = @connection.start_transaction 
     249      end 
     250 
     251      def commit_db_transaction() 
     252        @transaction.commit 
     253        @transaction = nil 
     254      end 
     255 
     256      def rollback_db_transaction() 
     257        @transaction.rollback 
     258        @transaction = nil 
     259      end 
     260 
     261      def quote(value, column = nil) 
     262        if [Time, DateTime].include?(value.class) 
     263          "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)" 
     264        else 
     265          super 
     266        end 
     267      end 
     268 
     269      def quote_string(string) 
     270        string.gsub(/'/, "''") 
     271      end 
     272 
     273      def quote_column_name(column_name) 
     274        %Q("#{ar_to_fb_case(column_name)}") 
     275      end 
     276 
     277      def adapter_name 
     278        'Firebird' 
     279      end 
     280 
     281      def add_limit_offset!(sql, options) 
     282        if options[:limit] 
     283          limit_string = "FIRST #{options[:limit]}" 
     284          limit_string << " SKIP #{options[:offset]}" if options[:offset] 
     285          sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ') 
     286        end 
     287      end 
     288 
     289      def prefetch_primary_key? 
     290        true 
     291      end 
     292 
     293      def next_sequence_value(sequence_name) 
     294        FireRuby::Generator.new(sequence_name, @connection).next(1) 
     295      end 
     296 
     297      private 
     298        def select(sql, name = nil) 
     299          execute(sql, name).collect do |row| 
     300            hashed_row = {} 
     301            row.aliases.zip(row.values) do |column_alias, value| 
     302              value = case value 
     303                when Time           then guess_date_or_time(value) 
     304                when FireRuby::Blob then value.to_s 
     305                else value 
     306              end 
     307              hashed_row[fb_to_ar_case(column_alias)] = value 
     308            end 
     309            hashed_row 
     310          end 
     311        end 
     312 
     313        # FireRuby (as of 0.3.2) returns a Time object for TIME, TIMESTAMP and 
     314        # DATE columns. This method guesses whether time is really a date, and 
     315        # returns a string representing the date if it is. This date string gets 
     316        # properly type-cast later (as a Time or Date object) based on the 
     317        # column type. 
     318        def guess_date_or_time(time) 
     319          if (time.hour + time.min + time.sec + time.usec).zero? 
     320            time.strftime("%Y-%m-%d") 
     321          else 
     322            time 
     323          end 
     324        end 
     325 
     326        def create_column(field_name, field_source, field_type, field_sub_type, 
     327                          field_length, field_precision, field_scale, default_source) 
     328          column_type = metadata_to_column_type(field_type, field_sub_type) 
     329          default = parse_default(default_source, column_type, field_length, field_precision, field_scale) if default_source 
     330          sql_type = field_source =~ /BOOLEAN/ ? 'BOOLEAN' : column_type 
     331          limit = field_type == 'BLOB' ? BLOB_MAX_LENGTH : field_length 
     332          FirebirdColumn.new(field_name.downcase, default, sql_type, limit) 
     333        end 
     334 
     335        # Maps the internal type returned by Firebird metadata tables to a 
     336        # SQL type that can be passed to #firebird_cast_type and Column#new 
     337        def metadata_to_column_type(field_type, field_sub_type) 
     338          case field_type 
     339            when 'TEXT'    then 'CHAR' 
     340            when 'VARYING' then 'VARCHAR' 
     341            when 'DOUBLE'  then 'DOUBLE PRECISION' 
     342            when 'BLOB'    then field_sub_type == 1 ? 'CLOB' : 'BLOB' 
     343            when 'SHORT', 'LONG', 'INT64' 
     344              case field_sub_type 
     345                when 1 then 'NUMERIC' 
     346                when 2 then 'DECIMAL' 
     347                else 'BIGINT' 
     348              end 
     349            else field_type 
     350          end 
     351        end 
     352 
     353        def parse_default(default_source, column_type, field_length, field_precision, field_scale) 
     354          default_source =~ /^DEFAULT\s+(.*)\s*$/i 
     355          default_value = $1 
     356          unless default_value.upcase == "NULL" 
     357            cast_type = firebird_cast_type(column_type, field_length, field_precision, field_scale) 
     358            { :value => default_value, :cast_type => cast_type } 
     359          end 
     360        end 
     361 
     362        # Returns a column definition that can be used in a Firebird CAST statement 
     363        def firebird_cast_type(sql_type, field_length, field_precision, field_scale) 
     364          case sql_type 
     365            when 'BLOB', 'CLOB'       then "VARCHAR(255)" 
     366            when 'CHAR', 'VARCHAR'    then "#{sql_type}(#{field_length})" 
     367            when 'NUMERIC', 'DECIMAL' then "#{sql_type}(#{field_precision},#{field_scale.abs})" 
     368            else sql_type 
     369          end 
     370        end 
     371 
     372        # Maps uppercase Firebird column names to lowercase for ActiveRecord; 
     373        # mixed-case columns retain their original case. 
     374        def fb_to_ar_case(column_name) 
     375          column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase 
     376        end 
     377 
     378        # Maps lowercase ActiveRecord column names to uppercase for Fierbird; 
     379        # mixed-case columns retain their original case. 
     380        def ar_to_fb_case(column_name) 
     381          column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase 
     382        end 
     383    end 
     384  end 
     385end 
  • lib/active_record/associations.rb

    old new  
    864864        end 
    865865 
    866866        def column_aliases(schema_abbreviations) 
    867           schema_abbreviations.collect { |cn, tc| "#{tc.join(".")} AS #{cn}" }.join(", ") 
     867          schema_abbreviations.collect { |cn, tc| "#{tc[0]}.#{connection.quote_column_name tc[1]} AS #{cn}" }.join(", ") 
    868868        end 
    869869 
    870870        def association_join(reflection) 
  • lib/active_record/base.rb

    old new  
    569569        "type" 
    570570      end 
    571571 
    572       # Defines the sequence_name (for Oracle) -- can be overridden in subclasses. 
     572      # Defines the sequence_name (for Oracle and Firebird) -- can be overridden in subclasses. 
    573573      def sequence_name 
    574574        "#{table_name}_seq" 
    575575      end 
     
    619619 
    620620      # Sets the name of the sequence to use when generating ids to the given 
    621621      # value, or (if the value is nil or false) to the value returned by the 
    622       # given block. Currently useful only when using Oracle, which requires 
    623       # explicit sequences. 
     622      # given block. Currently useful only when using Oracle or Firebird, which 
     623      # require explicit sequences. 
    624624      # 
    625625      # Setting the sequence name when using other dbs will have no effect. 
    626       # If a sequence name is not explicitly set when using Oracle, it will 
    627       # default to the commonly used pattern of: #{table_name}_seq 
     626      # If a sequence name is not explicitly set when using Oracle or Firebird, 
     627      # it will default to the commonly used pattern of: #{table_name}_seq 
    628628      # 
    629629      # Example: 
    630630      # 
     
    808808        end 
    809809 
    810810        def type_condition 
    811           type_condition = subclasses.inject("#{table_name}.#{inheritance_column} = '#{name.demodulize}' ") do |condition, subclass| 
    812             condition << "OR #{table_name}.#{inheritance_column} = '#{subclass.name.demodulize}' " 
     811          quoted_inheritance_column = connection.quote_column_name(inheritance_column) 
     812          type_condition = subclasses.inject("#{table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass| 
     813            condition << "OR #{table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' " 
    813814          end 
    814815 
    815816          " (#{type_condition}) " 
     
    837838            attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) } 
    838839 
    839840            attr_index = -1 
    840             conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ") 
     841            conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{connection.quote_column_name(attr_name)} #{attribute_condition(arguments[attr_index])} " }.join(" AND ") 
    841842 
    842843            if arguments[attributes.length].is_a?(Hash) 
    843844              find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length])) 
     
    12121213 
    12131214      # Creates a new record with values matching those of the instant attributes. 
    12141215      def create 
     1216        if self.id.nil? and connection.prefetch_primary_key? 
     1217          self.id = connection.next_sequence_value(self.class.sequence_name) 
     1218        end 
     1219 
    12151220        self.id = connection.insert( 
    12161221          "INSERT INTO #{self.class.table_name} " + 
    12171222          "(#{quoted_column_names.join(', ')}) " + 
  • lib/active_record.rb

    old new  
    6767require 'active_record/connection_adapters/sqlserver_adapter' 
    6868require 'active_record/connection_adapters/db2_adapter' 
    6969require 'active_record/connection_adapters/oci_adapter' 
     70require 'active_record/connection_adapters/firebird_adapter' 
    7071 
    7172require 'active_record/query_cache'