Skip to content

Commit 1051646

Browse files
committed
[mysql] backported bulk change table support from Rails (fixes #469)
1 parent f74ec77 commit 1051646

File tree

4 files changed

+218
-6
lines changed

4 files changed

+218
-6
lines changed

lib/arjdbc/mysql/adapter.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
require 'bigdecimal'
44
require 'active_record/connection_adapters/abstract/schema_definitions'
55
require 'arjdbc/mysql/column'
6+
require 'arjdbc/mysql/bulk_change_table'
67
require 'arjdbc/mysql/explain_support'
78
require 'arjdbc/mysql/schema_creation' # AR 4.x
89

910
module ArJdbc
1011
module MySQL
12+
include BulkChangeTable if const_defined? :BulkChangeTable
1113

1214
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
1315
def self.jdbc_connection_class
@@ -179,11 +181,6 @@ def supports_primary_key?
179181
true
180182
end
181183

182-
# @override
183-
def supports_bulk_alter?
184-
true
185-
end
186-
187184
# @override
188185
def supports_index_sort_order?
189186
# Technically MySQL allows to create indexes with the sort order syntax
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
module ArJdbc
2+
module MySQL
3+
module BulkChangeTable
4+
5+
# @override
6+
def supports_bulk_alter?
7+
true
8+
end
9+
10+
def bulk_change_table(table_name, operations)
11+
sqls = operations.map do |command, args|
12+
table, arguments = args.shift, args
13+
method = :"#{command}_sql"
14+
15+
if respond_to?(method, true)
16+
send(method, table, *arguments)
17+
else
18+
raise "Unknown method called : #{method}(#{arguments.inspect})"
19+
end
20+
end
21+
sqls.flatten!
22+
23+
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}")
24+
end
25+
26+
protected
27+
28+
def add_column_sql(table_name, column_name, type, options = {})
29+
add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
30+
add_column_options!(add_column_sql, options)
31+
add_column_position!(add_column_sql, options)
32+
add_column_sql
33+
end
34+
35+
def change_column_sql(table_name, column_name, type, options = {})
36+
column = column_for(table_name, column_name)
37+
38+
unless options_include_default?(options)
39+
options[:default] = column.default
40+
end
41+
42+
unless options.has_key?(:null)
43+
options[:null] = column.null
44+
end
45+
46+
change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
47+
add_column_options!(change_column_sql, options)
48+
add_column_position!(change_column_sql, options)
49+
change_column_sql
50+
end
51+
52+
def rename_column_sql(table_name, column_name, new_column_name)
53+
options = {}
54+
55+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
56+
options[:default] = column.default
57+
options[:null] = column.null
58+
options[:auto_increment] = (column.extra == "auto_increment")
59+
else
60+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
61+
end
62+
63+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
64+
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
65+
add_column_options!(rename_column_sql, options)
66+
rename_column_sql
67+
end
68+
69+
def remove_column_sql(table_name, column_name, type = nil, options = {})
70+
"DROP #{quote_column_name(column_name)}"
71+
end
72+
73+
def remove_columns_sql(table_name, *column_names)
74+
column_names.map {|column_name| remove_column_sql(table_name, column_name) }
75+
end
76+
77+
def add_index_sql(table_name, column_name, options = {})
78+
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
79+
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
80+
end
81+
82+
def remove_index_sql(table_name, options = {})
83+
index_name = index_name_for_remove(table_name, options)
84+
"DROP INDEX #{index_name}"
85+
end
86+
87+
def add_timestamps_sql(table_name)
88+
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
89+
end
90+
91+
def remove_timestamps_sql(table_name)
92+
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
93+
end
94+
95+
private
96+
97+
def add_column_position!(sql, options)
98+
if options[:first]
99+
sql << " FIRST"
100+
elsif options[:after]
101+
sql << " AFTER #{quote_column_name(options[:after])}"
102+
end
103+
end
104+
105+
end
106+
end
107+
end

