mysql_replication_adapter 0.2.0 → 0.4.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.
@@ -1,3 +1,8 @@
1
+ == 0.4.0 2007-11-02
2
+ * 1 major enhancement
3
+ * Fixed a bug in the slave selection algorithm that was causing the last item in the list to always be omitted.
4
+ * A bit of internal restructuring to make the code simpler.
5
+
1
6
  == 0.0.1 2007-07-23
2
7
 
3
8
  * 1 major enhancement:
@@ -5,6 +5,7 @@ README.txt
5
5
  Rakefile
6
6
  lib/mysql_replication_adapter.rb
7
7
  lib/mysql_replication_adapter/version.rb
8
+ lib/mysql_replication_adapter/ar_base_ext.rb
8
9
  scripts/txt2html
9
10
  setup.rb
10
11
  test/test_helper.rb
@@ -8,172 +8,22 @@ unless defined?(RAILS_CONNECTION_ADAPTERS) && RAILS_CONNECTION_ADAPTERS.include?
8
8
  RAILS_CONNECTION_ADAPTERS << "mysql_replication"
9
9
  end
10
10
 
11
- require 'active_record/connection_adapters/abstract_adapter'
12
11
  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
12
+ require "mysql_replication_adapter/ar_base_ext"
51
13
 
52
14
  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
- alias_method :old_find_by_sql, :find_by_sql
114
- # Override find_by_sql so that you can tell it to selectively use a slave machine
115
- def find_by_sql(sql, use_slave = false)
116
- if use_slave && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
117
- connection.load_balance_query {old_find_by_sql sql}
118
- else
119
- old_find_by_sql sql
120
- end
121
- end
122
-
123
- alias_method :old_calculate, :calculate
124
-
125
- def calculate(operation, column_name, options ={})
126
- use_slave = options.delete(:use_slave)
127
- if use_slave && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
128
- connection.load_balance_query {old_calculate(operation, column_name, options)}
129
- else
130
- old_calculate(operation, column_name, options)
131
- end
132
- end
133
- end
134
-
135
- end
136
-
137
15
  module ConnectionAdapters
138
16
  class CannotWriteToSlave < Exception
139
17
  end
140
18
 
141
- #class AbstractAdapter
19
+ class AbstractAdapter
142
20
  # Adding this method allows non-mysql-replication adapter applications to function without changing
143
21
  # code. Useful in development and test.
144
- #def load_balance_query
145
- # yield
146
- #end
147
- #end
148
-
149
- class MysqlColumn < Column #:nodoc:
150
- #TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
151
-
152
- def initialize(name, default, sql_type = nil, null = true)
153
- @original_default = default
154
- super
155
- @default = nil if missing_default_forged_as_empty_string?
22
+ def load_balance_query
23
+ yield
156
24
  end
157
-
158
- private
159
- def simplified_type(field_type)
160
- return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
161
- return :string if field_type =~ /enum/i
162
- super
163
- end
164
-
165
- # MySQL misreports NOT NULL column default when none is given.
166
- # We can't detect this for columns which may have a legitimate ''
167
- # default (string, text, binary) but we can for others (integer,
168
- # datetime, boolean, and the rest).
169
- #
170
- # Test whether the column has default '', is not null, and is not
171
- # a type allowing default ''.
172
- def missing_default_forged_as_empty_string?
173
- !null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
174
- end
175
25
  end
176
-
26
+
177
27
  # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
178
28
  # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
179
29
  #
@@ -196,51 +46,27 @@ module ActiveRecord
196
46
  # to your environment.rb file:
197
47
  #
198
48
  # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
199
- class MysqlReplicationAdapter < AbstractAdapter
200
- @@emulate_booleans = true
201
- cattr_accessor :emulate_booleans
202
- @@use_master_only = false
203
- cattr_accessor :use_master_only
204
-
205
- LOST_CONNECTION_ERROR_MESSAGES = [
206
- "Server shutdown in progress",
207
- "Broken pipe",
208
- "Lost connection to MySQL server during query",
209
- "MySQL server has gone away"
210
- ]
49
+ class MysqlReplicationAdapter < MysqlAdapter
50
+ # @@emulate_booleans = true
51
+ # cattr_accessor :emulate_booleans
52
+ #
53
+ # LOST_CONNECTION_ERROR_MESSAGES = [
54
+ # "Server shutdown in progress",
55
+ # "Broken pipe",
56
+ # "Lost connection to MySQL server during query",
57
+ # "MySQL server has gone away"
58
+ # ]
211
59
 
