rubyrep 1.1.0 → 1.1.1
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 +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
|
|