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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4000209f5f79dad8a7c6f427be2cdcf25abe219e
4
- data.tar.gz: 4980413b816f0d0aa150aedf7bbcb1141fb7caad
2
+ SHA256:
3
+ metadata.gz: 6dd029f7881055cb38f098ec23ecab2e70e06f118c7d50cfd09d43d5c8c6ad28
4
+ data.tar.gz: 24eff3b8de6a4a59b55dd749dcc3134785bfb2e1361aaec2fec1edc30a579875
5
5
  SHA512:
6
- metadata.gz: 3122cf01ba1dcda75172bc823507c5db684561797dbdb88be42a93866d3fbfb0147e068ab501d1c7a74f09fd88341f623f61e4a2751183a051ba5cefecbdcbb4
7
- data.tar.gz: cb8531e15f2ba2c714d1e022b286efe09208abd4b88c997139a23d6ff583e45c460bae46961735716978dd34b7448f42dc2b8f1cbdb4b06592b0d604e070a219
6
+ metadata.gz: e6eca6142c30b5bcf43608eaf338aea4c6a71b37781b979e34f9028e1418f50c3188a02998e6f4b74111ec58c85eff6b04ad005c8dfa9a1085f653b10e9b37db
7
+ data.tar.gz: d39a13d5a83b168f0ccb90b04309ca1f616dab646963a6a6b873da06fdbc883eeef7e758890aa486f4df06c211417ac00afd60ff843ee17e17421eb65bb6abff
@@ -1,3 +1,10 @@
1
+ # 0.4.1
2
+
3
+ - Made `psql` version check more robust
4
+ - Fixed issue with non-lowercase primary key
5
+ - Prefer `--init` over `--setup`
6
+ - Improved data rules
7
+
1
8
  # 0.4.0
2
9
 
3
10
  - Sync all schemas in search path by default
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 --setup
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 - like passwords and email addresses - from leaving the remote server.
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
- - null
168
- - value
169
- - statement
170
- - unique_email
171
- - unique_phone
172
- - unique_secret
173
- - random_letter
174
- - random_int
175
- - random_date
176
- - random_time
177
- - random_ip
178
- - random_string
179
- - random_number
180
- - untouched
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 --setup db2
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
- ## Setup Scripts
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: $(heroku config:get DATABASE_URL)
6
+ from: $(some_command)
7
7
 
8
8
  # destination database URL
9
9
  to: postgres://localhost:5432/myapp_development
data/exe/pgsync CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "pgsync"
4
3
  begin
4
+ require "pgsync"
5
5
  PgSync::Client.new(ARGV).perform
6
6
  rescue PgSync::Error => e
7
7
  abort e.message
8
8
  rescue Interrupt
9
- # do nothing
9
+ abort
10
10
  end
@@ -18,7 +18,7 @@ module PgSync
18
18
  end
19
19
  map_deprecations(args, opts)
20
20
 
21
- if opts[:setup]
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[:setup] = true
120
- deprecated "Use `psync --setup` instead"
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 "--setup", "setup", default: false
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
- [opts.arguments, opts.to_hash]
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
@@ -116,8 +116,8 @@ module PgSync
116
116
  end
117
117
 
118
118
  def restore_command
119
- psql_version = Gem::Version.new(`psql --version`.lines[0].chomp.split(" ")[-1].sub(/beta\d/, ""))
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
 
@@ -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
- # http://stackoverflow.com/questions/21105360/regex-find-comma-not-inside-quotes
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
@@ -44,7 +44,8 @@ module PgSync
44
44
  end
45
45
 
46
46
  if shared_fields.any?
47
- copy_fields = shared_fields.map { |f| f2 = bad_fields.to_a.find { |bf, bk| rule_match?(table, f, bf) }; f2 ? "#{apply_strategy(f2[1], table, f)} AS #{quote_ident(f)}" : "#{quote_ident_full(table)}.#{quote_ident(f)}" }.join(", ")
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
- strategies = {
181
- "unique_email" => "'email' || #{table}.id || '@example.org'",
182
- "untouched" => quote_ident(column),
183
- "unique_phone" => "(#{table}.id + 1000000000)::text",
184
- "random_int" => "(RAND() * 10)::int",
185
- "random_date" => "'1970-01-01'",
186
- "random_time" => "NOW()",
187
- "unique_secret" => "'secret' || #{table}.id",
188
- "random_ip" => "'127.0.0.1'",
189
- "random_letter" => "'A'",
190
- "random_string" => "right(md5(random()::text),10)",
191
- "random_number" => "(RANDOM() * 1000000)::int",
192
- "null" => "NULL",
193
- nil => "NULL"
194
- }
195
- if strategies[rule]
196
- strategies[rule]
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
@@ -1,3 +1,3 @@
1
1
  module PgSync
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
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.0
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-03-01 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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.13
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
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /.pgsync.yml
@@ -1,11 +0,0 @@
1
- language: ruby
2
- rvm: 2.4.1
3
- cache: bundler
4
- sudo: false
5
- script: bundle exec rake test
6
- addons:
7
- postgresql: "9.4"
8
- notifications:
9
- email:
10
- on_success: never
11
- on_failure: change
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in pgsync.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,9 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- task default: :test
5
- Rake::TestTask.new do |t|
6
- t.libs << "test"
7
- t.pattern = "test/**/*_test.rb"
8
- t.warning = false
9
- end
@@ -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