Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 627 lines (525 sloc) 19.639 kb
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
1 require 'active_record/connection_adapters/abstract_adapter'
2 require 'active_record/connection_adapters/statement_pool'
3 require 'arel/visitors/bind_visitor'
d00e164 @tenderlove add the gem requirement for sqlite3
tenderlove authored
4
fc1bf36 @aderyabin Upgrade sqlite3 version to 1.3.6
aderyabin authored
5 gem 'sqlite3', '~> 1.3.6'
b3c7766 @tenderlove just require sqlite3 when the database adapter is required
tenderlove authored
6 require 'sqlite3'
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
7
8 module ActiveRecord
79af9e9 @frodsan nodoc AR::ConnectionHandling for adapters [ci skip]
frodsan authored
9 module ConnectionHandling # :nodoc:
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
10 # sqlite3 adapter reuses sqlite_connection.
79af9e9 @frodsan nodoc AR::ConnectionHandling for adapters [ci skip]
frodsan authored
11 def sqlite3_connection(config)
7b0f853 @tenderlove moving parse_sqlite_config to the sqlite3_connection method (where it…
tenderlove authored
12 # Require database.
13 unless config[:database]
14 raise ArgumentError, "No database file specified. Missing argument: database"
15 end
16
17 # Allow database path relative to Rails.root, but only if
18 # the database path is not the special path that tells
19 # Sqlite to build a database only in memory.
f036239 @schneems Create sqlite3 directory if not present
schneems authored
20 if ':memory:' != config[:database]
b6c971e @rafaelfranca config[:database] should be a string
rafaelfranca authored
21 config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
22 dirname = File.dirname(config[:database])
23 Dir.mkdir(dirname) unless File.directory?(dirname)
7b0f853 @tenderlove moving parse_sqlite_config to the sqlite3_connection method (where it…
tenderlove authored
24 end
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
25
26 db = SQLite3::Database.new(
f036239 @schneems Create sqlite3 directory if not present
schneems authored
27 config[:database].to_s,
9ac9c35 @tenderlove removing useless code. [#5070 state:resolved]
tenderlove authored
28 :results_as_hash => true
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
29 )
30
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
31 db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
32
7a26a67 @aughey Ensure SQLite adapters stores the config [#1947 state:resolved] [John…
aughey authored
33 ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
34 end
35 end
36
37 module ConnectionAdapters #:nodoc:
dd05a49 @aderyabin renamed class SQLiteColumn to SQLite3Column
aderyabin authored
38 class SQLite3Column < Column #:nodoc:
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
39 class << self
40 def binary_to_string(value)
41 if value.encoding != Encoding::ASCII_8BIT
42 value = value.force_encoding(Encoding::ASCII_8BIT)
43 end
44 value
45 end
46 end
47 end
48
1a3d4f7 @aderyabin fix SQLite3Adapter doc
aderyabin authored
49 # The SQLite3 adapter works SQLite 3.6.16 or newer
50 # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
51 #
52 # Options:
53 #
54 # * <tt>:database</tt> - Path to the database file.
55 class SQLite3Adapter < AbstractAdapter
56 class Version
57 include Comparable
58
59 def initialize(version_string)
60 @version = version_string.split('.').map { |v| v.to_i }
61 end
62
63 def <=>(version_string)
64 @version <=> version_string.split('.').map { |v| v.to_i }
65 end
66 end
67
68 class StatementPool < ConnectionAdapters::StatementPool
69 def initialize(connection, max)
88636f7 @tenderlove escaping binary data encoding when inserting to sqlite3. Thanks Narus…
tenderlove authored
70 super
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
71 @cache = Hash.new { |h,pid| h[pid] = {} }
72 end
73
74 def each(&block); cache.each(&block); end
75 def key?(key); cache.key?(key); end
76 def [](key); cache[key]; end
77 def length; cache.length; end
78
79 def []=(sql, key)
80 while @max <= cache.size
81 dealloc(cache.shift.last[:stmt])
82 end
83 cache[sql] = key
84 end
85
86 def clear
87 cache.values.each do |hash|
88 dealloc hash[:stmt]
89 end
90 cache.clear
91 end
92
93 private
94 def cache
95 @cache[$$]
96 end
97
98 def dealloc(stmt)
99 stmt.close unless stmt.closed?
88636f7 @tenderlove escaping binary data encoding when inserting to sqlite3. Thanks Narus…
tenderlove authored
100 end
101 end
100d228 @tenderlove adding adapter tests, avoiding private apis, fixing code in 1.9 [#498…
tenderlove authored
102
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
103 class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
104 include Arel::Visitors::BindVisitor
105 end
106
107 def initialize(connection, logger, config)
108 super(connection, logger)
02f5655 @jonleighton Ensure disconnecting or reconnecting resets the transaction state
jonleighton authored
109
110 @active = nil
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
111 @statements = StatementPool.new(@connection,
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
112 self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
113 @config = config
114
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
115 if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
116 @visitor = Arel::Visitors::SQLite.new self
117 else
9f54921 @cfabianski Unprepared Visitor + unprepared_statement
cfabianski authored
118 @visitor = unprepared_visitor
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
119 end
120 end
121
122 def adapter_name #:nodoc:
123 'SQLite'
124 end
125
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
126 # Returns true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
127 def supports_ddl_transactions?
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
128 true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
129 end
130
131 # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
132 def supports_savepoints?
133 sqlite_version >= '3.6.8'
134 end
135
136 # Returns true, since this connection adapter supports prepared statement
137 # caching.
138 def supports_statement_cache?
139 true
140 end
141
142 # Returns true, since this connection adapter supports migrations.
143 def supports_migrations? #:nodoc:
144 true
145 end
146
147 # Returns true.
148 def supports_primary_key? #:nodoc:
149 true
150 end
151
152 def requires_reloading?
153 true
154 end
155
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
156 # Returns true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
157 def supports_add_column?
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
158 true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
159 end
160
02f5655 @jonleighton Ensure disconnecting or reconnecting resets the transaction state
jonleighton authored
161 def active?
162 @active != false
163 end
164
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
165 # Disconnects from the database if already connected. Otherwise, this
166 # method does nothing.
167 def disconnect!
168 super
02f5655 @jonleighton Ensure disconnecting or reconnecting resets the transaction state
jonleighton authored
169 @active = false
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
170 @connection.close rescue nil
171 end
172
173 # Clears the prepared statements cache.
174 def clear_cache!
175 @statements.clear
176 end
177
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
178 # Returns true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
179 def supports_count_distinct? #:nodoc:
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
180 true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
181 end
182
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
183 # Returns true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
184 def supports_autoincrement? #:nodoc:
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
185 true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
186 end
187
188 def supports_index_sort_order?
3cc9b5f @aderyabin removed tail of old sqlite versions
aderyabin authored
189 true
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
190 end
191
72ca2d7 @senny reserve less chars for internal sqlite3 operations
senny authored
192 # Returns 62. SQLite supports index names up to 64
cca4352 @senny reserve index name chars for internal rails operations
senny authored
193 # characters. The rest is used by rails internally to perform
194 # temporary rename operations
195 def allowed_index_name_length
72ca2d7 @senny reserve less chars for internal sqlite3 operations
senny authored
196 index_name_length - 2
cca4352 @senny reserve index name chars for internal rails operations
senny authored
197 end
198
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
199 def native_database_types #:nodoc:
200 {
201 :primary_key => default_primary_key_type,
202 :string => { :name => "varchar", :limit => 255 },
203 :text => { :name => "text" },
204 :integer => { :name => "integer" },
205 :float => { :name => "float" },
206 :decimal => { :name => "decimal" },
207 :datetime => { :name => "datetime" },
208 :timestamp => { :name => "datetime" },
ceb68d1 @drogus Revert "Merge pull request #6344"
drogus authored
209 :time => { :name => "time" },
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
210 :date => { :name => "date" },
211 :binary => { :name => "blob" },
212 :boolean => { :name => "boolean" }
213 }
214 end
215
9d65390 @mikel Added encoding qery support for SQLite3 to make rake db:charset work …
mikel authored
216 # Returns the current database encoding format as a string, eg: 'UTF-8'
217 def encoding
8523784 @tenderlove the required sqlite3 adapter responds to encoding, so stop checking.
tenderlove authored
218 @connection.encoding.to_s
9d65390 @mikel Added encoding qery support for SQLite3 to make rake db:charset work …
mikel authored
219 end
220
9fd6403 @aderyabin EXPLAIN only for sqlite3
aderyabin authored
221 # Returns true.
222 def supports_explain?
223 true
224 end
225
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
226 # QUOTING ==================================================
227
228 def quote(value, column = nil)
5ef02de @vipulnsward Remove redundant `string_to_binary` from type-casting
vipulnsward authored
229 if value.kind_of?(String) && column && column.type == :binary
230 s = value.unpack("H*")[0]
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
231 "x'#{s}'"
232 else
233 super
234 end
235 end
236
237 def quote_string(s) #:nodoc:
238 @connection.class.quote(s)
239 end
240
0a71c7b @derekkraan Fix cases where delete_records on a has_many association caused errors
derekkraan authored
241 def quote_table_name_for_assignment(table, attr)
242 quote_column_name(attr)
243 end
244
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
245 def quote_column_name(name) #:nodoc:
246 %Q("#{name.to_s.gsub('"', '""')}")
247 end
248
249 # Quote date/time values for use in SQL input. Includes microseconds
250 # if the value is a Time responding to usec.
251 def quoted_date(value) #:nodoc:
252 if value.respond_to?(:usec)
253 "#{super}.#{sprintf("%06d", value.usec)}"
254 else
255 super
256 end
257 end
258
259 def type_cast(value, column) # :nodoc:
260 return value.to_f if BigDecimal === value
261 return super unless String === value
262 return super unless column && value
263
264 value = super
265 if column.type == :string && value.encoding == Encoding::ASCII_8BIT
266 logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
b5133d0 SQLite3Adapter#type_cast should not mutate arguments
Stefan Rusterholz authored
267 value = value.encode Encoding::UTF_8
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
268 end
269 value
270 end
271
9fd6403 @aderyabin EXPLAIN only for sqlite3
aderyabin authored
272 # DATABASE STATEMENTS ======================================
273
274 def explain(arel, binds = [])
275 sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
276 ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
277 end
278
279 class ExplainPrettyPrinter
280 # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
281 # the output of the SQLite shell:
282 #
283 # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
284 # 0|1|1|SCAN TABLE posts (~100000 rows)
285 #
286 def pp(result) # :nodoc:
287 result.rows.map do |row|
288 row.join('|')
289 end.join("\n") + "\n"
290 end
291 end
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
292
293 def exec_query(sql, name = nil, binds = [])
294 log(sql, name, binds) do
295
296 # Don't cache statements without bind values
297 if binds.empty?
298 stmt = @connection.prepare(sql)
299 cols = stmt.columns
300 records = stmt.to_a
301 stmt.close
302 stmt = records
303 else
304 cache = @statements[sql] ||= {
305 :stmt => @connection.prepare(sql)
306 }
307 stmt = cache[:stmt]
308 cols = cache[:cols] ||= stmt.columns
309 stmt.reset!
310 stmt.bind_params binds.map { |col, val|
311 type_cast(val, col)
312 }
313 end
314
315 ActiveRecord::Result.new(cols, stmt.to_a)
316 end
317 end
318
319 def exec_delete(sql, name = 'SQL', binds = [])
320 exec_query(sql, name, binds)
321 @connection.changes
322 end
323 alias :exec_update :exec_delete
324
325 def last_inserted_id(result)
326 @connection.last_insert_row_id
327 end
328
329 def execute(sql, name = nil) #:nodoc:
330 log(sql, name) { @connection.execute(sql) }
331 end
332
333 def update_sql(sql, name = nil) #:nodoc:
334 super
335 @connection.changes
336 end
337
338 def delete_sql(sql, name = nil) #:nodoc:
339 sql += " WHERE 1=1" unless sql =~ /WHERE/i
340 super sql, name
341 end
342
343 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
344 super
345 id_value || @connection.last_insert_row_id
346 end
347 alias :create :insert_sql
348
349 def select_rows(sql, name = nil)
350 exec_query(sql, name).rows
351 end
352
353 def create_savepoint
354 execute("SAVEPOINT #{current_savepoint_name}")
355 end
356
357 def rollback_to_savepoint
358 execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
359 end
360
361 def release_savepoint
362 execute("RELEASE SAVEPOINT #{current_savepoint_name}")
363 end
364
365 def begin_db_transaction #:nodoc:
366 log('begin transaction',nil) { @connection.transaction }
367 end
368
369 def commit_db_transaction #:nodoc:
370 log('commit transaction',nil) { @connection.commit }
371 end
372
373 def rollback_db_transaction #:nodoc:
374 log('rollback transaction',nil) { @connection.rollback }
375 end
376
377 # SCHEMA STATEMENTS ========================================
378
576d700 @kennyj Fix logs name consistency.
kennyj authored
379 def tables(name = nil, table_name = nil) #:nodoc:
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
380 sql = <<-SQL
381 SELECT name
382 FROM sqlite_master
383 WHERE type = 'table' AND NOT name = 'sqlite_sequence'
384 SQL
385 sql << " AND name = #{quote_table_name(table_name)}" if table_name
386
576d700 @kennyj Fix logs name consistency.
kennyj authored
387 exec_query(sql, 'SCHEMA').map do |row|
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
388 row['name']
389 end
390 end
391
576d700 @kennyj Fix logs name consistency.
kennyj authored
392 def table_exists?(table_name)
393 table_name && tables(nil, table_name).any?
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
394 end
395
dd05a49 @aderyabin renamed class SQLiteColumn to SQLite3Column
aderyabin authored
396 # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
397 def columns(table_name) #:nodoc:
398 table_structure(table_name).map do |field|
399 case field["dflt_value"]
400 when /^null$/i
401 field["dflt_value"] = nil
3e8ab91 @tenderlove column default extraction should handle newlines.
tenderlove authored
402 when /^'(.*)'$/m
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
403 field["dflt_value"] = $1.gsub("''", "'")
3e8ab91 @tenderlove column default extraction should handle newlines.
tenderlove authored
404 when /^"(.*)"$/m
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
405 field["dflt_value"] = $1.gsub('""', '"')
406 end
407
dd05a49 @aderyabin renamed class SQLiteColumn to SQLite3Column
aderyabin authored
408 SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
409 end
410 end
411
412 # Returns an array of indexes for the given table.
413 def indexes(table_name, name = nil) #:nodoc:
576d700 @kennyj Fix logs name consistency.
kennyj authored
414 exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
415 IndexDefinition.new(
416 table_name,
417 row['name'],
418 row['unique'] != 0,
8933efb @kennyj Query for loading index info should be marked as SCHEMA.
kennyj authored
419 exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
420 col['name']
421 })
422 end
423 end
424
425 def primary_key(table_name) #:nodoc:
426 column = table_structure(table_name).find { |field|
427 field['pk'] == 1
428 }
429 column && column['name']
430 end
431
432 def remove_index!(table_name, index_name) #:nodoc:
433 exec_query "DROP INDEX #{quote_column_name(index_name)}"
434 end
435
436 # Renames a table.
437 #
438 # Example:
439 # rename_table('octopuses', 'octopi')
39eef1a @senny also rename indexes when a table or column is renamed
senny authored
440 def rename_table(table_name, new_name)
441 exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
442 rename_table_indexes(table_name, new_name)
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
443 end
444
445 # See: http://www.sqlite.org/lang_altertable.html
446 # SQLite has an additional restriction on the ALTER TABLE statement
447 def valid_alter_table_options( type, options)
448 type.to_sym != :primary_key
449 end
450
451 def add_column(table_name, column_name, type, options = {}) #:nodoc:
452 if supports_add_column? && valid_alter_table_options( type, options )
453 super(table_name, column_name, type, options)
454 else
455 alter_table(table_name) do |definition|
456 definition.column(column_name, type, options)
457 end
458 end
459 end
460
e28ddea @marcandre Differentiate between remove_column and remove_columns. Make remove_c…
marcandre authored
461 def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
462 alter_table(table_name) do |definition|
c5e03e8 @tenderlove keep ivars private, do not manipulate them outside their owner object
tenderlove authored
463 definition.remove_column column_name
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
464 end
465 end
466
467 def change_column_default(table_name, column_name, default) #:nodoc:
468 alter_table(table_name) do |definition|
469 definition[column_name].default = default
470 end
471 end
472
473 def change_column_null(table_name, column_name, null, default = nil)
474 unless null || default.nil?
475 exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
476 end
477 alter_table(table_name) do |definition|
478 definition[column_name].null = null
479 end
480 end
481
482 def change_column(table_name, column_name, type, options = {}) #:nodoc:
483 alter_table(table_name) do |definition|
484 include_default = options_include_default?(options)
485 definition[column_name].instance_eval do
486 self.type = type
487 self.limit = options[:limit] if options.include?(:limit)
488 self.default = options[:default] if include_default
489 self.null = options[:null] if options.include?(:null)
490 self.precision = options[:precision] if options.include?(:precision)
491 self.scale = options[:scale] if options.include?(:scale)
492 end
493 end
494 end
495
496 def rename_column(table_name, column_name, new_column_name) #:nodoc:
497 unless columns(table_name).detect{|c| c.name == column_name.to_s }
498 raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
499 end
500 alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
39eef1a @senny also rename indexes when a table or column is renamed
senny authored
501 rename_column_indexes(table_name, column_name, new_column_name)
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
502 end
503
504 protected
505 def select(sql, name = nil, binds = []) #:nodoc:
506 exec_query(sql, name, binds)
507 end
508
509 def table_structure(table_name)
510 structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
511 raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
512 structure
513 end
514
515 def alter_table(table_name, options = {}) #:nodoc:
72ca2d7 @senny reserve less chars for internal sqlite3 operations
senny authored
516 altered_table_name = "a#{table_name}"
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
517 caller = lambda {|definition| yield definition if block_given?}
518
519 transaction do
520 move_table(table_name, altered_table_name,
521 options.merge(:temporary => true))
522 move_table(altered_table_name, table_name, &caller)
523 end
524 end
525
526 def move_table(from, to, options = {}, &block) #:nodoc:
527 copy_table(from, to, options, &block)
528 drop_table(from)
529 end
530
531 def copy_table(from, to, options = {}) #:nodoc:
3188f81 @scally Set the primary key during #copy_table if necessary. Fixes [#2312]
scally authored
532 from_primary_key = primary_key(from)
b104157 @senny refactor `SQLite3Adapter#copy_table` to prevent primary key redefinit…
senny authored
533 options[:id] = false
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
534 create_table(to, options) do |definition|
535 @definition = definition
b104157 @senny refactor `SQLite3Adapter#copy_table` to prevent primary key redefinit…
senny authored
536 @definition.primary_key(from_primary_key) if from_primary_key.present?
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
537 columns(from).each do |column|
538 column_name = options[:rename] ?
539 (options[:rename][column.name] ||
540 options[:rename][column.name.to_sym] ||
541 column.name) : column.name
b104157 @senny refactor `SQLite3Adapter#copy_table` to prevent primary key redefinit…
senny authored
542 next if column_name == from_primary_key
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
543
544 @definition.column(column_name, column.type,
545 :limit => column.limit, :default => column.default,
546 :precision => column.precision, :scale => column.scale,
547 :null => column.null)
548 end
549 yield @definition if block_given?
550 end
551 copy_table_indexes(from, to, options[:rename] || {})
552 copy_table_contents(from, to,
553 @definition.columns.map {|column| column.name},
554 options[:rename] || {})
555 end
556
557 def copy_table_indexes(from, to, rename = {}) #:nodoc:
558 indexes(from).each do |index|
559 name = index.name
72ca2d7 @senny reserve less chars for internal sqlite3 operations
senny authored
560 if to == "a#{from}"
561 name = "t#{name}"
562 elsif from == "a#{to}"
563 name = name[1..-1]
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
564 end
565
566 to_column_names = columns(to).map { |c| c.name }
567 columns = index.columns.map {|c| rename[c] || c }.select do |column|
568 to_column_names.include?(column)
569 end
570
571 unless columns.empty?
572 # index name can't be the same
cca4352 @senny reserve index name chars for internal rails operations
senny authored
573 opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
574 opts[:unique] = true if index.unique
575 add_index(to, columns, opts)
576 end
577 end
578 end
579
580 def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
581 column_mappings = Hash[columns.map {|name| [name, name]}]
582 rename.each { |a| column_mappings[a.last] = a.first }
583 from_columns = columns(from).collect {|col| col.name}
584 columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
585 quoted_columns = columns.map { |col| quote_column_name(col) } * ','
586
587 quoted_to = quote_table_name(to)
d3e5118 @mmb Pass column to quote when copying a sqlite table.
mmb authored
588
589 raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
590
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
591 exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
592 sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
d3e5118 @mmb Pass column to quote when copying a sqlite table.
mmb authored
593
594 column_values = columns.map do |col|
595 quote(row[column_mappings[col]], raw_column_mappings[col])
596 end
597
598 sql << column_values * ', '
7572efc @aderyabin merged sqlite and sqlite3 adapters
aderyabin authored
599 sql << ')'
600 exec_query sql
601 end
602 end
603
604 def sqlite_version
605 @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
606 end
607
608 def default_primary_key_type
609 if supports_autoincrement?
610 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
611 else
612 'INTEGER PRIMARY KEY NOT NULL'
613 end
614 end
615
616 def translate_exception(exception, message)
617 case exception.message
618 when /column(s)? .* (is|are) not unique/
619 RecordNotUnique.new(message, exception)
620 else
621 super
622 end
623 end
cb7a17a @jeremy Load database adapters on demand. Eliminates config.connection_adapte…
jeremy authored
624 end
625 end
626 end
Something went wrong with that request. Please try again.