212
60
  def initialize(connection, logger, connection_options, config)
213
- super(connection, logger)
214
- @connection_options, @config = connection_options, config
215
-
216
- connect
61
+ @master = @clones = nil
62
+ @retries = config[:retries]
63
+ super(connection, logger, connection_options, config)
217
64
  end
218
65
 
219
66
  def adapter_name #:nodoc:
220
67
  'MySQLReplication'
221
68
  end
222
69
 
223
- def supports_migrations? #:nodoc:
224
- true
225
- end
226
-
227
- def native_database_types #:nodoc:
228
- {
229
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
230
- :string => { :name => "varchar", :limit => 255 },
231
- :text => { :name => "text" },
232
- :integer => { :name => "int", :limit => 11 },
233
- :float => { :name => "float" },
234
- :decimal => { :name => "decimal" },
235
- :datetime => { :name => "datetime" },
236
- :timestamp => { :name => "datetime" },
237
- :time => { :name => "time" },
238
- :date => { :name => "date" },
239
- :binary => { :name => "blob" },
240
- :boolean => { :name => "tinyint", :limit => 1 }
241
- }
242
- end
243
-
244
70
  # the magic load_balance method
245
71
  def load_balance_query
246
72
  old_connection = @connection
@@ -253,9 +79,9 @@ module ActiveRecord
253
79
  # choose a random clone to use for the moment
254
80
  def select_clone
255
81
  # if we happen not to be connected to any clones, just use the master
256
- return @master if @clones.empty?
82
+ return @master if @clones.nil? || @clones.empty?
257
83
  # return a random clone
258
- return @clones[rand(@clones.size - 1)]
84
+ return @clones[rand(@clones.size)]
259
85
  end
260
86
 
261
87
  # This method raises an exception if the current connection is a clone. It is called inside
@@ -264,65 +90,13 @@ module ActiveRecord
264
90
  def ensure_master
265
91
  raise CannotWriteToSlave, "You attempted to perform a write operation inside a slave-balanced read block." unless @connection == @master
266
92
  end
267
-
268
- # QUOTING ==================================================
269
-
270
- def quote(value, column = nil)
271
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
272
- s = column.class.string_to_binary(value).unpack("H*")[0]
273
- "x'#{s}'"
274
- elsif value.kind_of?(BigDecimal)
275
- "'#{value.to_s("F")}'"
276
- else
277
- super
278
- end
279
- end
280
-
281
- def quote_column_name(name) #:nodoc:
282
- "`#{name}`"
283
- end
284
-
285
- def quote_string(string) #:nodoc:
286
- @connection.quote(string)
287
- end
288
-
289
- def quoted_true
290
- "1"
291
- end
292
-
293
- def quoted_false
294
- "0"
295
- end
296
-
297
-
298
- # CONNECTION MANAGEMENT ====================================
299
-
300
- def active?
301
- if @connection.respond_to?(:stat)
302
- @connection.stat
303
- else
304
- @connection.query 'select 1'
305
- end
306
-
307
- # mysql-ruby doesn't raise an exception when stat fails.
308
- if @connection.respond_to?(:errno)
309
- @connection.errno.zero?
310
- else
311
- true
312
- end
313
- rescue Mysql::Error
314
- false
315
- end
316
-
317
- def reconnect!
318
- disconnect!
319
- connect
320
- end
321
-
93
+
322
94
  def disconnect!
323
- @connection.close rescue nil
324
- @clones.each do |clone|
325
- clone.close rescue nil
95
+ @master.close rescue nil
96
+ if @clones
97
+ @clones.each do |clone|
98
+ clone.close rescue nil
99
+ end
326
100
  end
327
101
  end
328
102
 
@@ -330,9 +104,23 @@ module ActiveRecord
330
104
  # DATABASE STATEMENTS ======================================
331
105
 
332
106
  def execute(sql, name = nil) #:nodoc:
107
+ retries = 0
333
108
  log(sql, "#{name} against #{@connection.host_info}") do
334
109
  @connection.query(sql)
335
110
  end
111
+ rescue Mysql::Error => ex
112
+ if ex.message =~ /MySQL server has gone away/
113
+ if @retries && retries < @retries
114
+ retries += 1
115
+ disconnect!
116
+ connect
117
+ retry
118
+ else
119
+ raise
120
+ end
121
+ else
122
+ raise
123
+ end
336
124
  rescue ActiveRecord::StatementInvalid => exception
