sqlite_adapter.rb 12.0 KB
Newer Older
1
# Author: Luke Holden <lholden@cablelan.net>
2
# Updated for SQLite3: Jamis Buck <jamis@37signals.com>
D
Initial  
David Heinemeier Hansson 已提交
3 4 5 6 7

require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
8 9 10 11 12 13 14 15 16 17
    class << self
      # sqlite3 adapter reuses sqlite_connection.
      def sqlite3_connection(config) # :nodoc:
        parse_config!(config)

        unless self.class.const_defined?(:SQLite3)
          require_library_or_gem(config[:adapter])
        end

        db = SQLite3::Database.new(
18
          config[:database],
19 20 21
          :results_as_hash => true,
          :type_translation => false
        )
22 23 24

        db.busy_timeout(config[:timeout]) unless config[:timeout].nil?

25 26 27 28 29 30 31 32 33 34
        ConnectionAdapters::SQLiteAdapter.new(db, logger)
      end

      # Establishes a connection to the database that's used by all Active Record objects
      def sqlite_connection(config) # :nodoc:
        parse_config!(config)

        unless self.class.const_defined?(:SQLite)
          require_library_or_gem(config[:adapter])

35
          db = SQLite::Database.new(config[:database], 0)
36 37 38 39 40 41
          db.show_datatypes   = "ON" if !defined? SQLite::Version
          db.results_as_hash  = true if defined? SQLite::Version
          db.type_translation = false

          # "Downgrade" deprecated sqlite API
          if SQLite.const_defined?(:Version)
42
            ConnectionAdapters::SQLite2Adapter.new(db, logger)
43 44 45 46
          else
            ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
          end
        end
D
Initial  
David Heinemeier Hansson 已提交
47 48
      end

49 50
      private
        def parse_config!(config)
51 52
          config[:database] ||= config[:dbfile]
          # Require database.
53
          unless config[:database]
54
            raise ArgumentError, "No database file specified. Missing argument: database"
55
          end
D
Initial  
David Heinemeier Hansson 已提交
56

57 58 59
          # Allow database path relative to RAILS_ROOT, but only if
          # the database path is not the special path that tells
          # 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 63
          end
        end
D
Initial  
David Heinemeier Hansson 已提交
64 65 66
    end
  end

67 68
  module ConnectionAdapters #:nodoc:
    class SQLiteColumn < Column #:nodoc:
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
      class <<  self
        def string_to_binary(value)
          value.gsub(/\0|\%/) do |b|
            case b
              when "\0" then "%00"
              when "%"  then "%25"
            end
          end                
        end
        
        def binary_to_string(value)
          value.gsub(/%00|%25/) do |b|
            case b
              when "%00" then "\0"
              when "%25" then "%"
            end
          end                
        end
87 88
      end
    end
89

90 91 92 93 94
    # 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
    # from http://rubyforge.org/projects/sqlite-ruby/).
    #
    # Options:
    #
95
    # * <tt>:database</tt> -- Path to the database file.
96
    class SQLiteAdapter < AbstractAdapter
97 98 99 100 101 102 103
      def adapter_name #:nodoc:
        'SQLite'
      end

      def supports_migrations? #:nodoc:
        true
      end
104 105 106 107
      
      def supports_count_distinct? #:nodoc:
        false
      end
108 109

      def native_database_types #:nodoc:
110 111
        {
          :primary_key => "INTEGER PRIMARY KEY NOT NULL",
112 113 114 115
          :string      => { :name => "varchar", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "integer" },
          :float       => { :name => "float" },
116
          :decimal     => { :name => "decimal" },
117 118 119 120 121
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "datetime" },
          :time        => { :name => "datetime" },
          :date        => { :name => "date" },
          :binary      => { :name => "blob" },
122
          :boolean     => { :name => "boolean" }
123 124 125
        }
      end

126 127 128 129 130 131 132 133

      # QUOTING ==================================================

      def quote_string(s) #:nodoc:
        @connection.class.quote(s)
      end

      def quote_column_name(name) #:nodoc:
134
        %Q("#{name}")
