rubyrep 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 1.2.0 2011-03-07
2
+
3
+ * Feature: compatibility with Rails 3
4
+ * Feature: do not replicate record updates that didn't change any fields (props to daudo)
5
+ * Bug fix: reducing deadlock problems (props to gtanzillo)
6
+ * Bug fix: adding missing schema prefix in PostgreSQL triggers
7
+ * Bug fix: scans / syncs fail due to incorrect handling of case sensitivity of string primary key columns
8
+ * Bug fix: reducing risk of foreign key conflicts during replication (props for root cause analysis to TonyB)
9
+
1
10
  == 1.1.2 2009-05-10
2
11
 
3
12
  * Bug fix: escape primary keys in replication triggers
data/bin/rubyrep CHANGED
File without changes
data/config/hoe.rb CHANGED
@@ -45,25 +45,23 @@ end
45
45
 
46
46
  # Generate all the Rake tasks
47
47
  # Run 'rake -T' to see list of generated tasks (from gem root directory)
48
- hoe = Hoe.new(GEM_NAME, VERS) do |p|
49
- p.author = AUTHOR
50
- p.description = DESCRIPTION
51
- p.email = EMAIL
52
- p.summary = DESCRIPTION
53
- p.url = HOMEPATH
54
- p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
55
- p.test_globs = ["test/**/test_*.rb"]
56
- p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
57
-
48
+ hoe = Hoe.spec(GEM_NAME) do
49
+ self.version = VERS
50
+ developer AUTHOR, EMAIL
51
+ description = DESCRIPTION
52
+ summary = DESCRIPTION
53
+ url = HOMEPATH
54
+ rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
55
+ test_globs = ["test/**/test_*.rb"]
56
+ clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
57
+
58
58
  # == Optional
59
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
60
- #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
61
- p.extra_deps = [
62
- ['activesupport', '>= 2.3.5'],
63
- ['activerecord' , '>= 2.3.5']
64
- ]
59
+ changes = paragraphs_of("History.txt", 0..1).join("\\n\\n")
60
+ #extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
61
+ extra_deps << ['activesupport', '>= 3.0.5']
62
+ extra_deps << ['activerecord' , '>= 3.0.5']
65
63
 
66
- #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+ #spec_extras = {} # A hash of extra values to set in the gemspec.
67
65
 
68
66
  end
69
67
 
data/lib/rubyrep.rb CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + "/rubyrep"
4
4
  require 'rubygems'
5
5
  require 'yaml'
6
6
 
7
- gem 'activerecord', '>= 2.2.2'
7
+ gem 'activerecord', '>= 3.0.5'
8
8
  require 'active_record'
9
9
 
10
10
  require 'version'
@@ -1,3 +1,8 @@
1
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+ # The current log subscriber
3
+ attr_accessor :log_subscriber
4
+ end
5
+
1
6
  class ActiveRecord::ConnectionAdapters::Column
2
7
  # Bug in ActiveRecord parsing of PostgreSQL timestamps with microseconds:
3
8
  # Certain values are incorrectly rounded, thus ending up with timestamps
@@ -107,9 +112,17 @@ module RR
107
112
  if config[:logger].respond_to?(:debug)
108
113
  logger = config[:logger]
109
114
  else
110
- logger = Logger.new(config[:logger])
115
+ logger = ActiveSupport::BufferedLogger.new(config[:logger])
111
116
  end
112
117
  db_connection.instance_variable_set :@logger, logger
118
+ if ActiveSupport.const_defined?(:Notifications)
119
+ connection_object_id = db_connection.object_id
120
+ db_connection.log_subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, payload|
121
+ if payload[:connection_id] == connection_object_id and logger.debug?
122
+ logger.debug payload[:sql].squeeze(" ")
123
+ end
124
+ end
125
+ end
113
126
  end
114
127
  end
115
128
 
@@ -58,23 +58,8 @@ module RR
58
58
  end
59
59
  end
60
60
 
