activerecord-amalgalite-adapter 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|