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 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