61
- # activerecord-jdbc-adapter 0.9.1 (7b3f3eca08149567070837fad63696052dc36cd6)
62
- # improves SQLite binary support by overwriting the global string_to_binary
63
- # methods.
64
- # This appears to break binary support for MySQL.
65
- # And here comes the monkey patch to revert it again...
66
- require 'active_record/connection_adapters/jdbc_adapter_spec'
67
- require 'jdbc_adapter/jdbc_sqlite3'
68
- module ::ActiveRecord
69
- module ConnectionAdapters
70
- class JdbcColumn < Column
71
- def self.string_to_binary(value)
72
- value
73
- end
74
-
75
- def self.binary_to_string(value)
76
- value
77
- end
78
- end
79
- end
61
+ require 'activerecord-jdbc-adapter'
62
+ if ArJdbc.const_defined?(:PostgreSQL)
63
+ ArJdbc::PostgreSQL::RecordNotUnique = ActiveRecord::RecordNotUnique unless ArJdbc::PostgreSQL.const_defined?(:RecordNotUnique)
64
+ ArJdbc::PostgreSQL::InvalidForeignKey = ActiveRecord::InvalidForeignKey unless ArJdbc::PostgreSQL.const_defined?(:InvalidForeignKey)
80
65
  end
@@ -239,6 +239,11 @@ module RR
239
239
  end
240
240
  cursors.clear
241
241
 
242
+ if connection.log_subscriber
243
+ ActiveSupport::Notifications.notifier.unsubscribe connection.log_subscriber
244
+ connection.log_subscriber = nil
245
+ end
246
+
242
247
  self.connection.disconnect!
243
248
  end
244
249
 
@@ -19,6 +19,10 @@ module RR
19
19
  # * :+no_diff+: changes in both databases constitute no difference
20
20
  attr_accessor :type
21
21
 
22
+ # Is set to +true+ if first replication attempt failed but it should be tried again later
23
+ attr_accessor :second_chance
24
+ alias_method :second_chance?, :second_chance
25
+
22
26
  # A hash with keys :+left+ and / or :+right+.
23
27
  # Hash values are LoggedChange instances.
24
28
  def changes
@@ -45,13 +45,25 @@ module RR
45
45
  activity_check = ""
46
46
  if params[:exclude_rr_activity] then
47
47
  activity_check = <<-end_sql
48
- PERFORM ACTIVE FROM #{params[:activity_table]};
48
+ PERFORM ACTIVE FROM #{schema_prefix}#{params[:activity_table]};
49
49
  IF FOUND THEN
50
50
  RETURN NULL;
51
51
  END IF;
52
52
  end_sql
53
53
  end
54
54
 
55
+ version_string = select_value("select version();")
56
+ version = version_string.gsub(/^\s*postgresql\s*([0-9.]+).*$/i, '\1')
57
+ if version >= '8.4'
58
+ modification_check = <<-end_sql
59
+ IF NEW IS NOT DISTINCT FROM OLD THEN
60
+ RETURN NULL;
61
+ END IF;
62
+ end_sql
63
+ else
64
+ modification_check = ""
65
+ end
66
+
55
67
  # now create the trigger
56
68
  execute(<<-end_sql)
57
69
  CREATE OR REPLACE FUNCTION "#{params[:trigger_name]}"() RETURNS TRIGGER AS $change_trigger$
@@ -61,6 +73,7 @@ module RR
61
73
  INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_type, change_time)
62
74
  SELECT '#{params[:table]}', #{key_clause('OLD', params)}, 'D', now();
63
75
  ELSIF (TG_OP = 'UPDATE') THEN
76
+ #{modification_check}
64
77
  INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_new_key, change_type, change_time)
65
78
  SELECT '#{params[:table]}', #{key_clause('OLD', params)}, #{key_clause('NEW', params)}, 'U', now();
66
79
  ELSIF (TG_OP = 'INSERT') THEN
@@ -11,6 +11,11 @@ module RR
11
11
  # The current TaskSweeper
12
12
  attr_accessor :sweeper
13
13
 
14
+ # An array of ReplicationDifference which originally failed replication but should be tried one more time
15
+ def second_chancers
16
+ @second_chancers ||= []
17
+ end
18
+
14
19
  # Returns the current ReplicationHelper; creates it if necessary
