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

Changeset 6833

Show
Ignore:
Timestamp:
05/25/07 03:19:35 (1 year ago)
Author:
bitsweat
Message:

Load database adapters on demand. Eliminates config.connection_adapters and RAILS_CONNECTION_ADAPTERS. Closes #8456.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r6828 r6833  
    11*SVN* 
     2 
     3* Load database adapters on demand. Eliminates config.connection_adapters and RAILS_CONNECTION_ADAPTERS. Add your lib directory to the $LOAD_PATH and put your custom adapter in lib/active_record/connection_adapters/adaptername_adapter.rb. This way you can provide custom adapters as plugins or gems without modifying Rails. [Jeremy Kemper] 
    24 
    35* Ensure that associations with :dependent => :delete_all respect :conditions option.  Closes #8034 [danger, joshpeek, Rick] 
  • trunk/activerecord/lib/active_record.rb

    r6119 r6833  
    7474end 
    7575 
    76 unless defined?(RAILS_CONNECTION_ADAPTERS) 
    77   RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase ) 
    78 end 
    79  
    80 RAILS_CONNECTION_ADAPTERS.each do |adapter| 
    81   require "active_record/connection_adapters/" + adapter + "_adapter" 
    82 end 
     76require 'active_record/connection_adapters/abstract_adapter' 
    8377 
    8478require 'active_record/query_cache' 
  • trunk/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb

    r6051 r6833  
    208208          unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end 
    209209          adapter_method = "#{spec[:adapter]}_connection" 
    210           unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end 
     210 
     211          require "active_record/connection_adapters/#{spec[:adapter]}_adapter" 
     212          unless respond_to?(adapter_method) 
     213            raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" 
     214          end 
     215 
    211216          remove_connection 
    212217          establish_connection(ConnectionSpecification.new(spec, adapter_method)) 
  • trunk/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb

    r6469 r6833  
    1 # Author: Luke Holden <lholden@cablelan.net> 
    2 # Updated for SQLite3: Jamis Buck <jamis@37signals.com> 
    3  
    41require 'active_record/connection_adapters/abstract_adapter' 
    52 
     
    74  class Base 
    85    class << self 
    9       # sqlite3 adapter reuses sqlite_connection. 
    10       def sqlite3_connection(config) # :nodoc: 
    11         parse_config!(config) 
    12  
    13         unless self.class.const_defined?(:SQLite3) 
    14           require_library_or_gem(config[:adapter]) 
    15         end 
    16  
    17         db = SQLite3::Database.new( 
    18           config[:database], 
    19           :results_as_hash => true, 
    20           :type_translation => false 
    21         ) 
    22  
    23         db.busy_timeout(config[:timeout]) unless config[:timeout].nil? 
    24  
    25         ConnectionAdapters::SQLite3Adapter.new(db, logger) 
    26       end 
    27  
    286      # Establishes a connection to the database that's used by all Active Record objects 
    297      def sqlite_connection(config) # :nodoc: 
    30         parse_config!(config) 
     8        parse_sqlite_config!(config) 
    319 
    3210        unless self.class.const_defined?(:SQLite) 
     
    4826 
    4927      private 
    50         def parse_config!(config) 
     28        def parse_sqlite_config!(config) 
    5129          config[:database] ||= config[:dbfile] 
    5230          # Require database. 
     
    382360    end 
    383361 
    384     class SQLite3Adapter < SQLiteAdapter # :nodoc: 
    385       def table_structure(table_name) 
    386         returning structure = @connection.table_info(table_name) do 
    387           raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? 
    388         end 
    389       end 
    390     end 
    391  
    392362    class SQLite2Adapter < SQLiteAdapter # :nodoc: 
    393363      def supports_count_distinct? #:nodoc: 
  • trunk/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

    r6469 r6833  
    1 # Author: Luke Holden <lholden@cablelan.net> 
    2 # Updated for SQLite3: Jamis Buck <jamis@37signals.com> 
    3  
    4 require 'active_record/connection_adapters/abstract_adapter' 
     1require 'active_record/connection_adapters/sqlite_adapter' 
    52 
    63module ActiveRecord 
    74  class Base 
    8     class << self 
    9       # sqlite3 adapter reuses sqlite_connection. 
    10       def sqlite3_connection(config) # :nodoc: 
    11         parse_config!(config) 
     5    # sqlite3 adapter reuses sqlite_connection. 
     6    def self.sqlite3_connection(config) # :nodoc: 
     7      parse_sqlite_config!(config) 
    128 
    13         unless self.class.const_defined?(:SQLite3) 
    14           require_library_or_gem(config[:adapter]) 
    15         end 
    16  
    17         db = SQLite3::Database.new( 
    18           config[:database], 
    19           :results_as_hash => true, 
    20           :type_translation => false 
    21         ) 
    22  
    23         db.busy_timeout(config[:timeout]) unless config[:timeout].nil? 
    24  
    25         ConnectionAdapters::SQLite3Adapter.new(db, logger) 
     9      unless self.class.const_defined?(:SQLite3) 
     10        require_library_or_gem(config[:adapter]) 
    2611      end 
    2712 
    28       # Establishes a connection to the database that's used by all Active Record objects 
    29       def sqlite_connection(config) # :nodoc: 
    30         parse_config!(config) 
     13      db = SQLite3::Database.new( 
     14        config[:database], 
     15        :results_as_hash => true, 
     16        :type_translation => false 
     17      ) 
    3118 
    32         unless self.class.const_defined?(:SQLite) 
    33           require_library_or_gem(config[:adapter]) 
     19      db.busy_timeout(config[:timeout]) unless config[:timeout].nil? 
    3420 
    35           db = SQLite::Database.new(config[:database], 0) 
    36           db.show_datatypes   = "ON" if !defined? SQLite::Version 
    37           db.results_as_hash  = true if defined? SQLite::Version 
    38           db.type_translation = false 
    39  
    40           # "Downgrade" deprecated sqlite API 
    41           if SQLite.const_defined?(:Version) 
    42             ConnectionAdapters::SQLite2Adapter.new(db, logger) 
    43           else 
    44             ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger) 
    45           end 
    46         end 
    47       end 
    48  
    49       private 
    50         def parse_config!(config) 
    51           config[:database] ||= config[:dbfile] 
    52           # Require database. 
    53           unless config[:database] 
    54             raise ArgumentError, "No database file specified. Missing argument: database" 
    55           end 
    56  
    57           # Allow database path relative to RAILS_ROOT, but only if 
    58           # the database path is not the special path that tells 
    59           # Sqlite build a database only in memory. 
    60           if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database] 
    61             config[:database] = File.expand_path(config[:database], RAILS_ROOT) 
    62           end 
    63         end 
     21      ConnectionAdapters::SQLite3Adapter.new(db, logger) 
    6422    end 
    6523  end 
    6624 
    6725  module ConnectionAdapters #:nodoc: 
    68     class SQLiteColumn < Column #:nodoc: 
    69       class <<  self 
    70         def string_to_binary(value) 
    71           value.gsub(/\0|\%/n) do |b| 
    72             case b 
    73               when "\0" then "%00" 
    74               when "%"  then "%25" 
    75             end 
    76           end 
    77         end 
    78  
    79         def binary_to_string(value) 
    80           value.gsub(/%00|%25/n) do |b| 
    81             case b 
    82               when "%00" then "\0" 
    83               when "%25" then "%" 
    84             end 
    85           end 
    86         end 
    87       end 
    88     end 
    89  
    90     # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and 
    91     # from http://rubyforge.org/projects/sqlite-ruby/). 
    92     # 
    93     # Options: 
    94     # 
    95     # * <tt>:database</tt> -- Path to the database file. 
    96     class SQLiteAdapter < AbstractAdapter 
    97       def adapter_name #:nodoc: 
    98         'SQLite' 
    99       end 
    100  
    101       def supports_migrations? #:nodoc: 
    102         true 
    103       end 
    104  
    105       def requires_reloading? 
    106         true 
    107       end 
    108   
    109       def disconnect! 
    110         super 
    111         @connection.close rescue nil 
    112       end 
    113  
    114       def supports_count_distinct? #:nodoc: 
    115         sqlite_version >= '3.2.6' 
    116       end 
    117  
    118       def supports_autoincrement? #:nodoc: 
    119         sqlite_version >= '3.1.0' 
    120       end 
    121  
    122       def native_database_types #:nodoc: 
    123         { 
    124           :primary_key => default_primary_key_type, 
    125           :string      => { :name => "varchar", :limit => 255 }, 
    126           :text        => { :name => "text" }, 
    127           :integer     => { :name => "integer" }, 
    128           :float       => { :name => "float" }, 
    129           :decimal     => { :name => "decimal" }, 
    130           :datetime    => { :name => "datetime" }, 
    131           :timestamp   => { :name => "datetime" }, 
    132           :time        => { :name => "datetime" }, 
    133           :date        => { :name => "date" }, 
    134           :binary      => { :name => "blob" }, 
    135           :boolean     => { :name => "boolean" } 
    136         } 
    137       end 
    138  
    139  
    140       # QUOTING ================================================== 
    141  
    142       def quote_string(s) #:nodoc: 
    143         @connection.class.quote(s) 
    144       end 
    145  
    146       def quote_column_name(name) #:nodoc: 
    147         %Q("#{name}") 
    148       end 
    149  
    150  
    151       # DATABASE STATEMENTS ====================================== 
    152  
    153       def execute(sql, name = nil) #:nodoc: 
    154         catch_schema_changes { log(sql, name) { @connection.execute(sql) } } 
    155       end 
    156  
    157       def update(sql, name = nil) #:nodoc: 
    158         execute(sql, name) 
    159         @connection.changes 
    160       end 
    161  
    162       def delete(sql, name = nil) #:nodoc: 
    163         sql += " WHERE 1=1" unless sql =~ /WHERE/i 
    164         execute(sql, name) 
    165         @connection.changes 
    166       end 
    167  
    168       def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
    169         execute(sql, name = nil) 
    170         id_value || @connection.last_insert_row_id 
    171       end 
    172  
    173       def select_all(sql, name = nil) #:nodoc: 
    174         execute(sql, name).map do |row| 
    175           record = {} 
    176           row.each_key do |key| 
    177             if key.is_a?(String) 
    178               record[key.sub(/^\w+\./, '')] = row[key] 
    179             end 
    180           end 
    181           record 
    182         end 
    183       end 
    184  
    185       def select_one(sql, name = nil) #:nodoc: 
    186         result = select_all(sql, name) 
    187         result.nil? ? nil : result.first 
    188       end 
    189  
    190  
    191       def begin_db_transaction #:nodoc: 
    192         catch_schema_changes { @connection.transaction } 
    193       end 
    194  
    195       def commit_db_transaction #:nodoc: 
    196         catch_schema_changes { @connection.commit } 
    197       end 
    198  
    199       def rollback_db_transaction #:nodoc: 
    200         catch_schema_changes { @connection.rollback } 
    201       end 
    202  
    203  
    204       # SELECT ... FOR UPDATE is redundant since the table is locked. 
    205       def add_lock!(sql, options) #:nodoc: 
    206         sql 
    207       end 
    208  
    209  
    210       # SCHEMA STATEMENTS ======================================== 
    211  
    212       def tables(name = nil) #:nodoc: 
    213         sql = <<-SQL 
    214           SELECT name 
    215           FROM sqlite_master 
    216           WHERE type = 'table' AND NOT name = 'sqlite_sequence' 
    217         SQL 
    218  
    219         execute(sql, name).map do |row| 
    220           row[0] 
    221         end 
    222       end 
    223  
    224       def columns(table_name, name = nil) #:nodoc: 
    225         table_structure(table_name).map do |field| 
    226           SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0") 
    227         end 
    228       end 
    229  
    230       def indexes(table_name, name = nil) #:nodoc: 
    231         execute("PRAGMA index_list(#{table_name})", name).map do |row| 
    232           index = IndexDefinition.new(table_name, row['name']) 
    233           index.unique = row['unique'] != '0' 
    234           index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] } 
    235           index 
    236         end 
    237       end 
    238  
    239       def primary_key(table_name) #:nodoc: 
    240         column = table_structure(table_name).find {|field| field['pk'].to_i == 1} 
    241         column ? column['name'] : nil 
    242       end 
    243  
    244       def remove_index(table_name, options={}) #:nodoc: 
    245         execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}" 
    246       end 
    247  
    248       def rename_table(name, new_name) 
    249         execute "ALTER TABLE #{name} RENAME TO #{new_name}" 
    250       end 
    251  
    252       def add_column(table_name, column_name, type, options = {}) #:nodoc: 
    253         super(table_name, column_name, type, options) 
    254         # See last paragraph on http://www.sqlite.org/lang_altertable.html 
    255         execute "VACUUM" 
    256       end 
    257  
    258       def remove_column(table_name, column_name) #:nodoc: 
    259         alter_table(table_name) do |definition| 
    260           definition.columns.delete(definition[column_name]) 
    261         end 
    262       end 
    263  
    264       def change_column_default(table_name, column_name, default) #:nodoc: 
    265         alter_table(table_name) do |definition| 
    266           definition[column_name].default = default 
    267         end 
    268       end 
    269  
    270       def change_column(table_name, column_name, type, options = {}) #:nodoc: 
    271         alter_table(table_name) do |definition| 
    272           include_default = options_include_default?(options) 
    273           definition[column_name].instance_eval do 
    274             self.type    = type 
    275             self.limit   = options[:limit] if options.include?(:limit) 
    276             self.default = options[:default] if include_default 
    277           end 
    278         end 
    279       end 
    280  
    281       def rename_column(table_name, column_name, new_column_name) #:nodoc: 
    282         alter_table(table_name, :rename => {column_name => new_column_name}) 
    283       end 
    284  
    285  
    286       protected 
    287         def table_structure(table_name) 
    288           returning structure = execute("PRAGMA table_info(#{table_name})") do 
    289             raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? 
    290           end 
    291         end 
    292  
    293         def alter_table(table_name, options = {}) #:nodoc: 
    294           altered_table_name = "altered_#{table_name}" 
    295           caller = lambda {|definition| yield definition if block_given?} 
    296  
    297           transaction do 
    298             move_table(table_name, altered_table_name, 
    299               options.merge(:temporary => true)) 
    300             move_table(altered_table_name, table_name, &caller) 
    301           end 
    302         end 
    303  
    304         def move_table(from, to, options = {}, &block) #:nodoc: 
    305           copy_table(from, to, options, &block) 
    306           drop_table(from) 
    307         end 
    308  
    309         def copy_table(from, to, options = {}) #:nodoc: 
    310           create_table(to, options) do |@definition| 
    311             columns(from).each do |column| 
    312               column_name = options[:rename] ? 
    313                 (options[:rename][column.name] || 
    314                  options[:rename][column.name.to_sym] || 
    315                  column.name) : column.name 
    316  
    317               @definition.column(column_name, column.type, 
    318                 :limit => column.limit, :default => column.default, 
    319                 :null => column.null) 
    320             end 
    321             @definition.primary_key(primary_key(from)) 
    322             yield @definition if block_given? 
    323           end 
    324  
    325           copy_table_indexes(from, to) 
    326           copy_table_contents(from, to, 
    327             @definition.columns.map {|column| column.name}, 
    328             options[:rename] || {}) 
    329         end 
    330  
    331         def copy_table_indexes(from, to) #:nodoc: 
    332           indexes(from).each do |index| 
    333             name = index.name 
    334             if to == "altered_#{from}" 
    335               name = "temp_#{name}" 
    336             elsif from == "altered_#{to}" 
    337               name = name[5..-1] 
    338             end 
    339  
    340             # index name can't be the same 
    341             opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") } 
    342             opts[:unique] = true if index.unique 
    343             add_index(to, index.columns, opts) 
    344           end 
    345         end 
    346  
    347         def copy_table_contents(from, to, columns, rename = {}) #:nodoc: 
    348           column_mappings = Hash[*columns.map {|name| [name, name]}.flatten] 
    349           rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map} 
    350           from_columns = columns(from).collect {|col| col.name} 
    351           columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} 
    352           @connection.execute "SELECT * FROM #{from}" do |row| 
    353             sql = "INSERT INTO #{to} ("+columns*','+") VALUES (" 
    354             sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' 
    355             sql << ')' 
    356             @connection.execute sql 
    357           end 
    358         end 
    359  
    360         def catch_schema_changes 
    361           return yield 
    362         rescue ActiveRecord::StatementInvalid => exception 
    363           if exception.message =~ /database schema has changed/ 
    364             reconnect! 
    365             retry 
    366           else 
    367             raise 
    368           end 
    369         end 
    370  
    371         def sqlite_version 
    372           @sqlite_version ||= select_value('select sqlite_version(*)') 
    373         end 
    374  
    375         def default_primary_key_type 
    376           if supports_autoincrement? 
    377             'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze 
    378           else 
    379             'INTEGER PRIMARY KEY NOT NULL'.freeze 
    380           end 
    381         end 
    382     end 
    383  
    38426    class SQLite3Adapter < SQLiteAdapter # :nodoc: 
    38527      def table_structure(table_name) 
     
    38931      end 
    39032    end 
    391  
    392     class SQLite2Adapter < SQLiteAdapter # :nodoc: 
    393       def supports_count_distinct? #:nodoc: 
    394         false 
    395       end 
    396  
    397       def rename_table(name, new_name) 
    398         move_table(name, new_name) 
    399       end 
    400  
    401       def add_column(table_name, column_name, type, options = {}) #:nodoc: 
    402         alter_table(table_name) do |definition| 
    403           definition.column(column_name, type, options) 
    404         end 
    405       end 
    406     end 
    407  
    408     class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc: 
    409       def insert(sql, name = nil, pk = nil, id_value = nil) 
    410         execute(sql, name = nil) 
    411         id_value || @connection.last_insert_rowid 
    412       end 
    413     end 
    41433  end 
    41534end