337
125
  if exception.message.split(":").first =~ /Packets out of order/
338
126
  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."
@@ -353,179 +141,82 @@ module ActiveRecord
353
141
  @connection.affected_rows
354
142
  end
355
143
 
356
- def begin_db_transaction #:nodoc:
357
- execute "BEGIN"
358
- rescue Exception
359
- # Transactions aren't supported
360
- end
361
-
362
- def commit_db_transaction #:nodoc:
363
- execute "COMMIT"
364
- rescue Exception
365
- # Transactions aren't supported
366
- end
367
-
368
- def rollback_db_transaction #:nodoc:
369
- execute "ROLLBACK"
370
- rescue Exception
371
- # Transactions aren't supported
372
- end
373
-
374
-
375
- def add_limit_offset!(sql, options) #:nodoc:
376
- if limit = options[:limit]
377
- unless offset = options[:offset]
378
- sql << " LIMIT #{limit}"
379
- else
380
- sql << " LIMIT #{offset}, #{limit}"
381
- end
382
- end
144
+ private
145
+ # Create the array of clone Mysql instances. Note that the instances
146
+ # actually don't correspond to the clone specs at this point. We're
147
+ # just getting Mysql object instances we can connect with later.
148
+ def init_clones
149
+ @clones = (@config[:clones] || @config[:slaves]).map{Mysql.init}
383
150
  end
384
-
385
-
386
- # SCHEMA STATEMENTS ========================================
387
-
388
- def structure_dump #:nodoc:
389
- if supports_views?
390
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
391
- else
392
- sql = "SHOW TABLES"
151
+
152
+ # Given a Mysql object and connection options, call #real_connect
153
+ # on the connection.
154
+ def setup_connection(conn, conn_opts)
155
+ # figure out if we're going to be doing any different
156
+ # encoding. if so, set it.
157
+ encoding = @config[:encoding]
158
+ if encoding
159
+ conn.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
393
160
  end
394
161
 
395
- select_all(sql).inject("") do |structure, table|
396
- table.delete('Table_type')
397
- structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
398
- end
399
- end
400
-
401
- def recreate_database(name) #:nodoc:
402
- drop_database(name)
403
- create_database(name)
404
- end
405
-
406
- def create_database(name) #:nodoc:
407
- execute "CREATE DATABASE `#{name}`"
408
- end
409
-
410
- def drop_database(name) #:nodoc:
411
- execute "DROP DATABASE IF EXISTS `#{name}`"
412
- end
413
-
414
- def current_database
415
- select_one("SELECT DATABASE() as db")["db"]
416
- end
417
-
418
- def tables(name = nil) #:nodoc:
419
- tables = []
420
- execute("SHOW TABLES", name).each { |field| tables << field[0] }
421
- tables
422
- end
423
-
424
- def indexes(table_name, name = nil)#:nodoc:
425
- indexes = []
426
- current_index = nil
427
- execute("SHOW KEYS FROM #{table_name}", name).each do |row|
428
- if current_index != row[2]
429
- next if row[2] == "PRIMARY" # skip the primary key
430
- current_index = row[2]
431
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
432
- end
433
-
434
- indexes.last.columns << row[4]
435
- end
436
- indexes
437
- end
438
-
439
- def columns(table_name, name = nil)#:nodoc:
440
- sql = "SHOW FIELDS FROM #{table_name}"
441
- columns = []
442
- execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
443
- columns
444
- end
445
-
446
- def create_table(name, options = {}) #:nodoc:
447
- super(name, {:options => "ENGINE=InnoDB"}.merge(options))
448
- end
449
-
450
- def rename_table(name, new_name)
451
- execute "RENAME TABLE #{name} TO #{new_name}"
452
- end
453
-
454
- def change_column_default(table_name, column_name, default) #:nodoc:
455
- current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
456
-
457
- execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
458
- end
459
-
460
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
461
- unless options_include_default?(options)
462
- options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
463
- end
464
-
465
- change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
466
- add_column_options!(change_column_sql, options)
467
- execute(change_column_sql)
468
- end
469
-
470
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
471
- current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
472
- execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
162
+ # set the ssl options
163
+ conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
164
+
165
+ # do the actual connect
166
+ conn.real_connect(*conn_opts)
167
+
168
+ # swap the current connection for the connection we just set up
169
+ old_conn, @connection = @connection, conn
170
+
171
+ # set these options!
172
+ execute("SET NAMES '#{encoding}'") if encoding
173
+ # By default, MySQL 'where id is null' selects the last inserted id.
174
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
175
+ execute("SET SQL_AUTO_IS_NULL=0")
176
+
177
+ # swap the old current connection back into place
178
+ @connection = old_conn
473
179
  end