15
20
  def helper
16
21
  @helper ||= ReplicationHelper.new(self)
@@ -39,6 +44,20 @@ module RR
39
44
  end
40
45
  end
41
46
 
47
+ # Returns the next available ReplicationDifference.
48
+ # (Either new unprocessed differences or if not available, the first available 'second chancer'.)
49
+ #
50
+ def load_difference
51
+ @loaders ||= LoggedChangeLoaders.new(session)
52
+ @loaders.update # ensure the cache of change log records is up-to-date
53
+ diff = ReplicationDifference.new @loaders
54
+ diff.load
55
+ unless diff.loaded? or second_chancers.empty?
56
+ diff = second_chancers.shift
57
+ end
58
+ diff
59
+ end
60
+
42
61
  # Executes the replication run.
43
62
  def run
44
63
  return unless [:left, :right].any? do |database|
@@ -57,29 +76,36 @@ module RR
57
76
  # Check for this and if timed out, return (silently).
58
77
  return if sweeper.terminated?
59
78
 
60
- loaders = LoggedChangeLoaders.new(session)
61
-
62
79
  success = false
63
80
  begin
64
81
  replicator # ensure that replicator is created and has chance to validate settings
65
82
 
66
83
  loop do
67
84
  begin
68
- loaders.update # ensure the cache of change log records is up-to-date
69
- diff = ReplicationDifference.new loaders
70
- diff.load
85
+ diff = load_difference
71
86
  break unless diff.loaded?
72
87
  break if sweeper.terminated?
73
88
  if diff.type != :no_diff and not event_filtered?(diff)
74
89
  replicator.replicate_difference diff
75
90
  end
76
91
  rescue Exception => e
77
- begin
78
- helper.log_replication_outcome diff, e.message,
79
- e.class.to_s + "\n" + e.backtrace.join("\n")
80
- rescue Exception => _
81
- # if logging to database itself fails, re-raise the original exception
82
- raise e
92
+ if e.message =~ /violates foreign key constraint|foreign key constraint fails/i and !diff.second_chance?
93
+ # Note:
94
+ # Identifying the foreign key constraint violation via regular expression is
95
+ # database dependent and *dirty*.
96
+ # It would be better to use the ActiveRecord #translate_exception mechanism.
97
+ # However as per version 3.0.5 this doesn't work yet properly.
98
+
99
+ diff.second_chance = true
100
+ second_chancers << diff
101
+ else
102
+ begin
103
+ helper.log_replication_outcome diff, e.message,
104
+ e.class.to_s + "\n" + e.backtrace.join("\n")
105
+ rescue Exception => _
106
+ # if logging to database itself fails, re-raise the original exception
107
+ raise e
108
+ end
83
109
  end
84
110
  end
85
111
  end
@@ -2,6 +2,12 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
 
3
3
  require 'optparse'
4
4
  require 'thread'
5
+ require 'monitor'
6
+
7
+ class Monitor
8
+ alias lock mon_enter
9
+ alias unlock mon_exit
10
+ end
5
11
 
6
12
  module RR
7
13
  # This class implements the functionality of the 'replicate' command.
@@ -94,7 +100,7 @@ EOS
94
100
  # Initializes the waiter thread used for replication pauses and processing
95
101
  # the process TERM signal.
96
102
  def init_waiter
97
- @termination_mutex = Mutex.new
103
+ @termination_mutex = Monitor.new
98
104
  @termination_mutex.lock
99
105
  @waiter_thread ||= Thread.new {@termination_mutex.lock; self.termination_requested = true}
100
106
  %w(TERM INT).each do |signal|
@@ -19,7 +19,15 @@ module RR
19
19
  return 1 unless left_row
20
20
  rank = 0
21
21
  primary_key_names.any? do |key|
