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 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
- # * +proxy_host+: name or IP address of where the proxy is running
14
- # * +proxy_port+: port on which the proxy is listening
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
- config_dump = Marshal.dump config.reject {|key, | [:proxy_host, :proxy_port].include? key}
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
- connection_cache[config_checksum] = db_connection if @@use_cache
116
- db_connection
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 #{params[:log_table]}(change_table, change_key, change_new_key, change_type, change_time)
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 #{params[:log_table]}(change_table, change_key, change_type, change_time)
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
 
@@ -2,7 +2,7 @@ module RR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 1
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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('rr', 'rr_duplicate').keys.should == ["rr_duplicate_id_seq"]
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.0
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: 2009-12-14 00:00:00 +09:00
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: 1.8.2
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.1
233
+ rubygems_version: 1.3.5
232
234
  signing_key:
233
- specification_version: 2
235
+ specification_version: 3
234
236
  summary: Asynchronous master-master replication of relational databases.
235
237
  test_files: []
236
238