Ticket #1874: firebird_adapter_r2032.diff
| File firebird_adapter_r2032.diff, 40.5 kB (added by Ken Kunz <kennethkunz@gmail.com>, 3 years ago) |
|---|
-
test/deprecated_associations_test.rb
old new 311 311 end 312 312 313 313 def test_has_many_find_all 314 assert_equal 2, Firm.find_first.find_all_in_clients(" type= 'Client'").length314 assert_equal 2, Firm.find_first.find_all_in_clients("#{QUOTED_TYPE} = 'Client'").length 315 315 assert_equal 1, Firm.find_first.find_all_in_clients("name = 'Summit'").length 316 316 end 317 317 -
test/connections/native_firebird/connection.rb
old new 1 print "Using native Firebird\n" 2 require 'fixtures/course' 3 require 'logger' 4 5 ActiveRecord::Base.logger = Logger.new("debug.log") 6 7 db1 = 'activerecord_unittest' 8 db2 = 'activerecord_unittest2' 9 10 ActiveRecord::Base.establish_connection( 11 :adapter => "firebird", 12 :host => "localhost", 13 :username => "rails", 14 :password => "rails", 15 :database => db1 16 ) 17 18 Course.establish_connection( 19 :adapter => "firebird", 20 :host => "localhost", 21 :username => "rails", 22 :password => "rails", 23 :database => db2 24 ) -
test/associations_test.rb
old new 338 338 def test_find_all 339 339 firm = Firm.find_first 340 340 assert_equal firm.clients, firm.clients.find_all 341 assert_equal 2, firm.clients.find(:all, :conditions => " type= 'Client'").length341 assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length 342 342 assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length 343 343 end 344 344 … … 354 354 firm = Firm.find_first 355 355 client2 = Client.find(2) 356 356 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'") 359 359 end 360 360 361 361 def test_find_first_sanitized 362 362 firm = Firm.find_first 363 363 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' }]) 367 367 end 368 368 369 369 def test_find_in_collection -
test/base_test.rb
old new 859 859 end 860 860 861 861 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'" 863 863 res2 = res + 1 864 864 assert_nothing_raised do 865 res2 = Post.count("posts. type= 'Post'",865 res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'", 866 866 "LEFT JOIN comments ON posts.id=comments.post_id") 867 867 end 868 868 assert_equal res, res2 -
test/binary_test.rb
old new 24 24 if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter 25 25 return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter) 26 26 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 27 34 bin = Binary.new 28 35 bin.data = @data 29 36 -
test/inheritance_test.rb
old new 11 11 if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) 12 12 Company.connection.execute "SET IDENTITY_INSERT companies ON" 13 13 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')" 15 15 #We then need to turn it back Off before continuing. 16 16 if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) 17 17 Company.connection.execute "SET IDENTITY_INSERT companies OFF" -
test/default_test_firebird.rb
old new 1 require 'abstract_unit' 2 require 'fixtures/default' 3 4 class 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 16 end -
test/abstract_unit.rb
old new 8 8 require 'active_support/breakpoint' 9 9 require 'connection' 10 10 11 QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') 12 11 13 class Test::Unit::TestCase #:nodoc: 12 14 def create_fixtures(*table_names) 13 15 if block_given? -
test/fixtures/comment.rb
old new 6 6 end 7 7 8 8 def self.search_by_type(q) 9 self.find(:all, :conditions => [ 'type = ?', q])9 self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q]) 10 10 end 11 11 end 12 12 -
test/fixtures/db_definitions/firebird.sql
old new 1 CREATE DOMAIN D_BOOLEAN AS CHAR(1) CHECK (VALUE IN ('t', 'f')); 2 3 CREATE TABLE accounts ( 4 id BIGINT NOT NULL, 5 firm_id BIGINT, 6 credit_limit INTEGER, 7 PRIMARY KEY (id) 8 ); 9 CREATE GENERATOR accounts_seq; 10 SET GENERATOR accounts_seq TO 10000; 11 12 CREATE 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 ); 22 CREATE GENERATOR companies_nonstd_seq; 23 SET GENERATOR companies_nonstd_seq TO 10000; 24 25 CREATE 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 ); 40 CREATE GENERATOR topics_seq; 41 SET GENERATOR topics_seq TO 10000; 42 43 CREATE 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 ); 51 CREATE GENERATOR developers_seq; 52 SET GENERATOR developers_seq TO 10000; 53 54 CREATE TABLE projects ( 55 id BIGINT NOT NULL, 56 name VARCHAR(100), 57 "TYPE" VARCHAR(255), 58 PRIMARY KEY (id) 59 ); 60 CREATE GENERATOR projects_seq; 61 SET GENERATOR projects_seq TO 10000; 62 63 CREATE 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 70 CREATE 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 ); 80 CREATE GENERATOR customers_seq; 81 SET GENERATOR customers_seq TO 10000; 82 83 CREATE TABLE movies ( 84 movieid BIGINT NOT NULL, 85 name varchar(100), 86 PRIMARY KEY (movieid) 87 ); 88 CREATE GENERATOR movies_seq; 89 SET GENERATOR movies_seq TO 10000; 90 91 CREATE TABLE subscribers ( 92 nick VARCHAR(100) NOT NULL, 93 name VARCHAR(100), 94 PRIMARY KEY (nick) 95 ); 96 97 CREATE TABLE booleantests ( 98 id BIGINT NOT NULL, 99 "VALUE" D_BOOLEAN, 100 PRIMARY KEY (id) 101 ); 102 CREATE GENERATOR booleantests_seq; 103 SET GENERATOR booleantests_seq TO 10000; 104 105 CREATE TABLE auto_id_tests ( 106 auto_id BIGINT NOT NULL, 107 "VALUE" INTEGER, 108 PRIMARY KEY (auto_id) 109 ); 110 CREATE GENERATOR auto_id_tests_seq; 111 SET GENERATOR auto_id_tests_seq TO 10000; 112 113 CREATE TABLE entrants ( 114 id BIGINT NOT NULL, 115 name VARCHAR(255) NOT NULL, 116 course_id INTEGER NOT NULL, 117 PRIMARY KEY (id) 118 ); 119 CREATE GENERATOR entrants_seq; 120 SET GENERATOR entrants_seq TO 10000; 121 122 CREATE TABLE colnametests ( 123 id BIGINT NOT NULL, 124 "REFERENCES" INTEGER NOT NULL, 125 PRIMARY KEY (id) 126 ); 127 CREATE GENERATOR colnametests_seq; 128 SET GENERATOR colnametests_seq TO 10000; 129 130 CREATE 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 ); 142 CREATE GENERATOR mixins_seq; 143 SET GENERATOR mixins_seq TO 10000; 144 145 CREATE 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 ); 151 CREATE GENERATOR people_seq; 152 SET GENERATOR people_seq TO 10000; 153 154 CREATE TABLE binaries ( 155 id BIGINT NOT NULL, 156 data BLOB, 157 PRIMARY KEY (id) 158 ); 159 CREATE GENERATOR binaries_seq; 160 SET GENERATOR binaries_seq TO 10000; 161 162 CREATE TABLE computers ( 163 id BIGINT NOT NULL, 164 developer INTEGER NOT NULL, 165 "extendedWarranty" INTEGER NOT NULL, 166 PRIMARY KEY (id) 167 ); 168 CREATE GENERATOR computers_seq; 169 SET GENERATOR computers_seq TO 10000; 170 171 CREATE 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 ); 179 CREATE GENERATOR posts_seq; 180 SET GENERATOR posts_seq TO 10000; 181 182 CREATE 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 ); 189 CREATE GENERATOR comments_seq; 190 SET GENERATOR comments_seq TO 10000; 191 192 CREATE TABLE authors ( 193 id BIGINT NOT NULL, 194 name VARCHAR(255) NOT NULL, 195 PRIMARY KEY (id) 196 ); 197 CREATE GENERATOR authors_seq; 198 SET GENERATOR authors_seq TO 10000; 199 200 CREATE TABLE tasks ( 201 id BIGINT NOT NULL, 202 "STARTING" TIMESTAMP, 203 ending TIMESTAMP, 204 PRIMARY KEY (id) 205 ); 206 CREATE GENERATOR tasks_seq; 207 SET GENERATOR tasks_seq TO 10000; 208 209 CREATE TABLE categories ( 210 id BIGINT NOT NULL, 211 name VARCHAR(255) NOT NULL, 212 "TYPE" VARCHAR(255) NOT NULL, 213 PRIMARY KEY (id) 214 ); 215 CREATE GENERATOR categories_seq; 216 SET GENERATOR categories_seq TO 10000; 217 218 CREATE TABLE categories_posts ( 219 category_id BIGINT NOT NULL, 220 post_id BIGINT NOT NULL, 221 PRIMARY KEY (category_id, post_id) 222 ); 223 224 CREATE TABLE fk_test_has_pk ( 225 id BIGINT NOT NULL, 226 PRIMARY KEY (id) 227 ); 228 229 CREATE 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 236 CREATE TABLE defaults ( 237 id BIGINT NOT NULL, 238 default_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP 239 ); 240 CREATE GENERATOR defaults_seq; 241 SET GENERATOR defaults_seq TO 10000; -
test/fixtures/db_definitions/firebird.drop.sql
old new 1 DROP TABLE accounts; 2 DROP TABLE companies; 3 DROP TABLE topics; 4 DROP TABLE developers; 5 DROP TABLE projects; 6 DROP TABLE developers_projects; 7 DROP TABLE customers; 8 DROP TABLE movies; 9 DROP TABLE subscribers; 10 DROP TABLE booleantests; 11 DROP TABLE auto_id_tests; 12 DROP TABLE entrants; 13 DROP TABLE colnametests; 14 DROP TABLE mixins; 15 DROP TABLE people; 16 DROP TABLE binaries; 17 DROP TABLE computers; 18 DROP TABLE posts; 19 DROP TABLE comments; 20 DROP TABLE authors; 21 DROP TABLE tasks; 22 DROP TABLE categories; 23 DROP TABLE categories_posts; 24 DROP TABLE fk_test_has_fk; 25 DROP TABLE fk_test_has_pk; 26 DROP TABLE defaults; 27 28 DROP DOMAIN D_BOOLEAN; 29 30 DROP GENERATOR accounts_seq; 31 DROP GENERATOR companies_nonstd_seq; 32 DROP GENERATOR topics_seq; 33 DROP GENERATOR developers_seq; 34 DROP GENERATOR projects_seq; 35 DROP GENERATOR customers_seq; 36 DROP GENERATOR movies_seq; 37 DROP GENERATOR booleantests_seq; 38 DROP GENERATOR auto_id_tests_seq; 39 DROP GENERATOR entrants_seq; 40 DROP GENERATOR colnametests_seq; 41 DROP GENERATOR mixins_seq; 42 DROP GENERATOR people_seq; 43 DROP GENERATOR binaries_seq; 44 DROP GENERATOR computers_seq; 45 DROP GENERATOR posts_seq; 46 DROP GENERATOR comments_seq; 47 DROP GENERATOR authors_seq; 48 DROP GENERATOR tasks_seq; 49 DROP GENERATOR categories_seq; 50 DROP GENERATOR defaults_seq; -
test/fixtures/db_definitions/firebird2.sql
old new 1 CREATE TABLE courses ( 2 id BIGINT NOT NULL PRIMARY KEY, 3 name VARCHAR(255) NOT NULL 4 ); 5 CREATE GENERATOR courses_seq; 6 SET GENERATOR courses_seq TO 10000; -
test/fixtures/db_definitions/firebird2.drop.sql
old new 1 DROP TABLE courses; 2 DROP GENERATOR courses_seq; -
test/fixtures/company.rb
old new 7 7 8 8 9 9 class 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' )" 11 13 has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC" 12 14 has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id" 13 15 has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" -
Rakefile
old new 26 26 27 27 # Run the unit tests 28 28 29 for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci )29 for adapter in %w( mysql postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci firebird ) 30 30 Rake::TestTask.new("test_#{adapter}") { |t| 31 31 t.libs << "test" << "test/connections/native_#{adapter}" 32 32 t.pattern = "test/*_test{,_#{adapter}}.rb" -
lib/active_record/validations.rb
old new 464 464 configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 465 465 configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) 466 466 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 470 474 end 471 else472 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) 474 478 end 479 if record.class.find(:first, :conditions => [condition_sql, *condition_params]) 480 record.errors.add(attr_name, configuration[:message]) 481 end 475 482 end 476 483 end 477 484 -
lib/active_record/connection_adapters/sqlite_adapter.rb
old new 171 171 end 172 172 173 173 def quote_column_name(name) 174 "'#{name}'"174 %Q("#{name}") 175 175 end 176 176 177 177 def adapter_name() -
lib/active_record/connection_adapters/abstract_adapter.rb
old new 155 155 attr_reader :name, :default, :type, :limit 156 156 # The name should contain the name of the column, such as "name" in "name varchar(250)" 157 157 # 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 163 169 end 164 170 165 171 def klass … … 365 371 sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil? 366 372 end 367 373 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 368 380 369 381 def initialize_schema_information 370 382 begin -
lib/active_record/connection_adapters/firebird_adapter.rb
old new 1 # Author: Ken Kunz <kennethkunz@gmail.com> 2 3 require 'active_record/connection_adapters/abstract_adapter' 4 5 module 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 22 end 23 24 module 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 385 end -
lib/active_record/associations.rb
old new 864 864 end 865 865 866 866 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(", ") 868 868 end 869 869 870 870 def association_join(reflection) -
lib/active_record/base.rb
old new 569 569 "type" 570 570 end 571 571 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. 573 573 def sequence_name 574 574 "#{table_name}_seq" 575 575 end … … 619 619 620 620 # Sets the name of the sequence to use when generating ids to the given 621 621 # 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 requires623 # explicit sequences.622 # given block. Currently useful only when using Oracle or Firebird, which 623 # require explicit sequences. 624 624 # 625 625 # 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 will627 # default to the commonly used pattern of: #{table_name}_seq626 # 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 628 628 # 629 629 # Example: 630 630 # … … 808 808 end 809 809 810 810 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}' " 813 814 end 814 815 815 816 " (#{type_condition}) " … … 837 838 attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) } 838 839 839 840 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 ") 841 842 842 843 if arguments[attributes.length].is_a?(Hash) 843 844 find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length])) … … 1212 1213 1213 1214 # Creates a new record with values matching those of the instant attributes. 1214 1215 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 1215 1220 self.id = connection.insert( 1216 1221 "INSERT INTO #{self.class.table_name} " + 1217 1222 "(#{quoted_column_names.join(', ')}) " + -
lib/active_record.rb
old new 67 67 require 'active_record/connection_adapters/sqlserver_adapter' 68 68 require 'active_record/connection_adapters/db2_adapter' 69 69 require 'active_record/connection_adapters/oci_adapter' 70 require 'active_record/connection_adapters/firebird_adapter' 70 71 71 72 require 'active_record/query_cache'