pg_easy_replicate 0.1.7 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b855bf14bd72bcf4ace60b2f746139d76408d6a00e0ae7ec3fd2d0c276cf22b
4
- data.tar.gz: 1eff23811d7e9d8608c241bcc7a67367c8de8271ea53d6960f57859aa5c186fd
3
+ metadata.gz: 02ca1ab51b854644e7a44fcb9a166a363b608784f4c38a6872cc6be5d35b21f3
4
+ data.tar.gz: dfb7b875e61b5c032940af84417ce7cf35b75897039ba4efd400513d7da5de20
5
5
  SHA512:
6
- metadata.gz: a7de21167ee2027115972442b9aa859fba7954bdc2e04b8e27e92d6fbadd2e7c9eb92c700d01f69ba9fecd5b40fe5593b5d0467ba4b2766106a7951311401842
7
- data.tar.gz: c38c45df66c9a4433b558a41f120f8c3304b5cbd9ea1072348df61397b7e51b38d1709b83c160917d69fbcf460e18dc9f9355ea7c96ae3d109ba40b4430ff06c
6
+ metadata.gz: 698b38142b8f8a8c9a64623d9a3bb06791a3a5bc174c4abffc49a480787d6cf33a002b75e9e6a61c23f443c962818544ef3c98fab5970bc1bccf1224ebaa8cb6
7
+ data.tar.gz: f6942a91cade0e60c9c5e245f4cabe9a7d0e698316855c7eacbd53bc1b2769a3d465e12dc7e1cda34f10149b24b3bfde35a585ab6b8583f9645b9faae5439021
data/CHANGELOG.md CHANGED
@@ -1,13 +1,23 @@
1
- ## [0.1.6] - 2023-06-22
1
+ ## [0.1.8] - 2023-07-23
2
+
3
+ - Introduce --copy_schema via pg_dump - #35
4
+
5
+ ## [0.1.7] - 2023-06-26
6
+
7
+ - Perform smoke test with retries in CI - #26
8
+ - Default schema to `public` #29
9
+ - Perform vacuum and analyze before and after switchover - #30
10
+
11
+ ## [0.1.6] - 2023-06-24
2
12
 
3
13
  - Bug fix: Support custom schema name
4
14
  - New smoke spec in CI
5
15
 
6
- ## [0.1.5] - 2023-06-22
16
+ ## [0.1.5] - 2023-06-24
7
17
 
8
18
  - Fix bug in `stop_sync`
9
19
 
10
- ## [0.1.4] - 2023-06-22
20
+ ## [0.1.4] - 2023-06-24
11
21
 
12
22
  - Drop lockbox dependency
13
23
  - Support password with special chars and test for url encoded URI
data/Dockerfile CHANGED
@@ -2,4 +2,6 @@ FROM ruby:3.1.4
2
2
 
3
3
  ARG VERSION
4
4
 
5
+ RUN apt-get update && apt-get install postgresql-client -y
6
+ RUN pg_dump --version
5
7
  RUN gem install pg_easy_replicate -v $VERSION
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg_easy_replicate (0.1.7)
4
+ pg_easy_replicate (0.1.9)
5
5
  ougai (~> 2.0.0)
6
6
  pg (~> 1.5.3)
7
- sequel (~> 5.69.0)
7
+ sequel (>= 5.69, < 5.71)
8
8
  thor (~> 1.2.2)
9
9
 
10
10
  GEM
@@ -18,6 +18,7 @@ GEM
18
18
  thor
19
19
  tilt
20
20
  json (2.6.3)
21
+ language_server-protocol (3.17.0.3)
21
22
  method_source (1.0.0)
22
23
  oj (3.14.3)
23
24
  ougai (2.0.0)
@@ -36,7 +37,7 @@ GEM
36
37
  rake (13.0.6)
37
38
  rbs (3.1.0)
38
39
  regexp_parser (2.8.1)
39
- rexml (3.2.5)
40
+ rexml (3.2.6)
40
41
  rspec (3.12.0)
41
42
  rspec-core (~> 3.12.0)
42
43
  rspec-expectations (~> 3.12.0)
@@ -50,14 +51,15 @@ GEM
50
51
  diff-lcs (>= 1.2.0, < 2.0)
51
52
  rspec-support (~> 3.12.0)
52
53
  rspec-support (3.12.0)
53
- rubocop (1.52.1)
54
+ rubocop (1.55.0)
54
55
  json (~> 2.3)