135 136
      end

137 138 139 140

      # DATABASE STATEMENTS ======================================

      def execute(sql, name = nil) #:nodoc:
141
        catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
D
Initial  
David Heinemeier Hansson 已提交
142 143
      end

144
      def update(sql, name = nil) #:nodoc:
145 146
        execute(sql, name)
        @connection.changes
D
Initial  
David Heinemeier Hansson 已提交
147 148
      end

149
      def delete(sql, name = nil) #:nodoc:
150 151 152
        sql += " WHERE 1=1" unless sql =~ /WHERE/i
        execute(sql, name)
        @connection.changes
D
Initial  
David Heinemeier Hansson 已提交
153 154
      end

155
      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
156
        execute(sql, name = nil)
157
        id_value || @connection.last_insert_row_id
D
Initial  
David Heinemeier Hansson 已提交
158 159
      end

160
      def select_all(sql, name = nil) #:nodoc:
161 162 163
        execute(sql, name).map do |row|
          record = {}
          row.each_key do |key|
164 165 166
            if key.is_a?(String)
              record[key.sub(/^\w+\./, '')] = row[key]
            end
D
Initial  
David Heinemeier Hansson 已提交
167
          end
168
          record
D
Initial  
David Heinemeier Hansson 已提交
169 170 171
        end
      end

172
      def select_one(sql, name = nil) #:nodoc:
173 174
        result = select_all(sql, name)
        result.nil? ? nil : result.first
175
      end
176 177


178
      def begin_db_transaction #:nodoc:
179
        catch_schema_changes { @connection.transaction }
180 181 182
      end
      
      def commit_db_transaction #:nodoc:
183
        catch_schema_changes { @connection.commit }
184 185 186
      end

      def rollback_db_transaction #:nodoc:
187
        catch_schema_changes { @connection.rollback }
188
      end
189

190

191 192 193 194 195 196
      # SELECT ... FOR UPDATE is redundant since the table is locked.
      def add_lock!(sql, options) #:nodoc:
        sql
      end


197 198 199
      # SCHEMA STATEMENTS ========================================

      def tables(name = nil) #:nodoc:
200 201 202
        execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
          row[0]
        end
203
      end
D
Initial  
David Heinemeier Hansson 已提交
204

205
      def columns(table_name, name = nil) #:nodoc:
D
David Heinemeier Hansson 已提交
206
        table_structure(table_name).map do |field|
207
          SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
D
David Heinemeier Hansson 已提交
208
        end
209
      end
D
Initial  
David Heinemeier Hansson 已提交
210

211
      def indexes(table_name, name = nil) #:nodoc:
212 213 214
        execute("PRAGMA index_list(#{table_name})", name).map do |row|
          index = IndexDefinition.new(table_name, row['name'])
          index.unique = row['unique'] != '0'
J
Jamis Buck 已提交
215
          index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
216 217 218 219
          index
        end
      end

220
      def primary_key(table_name) #:nodoc:
221 222 223 224
        column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
        column ? column['name'] : nil
      end

225
      def remove_index(table_name, options={}) #:nodoc:
226
        execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
227
      end
228 229 230 231
      
      def rename_table(name, new_name)
        move_table(name, new_name)
      end
232

233
      def add_column(table_name, column_name, type, options = {}) #:nodoc:
234 235 236 237 238
        alter_table(table_name) do |definition|
          definition.column(column_name, type, options)
        end
      end
      
239
      def remove_column(table_name, column_name) #:nodoc:
240 241 242 243 244
        alter_table(table_name) do |definition|
          definition.columns.delete(definition[column_name])
        end
      end
      
245
      def change_column_default(table_name, column_name, default) #:nodoc:
246 247 248 249 250
        alter_table(table_name) do |definition|
          definition[column_name].default = default
        end
      end

251
      def change_column(table_name, column_name, type, options = {}) #:nodoc:
252 253 254 255
        alter_table(table_name) do |definition|
          definition[column_name].instance_eval do
            self.type    = type
            self.limit   = options[:limit] if options[:limit]
256
            self.default = options[:default] unless options[:default].nil?
