Ticket #1874: firebird_adapter_r1957.diff
| File firebird_adapter_r1957.diff, 40.2 kB (added by Ken Kunz <kennethkunz@gmail.com>, 3 years ago) |
|---|
-
activerecord/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 -
activerecord/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 ) -
activerecord/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 -
activerecord/test/base_test.rb
old new 853 853 end 854 854 855 855 def test_count_with_join 856 res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts. type= 'Post'"856 res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" 857 857 res2 = res + 1 858 858 assert_nothing_raised do 859 res2 = Post.count("posts. type= 'Post'",859 res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'", 860 860 "LEFT JOIN comments ON posts.id=comments.post_id") 861 861 end 862 862 assert_equal res, res2 -
activerecord/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 -
activerecord/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" -
activerecord/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 9 # Variance should be small; increase if required -- e.g., if test db is on 10 # remote host and clocks aren't synchronized. 11 t1 = Time.new 12 accepted_variance = 1.0 13 assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance) 14 end 15 end -
activerecord/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? -
activerecord/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 -
activerecord/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; -
activerecord/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; -
activerecord/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; -
activerecord/test/fixtures/db_definitions/firebird2.drop.sql
old new 1 DROP TABLE courses; 2 DROP GENERATOR courses_seq; -
activerecord/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" -
activerecord/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" -
activerecord/lib/active_record/validations.rb
old new 463 463 configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 464 464 configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) 465 465 466 if scope = configuration[:scope] 467 validates_each(attr_names,configuration) do |record, attr_name, value| 468 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)])) 466 validates_each(attr_names,configuration) do |record, attr_name, value| 467 condition_sql = "#{attr_name} #{attribute_condition(value)}" 468 condition_params = [value] 469 if scope = configuration[:scope] 470 scope_value = record.send(scope) 471 condition_sql << " AND #{scope} #{attribute_condition(scope_value)}" 472 condition_params << scope_value 469 473 end 470 else471 validates_each(attr_names,configuration) do |record, attr_name, value|472 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) ] ))474 unless record.new_record? 475 condition_sql << " AND #{record.class.primary_key} <> ?" 476 condition_params << record.send(:id) 473 477 end 478 if record.class.find(:first, :conditions => [condition_sql, *condition_params]) 479 record.errors.add(attr_name, configuration[:message]) 480 end 474 481 end 475 482 end 476 483 -
activerecord/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() -
activerecord/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 -
activerecord/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 end 64 65 # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/] 66 # extension, version 0.3.2 or later (available as a gem or from 67 # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with 68 # Firebird 1.5.x on Linux, OS X and Win32 platforms. 69 # 70 # == Usage Notes 71 # 72 # === Sequence (Generator) Names 73 # The Firebird adapter supports the same approach adopted for the Oracle 74 # adapter. See ActiveRecord::Base#set_sequence_name for more details. 75 # 76 # Note that in general there is no need to create a BEFORE INSERT trigger 77 # corresponding to a Firebird sequence generator when using ActiveRecord. 78 # In other words, you don't have to try to make Firebird simulate an 79 # AUTO_INCREMENT or IDENTITY column. When saving a new record, ActiveRecord 80 # pre-fetches the next sequence value for the table and explicitly includes 81 # it in the INSERT statement. (Pre-fetching the next primary key value is 82 # the only way for the Firebird adapter to reliably report back the id after 83 # a successful insert.) 84 # 85 # === BOOLEAN Domain 86 # Firebird 1.5 does not provide a native BOOLEAN type. But you can easily 87 # define a BOOLEAN _domain_ for this purpose, e.g.: 88 # 89 # CREATE DOMAIN D_BOOLEAN AS CHAR(1) CHECK (VALUE IN ('t', 'f')); 90 # 91 # When the Firebird adapter encounters a column that is based on a domain 92 # that includes "BOOLEAN" in the domain name, it will attempt to treat 93 # the column as a BOOLEAN. 94 # 95 # === BLOB Elements 96 # The Firebird adapter currently provides only limited support for BLOB 97 # columns. You cannot currently retrieve or insert BLOBs as IO streams. When 98 # selecting a BLOB, the entire element is converted into a String. When 99 # inserting or updating a BLOB, the entire value is included in-line in 100 # the SQL statement, limiting you to values <= 32KB in size. 101 # 102 # === Case-Sensitive Column Names 103 # Firebird and ActiveRecord have somewhat conflicting case semantics for 104 # column names. 105 # 106 # [*Firebird*] 107 # The standard practice is to use unquoted column names, which can be 108 # thought of as case-insensitive. (In fact, Firebird converts them to 109 # all-caps.) Quoted column names (not typically used) are case-sensitive. 110 # [*ActiveRecord*] 111 # Attribute accessors corresponding to column names are case-sensitive. 112 # The defaults for primary key and inheritance columns are all-lowercase, 113 # and in general, people use all-lowercase attribute names. 114 # 115 # In order to map between the differing semantics in a way that conforms 116 # to common usage for both Firebird and ActiveRecord, all-uppercase column 117 # names in Firebird are converted to all-lowercase attribute names in 118 # ActiveRecord, and vice-versa. Mixed-case column names retain their case 119 # in both directions. Lowercase (quoted) Firebird column names are not 120 # supported. This is similar to the solutions adopted by other adapters. 121 # 122 # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words: 123 # In ActiveRecord, the default inheritance column name is +type+. The word 124 # "type" is a Firebird reserved word, so it must be quoted in any Firebird 125 # SQL statements. Because of the case mapping described above, you should 126 # always reference this column using quoted-uppercase syntax 127 # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements. This holds 128 # true for any other Firebird reserved words used as column names as well. 129 # 130 # === Migrations 131 # The Firebird adapter does not currently support Migrations. I hope to 132 # add this feature in the near future. 133 # 134 # == Connection Options 135 # 136 # The following options are supported by the Firebird adapter. All options 137 # default to +nil+. 138 # 139 # <tt>:database</tt>:: 140 # <i>Required option.</i> Specifies (i) a Firebird database alias; (ii) 141 # the full path of a database file; _or_ (iii) a full Firebird connection 142 # string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt> or 143 # <tt>:port</tt> as separate options when using a full connection 144 # string.</i> 145 # <tt>:host</tt>:: 146 # May be omitted for local connections if a full database path is 147 # specified for <tt>:database</tt>. Set to <tt>"localhost"</tt> for 148 # local connections if a database _alias_ is used. Set to 149 # <tt>"remote.host.name"</tt> for remote connections. 150 # <tt>:service</tt>:: 151 # Specifies a service name for the connection. Only used if <tt>:host</tt> 152 # is provided. Required when connecting to a non-standard service. 153 # <tt>:port</tt>:: 154 # Specifies the connection port. Only used if <tt>:host</tt> is provided 155 # and <tt>:service</tt> is not. Required when connecting to a non-standard 156 # port and <tt>:service</tt> is not defined. 157 # <tt>:username</tt>:: 158 # Specifies the database user. May be omitted or set to +nil+ (together 159 # with <tt>:password</tt>) to use the underlying operating system user 160 # credentials on supported platforms. 161 # <tt>:password</tt>:: 162 # Specifies the database password. Must be provided if <tt>:username</tt> 163 # is explicitly specified; should be omitted if OS user credentials are 164 # are being used. 165 class FirebirdAdapter < AbstractAdapter 166 BLOB_MAX_LENGTH = 32_767 167 168 def select_all(sql, name = nil) 169 select(sql, name) 170 end 171 172 def select_one(sql, name = nil) 173 result = select(sql, name) 174 result.nil? ? nil : result.first 175 end 176 177 def columns(table_name, name = nil) 178 sql = <<-END_OF_SQL 179 SELECT R.RDB$FIELD_NAME, R.RDB$FIELD_SOURCE, T.RDB$TYPE_NAME, F.RDB$FIELD_SUB_TYPE, 180 F.RDB$FIELD_LENGTH, F.RDB$FIELD_PRECISION, F.RDB$FIELD_SCALE, 181 COALESCE(R.RDB$DEFAULT_SOURCE, F.RDB$DEFAULT_SOURCE) RDB$DEFAULT_SOURCE 182 FROM RDB$RELATION_FIELDS R, RDB$FIELDS F, RDB$TYPES T 183 WHERE R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME 184 AND R.RDB$RELATION_NAME = '#{table_name.upcase}' 185 AND F.RDB$FIELD_TYPE = T.RDB$TYPE 186 AND T.RDB$FIELD_NAME = 'RDB$FIELD_TYPE' 187 ORDER BY R.RDB$FIELD_POSITION 188 END_OF_SQL 189 execute(sql, name).collect do |field| 190 field_values = field.values.collect do |value| 191 case value 192 when String then value.rstrip 193 when FireRuby::Blob then value.to_s 194 else value 195 end 196 end 197 create_column(*field_values) 198 end 199 end 200 201 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 202 execute(sql, name) 203 id_value 204 end 205 206 def execute(sql, name = nil, &block) 207 log(sql, name) do 208 if @transaction 209 @connection.execute(sql, @transaction, &block) 210 else 211 @connection.execute_immediate(sql, &block) 212 end 213 end 214 end 215 216 alias_method :update, :execute 217 alias_method :delete, :execute 218 219 def begin_db_transaction() 220 @transaction = @connection.start_transaction 221 end 222 223 def commit_db_transaction() 224 @transaction.commit 225 @transaction = nil 226 end 227 228 def rollback_db_transaction() 229 @transaction.rollback 230 @transaction = nil 231 end 232 233 def quote(value, column = nil) 234 if [Time, DateTime].include?(value.class) 235 "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)" 236 else 237 super 238 end 239 end 240 241 def quote_string(string) 242 string.gsub(/'/, "''") 243 end 244 245 def quote_column_name(column_name) 246 %Q("#{ar_to_fb_case(column_name)}") 247 end 248 249 def adapter_name 250 'Firebird' 251 end 252 253 def add_limit_offset!(sql, options) 254 if options[:limit] 255 limit_string = "FIRST #{options[:limit]}" 256 limit_string << " SKIP #{options[:offset]}" if options[:offset] 257 sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ') 258 end 259 end 260 261 def prefetch_primary_key? 262 true 263 end 264 265 def next_sequence_value(sequence_name) 266 FireRuby::Generator.new(sequence_name, @connection).next(1) 267 end 268 269 private 270 def select(sql, name = nil) 271 execute(sql, name).collect do |row| 272 hashed_row = {} 273 row.aliases.zip(row.values) do |column_alias, value| 274 value = case value 275 when Time then guess_date_or_time(value) 276 when FireRuby::Blob then value.to_s 277 else value 278 end 279 hashed_row[fb_to_ar_case(column_alias)] = value 280 end 281 hashed_row 282 end 283 end 284 285 # FireRuby (as of 0.3.2) returns a Time object for TIME, TIMESTAMP and 286 # DATE columns. This method guesses whether time is really a date, and 287 # returns a string representing the date if it is. This date string gets 288 # properly type-cast later (as a Time or Date object) based on the 289 # column type. 290 def guess_date_or_time(time) 291 if (time.hour + time.min + time.sec + time.usec).zero? 292 time.strftime("%Y-%m-%d") 293 else 294 time 295 end 296 end 297 298 def create_column(field_name, field_source, field_type, field_sub_type, 299 field_length, field_precision, field_scale, default_source) 300 column_type = metadata_to_column_type(field_type, field_sub_type) 301 default = parse_default(default_source, column_type, field_length, field_precision, field_scale) if default_source 302 sql_type = field_source =~ /BOOLEAN/ ? 'BOOLEAN' : column_type 303 limit = field_type == 'BLOB' ? BLOB_MAX_LENGTH : field_length 304 FirebirdColumn.new(field_name.downcase, default, sql_type, limit) 305 end 306 307 # Maps the internal type returned by Firebird metadata tables to a 308 # SQL type that can be passed to #firebird_cast_type and Column#new 309 def metadata_to_column_type(field_type, field_sub_type) 310 case field_type 311 when 'TEXT' then 'CHAR' 312 when 'VARYING' then 'VARCHAR' 313 when 'DOUBLE' then 'DOUBLE PRECISION' 314 when 'BLOB' then field_sub_type == 1 ? 'CLOB' : 'BLOB' 315 when 'SHORT', 'LONG', 'INT64' 316 case field_sub_type 317 when 1 then 'NUMERIC' 318 when 2 then 'DECIMAL' 319 else 'BIGINT' 320 end 321 else field_type 322 end 323 end 324 325 def parse_default(default_source, column_type, field_length, field_precision, field_scale) 326 default_source =~ /^DEFAULT\s+(.*)\s*$/i 327 default_value = $1 328 unless default_value.upcase == "NULL" 329 cast_type = firebird_cast_type(column_type, field_length, field_precision, field_scale) 330 { :value => default_value, :cast_type => cast_type } 331 end 332 end 333 334 # Returns a column definition that can be used in a Firebird CAST statement 335 def firebird_cast_type(sql_type, field_length, field_precision, field_scale) 336 case sql_type 337 when 'BLOB', 'CLOB' then "VARCHAR(255)" 338 when 'CHAR', 'VARCHAR' then "#{sql_type}(#{field_length})" 339 when 'NUMERIC', 'DECIMAL' then "#{sql_type}(#{field_precision},#{field_scale.abs})" 340 else sql_type 341 end 342 end 343 344 # Maps uppercase Firebird column names to lowercase for ActiveRecord; 345 # mixed-case columns retain their original case. 346 def fb_to_ar_case(column_name) 347 column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase 348 end 349 350 # Maps lowercase ActiveRecord column names to uppercase for Fierbird; 351 # mixed-case columns retain their original case. 352 def ar_to_fb_case(column_name) 353 column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase 354 end 355 end 356 end 357 end -
activerecord/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) -
activerecord/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(', ')}) " + -
activerecord/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'