474
-
475
-
476
- private
477
- def connect
478
- encoding = @config[:encoding]
479
- if encoding
480
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
481
- end
482
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
483
- @connection.real_connect(*@connection_options)
484
- execute("SET NAMES '#{encoding}'") if encoding
485
-
486
- # By default, MySQL 'where id is null' selects the last inserted id.
487
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
488
- execute("SET SQL_AUTO_IS_NULL=0")
489
-
490
- # save the master in a separate instance variable so we always know what it is
491
- @master = @connection
180
+
181
+ def connect
182
+ # if this is our first time in this method, then master will be
183
+ # nil, and should get set.
184
+ @master = @connection unless @master
185
+
186
+ # set up the master connection
187
+ setup_connection(@master, @connection_options)
188
+
189
+ clone_config = @config[:clones] || @config[:slaves]
190
+
191
+ # if clones are specified, then set up those connections
192
+ if clone_config
193
+ # create the clone connections if they don't already exist
194
+ init_clones unless @clones
492
195
 
493
- # connect to all the clone machines
494
- @clones = []
495
- clone_config = @config[:clones] || @config[:slaves]
196
+ # produce a pairing of connection options with an existing clone
197
+ clones_with_configs = @clones.zip(clone_config)
496
198
 
497
- if clone_config
498
- clone_config.each do |clone|
499
- conn = Mysql.init
500
- if encoding
501
- conn.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
502
- end
503
- conn.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
199
+ clones_with_configs.each do |clone_and_config|
200
+ clone, config = clone_and_config
504
201
 
505
- conn.real_connect(clone["host"], clone["username"], clone["password"], clone["database"], clone["port"], clone["socket"])
506
- @clones << conn
507
- end
508
- else
509
- # warning, no slaves specified.
510
- warn "Warning: MysqlReplicationAdapter in use, but no slave database connections specified."
202
+ # Cause the individual clone Mysql instances to (re)connect
203
+ # Note - the instances aren't being replaced. This is critical,
204
+ # as otherwise the current connection could end up pointed at a
205
+ # bad connection object in the case of a failure.
206
+ setup_connection(clone,
207
+ [
208
+ config["host"],
209
+ config["username"], config["password"],
210
+ config["database"], config["port"], config["socket"]
211
+ ]
212
+ )
511
213
  end
214
+ else
215
+ # warning, no slaves specified.
216
+ warn "Warning: MysqlReplicationAdapter in use, but no slave database connections specified."
512
217
  end
513
-
514
- def select(sql, name = nil)
515
- @connection.query_with_result = true
516
- result = execute(sql, name)
517
- rows = result.all_hashes
518
- result.free
519
- rows
520
- end
521
-
522
- def supports_views?
523
- version[0] >= 5
524
- end
525
-
526
- def version
527
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
528
- end
218
+ end
219
+
529
220
  end
530
221
  end
531
222
  end
@@ -0,0 +1,69 @@
1
+ module ActiveRecord
2
+
3
+ class Base
4
+ class << self
5
+
6
+ # Establishes a connection to the database that's used by all Active Record objects.
7
+ def mysql_replication_connection(config) # :nodoc:
8
+ config = config.symbolize_keys
9
+ host = config[:host]
10
+ port = config[:port]
11
+ socket = config[:socket]
12
+ username = config[:username] ? config[:username].to_s : 'root'
13
+ password = config[:password].to_s
14
+
15
+ if config.has_key?(:database)
16
+ database = config[:database]
17
+ else
18
+ raise ArgumentError, "No database specified. Missing argument: database."
19
+ end
20
+
21
+ require_mysql
22
+ mysql = Mysql.init
23
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
24
+
25
+ ConnectionAdapters::MysqlReplicationAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
26
+ end
27
+
28
+ alias_method :old_find_every, :find_every
29
+
30
+ VALID_FIND_OPTIONS << :use_slave
31
+
32
+ # Override the standard find to check for the :use_slave option. When specified, the
33
+ # resulting query will be sent to a slave machine.
34
+ def find_every(options)
35
+ result = if options[:use_slave] && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
36
+ connection.load_balance_query do
37
+ old_find_every(options)
38
+ end
39
+ else
40
+ old_find_every(options)
41
+ end
42
+ result
43
+ end
44
+
45
+ alias_method :old_find_by_sql, :find_by_sql
46
+ # Override find_by_sql so that you can tell it to selectively use a slave machine
47
+ def find_by_sql(sql, use_slave = false)
48
+ if use_slave && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
49
+ connection.load_balance_query {old_find_by_sql sql}
50
+ else
51
+ old_find_by_sql sql
52
+ end
53
+ end
54
+
55
+ alias_method :old_calculate, :calculate
56
+
57
+ def calculate(operation, column_name, options ={})
58
+ use_slave = options.delete(:use_slave)
59
+ if use_slave && connection.is_a?(ConnectionAdapters::MysqlReplicationAdapter)
60
+ connection.load_balance_query {old_calculate(operation, column_name, options)}
61
+ else
62
+ old_calculate(operation, column_name, options)
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -1,7 +1,7 @@
1
1
  module MysqlReplicationAdapter #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
