fast_change_table 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  *.gem
2
2
  .bundle
3
3
  pkg/*
4
+ .rvmrc
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
@@ -1,3 +1,3 @@
1
1
  module FastChangeTable
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,5 +1,44 @@
1
- require "activerecord"
2
- require "fast_change_table/version"
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
- execute "CREATE TABLE #{table_name} LIKE #{old_table_name}"
15
- change_table(table_name, &block)
16
- #prepare the columns names for the insert statements
17
- old = connection.columns(old_table_name).collect(&:name)
18
- current = connection.columns(table_name).collect(&:name)
19
- common = (current & old).sort
20
- columns_to_s = common.collect {|c| "`#{c}`"}.join(',')
21
- execute "INSERT INTO #{table_name}(#{columns_to_s}) SELECT #{columns_to_s} FROM #{old_table_name}"
22
- drop_table(old_table_name)
23
- rescue
24
- drop_table(table_name) if table_exists?(table_name)
25
- rename_table(old_table_name, table_name)
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: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
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: 2011-09-30 00:00:00 -04:00
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.4.2
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