rubyrep 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/lib/rubyrep/configuration.rb +11 -2
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +31 -11
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +23 -4
- data/lib/rubyrep/version.rb +1 -1
- data/spec/connection_extenders_registration_spec.rb +32 -1
- data/spec/postgresql_schema_support_spec.rb +31 -1
- data/tasks/database.rake +8 -0
- metadata +7 -5
data/History.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 1.1.1 2009-02-02
|
2
|
+
|
3
|
+
* Feature: optional use of an SQL logger
|
4
|
+
* Bug fix: under postgresql, rubyrep triggers now finds the pending_changes table also if not included in current search path
|
5
|
+
|
1
6
|
== 1.1.0 2009-12-15
|
2
7
|
|
3
8
|
* Feature: filtering of sync / replication events with custom specified functionality
|
@@ -10,8 +10,17 @@ module RR
|
|
10
10
|
# Connection settings for the "right" database.
|
11
11
|
# Takes a similar hash as ActiveRecord::Base.establish_connection.
|
12
12
|
# Additional settings in case a proxy is used:
|
13
|
-
# *
|
14
|
-
# *
|
13
|
+
# * :+proxy_host+: name or IP address of where the proxy is running
|
14
|
+
# * :+proxy_port+: port on which the proxy is listening
|
15
|
+
# Other additional settings:
|
16
|
+
# * :+logger+:
|
17
|
+
# Specify an SQL statement logger for this database connection.
|
18
|
+
# Can be either
|
19
|
+
# * a logger instance itself (Logger or Log4r::Logger) or
|
20
|
+
# * the parameter to create a Logger with Logger.new
|
21
|
+
# Examples:
|
22
|
+
# +config.left[:logger] = STDOUT
|
23
|
+
# +config.right[:logger] = Logger.new('rubyrep_debug.log')
|
15
24
|
attr_accessor :right
|
16
25
|
|
17
26
|
# Returns true unless running on windows...
|
@@ -59,7 +59,7 @@ module RR
|
|
59
59
|
DummyActiveRecord.establish_connection(config)
|
60
60
|
end
|
61
61
|
connection = DummyActiveRecord.connection
|
62
|
-
|
62
|
+
|
63
63
|
# Delete the database connection from ActiveRecords's 'memory'
|
64
64
|
ActiveRecord::Base.connection_handler.connection_pools.delete DummyActiveRecord.name
|
65
65
|
|
@@ -98,25 +98,45 @@ module RR
|
|
98
98
|
def self.connection_cache=(cache)
|
99
99
|
@@connection_cache = cache
|
100
100
|
end
|
101
|
+
|
102
|
+
# Installs the configured logger (if any) into the database connection.
|
103
|
+
# * +db_connection+: database connection (as produced by #db_connect)
|
104
|
+
# * +config+: database configuration (as provided to #db_connect)
|
105
|
+
def self.install_logger(db_connection, config)
|
106
|
+
if config[:logger]
|
107
|
+
if config[:logger].respond_to?(:debug)
|
108
|
+
logger = config[:logger]
|
109
|
+
else
|
110
|
+
logger = Logger.new(config[:logger])
|
111
|
+
end
|
112
|
+
db_connection.instance_variable_set :@logger, logger
|
113
|
+
end
|
114
|
+
end
|
101
115
|
|
102
116
|
# Creates database connections by calling #db_connect_without_cache with the
|
103
117
|
# provided +config+ configuration hash.
|
104
118
|
# A new database connection is created only if no according cached connection
|
105
119
|
# is available.
|
106
120
|
def self.db_connect(config)
|
107
|
-
|
108
|
-
config_checksum = Digest::SHA1.hexdigest(config_dump)
|
109
|
-
@@connection_cache ||= {}
|
110
|
-
cached_db_connection = connection_cache[config_checksum]
|
111
|
-
if use_cache? and cached_db_connection and cached_db_connection.active?
|
112
|
-
cached_db_connection
|
113
|
-
else
|
121
|
+
if not use_cache?
|
114
122
|
db_connection = db_connect_without_cache config
|
115
|
-
|
116
|
-
|
123
|
+
else
|
124
|
+
config_dump = Marshal.dump config.reject {|key, | [:proxy_host, :proxy_port, :logger].include? key}
|
125
|
+
config_checksum = Digest::SHA1.hexdigest(config_dump)
|
126
|
+
@@connection_cache ||= {}
|
127
|
+
|
128
|
+
db_connection = connection_cache[config_checksum]
|
129
|
+
unless db_connection and db_connection.active?
|
130
|
+
db_connection = db_connect_without_cache config
|
131
|
+
connection_cache[config_checksum] = db_connection
|
132
|
+
end
|
117
133
|
end
|
134
|
+
|
135
|
+
install_logger db_connection, config
|
136
|
+
|
137
|
+
db_connection
|
118
138
|
end
|
119
|
-
|
139
|
+
|
120
140
|
# If status == true: enable the cache. If status == false: don' use cache
|
121
141
|
# Returns the old connection caching status
|
122
142
|
def self.use_db_connection_cache(status)
|
@@ -15,6 +15,25 @@ module RR
|
|
15
15
|
end
|
16
16
|
private :key_clause
|
17
17
|
|
18
|
+
# Returns the schema prefix (including dot) that will be used by the
|
19
|
+
# triggers to write into the rubyrep infrastructure tables.
|
20
|
+
# To avoid setting the wrong prefix, it will only return a schema prefix
|
21
|
+
# if the current search path
|
22
|
+
# * consists of only a single schema
|
23
|
+
# * does not consists of a variable search path
|
24
|
+
# (i. e. the default "$user")
|
25
|
+
def schema_prefix
|
26
|
+
unless @schema_prefix
|
27
|
+
search_path = select_one("show search_path")['search_path']
|
28
|
+
if search_path =~ /[,$]/
|
29
|
+
@schema_prefix = ""
|
30
|
+
else
|
31
|
+
@schema_prefix = %("#{search_path}".)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@schema_prefix
|
35
|
+
end
|
36
|
+
|
18
37
|
# Creates or replaces the replication trigger function.
|
19
38
|
# See #create_replication_trigger for a descriptions of the +params+ hash.
|
20
39
|
def create_or_replace_replication_trigger_function(params)
|
@@ -39,13 +58,13 @@ module RR
|
|
39
58
|
BEGIN
|
40
59
|
#{activity_check}
|
41
60
|
IF (TG_OP = 'DELETE') THEN
|
42
|
-
INSERT INTO #{params[:log_table]}(change_table, change_key, change_type, change_time)
|
61
|
+
INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_type, change_time)
|
43
62
|
SELECT '#{params[:table]}', #{key_clause('OLD', params)}, 'D', now();
|
44
63
|
ELSIF (TG_OP = 'UPDATE') THEN
|
45
|
-
INSERT INTO
|
64
|
+
INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_new_key, change_type, change_time)
|
46
65
|
SELECT '#{params[:table]}', #{key_clause('OLD', params)}, #{key_clause('NEW', params)}, 'U', now();
|
47
66
|
ELSIF (TG_OP = 'INSERT') THEN
|
48
|
-
INSERT INTO
|
67
|
+
INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_type, change_time)
|
49
68
|
SELECT '#{params[:table]}', #{key_clause('NEW', params)}, 'I', now();
|
50
69
|
END IF;
|
51
70
|
RETURN NULL; -- result is ignored since this is an AFTER trigger
|
@@ -71,7 +90,7 @@ module RR
|
|
71
90
|
execute(<<-end_sql)
|
72
91
|
CREATE TRIGGER "#{params[:trigger_name]}"
|
73
92
|
AFTER INSERT OR UPDATE OR DELETE ON "#{params[:table]}"
|
74
|
-
FOR EACH ROW EXECUTE PROCEDURE "#{params[:trigger_name]}"();
|
93
|
+
FOR EACH ROW EXECUTE PROCEDURE #{schema_prefix}"#{params[:trigger_name]}"();
|
75
94
|
end_sql
|
76
95
|
end
|
77
96
|
|
data/lib/rubyrep/version.rb
CHANGED
@@ -2,6 +2,37 @@ require File.dirname(__FILE__) + '/spec_helper.rb'
|
|
2
2
|
|
3
3
|
include RR
|
4
4
|
|
5
|
+
describe ConnectionExtenders do
|
6
|
+
before(:each) do
|
7
|
+
Initializer.configuration = standard_config
|
8
|
+
end
|
9
|
+
|
10
|
+
it "db_connect should install the already created logger" do
|
11
|
+
configuration = deep_copy(Initializer.configuration)
|
12
|
+
io = StringIO.new
|
13
|
+
logger = Logger.new(io)
|
14
|
+
configuration.left[:logger] = logger
|
15
|
+
session = Session.new configuration
|
16
|
+
session.left.select_one "select 'left_query'"
|
17
|
+
session.right.select_one "select 'right_query'"
|
18
|
+
|
19
|
+
io.string.should =~ /left_query/
|
20
|
+
io.string.should_not =~ /right_query/
|
21
|
+
end
|
22
|
+
|
23
|
+
it "db_connect should create and install the specified logger" do
|
24
|
+
configuration = deep_copy(Initializer.configuration)
|
25
|
+
io = StringIO.new
|
26
|
+
configuration.left[:logger] = io
|
27
|
+
session = Session.new configuration
|
28
|
+
session.left.select_one "select 'left_query'"
|
29
|
+
session.right.select_one "select 'right_query'"
|
30
|
+
|
31
|
+
io.string.should =~ /left_query/
|
32
|
+
io.string.should_not =~ /right_query/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
5
36
|
describe ConnectionExtenders, "Registration" do
|
6
37
|
before(:each) do
|
7
38
|
Initializer.configuration = standard_config
|
@@ -34,7 +65,7 @@ describe ConnectionExtenders, "Registration" do
|
|
34
65
|
|
35
66
|
ConnectionExtenders.db_connect Initializer.configuration.left
|
36
67
|
end
|
37
|
-
|
68
|
+
|
38
69
|
it "db_connect should use jdbc configuration adapter and extender under jruby" do
|
39
70
|
fake_ruby_platform 'java' do
|
40
71
|
mock_active_record :once
|
@@ -7,6 +7,7 @@ require File.dirname(__FILE__) + "/../config/test_config.rb"
|
|
7
7
|
describe "PostgreSQL schema support" do
|
8
8
|
before(:each) do
|
9
9
|
config = deep_copy(standard_config)
|
10
|
+
config.options[:rep_prefix] = 'rx'
|
10
11
|
config.left[:schema_search_path] = 'rr'
|
11
12
|
config.right[:schema_search_path] = 'rr'
|
12
13
|
Initializer.configuration = config
|
@@ -79,7 +80,7 @@ describe "PostgreSQL schema support" do
|
|
79
80
|
|
80
81
|
it "sequence_values should pick the table in the target schema" do
|
81
82
|
session = Session.new
|
82
|
-
session.left.sequence_values('
|
83
|
+
session.left.sequence_values('rx', 'rr_duplicate').keys.should == ["rr_duplicate_id_seq"]
|
83
84
|
end
|
84
85
|
|
85
86
|
it "clear_sequence_setup should pick the table in the target schema" do
|
@@ -150,6 +151,24 @@ describe "PostgreSQL schema support" do
|
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
154
|
+
it "initializer should create tables in target schema" do
|
155
|
+
session = nil
|
156
|
+
begin
|
157
|
+
config = deep_copy(Initializer.configuration)
|
158
|
+
config.options[:rep_prefix] = 'ry'
|
159
|
+
session = Session.new config
|
160
|
+
session.left.begin_db_transaction
|
161
|
+
|
162
|
+
initializer = ReplicationInitializer.new(session)
|
163
|
+
initializer.create_change_log(:left)
|
164
|
+
|
165
|
+
# no exception ==> means table was created in target schema
|
166
|
+
session.left.select_one("select id from rr.ry_pending_changes")
|
167
|
+
ensure
|
168
|
+
session.left.rollback_db_transaction if session
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
153
172
|
it "create_trigger, trigger_exists? and drop_trigger should work" do
|
154
173
|
session = nil
|
155
174
|
begin
|
@@ -160,6 +179,17 @@ describe "PostgreSQL schema support" do
|
|
160
179
|
initializer.create_trigger :left, 'rr_trigger_test'
|
161
180
|
initializer.trigger_exists?(:left, 'rr_trigger_test').
|
162
181
|
should be_true
|
182
|
+
|
183
|
+
# Verify that the trigger can find the pending_changes table even if
|
184
|
+
# current search_path does not include it.
|
185
|
+
session.left.execute "set search_path = 'public'"
|
186
|
+
session.left.execute <<-EOF
|
187
|
+
insert into rr.rr_trigger_test(first_id, second_id) values(10, 11)
|
188
|
+
EOF
|
189
|
+
session.left.execute "set search_path = 'rr'"
|
190
|
+
session.left.select_one("select change_key from rx_pending_changes")['change_key'].
|
191
|
+
should == "first_id|10|second_id|11"
|
192
|
+
|
163
193
|
initializer.drop_trigger(:left, 'rr_trigger_test')
|
164
194
|
initializer.trigger_exists?(:left, 'rr_trigger_test').
|
165
195
|
should be_false
|
data/tasks/database.rake
CHANGED
@@ -94,6 +94,14 @@ def create_postgres_schema(config)
|
|
94
94
|
create_table :rr_duplicate do |t|
|
95
95
|
t.column :name, :string
|
96
96
|
end
|
97
|
+
|
98
|
+
create_table :rx_pending_changes do |t|
|
99
|
+
t.column :change_table, :string
|
100
|
+
t.column :change_key, :string
|
101
|
+
t.column :change_new_key, :string
|
102
|
+
t.column :change_type, :string
|
103
|
+
t.column :change_time, :timestamp
|
104
|
+
end
|
97
105
|
end
|
98
106
|
end
|
99
107
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyrep
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arndt Lehmann
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-02-03 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
43
|
+
version: 2.4.0
|
44
44
|
version:
|
45
45
|
description: Asynchronous master-master replication of relational databases.
|
46
46
|
email: mail@arndtlehman.com
|
@@ -207,6 +207,8 @@ files:
|
|
207
207
|
- tasks/website.rake
|
208
208
|
has_rdoc: true
|
209
209
|
homepage: http://rubyrep.rubyforge.org
|
210
|
+
licenses: []
|
211
|
+
|
210
212
|
post_install_message:
|
211
213
|
rdoc_options:
|
212
214
|
- --main
|
@@ -228,9 +230,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
228
230
|
requirements: []
|
229
231
|
|
230
232
|
rubyforge_project: rubyrep
|
231
|
-
rubygems_version: 1.3.
|
233
|
+
rubygems_version: 1.3.5
|
232
234
|
signing_key:
|
233
|
-
specification_version:
|
235
|
+
specification_version: 3
|
234
236
|
summary: Asynchronous master-master replication of relational databases.
|
235
237
|
test_files: []
|
236
238
|
|