lib/arjdbc/mysql/explain_support.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module ::ArJdbc
1+
module ArJdbc
22
module MySQL
33
module ExplainSupport
44
def supports_explain?

test/db/mysql/simple_test.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,114 @@ def test_mysql_indexes
246246
end
247247
end if defined? JRUBY_VERSION
248248

249+
test 'bulk change table' do
250+
assert ActiveRecord::Base.connection.supports_bulk_alter?
251+
252+
begin
253+
connection.create_table(:bulks, :force => true) { |t| t.string :it }
254+
255+
assert_queries(1) do
256+
with_bulk_change_table(:bulks) do |t|
257+
t.column :name, :string
258+
t.string :qualification, :experience
259+
t.integer :age, :default => 0
260+
t.date :birthdate
261+
t.timestamps
262+
end
263+
end
264+
assert_equal 9, connection.columns(:bulks).size
265+
266+
column = lambda do |name|
267+
indexes = connection.columns(:bulks)
268+
indexes.detect { |c| c.name == name.to_s }
269+
end
270+
271+
[:qualification, :experience].each { |c| assert column.call(c) }
272+
273+
assert_queries(1) do
274+
with_bulk_change_table('bulks') do |t|
275+
t.remove :qualification, :experience
276+
t.string :qualification_experience
277+
end
278+
end
279+
280+
[:qualification, :experience].each { |c| assert ! column.call(c) }
281+
assert column.call(:qualification_experience)
282+
283+
assert ! column.call(:name).default
284+
assert_equal :date, column.call(:birthdate).type
285+
286+
# One query for columns (delete_me table)
287+
# One query for primary key (delete_me table)
288+
# One query to do the bulk change
289+
#assert_queries(3, :ignore_none => true) do
290+
with_bulk_change_table('bulks') do |t|
291+
t.change :name, :string, :default => 'NONAME'
292+
t.change :birthdate, :datetime
293+
end
294+
#end
295+
296+
assert_equal 'NONAME', column.call(:name).default
297+
assert_equal :datetime, column.call(:birthdate).type
298+
299+
# test_adding_indexes
300+
with_bulk_change_table(:bulks) do |t|
301+
t.string :username # t.string :name t.integer :age
302+
end
303+
304+
index = lambda do |name|
305+
indexes = connection.indexes(:bulks)
306+
indexes.detect { |i| i.name == name.to_s }
307+
end
308+
309+
# Adding an index fires a query every time to check if an index already exists or not
310+
assert_queries(3) do
311+
with_bulk_change_table(:bulks) do |t|
312+
t.index :username, :unique => true, :name => :awesome_username_index
313+
t.index [:name, :age]
314+
end
315+
end
316+
317+
assert_equal 2, connection.indexes(:bulks).size
318+
319+
assert name_age_index = index.call(:index_bulks_on_name_and_age)
320+
assert_equal ['name', 'age'].sort, name_age_index.columns.sort
321+
assert ! name_age_index.unique
322+
323+
assert index.call(:awesome_username_index).unique
324+
325+
# test_removing_index
326+
with_bulk_change_table('bulks') do |t|
327+
t.string :name2; t.index :name2
328+
end
329+
330+
assert index.call(:index_bulks_on_name2)
331+
332+
assert_queries(3) do
333+
with_bulk_change_table('bulks') do |t|
334+
t.remove_index :name2
335+
t.index :name2, :name => :new_name2_index, :unique => true
336+
end
337+
end
338+
339+
assert ! index.call(:index_bulks_on_name2)
340+
341+
new_name_index = index.call(:new_name2_index)
342+
assert new_name_index.unique
343+
344+
ensure
345+
connection.drop_table(:bulks) rescue nil
346+
end
347+
end if ar_version('3.2')
348+
349+
protected
350+
351+
def with_bulk_change_table(table)
352+
connection.change_table(table, :bulk => true) do |t|
353+
yield t
354+
end
355+
end
356+
249357
private
250358

251359
def mysql_adapter_class

0 commit comments

Comments
 (0)