fast_change_table 0.0.3 → 0.0.4
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/.gitignore +1 -0
- data/README +25 -1
- data/lib/fast_change_table/version.rb +1 -1
- data/lib/fast_change_table.rb +177 -15
- metadata +5 -6
- data/.rvmrc +0 -1
data/.gitignore
CHANGED
data/README
CHANGED
@@ -2,4 +2,28 @@ Use fast_change_table instead of change_table in your migrations on large tables
|
|
2
2
|
|
3
3
|
# Known issues
|
4
4
|
- Probably only works with MySQL
|
5
|
-
- Not tested
|
5
|
+
- Not tested
|
6
|
+
|
7
|
+
uses ordinary change_table syntax but adds two options
|
8
|
+
- "replace_keys" to remove all indexes; new indexes will be specified
|
9
|
+
- "disable_keys" to remove indexes and apply them after data load; this is a tremendous performance enhancement for a dbms with fast index creation
|
10
|
+
|
11
|
+
other methods:
|
12
|
+
|
13
|
+
create_table_like(orignal_table, table_to_copy_to)
|
14
|
+
creates a table with the same structure
|
15
|
+
|
16
|
+
disable_indexes(table)
|
17
|
+
removes all indexes from a table, returns a list of index objects removed
|
18
|
+
|
19
|
+
enable_indexes(table, list_of_indexes)
|
20
|
+
restores a list of indexes to a table
|
21
|
+
|
22
|
+
fast_add_indexes(table, &block)
|
23
|
+
allows you to pass a block to add indexes. For mysql creates specified indexes in one statement; allows the data to be scanned once.
|
24
|
+
example
|
25
|
+
|
26
|
+
fast_add_indexes :sometable do |t|
|
27
|
+
t.index :some_column
|
28
|
+
t.index [:some_other_column, :column_three], :name => "a_multicolumn_index"
|
29
|
+
end
|
data/lib/fast_change_table.rb
CHANGED
@@ -1,5 +1,44 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters #:nodoc:
|
3
|
+
|
4
|
+
class MysqlAdapter < AbstractAdapter
|
5
|
+
def tables_without_views(name = nil) #:nodoc:
|
6
|
+
tables = []
|
7
|
+
result = execute("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'", name)
|
8
|
+
result.each { |field| tables << field[0] }
|
9
|
+
result.free
|
10
|
+
tables
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module SchemaStatements
|
15
|
+
def change_table_with_remaps(table_name)
|
16
|
+
t = Table.new(table_name, self)
|
17
|
+
yield t
|
18
|
+
return t.renamed_columns
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Table
|
23
|
+
|
24
|
+
def initialize(table_name, base)
|
25
|
+
@table_name = table_name
|
26
|
+
@base = base
|
27
|
+
@renamed_columns = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def renamed_columns
|
31
|
+
@renamed_columns
|
32
|
+
end
|
33
|
+
|
34
|
+
def rename(column_name, new_column_name)
|
35
|
+
@renamed_columns << [column_name, new_column_name]
|
36
|
+
@base.rename_column(@table_name, column_name, new_column_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
3
42
|
|
4
43
|
module FastChangeTable
|
5
44
|
def self.included(base)
|
@@ -7,22 +46,145 @@ module FastChangeTable
|
|
7
46
|
end
|
8
47
|
|
9
48
|
module ClassMethods
|
10
|
-
def fast_change_table(table_name, &block)
|
49
|
+
def fast_change_table(table_name, options = {}, &block)
|
50
|
+
options.symbolize_keys!
|
11
51
|
old_table_name = "old_#{table_name}"
|
12
52
|
rename_table(table_name, old_table_name)
|
13
53
|
begin
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
54
|
+
create_table_like(old_table_name, table_name, options)
|
55
|
+
renamed_columns = change_table_with_remaps(table_name, &block)
|
56
|
+
index_list = options[:disable_keys] ? disable_indexes(table_name) : []
|
57
|
+
#prepare the columns names for the insert statements
|
58
|
+
copy_table(old_table_name, table_name, renamed_columns)
|
59
|
+
enable_indexes(table_name, index_list) if options[:disable_keys]
|
60
|
+
drop_table(old_table_name)
|
61
|
+
rescue Exception => e
|
62
|
+
puts "#{e}\n#{e.backtrace}"
|
63
|
+
drop_table(table_name) if table_exists?(table_name)
|
64
|
+
rename_table(old_table_name, table_name)
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def fast_add_indexes(table, &blk)
|
70
|
+
phoney = PhoneyTable.new(table.to_s)
|
71
|
+
yield phoney
|
72
|
+
enable_indexes(table, phoney.indexes)
|
73
|
+
end
|
74
|
+
|
75
|
+
#create_table_like( :sometable, :newtable, :remove_keys => true)
|
76
|
+
def create_table_like(like_table, table, options = {})
|
77
|
+
options.symbolize_keys!
|
78
|
+
code = table_schema_code(like_table)
|
79
|
+
code.gsub!(/create_table\s+"#{like_table}"/, "create_table :#{table}")
|
80
|
+
if options[:replace_keys] or options[:remove_keys]
|
81
|
+
code.gsub!(/add_index\s+"#{like_table}"/, "#add_index :#{table}")
|
82
|
+
else
|
83
|
+
code.gsub!(/add_index\s+"#{like_table}"/, "add_index :#{table}")
|
84
|
+
end
|
85
|
+
class_eval(code)
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
#copy_table( :sometable, :newtable, [[:old_column, :new_column]])
|
90
|
+
def copy_table(from, to, remaps = [])
|
91
|
+
old = connection.columns(from).collect(&:name)
|
92
|
+
current = connection.columns(to).collect(&:name)
|
93
|
+
remapped_columns = remaps.collect {|c| c.first.to_s}.compact
|
94
|
+
common = (current & old).sort - remapped_columns
|
95
|
+
from_columns = common.collect {|c| "`#{c}`"}
|
96
|
+
to_columns = common.collect {|c| "`#{c}`"}
|
97
|
+
remaps.each do |remap|
|
98
|
+
remap = [remap].flatten
|
99
|
+
next if remap.length != 2
|
100
|
+
from_columns << remap.first
|
101
|
+
to_columns << remap.last
|
102
|
+
end
|
103
|
+
from_columns_to_s = from_columns.join(', ')
|
104
|
+
to_columns_to_s = to_columns.join(', ')
|
105
|
+
execute "INSERT INTO #{to}(#{to_columns_to_s}) SELECT #{from_columns_to_s} FROM #{from}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_table(table_name, options = {})
|
109
|
+
rtn = super
|
110
|
+
execute "alter table `#{table_name}` row_format=dynamic" if connection.adapter_name =~ /^mysql/i
|
111
|
+
rtn
|
112
|
+
end
|
113
|
+
|
114
|
+
def table_schema_code(table)
|
115
|
+
dumper = ActiveRecord::SchemaDumper.send(:new, connection)
|
116
|
+
stream = StringIO.new
|
117
|
+
dumper.table(table.to_s,stream)
|
118
|
+
stream.rewind
|
119
|
+
code = stream.read
|
120
|
+
end
|
121
|
+
|
122
|
+
#removes all the indexes
|
123
|
+
def disable_indexes(table)
|
124
|
+
list = connection.indexes(table)
|
125
|
+
if connection.adapter_name =~ /^mysql/i
|
126
|
+
sql = list.collect { |i| "DROP INDEX #{i.name}"}.join(', ')
|
127
|
+
execute "ALTER TABLE #{table} #{sql}"
|
128
|
+
else
|
129
|
+
list.each do |i|
|
130
|
+
remove_index table, :name => i.name
|
131
|
+
end
|
132
|
+
end
|
133
|
+
list
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
def enable_indexes(table, list)
|
138
|
+
if connection.adapter_name =~ /^mysql/i
|
139
|
+
sql = list.collect do |i|
|
140
|
+
cols = i.columns.collect {|c| i.lengths ? "`#{c}`(#{i.lengths[c]})" : "`#{c}`"}.join(',')
|
141
|
+
"ADD #{'UNIQUE ' if i.unique}INDEX #{i.name} (#{cols})"
|
142
|
+
end.join(', ')
|
143
|
+
execute "ALTER TABLE #{table} #{sql}"
|
144
|
+
else
|
145
|
+
list.each do |i|
|
146
|
+
options = {}
|
147
|
+
options[:name] = i.name if i.name
|
148
|
+
options[:length] = i.lengths if i.lengths
|
149
|
+
options[:unique] = i.unique if i.unique
|
150
|
+
add_index table, i.columns, options
|
151
|
+
end
|
152
|
+
end
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
class PhoneyTable
|
157
|
+
|
158
|
+
attr_accessor :indexes
|
159
|
+
|
160
|
+
def initialize(tablename)
|
161
|
+
@table = tablename
|
162
|
+
@indexes = []
|
163
|
+
end
|
164
|
+
|
165
|
+
def index(columns, options = {})
|
166
|
+
new_index = PhoneyIndex.new(@table, columns, options)
|
167
|
+
@indexes << new_index unless @indexes.to_a.any? {|i| i == new_index}
|
168
|
+
end
|
169
|
+
|
170
|
+
def indexes; @indexes.to_a.uniq end
|
171
|
+
end
|
172
|
+
|
173
|
+
class PhoneyIndex
|
174
|
+
|
175
|
+
attr_accessor :columns, :lengths, :name, :unique
|
176
|
+
|
177
|
+
def initialize(table, cols, options)
|
178
|
+
cols = [cols].flatten
|
179
|
+
self.columns = cols.collect(&:to_s)
|
180
|
+
self.lengths = options[:length]
|
181
|
+
self.unique = !!options[:unique]
|
182
|
+
self.name = options[:name] || "index_#{table}_on_#{columns.join('_and_')}"
|
183
|
+
self.name = "#{columns.join('_')}_idx" if name.length > 64
|
184
|
+
end
|
185
|
+
|
186
|
+
def ==(val)
|
187
|
+
columns == val.columns rescue false
|
26
188
|
end
|
27
189
|
end
|
28
190
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fast_change_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Grady Griffin
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-01-09 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -44,7 +44,6 @@ extra_rdoc_files: []
|
|
44
44
|
|
45
45
|
files:
|
46
46
|
- .gitignore
|
47
|
-
- .rvmrc
|
48
47
|
- Gemfile
|
49
48
|
- Gemfile.lock
|
50
49
|
- README
|
@@ -83,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
82
|
requirements: []
|
84
83
|
|
85
84
|
rubyforge_project: fast_change_table
|
86
|
-
rubygems_version: 1.
|
85
|
+
rubygems_version: 1.5.3
|
87
86
|
signing_key:
|
88
87
|
specification_version: 3
|
89
88
|
summary: Faster table changes
|
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm use default@fast_change_table
|