4
+ MINOR = 4
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -1,2 +1,3 @@
1
1
  require 'test/unit'
2
+ require "rubygems"
2
3
  require File.dirname(__FILE__) + '/../lib/mysql_replication_adapter'
@@ -1,11 +1,61 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
+ class Person < ActiveRecord::Base
4
+
5
+ end
6
+
3
7
  class TestMysqlReplicationAdapter < Test::Unit::TestCase
4
8
 
5
9
  def setup
10
+ @adapter = get_adapter
11
+ ActiveRecord::Base.connection = @adapter
12
+
13
+ @adapter.drop_table :people rescue nil
14
+
15
+ @adapter.create_table :people do |t|
16
+ t.column :name, :string
17
+ end
18
+
19
+ end
20
+
21
+ def teardown
22
+ @adapter.drop_table :people
23
+ end
24
+
25
+ def get_adapter
26
+ ActiveRecord::Base.mysql_replication_connection(
27
+ {"host" => "localhost",
28
+ "username" => "root",
29
+ "password" => "",
30
+ "database" => "mysql_repl_test_master",
31
+ "retries" => 2,
32
+ "slaves" => [
33
+ {"host" => "localhost",
34
+ "username" => "root",
35
+ "password" => "",
36
+ "database" => "mysql_repl_test_slave_1"
37
+ },
38
+ {"host" => "localhost",
39
+ "username" => "root",
40
+ "password" => "",
41
+ "database" => "mysql_repl_test_slave_2"
42
+ }
43
+ ]
44
+ }
45
+ )
46
+ end
47
+
48
+ def test_insert
49
+ assert_not_nil Person.create(:name => "blah")
50
+ end
51
+
52
+ def test_lookup
53
+ Person.create(:name => "blah")
54
+ assert_equal Person.find(1).id, 1
6
55
  end
7
56
 
8
- def test_truth
9
- assert true
57
+ def test_slave_lookup
58
+ Person.create(:name => "blah")
59
+ assert_nil Person.find_by_id(1, :use_slave => true)
10
60
  end
11
61
  end
@@ -33,7 +33,7 @@
33
33
  <h1>mysql_replication_adapter</h1>
34
34
  <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/mysql_replication_adapter"; return false'>
35
35
  <p>Get Version</p>
36
- <a href="http://rubyforge.org/projects/mysql_replication_adapter" class="numbers">0.2.0</a>
36
+ <a href="http://rubyforge.org/projects/mysql_replication_adapter" class="numbers">0.4.0</a>
37
37
  </div>
38
38
  <h1>&#x2192; &#8216;mysql_replication_adapter&#8217;</h1>
39
39
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: mysql_replication_adapter
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
7
- date: 2007-08-01 00:00:00 -07:00
6
+ version: 0.4.0
7
+ date: 2007-11-02 00:00:00 -07:00
8
8
  summary: An ActiveRecord database adapter that allows you to specify a single write master and multiple read-only slaves.
9
9
  require_paths:
10
10
  - lib
@@ -36,6 +36,7 @@ files:
36
36
  - Rakefile
37
37
  - lib/mysql_replication_adapter.rb
38
38
  - lib/mysql_replication_adapter/version.rb
39
+ - lib/mysql_replication_adapter/ar_base_ext.rb
39
40
  - scripts/txt2html
40
41
  - setup.rb
41
42
  - test/test_helper.rb