activerecord-amalgalite-adapter 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY +4 -0
- data/LICENSE +13 -0
- data/README +51 -0
- data/gemspec.rb +52 -0
- data/lib/active_record/connection_adapters/amalgalite_adapter.rb +442 -0
- data/lib/activerecord-amalgalite-adapter.rb +5 -0
- data/tasks/announce.rake +39 -0
- data/tasks/config.rb +108 -0
- data/tasks/distribution.rake +38 -0
- data/tasks/documentation.rake +32 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/testunit.rake +19 -0
- data/tasks/utils.rb +78 -0
- data/test/activerecord_amalgalite_adapter_test.rb +19 -0
- data/test/config.rb +20 -0
- data/test/connection.rb +29 -0
- data/test/schema/amalgalite_specific_schema.rb +22 -0
- data/test/schema/schema.rb +473 -0
- data/test/schema/schema2.rb +6 -0
- data/test/test_helper.rb +3 -0
- metadata +109 -0
data/HISTORY
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2009, Jeremy Hinegadner
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
9
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
11
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
12
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
13
|
+
PERFORMANCE OF THIS SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
== ActiveRecord Amalgalite Adapter
|
2
|
+
|
3
|
+
* Homepage[http://copiousfreetime.rubyforge.org/activerecord-amalgalite-adapter]
|
4
|
+
* {Rubyforge Project}[http://rubyforge.org/projects/copiousfreetime/]
|
5
|
+
* email jeremy at copiousfreetime dot org
|
6
|
+
* git clone url git://github.com/copiousfreetime/activerecord-amalgalite-adapter
|
7
|
+
|
8
|
+
== DESCRIPTION
|
9
|
+
|
10
|
+
This is that ActiveRecord adapter for the Amalgalite Database gem. The
|
11
|
+
Amalgalite gem embeds the SQLite database inside a ruby extensions.
|
12
|
+
|
13
|
+
== INSTALL
|
14
|
+
|
15
|
+
* gem install activerecord-amalgalite-adapter
|
16
|
+
|
17
|
+
== SYNOPSIS
|
18
|
+
|
19
|
+
You will need to have the 'activerecord-amalgalite-adapter' gem installed.
|
20
|
+
|
21
|
+
After that you can use the the 'amalgalite' adapter in your config/database.yml
|
22
|
+
|
23
|
+
For example config/database.yml:
|
24
|
+
|
25
|
+
development:
|
26
|
+
adapter: amalgalite
|
27
|
+
database: db/production.db
|
28
|
+
|
29
|
+
test:
|
30
|
+
adapter: amalgalite
|
31
|
+
database: db/test.db
|
32
|
+
|
33
|
+
development:
|
34
|
+
adapter: amalgalite
|
35
|
+
database: db/development.db
|
36
|
+
|
37
|
+
== LICENSE
|
38
|
+
|
39
|
+
Copyright (c) 2009, Jeremy Hinegardner
|
40
|
+
|
41
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
42
|
+
purpose with or without fee is hereby granted, provided that the above
|
43
|
+
copyright notice and this permission notice appear in all copies.
|
44
|
+
|
45
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
46
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
47
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
48
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
49
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
50
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
51
|
+
PERFORMANCE OF THIS SOFTWARE.
|
data/gemspec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord-amalgalite-adapter'
|
3
|
+
require 'tasks/config'
|
4
|
+
|
5
|
+
class ActiveRecord::ConnectionAdapters::AmalgaliteAdapter
|
6
|
+
|
7
|
+
GEM_SPEC = Gem::Specification.new do |spec|
|
8
|
+
proj = Configuration.for('project')
|
9
|
+
spec.name = proj.name
|
10
|
+
spec.version = VERSION
|
11
|
+
|
12
|
+
spec.author = proj.author
|
13
|
+
spec.email = proj.email
|
14
|
+
spec.homepage = proj.homepage
|
15
|
+
spec.summary = proj.summary
|
16
|
+
spec.description = proj.description
|
17
|
+
spec.platform = ::Gem::Platform::RUBY
|
18
|
+
|
19
|
+
|
20
|
+
pkg = ::Configuration.for('packaging')
|
21
|
+
spec.files = pkg.files.all
|
22
|
+
spec.executables = pkg.files.bin.collect { |b| File.basename(b) }
|
23
|
+
|
24
|
+
# add dependencies here
|
25
|
+
spec.add_dependency("configuration", ">= 0.0.5")
|
26
|
+
spec.add_dependency("amalgalite", "~> 0.8.0")
|
27
|
+
spec.add_dependency("activerecord", "= 2.3.2")
|
28
|
+
|
29
|
+
if ext_conf = ::Configuration.for_if_exist?("extension") then
|
30
|
+
spec.extensions << ext_conf.configs
|
31
|
+
spec.extensions.flatten!
|
32
|
+
spec.require_paths << "ext"
|
33
|
+
end
|
34
|
+
|
35
|
+
if rdoc = ::Configuration.for_if_exist?('rdoc') then
|
36
|
+
spec.has_rdoc = true
|
37
|
+
spec.extra_rdoc_files = pkg.files.rdoc
|
38
|
+
spec.rdoc_options = rdoc.options + [ "--main" , rdoc.main_page ]
|
39
|
+
else
|
40
|
+
spec.has_rdoc = false
|
41
|
+
end
|
42
|
+
|
43
|
+
if test = ::Configuration.for_if_exist?('testing') then
|
44
|
+
spec.test_files = test.files
|
45
|
+
end
|
46
|
+
|
47
|
+
if rf = ::Configuration.for_if_exist?('rubyforge') then
|
48
|
+
spec.rubyforge_project = rf.project
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,442 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Jeremy Hinegadner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details
|
4
|
+
#
|
5
|
+
# much of this behavior ported from the standard active record sqlite
|
6
|
+
# and sqlite3 # adapters
|
7
|
+
#++
|
8
|
+
require 'active_record'
|
9
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
10
|
+
require 'amalgalite'
|
11
|
+
require 'stringio'
|
12
|
+
|
13
|
+
module ActiveRecord
|
14
|
+
class Base
|
15
|
+
class << self
|
16
|
+
def amalgalite_connection( config ) # :nodoc:
|
17
|
+
config[:database] ||= config[:dbfile]
|
18
|
+
raise ArgumentError, "No database file specified. Missing argument: database" unless config[:database]
|
19
|
+
|
20
|
+
# Allow database path relative to RAILS_ROOT, but only if the database
|
21
|
+
# is not the in memory database
|
22
|
+
if Object.const_defined?( :RAILS_ROOT ) and (":memory:" != config[:database] ) then
|
23
|
+
config[:database] = File.expand_path( config[:database], RAILS_ROOT )
|
24
|
+
end
|
25
|
+
|
26
|
+
db = ::Amalgalite::Database.new( config[:database] )
|
27
|
+
ConnectionAdapters::AmalgaliteAdapter.new( db, logger )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
module ConnectionAdapters
|
34
|
+
class AmalgaliteColumn < Column
|
35
|
+
def self.from_amalgalite( am_col )
|
36
|
+
new( am_col.name,
|
37
|
+
am_col.default_value,
|
38
|
+
am_col.declared_data_type,
|
39
|
+
am_col.nullable? )
|
40
|
+
end
|
41
|
+
|
42
|
+
# unfortunately, not able to use the Blob interface as that requires
|
43
|
+
# knowing what column the blob is going to be stored in. Use the approach
|
44
|
+
# in the sqlite3 driver.
|
45
|
+
def self.string_to_binary( value )
|
46
|
+
value.gsub(/\0|\%/n) do |b|
|
47
|
+
case b
|
48
|
+
when "\0" then "%00"
|
49
|
+
when "%" then "%25"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# since the type is blog, the amalgalite drive extracts it as a blob and we need to
|
55
|
+
# convert back into a string and do the substitution
|
56
|
+
def self.binary_to_string(value)
|
57
|
+
value.to_s.gsub(/%00|%25/n) do |b|
|
58
|
+
case b
|
59
|
+
when "%00" then "\0"
|
60
|
+
when "%25" then "%"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# AR asks to convert a datetime column to a time and then passes in a
|
66
|
+
# string... WTF ?
|
67
|
+
def self.datetime_to_time( dt )
|
68
|
+
case dt
|
69
|
+
when String
|
70
|
+
return nil if dt.empty?
|
71
|
+
when DateTime
|
72
|
+
return dt.to_time
|
73
|
+
when Time
|
74
|
+
return dt.to_time
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# active record assumes that type casting is from a string to a value, and
|
79
|
+
# it might not be. It might be something appropriate for the field in
|
80
|
+
# question, like say a DateTime for a :datetime field?.
|
81
|
+
def type_cast_code( var_name )
|
82
|
+
case type
|
83
|
+
when :datetime then "#{self.class.name}.datetime_to_time(#{var_name})"
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
class AmalgaliteAdapter < AbstractAdapter
|
92
|
+
class Version
|
93
|
+
MAJOR = 0
|
94
|
+
MINOR = 8
|
95
|
+
BUILD = 0
|
96
|
+
|
97
|
+
def self.to_a() [MAJOR, MINOR, BUILD]; end
|
98
|
+
def self.to_s() to_a.join("."); end
|
99
|
+
def to_a() Version.to_a; end
|
100
|
+
def to_s() Version.to_s; end
|
101
|
+
|
102
|
+
STRING = Version.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
VERSION = Version.to_s
|
106
|
+
|
107
|
+
def adapter_name
|
108
|
+
"Amalgalite"
|
109
|
+
end
|
110
|
+
|
111
|
+
def supports_ddl_transactions?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def supports_migrations?
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def requires_reloading?
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
def supports_add_column?
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
def supports_count_distinct?
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
131
|
+
def supports_autoincrement?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def native_database_types #:nodoc:
|
137
|
+
{
|
138
|
+
:primary_key => default_primary_key_type,
|
139
|
+
:string => { :name => "varchar", :limit => 255 },
|
140
|
+
:text => { :name => "text" },
|
141
|
+
:integer => { :name => "integer" },
|
142
|
+
:float => { :name => "float" },
|
143
|
+
:decimal => { :name => "decimal" },
|
144
|
+
:datetime => { :name => "datetime" },
|
145
|
+
:timestamp => { :name => "datetime" },
|
146
|
+
:time => { :name => "time" },
|
147
|
+
:date => { :name => "date" },
|
148
|
+
:binary => { :name => "blob" },
|
149
|
+
:boolean => { :name => "boolean" }
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
# QUOTING ==================================================
|
154
|
+
|
155
|
+
# this is really escaping
|
156
|
+
def quote_string( s ) #:nodoc:
|
157
|
+
@connection.escape( s )
|
158
|
+
end
|
159
|
+
|
160
|
+
def quote_column_name( name ) #:nodoc:
|
161
|
+
return "\"#{name}\""
|
162
|
+
end
|
163
|
+
|
164
|
+
# DATABASE STATEMENTS ======================================
|
165
|
+
def execute( sql, name = nil )
|
166
|
+
log( sql, name) { @connection.execute( sql ) }
|
167
|
+
end
|
168
|
+
|
169
|
+
def update_sql( sql, name = nil )
|
170
|
+
super
|
171
|
+
@connection.row_changes
|
172
|
+
end
|
173
|
+
|
174
|
+
def delete_sql(sql, name = nil )
|
175
|
+
sql += "WHERE 1=1" unless sql =~ /WHERE/i
|
176
|
+
super sql, name
|
177
|
+
end
|
178
|
+
|
179
|
+
def insert_sql( sql, name = nil, pk = nil, id_value = nil, sequence_name = nil )
|
180
|
+
super || @connection.last_insert_rowid
|
181
|
+
end
|
182
|
+
|
183
|
+
def select_rows( sql, name = nil )
|
184
|
+
execute( sql, name )
|
185
|
+
end
|
186
|
+
|
187
|
+
def select( sql, name = nil )
|
188
|
+
execute( sql, name ).map do |row|
|
189
|
+
row.to_hash
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def begin_db_transaction() @connection.transaction; end
|
195
|
+
def commit_db_transaction() @connection.commit; end
|
196
|
+
def rollback_db_transaction() @connection.rollback; end
|
197
|
+
|
198
|
+
# there is no select for update in sqlite
|
199
|
+
def add_lock!( sql, options )
|
200
|
+
sql
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# SCHEMA STATEMENTS ========================================
|
205
|
+
|
206
|
+
def default_primary_key_type
|
207
|
+
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
|
208
|
+
end
|
209
|
+
|
210
|
+
def tables( name = nil )
|
211
|
+
sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND NOT name = 'sqlite_sequence'"
|
212
|
+
raw_list = execute( sql, nil ).map { |row| row['name'] }
|
213
|
+
if raw_list.sort != @connection.schema.tables.keys.sort then
|
214
|
+
@connection.schema.load_schema!
|
215
|
+
if raw_list.sort != @connection.schema.tables.keys.sort then
|
216
|
+
raise "raw_list - tables : #{raw_list - @connection.schema.tables.keys} :: tables - raw_list #{@connection.schema.tables.keys - raw_list}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
ActiveRecord::Base.logger.info "schema_migrations in tables? : #{raw_list.include?( "schema_migrations" )}"
|
220
|
+
ActiveRecord::Base.logger.info "schema_migrations(2) in tables? : #{@connection.schema.tables.keys.include?( "schema_migrations" )}"
|
221
|
+
@connection.schema.tables.keys
|
222
|
+
end
|
223
|
+
|
224
|
+
def columns( table_name, name = nil )
|
225
|
+
t = @connection.schema.tables[table_name.to_s]
|
226
|
+
raise "Invalid table #{table_name}" unless t
|
227
|
+
t.columns_in_order.map do |c|
|
228
|
+
AmalgaliteColumn.from_amalgalite( c )
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def indexes( table_name, name = nil )
|
233
|
+
table = @connection.schema.tables[table_name.to_s]
|
234
|
+
indexes = []
|
235
|
+
if table then
|
236
|
+
indexes = table.indexes.map do |key, idx|
|
237
|
+
index = IndexDefinition.new( table_name, idx.name )
|
238
|
+
index.unique = idx.unique?
|
239
|
+
index.columns = idx.columns.map { |col| col.name }
|
240
|
+
index
|
241
|
+
end
|
242
|
+
end
|
243
|
+
return indexes
|
244
|
+
end
|
245
|
+
|
246
|
+
def primary_key( table_name )
|
247
|
+
pk_list = @connection.schema.tables[table_name.to_s].primary_key
|
248
|
+
if pk_list.empty? then
|
249
|
+
return nil
|
250
|
+
else
|
251
|
+
return pk_list.first.name
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def remove_index( table_name, options = {} )
|
256
|
+
execute "DROP INDEX #{quote_column_name(index_name( table_name.to_s, options ) ) }"
|
257
|
+
@connection.schema.dirty!
|
258
|
+
end
|
259
|
+
|
260
|
+
def rename_table( name, new_name )
|
261
|
+
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
262
|
+
@connection.schema.dirty!
|
263
|
+
end
|
264
|
+
|
265
|
+
# See: http://www.sqlite.org/lang_altertable.html
|
266
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
267
|
+
def valid_alter_table_options( type, options )
|
268
|
+
type.to_sym != :primary_key
|
269
|
+
end
|
270
|
+
|
271
|
+
##
|
272
|
+
# Wrap the create table so we can mark the schema as dirty
|
273
|
+
#
|
274
|
+
def create_table( table_name, options = {}, &block )
|
275
|
+
super( table_name, options, &block )
|
276
|
+
@connection.schema.load_table( table_name.to_s )
|
277
|
+
end
|
278
|
+
|
279
|
+
def change_table( table_name, &block )
|
280
|
+
super( table_name, &block )
|
281
|
+
@connection.schema.load_table( table_name.to_s )
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
def drop_table( table_name, options = {} )
|
286
|
+
super( table_name, options )
|
287
|
+
@connection.schema.tables.delete( table_name.to_s )
|
288
|
+
puts "dropped table #{table_name} : #{@connection.schema.tables.include?( table_name )}" if table_name == "delete_me"
|
289
|
+
end
|
290
|
+
|
291
|
+
def add_column(table_name, column_name, type, options = {})
|
292
|
+
rc = nil
|
293
|
+
if valid_alter_table_options( type, options ) then
|
294
|
+
rc = super( table_name, column_name, type, options )
|
295
|
+
else
|
296
|
+
table_name = table_name.to_s
|
297
|
+
rc = alter_table( table_name ) do |definition|
|
298
|
+
definition.column( column_name, type, options )
|
299
|
+
end
|
300
|
+
end
|
301
|
+
@connection.schema.load_table( table_name.to_s )
|
302
|
+
return rc
|
303
|
+
end
|
304
|
+
|
305
|
+
def add_index( table_name, column_name, options = {} )
|
306
|
+
super
|
307
|
+
@connection.schema.load_table( table_name.to_s )
|
308
|
+
end
|
309
|
+
|
310
|
+
def remove_column( table_name, *column_names )
|
311
|
+
column_names.flatten.each do |column_name|
|
312
|
+
alter_table( table_name ) do |definition|
|
313
|
+
definition.columns.delete( definition[column_name] )
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
alias :remove_columns :remove_column
|
318
|
+
|
319
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
320
|
+
alter_table(table_name) do |definition|
|
321
|
+
definition[column_name].default = default
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
326
|
+
unless null || default.nil?
|
327
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
328
|
+
end
|
329
|
+
alter_table(table_name) do |definition|
|
330
|
+
definition[column_name].null = null
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
335
|
+
alter_table(table_name) do |definition|
|
336
|
+
include_default = options_include_default?(options)
|
337
|
+
definition[column_name].instance_eval do
|
338
|
+
self.type = type
|
339
|
+
self.limit = options[:limit] if options.include?(:limit)
|
340
|
+
self.default = options[:default] if include_default
|
341
|
+
self.null = options[:null] if options.include?(:null)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
347
|
+
unless columns(table_name).detect{|c| c.name == column_name.to_s }
|
348
|
+
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
|
349
|
+
end
|
350
|
+
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
|
351
|
+
end
|
352
|
+
|
353
|
+
def empty_insert_statement(table_name)
|
354
|
+
"INSERT INTO #{table_name} VALUES(NULL)"
|
355
|
+
end
|
356
|
+
|
357
|
+
#################
|
358
|
+
protected
|
359
|
+
#################
|
360
|
+
|
361
|
+
def alter_table(table_name, options = {}) #:nodoc:
|
362
|
+
altered_table_name = "altered_#{table_name}"
|
363
|
+
caller = lambda {|definition| yield definition if block_given?}
|
364
|
+
|
365
|
+
transaction do
|
366
|
+
move_table(table_name, altered_table_name,
|
367
|
+
options.merge(:temporary => true))
|
368
|
+
move_table(altered_table_name, table_name, &caller)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def move_table(from, to, options = {}, &block) #:nodoc:
|
373
|
+
copy_table(from, to, options, &block)
|
374
|
+
drop_table(from)
|
375
|
+
end
|
376
|
+
|
377
|
+
def copy_table(from, to, options = {}) #:nodoc:
|
378
|
+
options = options.merge( :id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
|
379
|
+
options = options.merge( :primary_key => primary_key(from).to_s )
|
380
|
+
create_table(to, options) do |definition|
|
381
|
+
@definition = definition
|
382
|
+
columns(from).each do |column|
|
383
|
+
column_name = options[:rename] ?
|
384
|
+
(options[:rename][column.name] ||
|
385
|
+
options[:rename][column.name.to_sym] ||
|
386
|
+
column.name) : column.name
|
387
|
+
|
388
|
+
@definition.column(column_name, column.type,
|
389
|
+
:limit => column.limit, :default => column.default,
|
390
|
+
:null => column.null)
|
391
|
+
end
|
392
|
+
@definition.primary_key(primary_key(from)) if primary_key(from)
|
393
|
+
yield @definition if block_given?
|
394
|
+
end
|
395
|
+
|
396
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
397
|
+
copy_table_contents(from, to,
|
398
|
+
@definition.columns.map {|column| column.name},
|
399
|
+
options[:rename] || {})
|
400
|
+
end
|
401
|
+
|
402
|
+
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
403
|
+
indexes(from).each do |index|
|
404
|
+
name = index.name
|
405
|
+
if to == "altered_#{from}"
|
406
|
+
name = "temp_#{name}"
|
407
|
+
elsif from == "altered_#{to}"
|
408
|
+
name = name[5..-1]
|
409
|
+
end
|
410
|
+
|
411
|
+
to_column_names = columns(to).map(&:name)
|
412
|
+
columns = index.columns.map {|c| rename[c] || c }.select do |column|
|
413
|
+
to_column_names.include?(column)
|
414
|
+
end
|
415
|
+
|
416
|
+
unless columns.empty?
|
417
|
+
# index name can't be the same
|
418
|
+
opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
|
419
|
+
opts[:unique] = true if index.unique
|
420
|
+
add_index(to, columns, opts)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
426
|
+
column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
|
427
|
+
rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
|
428
|
+
from_columns = columns(from).collect {|col| col.name}
|
429
|
+
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
430
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
431
|
+
|
432
|
+
quoted_to = quote_table_name(to)
|
433
|
+
@connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
|
434
|
+
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
435
|
+
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
436
|
+
sql << ')'
|
437
|
+
@connection.execute sql
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|