22
- rank = left_row[key] <=> right_row[key]
22
+ if left_row[key].kind_of?(String)
23
+ # When databases order strings, then 'a' < 'A' while for Ruby 'A' < 'a'
24
+ # ==> Use a combination of case sensitive and case insensitive comparing to
25
+ # reproduce the database behaviour.
26
+ rank = left_row[key].casecmp(right_row[key]) # deal with 'a' to 'B' comparisons
27
+ rank = -(left_row[key] <=> right_row[key]) if rank == 0 # deal with 'a' to 'A' comparisons
28
+ else
29
+ rank = left_row[key] <=> right_row[key]
30
+ end
23
31
  rank != 0
24
32
  end
25
33
  rank
@@ -1,8 +1,8 @@
1
1
  module RR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 1
5
- TINY = 2
4
+ MINOR = 2
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,3 +1,4 @@
1
+
1
2
  require 'rake'
2
3
  require 'benchmark'
3
4
 
@@ -136,7 +137,7 @@ def populate_rep_data
136
137
  # Updating progress bar
137
138
  progress_bar.step
138
139
 
139
- database = [:left, :right].rand
140
+ database = [:left, :right][rand(2)]
140
141
 
141
142
  case rand(100)
142
143
  when 0...BIG_REP_INSERT
@@ -147,7 +148,7 @@ def populate_rep_data
147
148
  next_id += 1
148
149
  session.send(database).insert_record 'big_rep', attributes
149
150
  when BIG_REP_INSERT...BIG_REP_UPDATE
150
- id = all_ids[database].rand
151
+ id = all_ids[database][rand(all_ids[database].size)]
151
152
  attributes = session.send(database).select_one("select * from big_rep where id = '#{id}'")
152
153
  column = number_columns[rand(number_columns.size)]
153
154
  attributes[column] = rand(1000)
@@ -10,9 +10,13 @@ describe ConnectionExtenders do
10
10
  it "db_connect should install the already created logger" do
11
11
  configuration = deep_copy(Initializer.configuration)
12
12
  io = StringIO.new
13
- logger = Logger.new(io)
13
+ logger = ActiveSupport::BufferedLogger.new(io)
14
14
  configuration.left[:logger] = logger
15
15
  session = Session.new configuration
16
+
17
+ session.left.connection.instance_eval {@logger}.should == logger
18
+ session.right.connection.instance_eval {@logger}.should_not == logger
19
+
16
20
  session.left.select_one "select 'left_query'"
17
21
  session.right.select_one "select 'right_query'"
18
22
 
@@ -9,7 +9,7 @@ describe ProxyConnection do
9
9
  end
10
10
 
11
11
  it "initialize should connect to the database" do
12
- @connection.connection.active?.should == true
12
+ (!!@connection.connection.active?).should == true
13
13
  end
14
14
 
15
15
  it "initialize should store the configuratin" do
@@ -17,9 +17,21 @@ describe ProxyConnection do
17
17
  end
18
18
 
19
19
  it "destroy should disconnect from the database" do
20
+ if ActiveSupport.const_defined?(:Notifications)
21
+ ConnectionExtenders::install_logger @connection.connection, :logger => StringIO.new
22
+ log_subscriber = @connection.connection.log_subscriber
23
+
24
+ ActiveSupport::Notifications.notifier.listeners_for("sql.active_record").should include(log_subscriber)
25
+ end
26
+
20
27
  @connection.destroy
21
28
 
22
- @connection.connection.active?.should == false
29
+ if ActiveSupport.const_defined?(:Notifications)
30
+ ActiveSupport::Notifications.notifier.listeners_for("sql.active_record").should_not include(log_subscriber)
31
+ @connection.connection.log_subscriber.should be_nil
32
+ end
33
+
34
+ (!!@connection.connection.active?).should == false
23
35
  end
24
36
 
25
37
  it "cursors should return the current cursor hash or an empty hash if nil" do
@@ -135,6 +135,68 @@ describe ReplicationRun do
135
135
  end
136
136
  end
137
137
 
