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