pgsync 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pgsync might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +7 -0
- data/README.md +54 -19
- data/config.yml +1 -1
- data/exe/pgsync +2 -2
- data/lib/pgsync/client.rb +10 -5
- data/lib/pgsync/data_source.rb +2 -2
- data/lib/pgsync/table_list.rb +3 -1
- data/lib/pgsync/table_sync.rb +34 -25
- data/lib/pgsync/version.rb +1 -1
- metadata +7 -13
- data/.gitignore +0 -10
- data/.travis.yml +0 -11
- data/Gemfile +0 -4
- data/Rakefile +0 -9
- data/pgsync.gemspec +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6dd029f7881055cb38f098ec23ecab2e70e06f118c7d50cfd09d43d5c8c6ad28
|
4
|
+
data.tar.gz: 24eff3b8de6a4a59b55dd749dcc3134785bfb2e1361aaec2fec1edc30a579875
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6eca6142c30b5bcf43608eaf338aea4c6a71b37781b979e34f9028e1418f50c3188a02998e6f4b74111ec58c85eff6b04ad005c8dfa9a1085f653b10e9b37db
|
7
|
+
data.tar.gz: d39a13d5a83b168f0ccb90b04309ca1f616dab646963a6a6b873da06fdbc883eeef7e758890aa486f4df06c211417ac00afd60ff843ee17e17421eb65bb6abff
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -18,12 +18,12 @@ pgsync is a command line tool. To install, run:
|
|
18
18
|
gem install pgsync
|
19
19
|
```
|
20
20
|
|
21
|
-
This will give you the `pgsync` command.
|
21
|
+
This will give you the `pgsync` command. If installation fails, you may need to install [dependencies](#dependencies).
|
22
22
|
|
23
23
|
In your project directory, run:
|
24
24
|
|
25
25
|
```sh
|
26
|
-
pgsync --
|
26
|
+
pgsync --init
|
27
27
|
```
|
28
28
|
|
29
29
|
This creates `.pgsync.yml` for you to customize. We recommend checking this into your version control (assuming it doesn’t contain sensitive information). `pgsync` commands can be run from this directory or any subdirectory.
|
@@ -142,9 +142,15 @@ Or just the schema
|
|
142
142
|
pgsync --schema-only
|
143
143
|
```
|
144
144
|
|
145
|
+
pgsync does not try to sync Postgres extensions.
|
146
|
+
|
147
|
+
## Data Protection
|
148
|
+
|
149
|
+
Always make sure your [connection is secure](https://ankane.org/postgres-sslmode-explained) when connecting to your database over a network you don’t fully trust. Your best option is to connect over SSH or a VPN. Another option is to use `sslmode=verify-full`. If you don’t do this, your database credentials can be compromised.
|
150
|
+
|
145
151
|
## Sensitive Information
|
146
152
|
|
147
|
-
Prevent sensitive information
|
153
|
+
Prevent sensitive information like email addresses from leaving the remote server.
|
148
154
|
|
149
155
|
Define rules in `.pgsync.yml`:
|
150
156
|
|
@@ -164,27 +170,27 @@ data_rules:
|
|
164
170
|
|
165
171
|
Options for replacement are:
|
166
172
|
|
167
|
-
-
|
168
|
-
-
|
169
|
-
-
|
170
|
-
-
|
171
|
-
-
|
172
|
-
-
|
173
|
-
-
|
174
|
-
-
|
175
|
-
-
|
176
|
-
-
|
177
|
-
-
|
178
|
-
-
|
179
|
-
|
180
|
-
|
173
|
+
- `unique_email`
|
174
|
+
- `unique_phone`
|
175
|
+
- `unique_secret`
|
176
|
+
- `random_letter`
|
177
|
+
- `random_int`
|
178
|
+
- `random_date`
|
179
|
+
- `random_time`
|
180
|
+
- `random_ip`
|
181
|
+
- `value`
|
182
|
+
- `statement`
|
183
|
+
- `null`
|
184
|
+
- `untouched`
|
185
|
+
|
186
|
+
Rules starting with `unique_` require the table to have a primary key.
|
181
187
|
|
182
188
|
## Multiple Databases
|
183
189
|
|
184
190
|
To use with multiple databases, run:
|
185
191
|
|
186
192
|
```sh
|
187
|
-
pgsync --
|
193
|
+
pgsync --init db2
|
188
194
|
```
|
189
195
|
|
190
196
|
This creates `.pgsync-db2.yml` for you to edit. Specify a database in commands with:
|
@@ -231,7 +237,7 @@ Version
|
|
231
237
|
pgsync --version
|
232
238
|
```
|
233
239
|
|
234
|
-
##
|
240
|
+
## Scripts
|
235
241
|
|
236
242
|
Use groups when possible to take advantage of parallelism.
|
237
243
|
|
@@ -243,6 +249,22 @@ Bundler.with_clean_env do
|
|
243
249
|
end
|
244
250
|
```
|
245
251
|
|
252
|
+
## Dependencies
|
253
|
+
|
254
|
+
If installation fails, your system may be missing Ruby or libpq.
|
255
|
+
|
256
|
+
On Mac, run:
|
257
|
+
|
258
|
+
```sh
|
259
|
+
brew install postgresql
|
260
|
+
```
|
261
|
+
|
262
|
+
On Ubuntu, run:
|
263
|
+
|
264
|
+
```sh
|
265
|
+
sudo apt-get install ruby-dev libpq-dev build-essential
|
266
|
+
```
|
267
|
+
|
246
268
|
## Upgrading
|
247
269
|
|
248
270
|
Run:
|
@@ -270,3 +292,16 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
270
292
|
- Fix bugs and [submit pull requests](https://github.com/ankane/pgsync/pulls)
|
271
293
|
- Write, clarify, or fix documentation
|
272
294
|
- Suggest or add new features
|
295
|
+
|
296
|
+
To run tests, do:
|
297
|
+
|
298
|
+
```sh
|
299
|
+
git clone https://github.com/ankane/pgsync.git
|
300
|
+
cd pgsync
|
301
|
+
bundle install
|
302
|
+
|
303
|
+
createdb pgsync_test1
|
304
|
+
createdb pgsync_test2
|
305
|
+
|
306
|
+
bundle exec rake
|
307
|
+
```
|
data/config.yml
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# postgres://user:password@host:port/dbname
|
4
4
|
# we recommend a command which outputs a database URL
|
5
5
|
# so sensitive information is not included in this file
|
6
|
-
from: $(
|
6
|
+
from: $(some_command)
|
7
7
|
|
8
8
|
# destination database URL
|
9
9
|
to: postgres://localhost:5432/myapp_development
|
data/exe/pgsync
CHANGED
data/lib/pgsync/client.rb
CHANGED
@@ -18,7 +18,7 @@ module PgSync
|
|
18
18
|
end
|
19
19
|
map_deprecations(args, opts)
|
20
20
|
|
21
|
-
if opts[:
|
21
|
+
if opts[:init]
|
22
22
|
setup(db_config_file(args[0]) || config_file || ".pgsync.yml")
|
23
23
|
else
|
24
24
|
sync(args, opts)
|
@@ -116,8 +116,8 @@ module PgSync
|
|
116
116
|
case command
|
117
117
|
when "setup"
|
118
118
|
args.shift
|
119
|
-
opts[:
|
120
|
-
deprecated "Use `psync --
|
119
|
+
opts[:init] = true
|
120
|
+
deprecated "Use `psync --init` instead"
|
121
121
|
when "schema"
|
122
122
|
args.shift
|
123
123
|
opts[:schema_only] = true
|
@@ -178,7 +178,8 @@ Options:}
|
|
178
178
|
o.boolean "--schema-only", "schema only", default: false
|
179
179
|
o.boolean "--all-schemas", "all schemas", default: false
|
180
180
|
o.boolean "--no-rules", "do not apply data rules", default: false
|
181
|
-
o.boolean "--
|
181
|
+
o.boolean "--init", "init", default: false
|
182
|
+
o.boolean "--setup", "setup", default: false, help: false
|
182
183
|
o.boolean "--in-batches", "in batches", default: false, help: false
|
183
184
|
o.integer "--batch-size", "batch size", default: 10000, help: false
|
184
185
|
o.float "--sleep", "sleep", default: 0, help: false
|
@@ -191,7 +192,11 @@ Options:}
|
|
191
192
|
@exit = true
|
192
193
|
end
|
193
194
|
end
|
194
|
-
|
195
|
+
|
196
|
+
opts_hash = opts.to_hash
|
197
|
+
opts_hash[:init] = opts_hash[:setup] if opts_hash[:setup]
|
198
|
+
|
199
|
+
[opts.arguments, opts_hash]
|
195
200
|
rescue Slop::Error => e
|
196
201
|
raise PgSync::Error, e.message
|
197
202
|
end
|
data/lib/pgsync/data_source.rb
CHANGED
@@ -116,8 +116,8 @@ module PgSync
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def restore_command
|
119
|
-
psql_version =
|
120
|
-
if_exists = psql_version >= Gem::Version.new("9.4.0")
|
119
|
+
psql_version = `psql --version`.lines[0].chomp.split(" ")[-1].split(/[^\d.]/)[0]
|
120
|
+
if_exists = Gem::Version.new(psql_version) >= Gem::Version.new("9.4.0")
|
121
121
|
"pg_restore --verbose --no-owner --no-acl --clean #{if_exists ? "--if-exists" : nil} -d #{@url}"
|
122
122
|
end
|
123
123
|
|
data/lib/pgsync/table_list.rb
CHANGED
@@ -29,6 +29,7 @@ module PgSync
|
|
29
29
|
tables ||= Hash.new { |hash, key| hash[key] = {} }
|
30
30
|
to_arr(opts[:tables]).each do |tag|
|
31
31
|
table, id = tag.split(":", 2)
|
32
|
+
raise PgSync::Error, "Cannot use parameters with tables" if id
|
32
33
|
add_table(tables, table, id, args[1])
|
33
34
|
end
|
34
35
|
end
|
@@ -42,6 +43,7 @@ module PgSync
|
|
42
43
|
if (t = (config["groups"] || {})[group])
|
43
44
|
add_tables(tables, t, id, args[1])
|
44
45
|
else
|
46
|
+
raise PgSync::Error, "Cannot use parameters with tables" if id
|
45
47
|
add_table(tables, group, id, args[1])
|
46
48
|
end
|
47
49
|
end
|
@@ -70,7 +72,7 @@ module PgSync
|
|
70
72
|
value
|
71
73
|
else
|
72
74
|
# Split by commas, but don't use commas inside double quotes
|
73
|
-
#
|
75
|
+
# https://stackoverflow.com/questions/21105360/regex-find-comma-not-inside-quotes
|
74
76
|
value.to_s.split(/(?!\B"[^"]*),(?![^"]*"\B)/)
|
75
77
|
end
|
76
78
|
end
|
data/lib/pgsync/table_sync.rb
CHANGED
@@ -44,7 +44,8 @@ module PgSync
|
|
44
44
|
end
|
45
45
|
|
46
46
|
if shared_fields.any?
|
47
|
-
|
47
|
+
primary_key = destination.primary_key(table)
|
48
|
+
copy_fields = shared_fields.map { |f| f2 = bad_fields.to_a.find { |bf, _| rule_match?(table, f, bf) }; f2 ? "#{apply_strategy(f2[1], table, f, primary_key)} AS #{quote_ident(f)}" : "#{quote_ident_full(table)}.#{quote_ident(f)}" }.join(", ")
|
48
49
|
fields = shared_fields.map { |f| quote_ident(f) }.join(", ")
|
49
50
|
|
50
51
|
seq_values = {}
|
@@ -55,8 +56,6 @@ module PgSync
|
|
55
56
|
copy_to_command = "COPY (SELECT #{copy_fields} FROM #{quote_ident_full(table)}#{sql_clause}) TO STDOUT"
|
56
57
|
if opts[:in_batches]
|
57
58
|
raise PgSync::Error, "Cannot use --overwrite with --in-batches" if opts[:overwrite]
|
58
|
-
|
59
|
-
primary_key = source.primary_key(table)
|
60
59
|
raise PgSync::Error, "No primary key" unless primary_key
|
61
60
|
|
62
61
|
destination.truncate(table) if opts[:truncate]
|
@@ -99,14 +98,13 @@ module PgSync
|
|
99
98
|
end
|
100
99
|
end
|
101
100
|
elsif !opts[:truncate] && (opts[:overwrite] || opts[:preserve] || !sql_clause.empty?)
|
102
|
-
primary_key = destination.primary_key(table)
|
103
101
|
raise PgSync::Error, "No primary key" unless primary_key
|
104
102
|
|
105
103
|
temp_table = "pgsync_#{rand(1_000_000_000)}"
|
106
104
|
file = Tempfile.new(temp_table)
|
107
105
|
begin
|
108
106
|
from_connection.copy_data copy_to_command do
|
109
|
-
while row = from_connection.get_copy_data
|
107
|
+
while (row = from_connection.get_copy_data)
|
110
108
|
file.write(row)
|
111
109
|
end
|
112
110
|
end
|
@@ -124,7 +122,7 @@ module PgSync
|
|
124
122
|
|
125
123
|
if opts[:preserve]
|
126
124
|
# insert into
|
127
|
-
to_connection.exec("INSERT INTO #{quote_ident_full(table)} (SELECT * FROM #{quote_ident_full(temp_table)} WHERE NOT EXISTS (SELECT 1 FROM #{quote_ident_full(table)} WHERE #{quote_ident_full(table)}.#{primary_key} = #{quote_ident_full(temp_table)}.#{quote_ident(primary_key)}))")
|
125
|
+
to_connection.exec("INSERT INTO #{quote_ident_full(table)} (SELECT * FROM #{quote_ident_full(temp_table)} WHERE NOT EXISTS (SELECT 1 FROM #{quote_ident_full(table)} WHERE #{quote_ident_full(table)}.#{quote_ident(primary_key)} = #{quote_ident_full(temp_table)}.#{quote_ident(primary_key)}))")
|
128
126
|
else
|
129
127
|
to_connection.transaction do
|
130
128
|
to_connection.exec("DELETE FROM #{quote_ident_full(table)} WHERE #{quote_ident(primary_key)} IN (SELECT #{quote_ident(primary_key)} FROM #{quote_ident_full(temp_table)})")
|
@@ -139,7 +137,7 @@ module PgSync
|
|
139
137
|
destination.truncate(table)
|
140
138
|
to_connection.copy_data "COPY #{quote_ident_full(table)} (#{fields}) FROM STDIN" do
|
141
139
|
from_connection.copy_data copy_to_command do
|
142
|
-
while row = from_connection.get_copy_data
|
140
|
+
while (row = from_connection.get_copy_data)
|
143
141
|
to_connection.put_copy_data(row)
|
144
142
|
end
|
145
143
|
end
|
@@ -167,7 +165,7 @@ module PgSync
|
|
167
165
|
end
|
168
166
|
|
169
167
|
# TODO wildcard rules
|
170
|
-
def apply_strategy(rule, table, column)
|
168
|
+
def apply_strategy(rule, table, column, primary_key)
|
171
169
|
if rule.is_a?(Hash)
|
172
170
|
if rule.key?("value")
|
173
171
|
escape(rule["value"])
|
@@ -177,29 +175,40 @@ module PgSync
|
|
177
175
|
raise PgSync::Error, "Unknown rule #{rule.inspect} for column #{column}"
|
178
176
|
end
|
179
177
|
else
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
"
|
185
|
-
|
186
|
-
"
|
187
|
-
|
188
|
-
"
|
189
|
-
|
190
|
-
"
|
191
|
-
|
192
|
-
"
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
178
|
+
case rule
|
179
|
+
when "untouched"
|
180
|
+
quote_ident(column)
|
181
|
+
when "unique_email"
|
182
|
+
"'email' || #{quoted_primary_key(table, primary_key, rule)}::text || '@example.org'"
|
183
|
+
when "unique_phone"
|
184
|
+
"(#{quoted_primary_key(table, primary_key, rule)}::bigint + 1000000000)::text"
|
185
|
+
when "unique_secret"
|
186
|
+
"'secret' || #{quoted_primary_key(table, primary_key, rule)}::text"
|
187
|
+
when "random_int", "random_number"
|
188
|
+
"(RANDOM() * 100)::int"
|
189
|
+
when "random_date"
|
190
|
+
"date '1970-01-01' + (RANDOM() * 10000)::int"
|
191
|
+
when "random_time"
|
192
|
+
"NOW() - (RANDOM() * 100000000)::int * INTERVAL '1 second'"
|
193
|
+
when "random_ip"
|
194
|
+
"(1 + RANDOM() * 254)::int::text || '.0.0.1'"
|
195
|
+
when "random_letter"
|
196
|
+
"chr(65 + (RANDOM() * 26)::int)"
|
197
|
+
when "random_string"
|
198
|
+
"RIGHT(MD5(RANDOM()::text), 10)"
|
199
|
+
when "null", nil
|
200
|
+
"NULL"
|
197
201
|
else
|
198
202
|
raise PgSync::Error, "Unknown rule #{rule} for column #{column}"
|
199
203
|
end
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
207
|
+
def quoted_primary_key(table, primary_key, rule)
|
208
|
+
raise "Primary key required for this data rule: #{rule}" unless primary_key
|
209
|
+
"#{quote_ident_full(table)}.#{quote_ident(primary_key)}"
|
210
|
+
end
|
211
|
+
|
203
212
|
def log(message = nil)
|
204
213
|
$stderr.puts message
|
205
214
|
end
|
data/lib/pgsync/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgsync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multiprocessing
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.18.2
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.18.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: slop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,20 +109,15 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
description:
|
112
|
-
email:
|
113
|
-
- andrew@chartkick.com
|
112
|
+
email: andrew@chartkick.com
|
114
113
|
executables:
|
115
114
|
- pgsync
|
116
115
|
extensions: []
|
117
116
|
extra_rdoc_files: []
|
118
117
|
files:
|
119
|
-
- ".gitignore"
|
120
|
-
- ".travis.yml"
|
121
118
|
- CHANGELOG.md
|
122
|
-
- Gemfile
|
123
119
|
- LICENSE.txt
|
124
120
|
- README.md
|
125
|
-
- Rakefile
|
126
121
|
- config.yml
|
127
122
|
- exe/pgsync
|
128
123
|
- lib/pgsync.rb
|
@@ -131,7 +126,6 @@ files:
|
|
131
126
|
- lib/pgsync/table_list.rb
|
132
127
|
- lib/pgsync/table_sync.rb
|
133
128
|
- lib/pgsync/version.rb
|
134
|
-
- pgsync.gemspec
|
135
129
|
homepage: https://github.com/ankane/pgsync
|
136
130
|
licenses:
|
137
131
|
- MIT
|
@@ -144,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
138
|
requirements:
|
145
139
|
- - ">="
|
146
140
|
- !ruby/object:Gem::Version
|
147
|
-
version: '
|
141
|
+
version: '2.2'
|
148
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
143
|
requirements:
|
150
144
|
- - ">="
|
@@ -152,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
146
|
version: '0'
|
153
147
|
requirements: []
|
154
148
|
rubyforge_project:
|
155
|
-
rubygems_version: 2.6
|
149
|
+
rubygems_version: 2.7.6
|
156
150
|
signing_key:
|
157
151
|
specification_version: 4
|
158
152
|
summary: Sync Postgres data between databases
|
data/.gitignore
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/pgsync.gemspec
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require "pgsync/version"
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "pgsync"
|
8
|
-
spec.version = PgSync::VERSION
|
9
|
-
spec.authors = ["Andrew Kane"]
|
10
|
-
spec.email = ["andrew@chartkick.com"]
|
11
|
-
|
12
|
-
spec.summary = "Sync Postgres data between databases"
|
13
|
-
spec.homepage = "https://github.com/ankane/pgsync"
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
-
spec.bindir = "exe"
|
18
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_dependency "multiprocessing"
|
22
|
-
spec.add_dependency "parallel"
|
23
|
-
spec.add_dependency "pg"
|
24
|
-
spec.add_dependency "slop", ">= 4.2.0"
|
25
|
-
|
26
|
-
spec.add_development_dependency "bundler"
|
27
|
-
spec.add_development_dependency "minitest"
|
28
|
-
spec.add_development_dependency "rake"
|
29
|
-
end
|