257 258 259 260
          end
        end
      end

261
      def rename_column(table_name, column_name, new_column_name) #:nodoc:
262 263 264
        alter_table(table_name, :rename => {column_name => new_column_name})
      end
          
265

266
      protected
D
Initial  
David Heinemeier Hansson 已提交
267
        def table_structure(table_name)
268 269 270 271 272 273 274 275 276 277 278
          returning structure = execute("PRAGMA table_info(#{table_name})") do
            raise ActiveRecord::StatementInvalid if structure.empty?
          end
        end
        
        def alter_table(table_name, options = {}) #:nodoc:
          altered_table_name = "altered_#{table_name}"
          caller = lambda {|definition| yield definition if block_given?}

          transaction do
            move_table(table_name, altered_table_name, 
279
              options.merge(:temporary => true))
280 281 282 283 284 285 286 287 288 289 290 291
            move_table(altered_table_name, table_name, &caller)
          end
        end
        
        def move_table(from, to, options = {}, &block) #:nodoc:
          copy_table(from, to, options, &block)
          drop_table(from)
        end
        
        def copy_table(from, to, options = {}) #:nodoc:
          create_table(to, options) do |@definition|
            columns(from).each do |column|
292 293 294 295 296 297
              column_name = options[:rename] ?
                (options[:rename][column.name] ||
                 options[:rename][column.name.to_sym] ||
                 column.name) : column.name

              @definition.column(column_name, column.type, 
298 299
                :limit => column.limit, :default => column.default,
                :null => column.null)
300 301 302 303 304 305 306 307 308 309 310 311 312
            end
            @definition.primary_key(primary_key(from))
            yield @definition if block_given?
          end
          
          copy_table_indexes(from, to)
          copy_table_contents(from, to, 
            @definition.columns.map {|column| column.name}, 
            options[:rename] || {})
        end
        
        def copy_table_indexes(from, to) #:nodoc:
          indexes(from).each do |index|
313 314 315 316 317 318 319 320 321 322
            name = index.name
            if to == "altered_#{from}"
              name = "temp_#{name}"
            elsif from == "altered_#{to}"
              name = name[5..-1]
            end

            opts = { :name => name }
            opts[:unique] = true if index.unique
            add_index(to, index.columns, opts)
323 324 325 326 327 328
          end
        end
        
        def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
          column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
          rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
329 330
          from_columns = columns(from).collect {|col| col.name}
          columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
331
          @connection.execute "SELECT * FROM #{from}" do |row|
332
            sql = "INSERT INTO #{to} ("+columns*','+") VALUES ("            
333 334 335 336
            sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
            sql << ')'
            @connection.execute sql
          end
D
Initial  
David Heinemeier Hansson 已提交
337
        end
338 339 340 341 342 343 344 345 346 347 348
        
        def catch_schema_changes
          return yield
        rescue ActiveRecord::StatementInvalid => exception
          if exception.message =~ /database schema has changed/
            reconnect!
            retry
          else
            raise
          end
        end
D
Initial  
David Heinemeier Hansson 已提交
349
    end
350 351 352 353 354 355 356 357 358 359
    
    class SQLite2Adapter < SQLiteAdapter # :nodoc:
      # SQLite 2 does not support COUNT(DISTINCT) queries:
      #
      #   select COUNT(DISTINCT ArtistID) from CDs;    
      #
      # In order to get  the number of artists we execute the following statement
      # 
      #   SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
      def execute(sql, name = nil) #:nodoc:
360 361 362 363
        super(rewrite_count_distinct_queries(sql), name)
      end
      
      def rewrite_count_distinct_queries(sql)
364 365 366 367
        if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
          distinct_column = $1
          distinct_query  = $3
          column_name     = distinct_column.split('.').last
368 369 370
          "SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
        else
          sql
371 372 373
        end
      end
    end
374

375
    class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
376 377 378 379 380
      def insert(sql, name = nil, pk = nil, id_value = nil)
        execute(sql, name = nil)
        id_value || @connection.last_insert_rowid
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
381
  end
382
end