mysql_replication_adapter 0.1.0

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/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-07-23
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 FIXME full name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,16 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/mysql_replication_adapter.rb
7
+ lib/mysql_replication_adapter/version.rb
8
+ scripts/txt2html
9
+ setup.rb
10
+ test/test_helper.rb
11
+ test/test_mysql_replication_adapter.rb
12
+ website/index.html
13
+ website/index.txt
14
+ website/javascripts/rounded_corners_lite.inc.js
15
+ website/stylesheets/screen.css
16
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ README for mysql_replication_adapter
2
+ ====================================
3
+
data/Rakefile ADDED
@@ -0,0 +1,123 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+
12
+ include FileUtils
13
+ require File.join(File.dirname(__FILE__), 'lib', 'mysql_replication_adapter', 'version')
14
+
15
+ AUTHOR = 'Bryan Duxbury' # can also be an array of Authors
16
+ EMAIL = "bryan@rapleaf.com"
17
+ DESCRIPTION = "An ActiveRecord database adapter that allows you to specify a single write master and multiple read-only slaves."
18
+ GEM_NAME = 'mysql_replication_adapter' # what ppl will type to install your gem
19
+
20
+ @config_file = "~/.rubyforge/user-config.yml"
21
+ @config = nil
22
+ def rubyforge_username
23
+ unless @config
24
+ begin
25
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
26
+ rescue
27
+ puts <<-EOS
28
+ ERROR: No rubyforge config file found: #{@config_file}"
29
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
30
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
31
+ EOS
32
+ exit
33
+ end
34
+ end
35
+ @rubyforge_username ||= @config["username"]
36
+ end
37
+
38
+ RUBYFORGE_PROJECT = 'mysql-replicate' # The unix name for your project
39
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
40
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
41
+
42
+ NAME = "mysql_replication_adapter"
43
+ REV = nil
44
+ # UNCOMMENT IF REQUIRED:
45
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
46
+ VERS = MysqlReplicationAdapter::VERSION::STRING + (REV ? ".#{REV}" : "")
47
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
48
+ RDOC_OPTS = ['--quiet', '--title', 'mysql_replication_adapter documentation',
49
+ "--opname", "index.html",
50
+ "--line-numbers",
51
+ "--main", "README",
52
+ "--inline-source"]
53
+
54
+ class Hoe
55
+ def extra_deps
56
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
57
+ end
58
+ end
59
+
60
+ # Generate all the Rake tasks
61
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
62
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
63
+ p.author = AUTHOR
64
+ p.description = DESCRIPTION
65
+ p.email = EMAIL
66
+ p.summary = DESCRIPTION
67
+ p.url = HOMEPATH
68
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
69
+ p.test_globs = ["test/**/test_*.rb"]
70
+ p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
71
+
72
+ # == Optional
73
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
74
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
75
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
76
+ end
77
+
78
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
79
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
80
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
81
+
82
+ desc 'Generate website files'
83
+ task :website_generate do
84
+ Dir['website/**/*.txt'].each do |txt|
85
+ sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
86
+ end
87
+ end
88
+
89
+ desc 'Upload website files to rubyforge'
90
+ task :website_upload do
91
+ host = "#{rubyforge_username}@rubyforge.org"
92
+ remote_dir = "/var/www/gforge-projects/#{PATH}/"
93
+ local_dir = 'website'
94
+ sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
95
+ end
96
+
97
+ desc 'Generate and upload website files'
98
+ task :website => [:website_generate, :website_upload, :publish_docs]
99
+
100
+ desc 'Release the website and new gem version'
101
+ task :deploy => [:check_version, :website, :release] do
102
+ puts "Remember to create SVN tag:"
103
+ puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
104
+ "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
105
+ puts "Suggested comment:"
106
+ puts "Tagging release #{CHANGES}"
107
+ end
108
+
109
+ desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
110
+ task :local_deploy => [:website_generate, :install_gem]
111
+
112
+ task :check_version do
113
+ unless ENV['VERSION']
114
+ puts 'Must pass a VERSION=x.y.z release version'
115
+ exit
116
+ end
117
+ unless ENV['VERSION'] == VERS
118
+ puts "Please update your version.rb to match the release version, currently #{VERS}"
119
+ exit
120
+ end
121
+ end
122
+
123
+
@@ -0,0 +1,520 @@
1
+ module MysqlReplicationAdapter
2
+ end
3
+
4
+ require 'mysql_replication_adapter/version'
5
+
6
+ unless defined?(RAILS_CONNECTION_ADAPTERS) && RAILS_CONNECTION_ADAPTERS.include?("mysql_replication")
7
+ require 'active_record' unless defined?(RAILS_CONNECTION_ADAPTERS)
8
+ RAILS_CONNECTION_ADAPTERS << "mysql_replication"
9
+ end
10
+
11
+ require 'active_record/connection_adapters/abstract_adapter'
12
+ require 'set'
13
+
14
+ module MysqlCompat #:nodoc:
15
+ # add all_hashes method to standard mysql-c bindings or pure ruby version
16
+ def self.define_all_hashes_method!
17
+ raise 'Mysql not loaded' unless defined?(::Mysql)
18
+
19
+ target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
20
+ return if target.instance_methods.include?('all_hashes')
21
+
22
+ # Ruby driver has a version string and returns null values in each_hash
23
+ # C driver >= 2.7 returns null values in each_hash
24
+ if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
25
+ target.class_eval <<-'end_eval'
26
+ def all_hashes
27
+ rows = []
28
+ each_hash { |row| rows << row }
29
+ rows
30
+ end
31
+ end_eval
32
+
33
+ # adapters before 2.7 don't have a version constant
34
+ # and don't return null values in each_hash
35
+ else
36
+ target.class_eval <<-'end_eval'
37
+ def all_hashes
38
+ rows = []
39
+ all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
40
+ each_hash { |row| rows << all_fields.dup.update(row) }
41
+ rows
42
+ end
43
+ end_eval
44
+ end
45
+
46
+ unless target.instance_methods.include?('all_hashes')
47
+ raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
48
+ end
49
+ end
50
+ end
51
+
52
+ module ActiveRecord
53
+ class Base
54
+ def self.require_mysql
55
+ # Include the MySQL driver if one hasn't already been loaded
56
+ unless defined? Mysql
57
+ begin
58
+ require_library_or_gem 'mysql'
59
+ rescue LoadError => cannot_require_mysql
60
+ # Use the bundled Ruby/MySQL driver if no driver is already in place
61
+ begin
62
+ require 'active_record/vendor/mysql'
63
+ rescue LoadError
64
+ raise cannot_require_mysql
65
+ end
66
+ end
67
+ end
68
+
69
+ # Define Mysql::Result.all_hashes
70
+ MysqlCompat.define_all_hashes_method!
71
+ end
72
+
73
+ # Establishes a connection to the database that's used by all Active Record objects.
74
+ def self.mysql_replication_connection(config) # :nodoc:
75
+ config = config.symbolize_keys
76
+ host = config[:host]
77
+ port = config[:port]
78
+ socket = config[:socket]
79
+ username = config[:username] ? config[:username].to_s : 'root'
80
+ password = config[:password].to_s
81
+
82
+ if config.has_key?(:database)
83
+ database = config[:database]
84
+ else
85
+ raise ArgumentError, "No database specified. Missing argument: database."
86
+ end
87
+
88
+ require_mysql
89
+ mysql = Mysql.init
90
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
91
+
92
+ ConnectionAdapters::MysqlReplicationAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
93
+ end
94
+
95
+ class << self
96
+ alias_method :old_find_every, :find_every
97
+
98
+ VALID_FIND_OPTIONS << :use_slave
99
+
100
+ # Override the standard find to check for the :use_slave option. When specified, the
101
+ # resulting query will be sent to a slave machine.
102
+ def find_every(options)
103
+ result = if options[:use_slave] && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
104
+ connection.load_balance_query do
105
+ old_find_every(options)
106
+ end
107
+ else
108
+ old_find_every(options)
109
+ end
110
+ result
111
+ end
112
+
113
+
114
+
115
+ alias_method :old_find_by_sql, :find_by_sql
116
+ # Override find_by_sql so that you can tell it to selectively use a slave machine
117
+ def find_by_sql(sql, use_slave = false)
118
+ if use_slave && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
119
+ connection.load_balance_query {old_find_by_sql sql}
120
+ else
121
+ old_find_by_sql sql
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ module ConnectionAdapters
129
+ class CannotWriteToSlave < Exception
130
+ end
131
+
132
+ #class AbstractAdapter
133
+ # Adding this method allows non-mysql-replication adapter applications to function without changing
134
+ # code. Useful in development and test.
135
+ #def load_balance_query
136
+ # yield
137
+ #end
138
+ #end
139
+
140
+ class MysqlColumn < Column #:nodoc:
141
+ #TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
142
+
143
+ def initialize(name, default, sql_type = nil, null = true)
144
+ @original_default = default
145
+ super
146
+ @default = nil if missing_default_forged_as_empty_string?
147
+ end
148
+
149
+ private
150
+ def simplified_type(field_type)
151
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
152
+ return :string if field_type =~ /enum/i
153
+ super
154
+ end
155
+
156
+ # MySQL misreports NOT NULL column default when none is given.
157
+ # We can't detect this for columns which may have a legitimate ''
158
+ # default (string, text, binary) but we can for others (integer,
159
+ # datetime, boolean, and the rest).
160
+ #
161
+ # Test whether the column has default '', is not null, and is not
162
+ # a type allowing default ''.
163
+ def missing_default_forged_as_empty_string?
164
+ !null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
165
+ end
166
+ end
167
+
168
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
169
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
170
+ #
171
+ # Options:
172
+ #
173
+ # * <tt>:host</tt> -- Defaults to localhost
174
+ # * <tt>:port</tt> -- Defaults to 3306
175
+ # * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
176
+ # * <tt>:username</tt> -- Defaults to root
177
+ # * <tt>:password</tt> -- Defaults to nothing
178
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
179
+ # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
180
+ # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
181
+ # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
182
+ # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
183
+ #
184
+ # By default, the MysqlAdapter will consider all columns of type tinyint(1)
185
+ # as boolean. If you wish to disable this emulation (which was the default
186
+ # behavior in versions 0.13.1 and earlier) you can add the following line
187
+ # to your environment.rb file:
188
+ #
189
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
190
+ class MysqlReplicationAdapter < AbstractAdapter
191
+ @@emulate_booleans = true
192
+ cattr_accessor :emulate_booleans
193
+ @@use_master_only = false
194
+ cattr_accessor :use_master_only
195
+
196
+ LOST_CONNECTION_ERROR_MESSAGES = [
197
+ "Server shutdown in progress",
198
+ "Broken pipe",
199
+ "Lost connection to MySQL server during query",
200
+ "MySQL server has gone away"
201
+ ]
202
+
203
+ def initialize(connection, logger, connection_options, config)
204
+ super(connection, logger)
205
+ @connection_options, @config = connection_options, config
206
+
207
+ connect
208
+ end
209
+
210
+ def adapter_name #:nodoc:
211
+ 'MySQLReplication'
212
+ end
213
+
214
+ def supports_migrations? #:nodoc:
215
+ true
216
+ end
217
+
218
+ def native_database_types #:nodoc:
219
+ {
220
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
221
+ :string => { :name => "varchar", :limit => 255 },
222
+ :text => { :name => "text" },
223
+ :integer => { :name => "int", :limit => 11 },
224
+ :float => { :name => "float" },
225
+ :decimal => { :name => "decimal" },
226
+ :datetime => { :name => "datetime" },
227
+ :timestamp => { :name => "datetime" },
228
+ :time => { :name => "time" },
229
+ :date => { :name => "date" },
230
+ :binary => { :name => "blob" },
231
+ :boolean => { :name => "tinyint", :limit => 1 }
232
+ }
233
+ end
234
+
235
+ # the magic load_balance method
236
+ def load_balance_query
237
+ old_connection = @connection
238
+ @connection = select_clone
239
+ yield
240
+ ensure
241
+ @connection = old_connection
242
+ end
243
+
244
+ # choose a random clone to use for the moment
245
+ def select_clone
246
+ # if we happen not to be connected to any clones, just use the master
247
+ return @master if @clones.empty?
248
+ # return a random clone
249
+ return @clones[rand(@clones.size - 1)]
250
+ end
251
+
252
+ # This method raises an exception if the current connection is a clone. It is called inside
253
+ # all of the methods that typically cause database writes. This keeps the developer from
254
+ # doing any writes when inside a slave query block.
255
+ def ensure_master
256
+ raise CannotWriteToSlave, "You attempted to perform a write operation inside a slave-balanced read block." unless @connection == @master
257
+ end
258
+
259
+ # QUOTING ==================================================
260
+
261
+ def quote(value, column = nil)
262
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
263
+ s = column.class.string_to_binary(value).unpack("H*")[0]
264
+ "x'#{s}'"
265
+ elsif value.kind_of?(BigDecimal)
266
+ "'#{value.to_s("F")}'"
267
+ else
268
+ super
269
+ end
270
+ end
271
+
272
+ def quote_column_name(name) #:nodoc:
273
+ "`#{name}`"
274
+ end
275
+
276
+ def quote_string(string) #:nodoc:
277
+ @connection.quote(string)
278
+ end
279
+
280
+ def quoted_true
281
+ "1"
282
+ end
283
+
284
+ def quoted_false
285
+ "0"
286
+ end
287
+
288
+
289
+ # CONNECTION MANAGEMENT ====================================
290
+
291
+ def active?
292
+ if @connection.respond_to?(:stat)
293
+ @connection.stat
294
+ else
295
+ @connection.query 'select 1'
296
+ end
297
+
298
+ # mysql-ruby doesn't raise an exception when stat fails.
299
+ if @connection.respond_to?(:errno)
300
+ @connection.errno.zero?
301
+ else
302
+ true
303
+ end
304
+ rescue Mysql::Error
305
+ false
306
+ end
307
+
308
+ def reconnect!
309
+ disconnect!
310
+ connect
311
+ end
312
+
313
+ def disconnect!
314
+ @connection.close rescue nil
315
+ @clones.each do |clone|
316
+ clone.close rescue nil
317
+ end
318
+ end
319
+
320
+
321
+ # DATABASE STATEMENTS ======================================
322
+
323
+ def execute(sql, name = nil) #:nodoc:
324
+ log(sql, "#{name} against #{@connection.host_info}") do
325
+ @connection.query(sql)
326
+ end
327
+ rescue ActiveRecord::StatementInvalid => exception
328
+ if exception.message.split(":").first =~ /Packets out of order/
329
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
330
+ else
331
+ raise
332
+ end
333
+ end
334
+
335
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
336
+ ensure_master
337
+ execute(sql, name = nil)
338
+ id_value || @connection.insert_id
339
+ end
340
+
341
+ def update(sql, name = nil) #:nodoc:
342
+ ensure_master
343
+ execute(sql, name)
344
+ @connection.affected_rows
345
+ end
346
+
347
+ def begin_db_transaction #:nodoc:
348
+ execute "BEGIN"
349
+ rescue Exception
350
+ # Transactions aren't supported
351
+ end
352
+
353
+ def commit_db_transaction #:nodoc:
354
+ execute "COMMIT"
355
+ rescue Exception
356
+ # Transactions aren't supported
357
+ end
358
+
359
+ def rollback_db_transaction #:nodoc:
360
+ execute "ROLLBACK"
361
+ rescue Exception
362
+ # Transactions aren't supported
363
+ end
364
+
365
+
366
+ def add_limit_offset!(sql, options) #:nodoc:
367
+ if limit = options[:limit]
368
+ unless offset = options[:offset]
369
+ sql << " LIMIT #{limit}"
370
+ else
371
+ sql << " LIMIT #{offset}, #{limit}"
372
+ end
373
+ end
374
+ end
375
+
376
+
377
+ # SCHEMA STATEMENTS ========================================
378
+
379
+ def structure_dump #:nodoc:
380
+ if supports_views?
381
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
382
+ else
383
+ sql = "SHOW TABLES"
384
+ end
385
+
386
+ select_all(sql).inject("") do |structure, table|
387
+ table.delete('Table_type')
388
+ structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
389
+ end
390
+ end
391
+
392
+ def recreate_database(name) #:nodoc:
393
+ drop_database(name)
394
+ create_database(name)
395
+ end
396
+
397
+ def create_database(name) #:nodoc:
398
+ execute "CREATE DATABASE `#{name}`"
399
+ end
400
+
401
+ def drop_database(name) #:nodoc:
402
+ execute "DROP DATABASE IF EXISTS `#{name}`"
403
+ end
404
+
405
+ def current_database
406
+ select_one("SELECT DATABASE() as db")["db"]
407
+ end
408
+
409
+ def tables(name = nil) #:nodoc:
410
+ tables = []
411
+ execute("SHOW TABLES", name).each { |field| tables << field[0] }
412
+ tables
413
+ end
414
+
415
+ def indexes(table_name, name = nil)#:nodoc:
416
+ indexes = []
417
+ current_index = nil
418
+ execute("SHOW KEYS FROM #{table_name}", name).each do |row|
419
+ if current_index != row[2]
420
+ next if row[2] == "PRIMARY" # skip the primary key
421
+ current_index = row[2]
422
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
423
+ end
424
+
425
+ indexes.last.columns << row[4]
426
+ end
427
+ indexes
428
+ end
429
+
430
+ def columns(table_name, name = nil)#:nodoc:
431
+ sql = "SHOW FIELDS FROM #{table_name}"
432
+ columns = []
433
+ execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
434
+ columns
435
+ end
436
+
437
+ def create_table(name, options = {}) #:nodoc:
438
+ super(name, {:options => "ENGINE=InnoDB"}.merge(options))
439
+ end
440
+
441
+ def rename_table(name, new_name)
442
+ execute "RENAME TABLE #{name} TO #{new_name}"
443
+ end
444
+
445
+ def change_column_default(table_name, column_name, default) #:nodoc:
446
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
447
+
448
+ execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
449
+ end
450
+
451
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
452
+ unless options_include_default?(options)
453
+ options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
454
+ end
455
+
456
+ change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
457
+ add_column_options!(change_column_sql, options)
458
+ execute(change_column_sql)
459
+ end
460
+
461
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
462
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
463
+ execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
464
+ end
465
+
466
+
467
+ private
468
+ def connect
469
+ encoding = @config[:encoding]
470
+ if encoding
471
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
472
+ end
473
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
474
+ @connection.real_connect(*@connection_options)
475
+ execute("SET NAMES '#{encoding}'") if encoding
476
+
477
+ # By default, MySQL 'where id is null' selects the last inserted id.
478
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
479
+ execute("SET SQL_AUTO_IS_NULL=0")
480
+
481
+ # save the master in a separate instance variable so we always know what it is
482
+ @master = @connection
483
+
484
+ # connect to all the clone machines
485
+ @clones = []
486
+ if @config[:clones]
487
+ @config[:clones].each do |clone|
488
+ conn = Mysql.init
489
+ if encoding
490
+ conn.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
491
+ end
492
+ conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
493
+
494
+ conn.real_connect(clone["host"], clone["username"], clone["password"], clone["database"], clone["port"], clone["socket"])
495
+ @clones << conn
496
+ end
497
+ else
498
+ # warning, no slaves specified.
499
+ warn "Warning: MysqlReplicationAdapter in use, but no slave database connections specified."
500
+ end
501
+ end
502
+
503
+ def select(sql, name = nil)
504
+ @connection.query_with_result = true
505
+ result = execute(sql, name)
506
+ rows = result.all_hashes
507
+ result.free
508
+ rows
509
+ end
510
+
511
+ def supports_views?
512
+ version[0] >= 5
513
+ end
514
+
515
+ def version
516
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
517
+ end
518
+ end
519
+ end
520
+ end