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 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