138
+ it "run should replication records with foreign key constraints" do
139
+ begin
140
+ config = deep_copy(standard_config)
141
+ config.options[:committer] = :never_commit
142
+
143
+ session = Session.new(config)
144
+
145
+ session.left.insert_record 'referencing_table', {
146
+ 'id' => '5',
147
+ }
148
+ session.left.insert_record 'rr_pending_changes', {
149
+ 'change_table' => 'referencing_table',
150
+ 'change_key' => 'id|5',
151
+ 'change_type' => 'I',
152
+ 'change_time' => Time.now
153
+ }
154
+
155
+ session.left.insert_record 'referenced_table2', {
156
+ 'id' => '6',
157
+ }
158
+ session.left.insert_record 'rr_pending_changes', {
159
+ 'change_table' => 'referenced_table2',
160
+ 'change_key' => 'id|6',
161
+ 'change_type' => 'I',
162
+ 'change_time' => Time.now
163
+ }
164
+
165
+ session.left.update_record 'referencing_table', {
166
+ 'id' => 5,
167
+ 'third_fk' => '6'
168
+ }
169
+ session.left.insert_record 'rr_pending_changes', {
170
+ 'change_table' => 'referencing_table',
171
+ 'change_key' => 'id|5',
172
+ 'change_new_key' => 'id|5',
173
+ 'change_type' => 'U',
174
+ 'change_time' => Time.now
175
+ }
176
+
177
+ run = ReplicationRun.new session, TaskSweeper.new(1)
178
+ run.run
179
+
180
+ session.right.select_record(:table => "referencing_table", :from => {'id' => 5}).should == {
181
+ 'id' => 5,
182
+ 'first_fk' => nil,
183
+ 'second_fk' => nil,
184
+ 'third_fk' => 6
185
+ }
186
+ ensure
187
+ Committers::NeverCommitter.rollback_current_session
188
+ if session
189
+ session.left.execute "delete from referencing_table where id = 5"
190
+ session.left.execute "delete from referenced_table2 where id = 6"
191
+
192
+ session.right.execute "delete from referencing_table where id = 5"
193
+ session.right.execute "delete from referenced_table2 where id = 6"
194
+
195
+ session.left.execute "delete from rr_pending_changes"
196
+ end
197
+ end
198
+ end
199
+
138
200
  it "run should not replicate filtered changes" do
139
201
  begin
140
202
  config = deep_copy(standard_config)
@@ -13,6 +13,13 @@ describe TableScanHelper do
13
13
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 1}).should == 0
14
14
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 2}).should == -1
15
15
  @scan.rank_rows({'first_id' => 2, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 1}).should == 1
16
+
17
+ # should rank strings according to database logic ('a' < 'A')
18
+ # instead of the Ruby logic (which is the other way round)
19
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'B', 'second_id' => 1}).should == -1
20
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'A', 'second_id' => 1}).should == -1
21
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'a', 'second_id' => 1}).should == 0
22
+
16
23
  lambda {@scan.rank_rows(nil,nil)}.should raise_error(RuntimeError, 'At least one of left_row and right_row must not be nil!')
17
24
  @scan.rank_rows(nil, {'first_id' => 1, 'second_id' => 1}).should == 1
18
25
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, nil).should == -1
data/tasks/database.rake CHANGED
@@ -18,7 +18,7 @@ def create_database(config)
18
18
  @charset = ENV['CHARSET'] || 'utf8'
19
19
  @collation = ENV['COLLATION'] || 'utf8_general_ci'
20
20
  begin
21
- connection = RR::ConnectionExtenders.db_connect(config.merge({'database' => nil}))
21
+ connection = RR::ConnectionExtenders.db_connect(config.merge({:database => nil}))
22
22
  connection.create_database(config[:database], {:charset => @charset, :collation => @collation})
23
23
  RR::ConnectionExtenders.db_connect(config)
24
24
  rescue
data/tasks/rspec.rake CHANGED
@@ -41,18 +41,14 @@ namespace :spec do
41
41
  desc "Run the specs for all supported databases"
42
42
  task :all_dbs do
43
43
  [:postgres, :mysql].each do |test_db|
44
- puts "Running specs for #{test_db.id2name}"
45
- ENV['RR_TEST_DB'] = test_db.id2name
46
- system "spec spec"
44
+ puts "Running specs for #{test_db}"
45
+ system "bash -c 'RR_TEST_DB=#{test_db} spec spec'"
47
46
  end
