sqlite_adapter.rb 9.7 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
# sqlite_adapter.rb
2 3
# author: Luke Holden <lholden@cablelan.net>
# updated for SQLite3: Jamis Buck <jamis_buck@byu.edu>
D
Initial  
David Heinemeier Hansson 已提交
4 5 6 7 8

require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
    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(
          config[:dbfile],
          :results_as_hash => true,
          :type_translation => false
        )
        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])

          db = SQLite::Database.new(config[:dbfile], 0)
          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)
            ConnectionAdapters::SQLiteAdapter.new(db, logger)
          else
            ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
          end
        end
D
Initial  
David Heinemeier Hansson 已提交
45 46
      end

47 48 49 50 51 52
      private
        def parse_config!(config)
          # Require dbfile.
          unless config.has_key?(:dbfile)
            raise ArgumentError, "No database file specified. Missing argument: dbfile"
          end
D
Initial  
David Heinemeier Hansson 已提交
53

54 55 56 57 58
          # Allow database path relative to RAILS_ROOT.
          if Object.const_defined?(:RAILS_ROOT)
            config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT)
          end
        end
D
Initial  
David Heinemeier Hansson 已提交
59 60 61
    end
  end

62 63
  module ConnectionAdapters #:nodoc:
    class SQLiteColumn < Column #:nodoc:
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
      def string_to_binary(value)
        value.gsub(/(\0|\%)/) do
          case $1
            when "\0" then "%00"
            when "%" then "%25"
          end
        end                
      end
      
      def binary_to_string(value)
        value.gsub(/(%00|%25)/) do
          case $1
            when "%00" then "\0"
            when "%25" then "%"
          end
        end                
      end
    end
82

83 84 85 86 87 88 89
    # 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:
    #
    # * <tt>:dbfile</tt> -- Path to the database file.
    class SQLiteAdapter < AbstractAdapter
90 91 92
      def native_database_types
        {
          :primary_key => "INTEGER PRIMARY KEY NOT NULL",
93 94 95 96 97 98 99 100 101 102
          :string      => { :name => "varchar", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "integer" },
          :float       => { :name => "float" },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "datetime" },
          :time        => { :name => "datetime" },
          :date        => { :name => "date" },
          :binary      => { :name => "blob" },
          :boolean     => { :name => "integer" }
103 104 105
        }
      end

106 107 108 109
      def supports_migrations?
        true
      end

110
      def execute(sql, name = nil)
111
        #log(sql, name, @connection) { |connection| connection.execute(sql) }
112
        log(sql, name) { @connection.execute(sql) }
D
Initial  
David Heinemeier Hansson 已提交
113 114
      end

115 116 117
      def update(sql, name = nil)
        execute(sql, name)
        @connection.changes
D
Initial  
David Heinemeier Hansson 已提交
118 119
      end

120 121 122 123
      def delete(sql, name = nil)
        sql += " WHERE 1=1" unless sql =~ /WHERE/i
        execute(sql, name)
        @connection.changes
D
Initial  
David Heinemeier Hansson 已提交
124 125
      end

126
      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
D
Initial  
David Heinemeier Hansson 已提交
127
        execute(sql, name = nil)
128
        id_value || @connection.last_insert_row_id
D
Initial  
David Heinemeier Hansson 已提交
129 130
      end

131 132 133 134
      def select_all(sql, name = nil)
        execute(sql, name).map do |row|
          record = {}
          row.each_key do |key|
135 136 137
            if key.is_a?(String)
              record[key.sub(/^\w+\./, '')] = row[key]
            end
D
Initial  
David Heinemeier Hansson 已提交
138
          end
139
          record
D
Initial  
David Heinemeier Hansson 已提交
140 141 142
        end
      end

143 144 145
      def select_one(sql, name = nil)
        result = select_all(sql, name)
        result.nil? ? nil : result.first
146
      end
147 148 149 150 151 152


      def begin_db_transaction()    @connection.transaction end
      def commit_db_transaction()   @connection.commit      end
      def rollback_db_transaction() @connection.rollback    end

153 154 155 156
      def tables(name = nil)
        execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
          row[0]
        end
157
      end
D
Initial  
David Heinemeier Hansson 已提交
158

159 160
      def columns(table_name, name = nil)
        table_structure(table_name).map { |field|
161
          SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
162 163
        }
      end
D
Initial  
David Heinemeier Hansson 已提交
164

165 166 167 168
      def indexes(table_name, name = nil)
        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 已提交
169
          index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
170 171 172 173
          index
        end
      end

174 175 176 177 178
      def primary_key(table_name)
        column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
        column ? column['name'] : nil
      end

D
Initial  
David Heinemeier Hansson 已提交
179
      def quote_string(s)
180
        @connection.class.quote(s)
D
Initial  
David Heinemeier Hansson 已提交
181
      end
182

D
Initial  
David Heinemeier Hansson 已提交
183
      def quote_column_name(name)
184
        "'#{name}'"
D
Initial  
David Heinemeier Hansson 已提交
185 186
      end

187 188 189 190
      def adapter_name()
        'SQLite'
      end

J
Jamis Buck 已提交
191 192 193 194 195 196 197 198
      def remove_index(table_name, options={})
        if Hash === options
          index_name = options[:name]
        else
          index_name = "#{table_name}_#{options}_index"
        end

        execute "DROP INDEX #{index_name}"
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
      end

      def add_column(table_name, column_name, type, options = {})
        alter_table(table_name) do |definition|
          definition.column(column_name, type, options)
        end
      end
      
      def remove_column(table_name, column_name)
        alter_table(table_name) do |definition|
          definition.columns.delete(definition[column_name])
        end
      end
      
      def change_column_default(table_name, column_name, default)
        alter_table(table_name) do |definition|
          definition[column_name].default = default
        end
      end

      def change_column(table_name, column_name, type, options = {})
        alter_table(table_name) do |definition|
          definition[column_name].instance_eval do
            self.type    = type
            self.limit   = options[:limit] if options[:limit]
            self.default = options[:default] if options[:default]
          end
        end
      end

      def rename_column(table_name, column_name, new_column_name)
        alter_table(table_name, :rename => {column_name => new_column_name})
      end
          
233

234
      protected
D
Initial  
David Heinemeier Hansson 已提交
235
        def table_structure(table_name)
236 237 238 239 240 241 242 243 244 245 246
          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, 
247
              options.merge(:temporary => true))
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
            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|
              column_name = options[:rename][column.name] if 
                options[:rename][column.name] if options[:rename]
              
              @definition.column(column_name || column.name, column.type, 
264 265
                :limit => column.limit, :default => column.default,
                :null => column.null)
266 267 268 269 270 271 272 273 274 275 276 277 278
            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|
279 280 281 282 283 284 285 286 287 288
            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)
289 290 291 292 293 294 295 296 297 298 299 300 301
          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}
          
          @connection.execute "SELECT * FROM #{from}" do |row|
            sql = "INSERT INTO #{to} VALUES ("
            sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
            sql << ')'
            @connection.execute sql
          end
D
Initial  
David Heinemeier Hansson 已提交
302 303
        end
    end
304 305 306 307 308 309 310

    class DeprecatedSQLiteAdapter < SQLiteAdapter # :nodoc:
      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 已提交
311
  end
312
end