56
+ language_server-protocol (>= 3.17.0)
55
57
  parallel (~> 1.10)
56
58
  parser (>= 3.2.2.3)
57
59
  rainbow (>= 2.2.2, < 4.0)
58
60
  regexp_parser (>= 1.8, < 3.0)
59
61
  rexml (>= 3.2.5, < 4.0)
60
- rubocop-ast (>= 1.28.0, < 2.0)
62
+ rubocop-ast (>= 1.28.1, < 2.0)
61
63
  ruby-progressbar (~> 1.7)
62
64
  unicode-display_width (>= 2.4.0, < 3.0)
63
65
  rubocop-ast (1.29.0)
@@ -73,12 +75,12 @@ GEM
73
75
  rubocop-ast (>= 0.4.0)
74
76
  rubocop-rake (0.6.0)
75
77
  rubocop (~> 1.0)
76
- rubocop-rspec (2.22.0)
78
+ rubocop-rspec (2.23.0)
77
79
  rubocop (~> 1.33)
78
80
  rubocop-capybara (~> 2.17)
79
81
  rubocop-factory_bot (~> 2.22)
80
82
  ruby-progressbar (1.13.0)
81
- sequel (5.69.0)
83
+ sequel (5.70.0)
82
84
  syntax_tree (6.1.1)
83
85
  prettier_print (>= 1.2.0)
84
86
  syntax_tree-haml (4.0.3)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/shayonj/pg_easy_replicate/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/shayonj/pg_easy_replicate/actions/workflows/ci.yaml)