48
47
  end
49
48
 
50
49
  desc "Run the specs for all supported databases and ruby platforms"
51
50
  task :all_rubies do
52
- puts "Running spec:all_dbs in standard ruby"
53
- system "rake spec:all_dbs"
54
- puts "Running spec:all_dbs in jruby"
55
- system "export PATH=#{JRUBY_HOME}/bin:$PATH; rake spec:all_dbs"
51
+ system %(rvm exec bash -c 'for db in postgres mysql; do echo "`rvm current` - $db:"; RR_TEST_DB=$db spec spec; done')
56
52
  end
57
53
 
58
54
  begin
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyrep
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 31
5
+ prerelease:
5
6
  segments:
6
7
  - 1
7
- - 1
8
8
  - 2
9
- version: 1.1.2
9
+ - 0
10
+ version: 1.2.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Arndt Lehmann
@@ -14,53 +15,60 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-05-10 00:00:00 +09:00
18
+ date: 2011-03-07 00:00:00 +09:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: activesupport
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 13
27
30
  segments:
28
- - 2
29
31
  - 3
32
+ - 0
30
33
  - 5
31
- version: 2.3.5
34
+ version: 3.0.5
32
35
  type: :runtime
33
36
  version_requirements: *id001
34
37
  - !ruby/object:Gem::Dependency
35
38
  name: activerecord
36
39
  prerelease: false
37
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
38
42
  requirements:
39
43
  - - ">="
40
44
  - !ruby/object:Gem::Version
45
+ hash: 13
41
46
  segments:
42
- - 2
43
47
  - 3
48
+ - 0
44
49
  - 5
45
- version: 2.3.5
50
+ version: 3.0.5
46
51
  type: :runtime
47
52
  version_requirements: *id002
48
53
  - !ruby/object:Gem::Dependency
49
54
  name: hoe
50
55
  prerelease: false
51
56
  requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
52
58
  requirements:
53
59
  - - ">="
54
60
  - !ruby/object:Gem::Version
61
+ hash: 41
55
62
  segments:
56
63
  - 2
57
- - 4
58
- - 0
59
- version: 2.4.0
64
+ - 9
65
+ - 1
66
+ version: 2.9.1
60
67
  type: :development
61
68
  version_requirements: *id003
62
- description: Asynchronous master-master replication of relational databases.
63
- email: mail@arndtlehman.com
69
+ description: ""
70
+ email:
71
+ - mail@arndtlehman.com
64
72
  executables:
65
73
  - rubyrep
66
74
  extensions: []
@@ -222,8 +230,9 @@ files:
222
230
  - tasks/stats.rake
223
231
  - tasks/task_helper.rb
224
232
  - tasks/website.rake
233
+ - .gemtest
225
234
  has_rdoc: true
226
- homepage: http://rubyrep.rubyforge.org
235
+ homepage:
227
236
  licenses: []
228
237
 
229
238
  post_install_message:
@@ -233,25 +242,29 @@ rdoc_options:
233
242
  require_paths:
234
243
  - lib
235
244
  required_ruby_version: !ruby/object:Gem::Requirement
245
+ none: false
236
246
  requirements:
237
247
  - - ">="
238
248
  - !ruby/object:Gem::Version
249
+ hash: 3
239
250
  segments:
240
251
  - 0
241
252
  version: "0"
242
253
  required_rubygems_version: !ruby/object:Gem::Requirement
254
+ none: false
243
255
  requirements:
244
256
  - - ">="
245
257
  - !ruby/object:Gem::Version
258
+ hash: 3
246
259
  segments:
247
260
  - 0
248
261
  version: "0"
249
262
  requirements: []
250
263
 
251
264
  rubyforge_project: rubyrep
252
- rubygems_version: 1.3.6
265
+ rubygems_version: 1.5.2
253
266
  signing_key:
254
267
  specification_version: 3
255
- summary: Asynchronous master-master replication of relational databases.
268
+ summary: ""
256
269
  test_files: []
257
270