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

root/tags/rel_1-0-0/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb

Revision 3296, 10.4 kB (checked in by bitsweat, 4 years ago)

r3458@asus: jeremy | 2005-12-13 09:33:50 -0800
Roll back [3245] on stable. References #3116.

Line 
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'
5
6 module ActiveRecord
7   class Base
8     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         ConnectionAdapters::SQLiteAdapter.new(db, logger)
23       end
24
25       # Establishes a connection to the database that's used by all Active Record objects
26       def sqlite_connection(config) # :nodoc:
27         parse_config!(config)
28
29         unless self.class.const_defined?(:SQLite)
30           require_library_or_gem(config[:adapter])
31
32           db = SQLite::Database.new(config[:database], 0)
33           db.show_datatypes   = "ON" if !defined? SQLite::Version
34           db.results_as_hash  = true if defined? SQLite::Version
35           db.type_translation = false
36
37           # "Downgrade" deprecated sqlite API
38           if SQLite.const_defined?(:Version)
39             ConnectionAdapters::SQLiteAdapter.new(db, logger)
40           else
41             ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
42           end
43         end
44       end
45
46       private
47         def parse_config!(config)
48           config[:database] ||= config[:dbfile]
49           # Require database.
50           unless config[:database]
51             raise ArgumentError, "No database file specified. Missing argument: database"
52           end
53
54           # Allow database path relative to RAILS_ROOT, but only if
55           # the database path is not the special path that tells
56           # Sqlite build a database only in memory.
57           if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
58             config[:database] = File.expand_path(config[:database], RAILS_ROOT)
59           end
60         end
61     end
62   end
63
64   module ConnectionAdapters #:nodoc:
65     class SQLiteColumn < Column #:nodoc:
66       class <<  self
67         def string_to_binary(value)
68           value.gsub(/\0|\%/) do |b|
69             case b
70               when "\0" then "%00"
71               when "%"  then "%25"
72             end
73           end               
74         end
75        
76         def binary_to_string(value)
77           value.gsub(/%00|%25/) do |b|
78             case b
79               when "%00" then "\0"
80               when "%25" then "%"
81             end
82           end               
83         end
84       end
85     end
86
87     # 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
88     # from http://rubyforge.org/projects/sqlite-ruby/).
89     #
90     # Options:
91     #
92     # * <tt>:database</tt> -- Path to the database file.
93     class SQLiteAdapter < AbstractAdapter
94       def adapter_name #:nodoc:
95         'SQLite'
96       end
97
98       def supports_migrations? #:nodoc:
99         true
100       end
101
102       def native_database_types #:nodoc:
103         {
104           :primary_key => "INTEGER PRIMARY KEY NOT NULL",
105           :string      => { :name => "varchar", :limit => 255 },
106           :text        => { :name => "text" },
107           :integer     => { :name => "integer" },
108           :float       => { :name => "float" },
109           :datetime    => { :name => "datetime" },
110           :timestamp   => { :name => "datetime" },
111           :time        => { :name => "datetime" },
112           :date        => { :name => "date" },
113           :binary      => { :name => "blob" },
114           :boolean     => { :name => "boolean" }
115         }
116       end
117
118
119       # QUOTING ==================================================
120
121       def quote_string(s) #:nodoc:
122         @connection.class.quote(s)
123       end
124
125       def quote_column_name(name) #:nodoc:
126         %Q("#{name}")
127       end
128
129
130       # DATABASE STATEMENTS ======================================
131
132       def execute(sql, name = nil) #:nodoc:
133         log(sql, name) { @connection.execute(sql) }
134       end
135
136       def update(sql, name = nil) #:nodoc:
137         execute(sql, name)
138         @connection.changes
139       end
140
141       def delete(sql, name = nil) #:nodoc:
142         sql += " WHERE 1=1" unless sql =~ /WHERE/i
143         execute(sql, name)
144         @connection.changes
145       end
146
147       def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
148         execute(sql, name = nil)
149         id_value || @connection.last_insert_row_id
150       end
151
152       def select_all(sql, name = nil) #:nodoc:
153         execute(sql, name).map do |row|
154           record = {}
155           row.each_key do |key|
156             if key.is_a?(String)
157               record[key.sub(/^\w+\./, '')] = row[key]
158             end
159           end
160           record
161         end
162       end
163
164       def select_one(sql, name = nil) #:nodoc:
165         result = select_all(sql, name)
166         result.nil? ? nil : result.first
167       end
168
169
170       def begin_db_transaction #:nodoc:
171         @connection.transaction
172       end
173      
174       def commit_db_transaction #:nodoc:
175         @connection.commit
176       end
177
178       def rollback_db_transaction #:nodoc:
179         @connection.rollback
180       end
181
182
183       # SCHEMA STATEMENTS ========================================
184
185       def tables(name = nil) #:nodoc:
186         execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
187           row[0]
188         end
189       end
190
191       def columns(table_name, name = nil) #:nodoc:
192         table_structure(table_name).map { |field|
193           SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
194         }
195       end
196
197       def indexes(table_name, name = nil) #:nodoc:
198         execute("PRAGMA index_list(#{table_name})", name).map do |row|
199           index = IndexDefinition.new(table_name, row['name'])
200           index.unique = row['unique'] != '0'
201           index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
202           index
203         end
204       end
205
206       def primary_key(table_name) #:nodoc:
207         column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
208         column ? column['name'] : nil
209       end
210
211       def remove_index(table_name, options={}) #:nodoc:
212         if Hash === options
213           index_name = options[:name]
214         else
215           index_name = "#{table_name}_#{options}_index"
216         end
217
218         execute "DROP INDEX #{index_name}"
219       end
220      
221       def rename_table(name, new_name)
222         move_table(name, new_name)
223       end
224
225       def add_column(table_name, column_name, type, options = {}) #:nodoc:
226         alter_table(table_name) do |definition|
227           definition.column(column_name, type, options)
228         end
229       end
230      
231       def remove_column(table_name, column_name) #:nodoc:
232         alter_table(table_name) do |definition|
233           definition.columns.delete(definition[column_name])
234         end
235       end
236      
237       def change_column_default(table_name, column_name, default) #:nodoc:
238         alter_table(table_name) do |definition|
239           definition[column_name].default = default
240         end
241       end
242
243       def change_column(table_name, column_name, type, options = {}) #:nodoc:
244         alter_table(table_name) do |definition|
245           definition[column_name].instance_eval do
246             self.type    = type
247             self.limit   = options[:limit] if options[:limit]
248             self.default = options[:default] if options[:default]
249           end
250         end
251       end
252
253       def rename_column(table_name, column_name, new_column_name) #:nodoc:
254         alter_table(table_name, :rename => {column_name => new_column_name})
255       end
256          
257
258       protected
259         def table_structure(table_name)
260           returning structure = execute("PRAGMA table_info(#{table_name})") do
261             raise ActiveRecord::StatementInvalid if structure.empty?
262           end
263         end
264        
265         def alter_table(table_name, options = {}) #:nodoc:
266           altered_table_name = "altered_#{table_name}"
267           caller = lambda {|definition| yield definition if block_given?}
268
269           transaction do
270             move_table(table_name, altered_table_name,
271               options.merge(:temporary => true))
272             move_table(altered_table_name, table_name, &caller)
273           end
274         end
275        
276         def move_table(from, to, options = {}, &block) #:nodoc:
277           copy_table(from, to, options, &block)
278           drop_table(from)
279         end
280        
281         def copy_table(from, to, options = {}) #:nodoc:
282           create_table(to, options) do |@definition|
283             columns(from).each do |column|
284               column_name = options[:rename] ?
285                 (options[:rename][column.name] ||
286                  options[:rename][column.name.to_sym] ||
287                  column.name) : column.name
288
289               @definition.column(column_name, column.type,
290                 :limit => column.limit, :default => column.default,
291                 :null => column.null)
292             end
293             @definition.primary_key(primary_key(from))
294             yield @definition if block_given?
295           end
296          
297           copy_table_indexes(from, to)
298           copy_table_contents(from, to,
299             @definition.columns.map {|column| column.name},
300             options[:rename] || {})
301         end
302        
303         def copy_table_indexes(from, to) #:nodoc:
304           indexes(from).each do |index|
305             name = index.name
306             if to == "altered_#{from}"
307               name = "temp_#{name}"
308             elsif from == "altered_#{to}"
309               name = name[5..-1]
310             end
311
312             opts = { :name => name }
313             opts[:unique] = true if index.unique
314             add_index(to, index.columns, opts)
315           end
316         end
317        
318         def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
319           column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
320           rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
321          
322           @connection.execute "SELECT * FROM #{from}" do |row|
323             sql = "INSERT INTO #{to} VALUES ("
324             sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
325             sql << ')'
326             @connection.execute sql
327           end
328         end
329     end
330
331     class DeprecatedSQLiteAdapter < SQLiteAdapter # :nodoc:
332       def insert(sql, name = nil, pk = nil, id_value = nil)
333         execute(sql, name = nil)
334         id_value || @connection.last_insert_rowid
335       end
336     end
337   end
338 end
Note: See TracBrowser for help on using the browser.