4
4
  [![Smoke spec](https://github.com/shayonj/pg_easy_replicate/actions/workflows/smoke.yaml/badge.svg?branch=main)](https://github.com/shayonj/pg_easy_replicate/actions/workflows/ci.yaml)
5
- [![Gem Version](https://badge.fury.io/rb/pg_easy_replicate.svg?1)](https://badge.fury.io/rb/pg_easy_replicate)
5
+ [![Gem Version](https://badge.fury.io/rb/pg_easy_replicate.svg?2)](https://badge.fury.io/rb/pg_easy_replicate)
6
6
 
7
7
  `pg_easy_replicate` is a CLI orchestrator tool that simplifies the process of setting up [logical replication](https://www.postgresql.org/docs/current/logical-replication.html) between two PostgreSQL databases. `pg_easy_replicate` also supports switchover. After the source (primary database) is fully replicating, `pg_easy_replicate` puts it into read-only mode and via logical replication flushes all data to the new target database. This ensures zero data loss and minimal downtime for the application. This method can be useful for performing minimal downtime (up to <1min, depending) major version upgrades between two PostgreSQL databases, load testing with blue/green database setup and other similar use cases.
8
8
 
@@ -26,6 +26,8 @@ Battle tested in production at [Tines](https://www.tines.com/) 🚀
26
26
  - [Switchover strategies with minimal downtime](#switchover-strategies-with-minimal-downtime)
27
27
  - [Rolling restart strategy](#rolling-restart-strategy)
28
28
  - [DNS Failover strategy](#dns-failover-strategy)
29
+ - [FAQ](#faq)
30
+ - [Adding internal user to pgBouncer `userlist`](#adding-internal-user-to-pgbouncer-userlist)
29
31
  - [Contributing](#contributing)
30
32
 
31
33
  ## Installation
@@ -57,7 +59,6 @@ https://hub.docker.com/r/shayonj/pg_easy_replicate
57
59
  - PostgreSQL 10 and later
58
60
  - Ruby 2.7 and later
59
61
  - Database user should have permissions for `SUPERUSER` or pass in the special user role that has the privileges to create role, schema, publication and subscription on both databases. More on `--special-user-role` section below.
60
- - Both databases should have the same schema
61
62
 
62
63
  ## Limits
63
64
 
@@ -115,7 +116,7 @@ $ pg_easy_replicate config_check
115
116
  Every sync will need to be bootstrapped before you can set up the sync between two databases. Bootstrap creates a new super user to perform the orchestration required during the rest of the process. It also creates some internal metadata tables for record keeping.
116
117
 
117
118
  ```bash
118
- $ pg_easy_replicate bootstrap --group-name database-cluster-1
119
+ $ pg_easy_replicate bootstrap --group-name database-cluster-1 --copy-schema
119
120
 
120
121
  {"name":"pg_easy_replicate","hostname":"PKHXQVK6DW","pid":21485,"level":30,"time":"2023-06-19T15:51:11.015-04:00","v":0,"msg":"Setting up schema","version":"0.1.0"}
121
122
  ...
@@ -132,7 +133,7 @@ For AWS the special user role is `rds_superuser`, and for GCP it is `cloudsqlsup
132
133
  #### Config Check
133
134
 
134
135
  ```bash
135
- $ pg_easy_replicate config_check --special-user-role="rds_superuser"
136
+ $ pg_easy_replicate config_check --special-user-role="rds_superuser" --copy-schema
136
137
 
137
138
  ✅ Config is looking good.
138
139
  ```
@@ -140,7 +141,7 @@ $ pg_easy_replicate config_check --special-user-role="rds_superuser"
140
141
  #### Bootstrap
141
142
 
142
143
  ```bash
143
- $ pg_easy_replicate bootstrap --group-name database-cluster-1 --special-user-role="rds_superuser"
144
+ $ pg_easy_replicate bootstrap --group-name database-cluster-1 --special-user-role="rds_superuser" --copy-schema
144
145
 
145
146
  {"name":"pg_easy_replicate","hostname":"PKHXQVK6DW","pid":21485,"level":30,"time":"2023-06-19T15:51:11.015-04:00","v":0,"msg":"Setting up schema","version":"0.1.0"}
146
147
  ...
@@ -217,12 +218,12 @@ By default all tables are added for replication but you can create multiple grou
217
218
 
218
219
  ```bash
219
220
 
220
- $ pg_easy_replicate bootstrap --group-name database-cluster-1
221
+ $ pg_easy_replicate bootstrap --group-name database-cluster-1 --copy-schema
221
222
  $ pg_easy_replicate start_sync --group-name database-cluster-1 --schema-name public --tables "users, posts, events"
222
223
 
223
224
  ...
224
225
 
225
- $ pg_easy_replicate bootstrap --group-name database-cluster-2
226
+ $ pg_easy_replicate bootstrap --group-name database-cluster-2 --copy-schema
226
227
  $ pg_easy_replicate start_sync --group-name database-cluster-2 --schema-name public --tables "comments, views"
227
228
 
228
229
  ...
@@ -247,6 +248,12 @@ In this strategy, you have a weighted based DNS system (example [AWS Route53 wei
247
248
 
248
249
  Next, you can set up a program that watches the `stats` and waits until `switchover_completed_at` is reporting as `true`. Once that happens it updates the weight in the DNS weighted group where 100% of the requests now go to the new/target database. Note: Keeping a low `ttl` is recommended.
249
250
 
251
+ ## FAQ
252
+
253
+ ### Adding internal user to pgBouncer `userlist`
254
+
255
+ `pg_easy_replicate` creates a special user to orchestrate the replication. If you us pgBouncer, you may need to allow `pger_su_h1a4fb` as a user that can perform login by adding it to the `userlist`.
256
+
250
257
  ## Contributing
251
258
 
252
259
  PRs most welcome. You can get started locally by
data/docker-compose.yml CHANGED
@@ -5,8 +5,8 @@ services:
5
5
  ports:
6
6
  - "5432:5432"
7
7
  environment:
8
- POSTGRES_USER: jamesbond
9
- POSTGRES_PASSWORD: jamesbond123@7!'3aaR
8
+ POSTGRES_USER: james-bond
9
+ POSTGRES_PASSWORD: james-bond123@7!'3aaR
10
10
  POSTGRES_DB: postgres
11
11
  command: >
12
12
  -c wal_level=logical
@@ -21,8 +21,8 @@ services:
21
21
  ports:
22
22
  - "5433:5432"
23
23
  environment:
24
- POSTGRES_USER: jamesbond
25
- POSTGRES_PASSWORD: jamesbond123@7!'3aaR
24
+ POSTGRES_USER: james-bond
25
+ POSTGRES_PASSWORD: james-bond123@7!'3aaR
26
26
  POSTGRES_DB: postgres
27
27
  command: >
28
28
  -c wal_level=logical
@@ -12,9 +12,14 @@ module PgEasyReplicate
12
12
  aliases: "-s",
13
13
  desc:
14
14
  "Name of the role that has superuser permissions. Usually useful for AWS (rds_superuser) or GCP (cloudsqlsuperuser)."
15
+ method_option :copy_schema,
16
+ aliases: "-c",
17
+ boolean: true,
18
+ desc: "Copy schema to the new database"
15
19
  def config_check
16
20
  PgEasyReplicate.assert_config(
17
21
  special_user_role: options[:special_user_role],
22
+ copy_schema: options[:copy_schema],
18
23
  )
19
24
 
20
25
  puts "✅ Config is looking good."
@@ -28,6 +33,10 @@ module PgEasyReplicate
28
33
  aliases: "-s",
29
34
  desc:
30
35
  "Name of the role that has superuser permissions. Usually useful with AWS (rds_superuser) or GCP (cloudsqlsuperuser)."
36
+ method_option :copy_schema,
37
+ aliases: "-c",
38
+ boolean: true,
39
+ desc: "Copy schema to the new database"
31
40
  desc "bootstrap",
32
41
  "Sets up temporary tables for information required during runtime"
33
42
  def bootstrap
@@ -36,13 +36,17 @@ module PgEasyReplicate
36
36
 
37
37
  def underscore(str)
38
38
  str
39
- .gsub(/::/, "/")
39
+ .gsub("::", "/")
40
40
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
41
41
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
42
42
  .tr("-", "_")
43
43
  .downcase
44
44
  end
45
45
 
46
+ def quote_ident(sql_ident)
47
+ PG::Connection.quote_ident(sql_ident)
48
+ end
49
+
46
50
  def test_env?
47
51
  ENV.fetch("RACK_ENV", nil) == "test"
48
52
  end
@@ -94,7 +94,8 @@ module PgEasyReplicate
94
94
  .map do |table_name|
95
95
  Query.run(
96
96
  query:
97
- "ALTER PUBLICATION #{publication_name(group_name)} ADD TABLE \"#{table_name}\"",
97
+ "ALTER PUBLICATION #{quote_ident(publication_name(group_name))}
98
+ ADD TABLE #{quote_ident(table_name)}",
98
99
  connection_url: conn_string,
99
100
  schema: schema,
100
101
  )
@@ -107,7 +108,11 @@ module PgEasyReplicate
107
108
  Query
108
109
  .run(
109
110
  query:
110
- "SELECT table_name FROM information_schema.tables WHERE table_schema = '#{schema}' ORDER BY table_name",
111
+ "SELECT table_name
112
+ FROM information_schema.tables
113
+ WHERE table_schema = '#{schema}' AND
114
+ table_type = 'BASE TABLE'
115
+ ORDER BY table_name",
111
116
  connection_url: conn_string,
112
117
  )
113
118
  .map(&:values)
@@ -121,7 +126,8 @@ module PgEasyReplicate
121
126
  { publication_name: publication_name(group_name) },
122
127
  )
123
128
  Query.run(
124
- query: "DROP PUBLICATION IF EXISTS #{publication_name(group_name)}",
129
+ query:
130
+ "DROP PUBLICATION IF EXISTS #{quote_ident(publication_name(group_name))}",
125
131
  connection_url: conn_string,
126
132
  user: db_user(conn_string),
127
133
  )
@@ -144,7 +150,9 @@ module PgEasyReplicate
144
150
 
145
151
  Query.run(
146
152
  query:
147
- "CREATE SUBSCRIPTION #{subscription_name(group_name)} CONNECTION '#{source_conn_string}' PUBLICATION #{publication_name(group_name)}",
153
+ "CREATE SUBSCRIPTION #{quote_ident(subscription_name(group_name))}
154
+ CONNECTION '#{source_conn_string}'
155
+ PUBLICATION #{quote_ident(publication_name(group_name))}",
148
156
  connection_url: target_conn_string,
149
157
  user: db_user(target_conn_string),
150
158
  transaction: false,
@@ -282,7 +290,7 @@ module PgEasyReplicate
282
290
  )
283
291
 
284
292
  alter_sql =
285
- "ALTER USER #{db_user(source_db_url)} set default_transaction_read_only = true"
293
+ "ALTER USER #{quote_ident(db_user(source_db_url))} set default_transaction_read_only = true"
286
294
  Query.run(query: alter_sql, connection_url: source_db_url)
287
295
 
288
296
  kill_sql =
@@ -297,7 +305,7 @@ module PgEasyReplicate
297
305
  logger.info("Restoring connections")
298
306
 
299
307
  alter_sql =
300
- "ALTER USER #{db_user(source_db_url)} set default_transaction_read_only = false"
308
+ "ALTER USER #{quote_ident(db_user(source_db_url))} set default_transaction_read_only = false"
301
309
  Query.run(query: alter_sql, connection_url: source_db_url)
302
310
  end
303
311
 
@@ -17,12 +17,12 @@ module PgEasyReplicate
17
17
  if transaction
18
18
  r =
19
19
  conn.transaction do
20
- conn.run("SET search_path to #{schema}") if schema
20
+ conn.run("SET search_path to #{quote_ident(schema)}") if schema
21
21
  conn.run("SET statement_timeout to '5s'")
22
22
  conn.fetch(query).to_a
23
23
  end
24
24
  else
25
- conn.run("SET search_path to #{schema}") if schema
25
+ conn.run("SET search_path to #{quote_ident(schema)}") if schema
26
26
  conn.run("SET statement_timeout to '5s'")
27
27
  r = conn.fetch(query).to_a
28
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEasyReplicate
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.9"
5
5
  end
@@ -4,6 +4,8 @@ require "json"
4
4
  require "ougai"
5
5
  require "pg"
6
6
  require "sequel"
7
+ require "open3"
8
+ require "English"
7
9
 
8
10
  require "pg_easy_replicate/helper"
9
11
  require "pg_easy_replicate/version"
@@ -15,16 +17,21 @@ require "pg_easy_replicate/cli"
15
17
 
16
18
  Sequel.default_timezone = :utc
17
19
  module PgEasyReplicate
20
+ SCHEMA_FILE_LOCATION = "/tmp/pger_schema.sql"
21
+
18
22
  class Error < StandardError
19
23
  end
20
24
 
21
25
  extend Helper
22
26
 
23
27
  class << self
24
- def config(special_user_role: nil)
28
+ def config(special_user_role: nil, copy_schema: false)
25
29
  abort_with("SOURCE_DB_URL is missing") if source_db_url.nil?
26
30
  abort_with("TARGET_DB_URL is missing") if target_db_url.nil?
27
31
 
32
+ system("which pg_dump")
33
+ pg_dump_exists = $CHILD_STATUS.success?
34
+
28
35
  @config ||=
29
36
  begin
30
37
  q =
@@ -47,14 +54,20 @@ module PgEasyReplicate
47
54
  connection_url: target_db_url,
48
55
  user: db_user(target_db_url),
49
56
  ),
57
+ pg_dump_exists: pg_dump_exists,
50
58
  }
51
59
  rescue => e
52
60
  abort_with("Unable to check config: #{e.message}")
53
61
  end
54
62
  end
55
63
 
56
- def assert_config(special_user_role: nil)
57
- config_hash = config(special_user_role: special_user_role)
64
+ def assert_config(special_user_role: nil, copy_schema: false)
65
+ config_hash =
66
+ config(special_user_role: special_user_role, copy_schema: copy_schema)
67
+
68
+ if copy_schema && !config_hash.dig(:pg_dump_exists)
69
+ abort_with("pg_dump must exist if copy_schema (-c) is passed")
70
+ end
58
71
 
59
72
  unless assert_wal_level_logical(config_hash.dig(:source_db))
60
73
  abort_with("WAL_LEVEL should be LOGICAL on source DB")
@@ -74,7 +87,15 @@ module PgEasyReplicate
74
87
 
75
88
  def bootstrap(options)
76
89
  logger.info("Setting up schema")
77
- setup_schema
90
+ setup_internal_schema
91
+
92
+ if options[:copy_schema]
93
+ logger.info("Setting up schema on targer database")
94
+ copy_schema(
95
+ source_conn_string: source_db_url,
96
+ target_conn_string: target_db_url,
97
+ )
98
+ end
78
99
 
79
100
  logger.info("Setting up replication user on source database")
80
101
  create_user(
@@ -103,7 +124,7 @@ module PgEasyReplicate
103
124
 
104
125
  if options[:everything]
105
126
  logger.info("Dropping schema")
106
- drop_schema
127
+ drop_internal_schema
107
128
  end
108
129
 
109
130
  if options[:everything] || options[:sync]
@@ -130,9 +151,10 @@ module PgEasyReplicate
130
151
  abort_with("Unable to cleanup: #{e.message}")
131
152
  end
132
153
 
133
- def drop_schema
154
+ def drop_internal_schema
134
155
  Query.run(
135
- query: "DROP SCHEMA IF EXISTS #{internal_schema_name} CASCADE",
156
+ query:
157
+ "DROP SCHEMA IF EXISTS #{quote_ident(internal_schema_name)} CASCADE",
136
158
  connection_url: source_db_url,
137
159
  schema: internal_schema_name,
138
160
  user: db_user(target_db_url),
@@ -141,11 +163,11 @@ module PgEasyReplicate
141
163
  raise "Unable to drop schema: #{e.message}"
142
164
  end
143
165
 
144
- def setup_schema
166
+ def setup_internal_schema
145
167
  sql = <<~SQL
146
- create schema if not exists #{internal_schema_name};
147
- grant usage on schema #{internal_schema_name} to #{db_user(source_db_url)};
148
- grant create on schema #{internal_schema_name} to #{db_user(source_db_url)};
168
+ create schema if not exists #{quote_ident(internal_schema_name)};
169
+ grant usage on schema #{quote_ident(internal_schema_name)} to #{quote_ident(db_user(source_db_url))};
170
+ grant create on schema #{quote_ident(internal_schema_name)} to #{quote_ident(db_user(source_db_url))};
149
171
  SQL
150
172
 
151
173
  Query.run(
@@ -169,7 +191,39 @@ module PgEasyReplicate
169
191
  end
170
192
  end
171
193
 
172
- private
194
+ def copy_schema(source_conn_string:, target_conn_string:)
195
+ export_schema(conn_string: source_conn_string)
196
+ import_schema(conn_string: target_conn_string)
197
+ end
198
+
199
+ def export_schema(conn_string:)
200
+ logger.info("Exporting schema to #{SCHEMA_FILE_LOCATION}")
201
+ _, stderr, status =
202
+ Open3.capture3(
203
+ "pg_dump",
204
+ conn_string,
205
+ "-f",
206
+ SCHEMA_FILE_LOCATION,
207
+ "--schema-only",
208
+ )
209
+
210
+ success = status.success?
211
+ raise stderr unless success
212
+ rescue => e
213
+ raise "Unable to export schema: #{e.message}"
214
+ end
215
+
216
+ def import_schema(conn_string:)
217
+ logger.info("Importing schema from #{SCHEMA_FILE_LOCATION}")
218
+
219
+ _, stderr, status =
220
+ Open3.capture3("psql", "-f", SCHEMA_FILE_LOCATION, conn_string)
221
+
222
+ success = status.success?
223
+ raise stderr unless success
224
+ rescue => e
225
+ raise "Unable to import schema: #{e.message}"
226
+ end
173
227
 
174
228
  def assert_wal_level_logical(db_config)
175
229
  db_config&.find do |r|
@@ -221,9 +275,9 @@ module PgEasyReplicate
221
275
  password = connection_info(conn_string)[:password].gsub("'") { "''" }
222
276
 
223
277
  sql = <<~SQL
224
- drop role if exists #{internal_user_name};
225
- create role #{internal_user_name} with password '#{password}' login createdb createrole;
226
- grant all privileges on database #{db_name(conn_string)} TO #{internal_user_name};
278
+ drop role if exists #{quote_ident(internal_user_name)};
279
+ create role #{quote_ident(internal_user_name)} with password '#{password}' login createdb createrole;
280
+ grant all privileges on database #{quote_ident(db_name(conn_string))} TO #{quote_ident(internal_user_name)};
227
281
  SQL
228
282
 
229
283
  Query.run(
@@ -235,9 +289,9 @@ module PgEasyReplicate
235
289
 
236
290
  sql =
237
291
  if special_user_role
238
- "grant #{special_user_role} to #{internal_user_name};"
292
+ "grant #{quote_ident(special_user_role)} to #{quote_ident(internal_user_name)};"
239
293
  else
240
- "alter user #{internal_user_name} with superuser;"
294
+ "alter user #{quote_ident(internal_user_name)} with superuser;"
241
295
  end
242
296
 
243
297
  Query.run(
@@ -250,7 +304,7 @@ module PgEasyReplicate
250
304
  return unless grant_permissions_on_schema
251
305
  Query.run(
252
306
  query:
253
- "grant all on schema #{internal_schema_name} to #{internal_user_name}",
307
+ "grant all on schema #{quote_ident(internal_schema_name)} to #{quote_ident(internal_user_name)}",
254
308
  connection_url: conn_string,
255
309
  user: db_user(conn_string),
256
310
  transaction: false,
@@ -261,7 +315,7 @@ module PgEasyReplicate
261
315
 
262
316
  def drop_user(conn_string:, group_name:)
263
317
  sql = <<~SQL
264
- revoke all privileges on database #{db_name(conn_string)} from #{internal_user_name};
318
+ revoke all privileges on database #{db_name(conn_string)} from #{quote_ident(internal_user_name)};
265
319
  SQL
266
320
  Query.run(
267
321
  query: sql,
@@ -270,7 +324,7 @@ module PgEasyReplicate
270
324
  )
271
325
 
272
326
  sql = <<~SQL
273
- drop role if exists #{internal_user_name};
327
+ drop role if exists #{quote_ident(internal_user_name)};
274
328
  SQL
275
329
 
276
330
  Query.run(
@@ -3,17 +3,13 @@
3
3
  set -eo pipefail
4
4
 
5
5
  if [[ -z ${GITHUB_WORKFLOW} ]]; then
6
- export SECONDARY_SOURCE_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@source_db/postgres"
6
+ export SECONDARY_SOURCE_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@source_db/postgres"
7
7
  fi
8
8
 
9
- export SOURCE_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@localhost:5432/postgres"
10
- export TARGET_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@localhost:5433/postgres"
11
- export PGPASSWORD='jamesbond123@7!'"'"'3aaR'
9
+ export SOURCE_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@localhost:5432/postgres"
10
+ export TARGET_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@localhost:5433/postgres"
11
+ export PGPASSWORD='james-bond123@7!'"'"'3aaR'
12
12
 
13
- pgbench --initialize -s 5 --foreign-keys --host localhost -U jamesbond -d postgres
13
+ pgbench --initialize -s 5 --foreign-keys --host localhost -U james-bond -d postgres
14
14
 
15
- pg_dump --schema-only --host localhost -U jamesbond -d postgres >schema.sql
16
- cat schema.sql | psql --host localhost -U jamesbond -d postgres -p 5433
17
- rm schema.sql
18
-
19
- bundle exec bin/pg_easy_replicate config_check
15
+ bundle exec bin/pg_easy_replicate config_check --copy-schema
data/scripts/e2e-start.sh CHANGED
@@ -3,16 +3,16 @@
3
3
  set -eo pipefail
4
4
 
5
5
  if [[ -z ${GITHUB_WORKFLOW} ]]; then
6
- export SECONDARY_SOURCE_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@source_db/postgres"
6
+ export SECONDARY_SOURCE_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@source_db/postgres"
7
7
  fi
8
8
 
9
- export SOURCE_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@localhost:5432/postgres"
10
- export TARGET_DB_URL="postgres://jamesbond:jamesbond123%407%21%273aaR@localhost:5433/postgres"
11
- export PGPASSWORD='jamesbond123@7!'"'"''"'"'3aaR'
9
+ export SOURCE_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@localhost:5432/postgres"
10
+ export TARGET_DB_URL="postgres://james-bond:james-bond123%407%21%273aaR@localhost:5433/postgres"
11
+ export PGPASSWORD='james-bond123@7!'"'"''"'"'3aaR'
12
12
 
13
13
  # Bootstrap and cleanup
14
14
  echo "===== Performing Bootstrap and cleanup"
15
- bundle exec bin/pg_easy_replicate bootstrap -g cluster-1
15
+ bundle exec bin/pg_easy_replicate bootstrap -g cluster-1 --copy-schema
16
16
  bundle exec bin/pg_easy_replicate start_sync -g cluster-1 -s public
17
17
  bundle exec bin/pg_easy_replicate stats -g cluster-1
18
18
  bundle exec bin/pg_easy_replicate switchover -g cluster-1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_easy_replicate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shayon Mukherjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-27 00:00:00.000000000 Z
11
+ date: 2023-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ougai
@@ -42,16 +42,22 @@ dependencies:
42
42
  name: sequel
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '5.69'
48
+ - - "<"
46
49
  - !ruby/object:Gem::Version
47
- version: 5.69.0
50
+ version: '5.71'
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
- - - "~>"
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '5.69'
58
+ - - "<"
53
59
  - !ruby/object:Gem::Version
54
- version: 5.69.0
60
+ version: '5.71'
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: thor
57
63
  requirement: !ruby/object:Gem::Requirement