pgsync 0.4.3 → 0.5.0

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
2
  SHA256:
3
- metadata.gz: b754f24353de37a0b8b8800d9ea0ac010c3dcd16234a6554abab564c1fb6b839
4
- data.tar.gz: 7ec8238e286e7c145934be5309e3a67ab3f7a9b0267840e046d8b7e6b0c703bb
3
+ metadata.gz: 855b6fc121c88a53bc7fda6243add1d965c880e4fcb33328d5cfd931cb7bb588
4
+ data.tar.gz: fc157143ac24e7cfad51d57bc10c2859ea8225d74a6a3376821e7ac45e8dee13
5
5
  SHA512:
6
- metadata.gz: 62ee273ca358509668ce44a7b9b599e156c31ef47a457d9ff857a1384c97e3e73379f84b8d56f6347b612249725c111b62671be7ac0703d6a96a89e6a920c33f
7
- data.tar.gz: 1e55d84d7a142ae3b1b79e48cbc55171e2f78265a478428427a7e8f38712d7a26f9801ff958f4e68011c758f881b365656128e8888ec9f81efac1a087e1cb03c
6
+ metadata.gz: 6fbdbb036ea9ca16138af36fec1a01ae31c9b7963065bf8014ae3a09d564cdc7ecb82ee36f32531435a793ae9e933a49685e348fcaed674f702b2cce1d7b7c74
7
+ data.tar.gz: f3ff567e7e2b0720696efe209c98cc2b983969aefe28c9ee3ac68c829ccd5a755820f1d77008f1c4e86acd184f56ec08c1762a3460b08c27679e93cb1313718c
@@ -1,26 +1,32 @@
1
- # 0.4.3
1
+ ## 0.5.0 (2020-03-26)
2
+
3
+ - Improved output when syncing
4
+ - Improved output on interrupt
5
+ - Added `--no-sequences` option
6
+
7
+ ## 0.4.3 (2019-10-27)
2
8
 
3
9
  - Added `sslmode` to template
4
10
 
5
- # 0.4.2
11
+ ## 0.4.2 (2019-10-27)
6
12
 
7
13
  - Improved flexibility of commands
8
14
  - Sync all objects when no tables specified
9
15
 
10
- # 0.4.1
16
+ ## 0.4.1 (2018-12-15)
11
17
 
12
18
  - Made `psql` version check more robust
13
19
  - Fixed issue with non-lowercase primary key
14
20
  - Prefer `--init` over `--setup`
15
21
  - Improved data rules
16
22
 
17
- # 0.4.0
23
+ ## 0.4.0 (2018-02-28)
18
24
 
19
25
  - Sync all schemas in search path by default
20
26
  - Added support for socket connections
21
27
  - Added support for environment variables
22
28
 
23
- # 0.3.9
29
+ ## 0.3.9 (2018-02-27)
24
30
 
25
31
  - Better support for schemas
26
32
  - Added `--schemas` option
@@ -28,47 +34,47 @@
28
34
  - Added `--schema-first` option
29
35
  - Fixed issue with non-lowercase tables and partial syncs
30
36
 
31
- # 0.3.8
37
+ ## 0.3.8 (2017-10-01)
32
38
 
33
39
  - Added Windows support
34
40
  - Added `random_string` and `random_number` replacement options
35
41
  - Improved performance of `--in-batches` for large tables
36
42
 
37
- # 0.3.7
43
+ ## 0.3.7 (2017-08-30)
38
44
 
39
45
  - Fixed non-lowercase tables and columns
40
46
  - Fixed `--truncate` option with `--in-batches`
41
47
 
42
- # 0.3.6
48
+ ## 0.3.6 (2016-10-02)
43
49
 
44
50
  - Fixed `Table does not exist in source` error
45
51
 
46
- # 0.3.5
52
+ ## 0.3.5 (2016-07-23)
47
53
 
48
54
  - Support schemas other than public
49
55
 
50
- # 0.3.4
56
+ ## 0.3.4 (2016-04-29)
51
57
 
52
58
  - Added `--in-batches` mode for production transfers with `--batch-size` and `--sleep`
53
59
 
54
- # 0.3.3
60
+ ## 0.3.3 (2016-04-25)
55
61
 
56
62
  - Added `-d` option as an alias for `--db`
57
63
  - Added support for wildcard tables
58
64
  - Fixed `--schema-only` errors
59
65
 
60
- # 0.3.2
66
+ ## 0.3.2 (2016-04-19)
61
67
 
62
68
  - Prefer `{1}` for interpolation
63
69
  - Added `--overwrite` option
64
70
  - Deprecated `--where` and `--limit`
65
71
 
66
- # 0.3.1
72
+ ## 0.3.1 (2016-04-06)
67
73
 
68
74
  - Added `-t` or `--tables`, `-g` or `--groups` options
69
75
  - Deprecated `tables`, `groups`, and `setup` commands
70
76
 
71
- # 0.3.0
77
+ ## 0.3.0 (2016-04-06)
72
78
 
73
79
  - More powerful groups
74
80
  - Overwrite rows by default when `WHERE` clause (previously truncated)
@@ -79,34 +85,34 @@
79
85
  - Added `--setup` option
80
86
  - Added `--truncate` option
81
87
 
82
- # 0.2.4
88
+ ## 0.2.4 (2016-04-04)
83
89
 
84
90
  - Added `--preserve` option
85
91
  - Added `--list` option for groups and tables
86
92
  - Added `--limit` option
87
93
 
88
- # 0.2.3
94
+ ## 0.2.3 (2016-03-30)
89
95
 
90
96
  - Fixed `no PostgreSQL user name specified in startup packet`
91
97
 
92
- # 0.2.2
98
+ ## 0.2.2 (2016-03-28)
93
99
 
94
100
  - Added `--debug` option
95
101
 
96
- # 0.2.1
102
+ ## 0.2.1 (2016-03-27)
97
103
 
98
104
  - Do not require config file
99
105
 
100
- # 0.2.0
106
+ ## 0.2.0 (2016-03-26)
101
107
 
102
108
  - Fixed idle transaction timeout errors - respects `statement_timeout` as a result
103
109
  - Raise error when command exits with non-zero status
104
110
 
105
- # 0.1.1
111
+ ## 0.1.1 (2016-03-23)
106
112
 
107
113
  - Better support for multiple databases
108
114
  - Search parent directories for config file
109
115
 
110
- # 0.1.0
116
+ ## 0.1.0 (2015-12-07)
111
117
 
112
118
  - First release
data/README.md CHANGED
@@ -122,6 +122,8 @@ pgsync product:123
122
122
 
123
123
  ### Schema
124
124
 
125
+ **Note:** pgsync is designed to sync data. You should use a schema migration tool to manage schema changes. The methods in this section are provided for convenience but not recommended.
126
+
125
127
  Sync schema before the data
126
128
 
127
129
  ```sh
@@ -146,7 +148,7 @@ pgsync does not try to sync Postgres extensions.
146
148
 
147
149
  ## Data Protection
148
150
 
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.
151
+ Always make sure your [connection is secure](https://ankane.org/postgres-sslmode-explained) when connecting to a 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
152
 
151
153
  ## Sensitive Information
152
154
 
@@ -183,7 +185,7 @@ Options for replacement are:
183
185
  - `null`
184
186
  - `untouched`
185
187
 
186
- Rules starting with `unique_` require the table to have a primary key.
188
+ Rules starting with `unique_` require the table to have a primary key. `unique_phone` requires a numeric primary key.
187
189
 
188
190
  ## Multiple Databases
189
191
 
@@ -280,6 +282,14 @@ gem install specific_install
280
282
  gem specific_install https://github.com/ankane/pgsync.git
281
283
  ```
282
284
 
285
+ ## Related Projects
286
+
287
+ Also check out:
288
+
289
+ - [Dexter](https://github.com/ankane/dexter) - The automatic indexer for Postgres
290
+ - [PgHero](https://github.com/ankane/pghero) - A performance dashboard for Postgres
291
+ - [pgslice](https://github.com/ankane/pgslice) - Postgres partitioning as easy as pie
292
+
283
293
  ## Thanks
284
294
 
285
295
  Inspired by [heroku-pg-transfer](https://github.com/ddollar/heroku-pg-transfer).
@@ -293,7 +303,7 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
293
303
  - Write, clarify, or fix documentation
294
304
  - Suggest or add new features
295
305
 
296
- To run tests, do:
306
+ To get started with development:
297
307
 
298
308
  ```sh
299
309
  git clone https://github.com/ankane/pgsync.git
@@ -304,5 +314,5 @@ createdb pgsync_test1
304
314
  createdb pgsync_test2
305
315
  createdb pgsync_test3
306
316
 
307
- bundle exec rake
317
+ bundle exec rake test
308
318
  ```
data/exe/pgsync CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ trap("SIGINT") { abort }
4
+
3
5
  begin
4
6
  require "pgsync"
5
7
  PgSync::Client.new(ARGV).perform
6
8
  rescue PgSync::Error => e
7
- abort e.message
9
+ abort PgSync::Client.colorize(e.message, 31) # red
8
10
  rescue Interrupt
9
11
  abort
10
12
  end
@@ -1,17 +1,21 @@
1
+ # dependencies
2
+ require "parallel"
3
+ require "pg"
4
+ require "slop"
5
+ require "tty-spinner"
6
+
7
+ # stdlib
1
8
  require "cgi"
2
9
  require "erb"
3
10
  require "fileutils"
4
- require "multiprocessing"
5
- require "pg"
6
- require "parallel"
7
11
  require "set"
8
12
  require "shellwords"
9
- require "slop"
10
13
  require "tempfile"
11
14
  require "thread" # windows only
12
15
  require "uri"
13
16
  require "yaml"
14
17
 
18
+ # modules
15
19
  require "pgsync/client"
16
20
  require "pgsync/data_source"
17
21
  require "pgsync/table_list"
@@ -5,7 +5,6 @@ module PgSync
5
5
  $stderr.sync = true
6
6
  @exit = false
7
7
  @arguments, @options = parse_args(args)
8
- @mutex = windows? ? Mutex.new : MultiProcessing::Mutex.new
9
8
  end
10
9
 
11
10
  # TODO clean up this mess
@@ -92,8 +91,8 @@ module PgSync
92
91
  unless opts[:schema_only]
93
92
  confirm_tables_exist(destination, tables, "destination")
94
93
 
95
- in_parallel(tables) do |table, table_opts|
96
- TableSync.new.sync(@mutex, config, table, opts.merge(table_opts), source.url, destination.url, source.search_path.find { |sp| sp != "pg_catalog" })
94
+ in_parallel(tables, first_schema: source.search_path.find { |sp| sp != "pg_catalog" }) do |table, table_opts|
95
+ TableSync.new.sync(config, table, opts.merge(table_opts), source.url, destination.url)
97
96
  end
98
97
  end
99
98
 
@@ -181,6 +180,7 @@ Options:}
181
180
  o.boolean "--schema-only", "schema only", default: false
182
181
  o.boolean "--all-schemas", "all schemas", default: false
183
182
  o.boolean "--no-rules", "do not apply data rules", default: false
183
+ o.boolean "--no-sequences", "do not sync sequences", default: false
184
184
  o.boolean "--init", "init", default: false
185
185
  o.boolean "--setup", "setup", default: false, help: false
186
186
  o.boolean "--in-batches", "in batches", default: false, help: false
@@ -267,14 +267,56 @@ Options:}
267
267
  $stderr.puts message
268
268
  end
269
269
 
270
- def in_parallel(tables, &block)
270
+ def in_parallel(tables, first_schema:, &block)
271
+ spinners = TTY::Spinner::Multi.new(format: :dots)
272
+ item_spinners = {}
273
+
274
+ start = lambda do |item, i|
275
+ table, opts = item
276
+ message = String.new(":spinner ")
277
+ message << table.sub("#{first_schema}.", "")
278
+ # maybe output later
279
+ # message << " #{opts[:sql]}" if opts[:sql]
280
+ spinner = spinners.register(message)
281
+ spinner.auto_spin
282
+ item_spinners[item] = spinner
283
+ end
284
+
285
+ errors = 0
286
+
287
+ finish = lambda do |item, i, result|
288
+ spinner = item_spinners[item]
289
+ if result[:status] == "success"
290
+ spinner.success(display_message(result))
291
+ else
292
+ # TODO add option to fail fast
293
+ spinner.error(display_message(result))
294
+ errors += 1
295
+ end
296
+
297
+ unless spinner.send(:tty?)
298
+ status = result[:status] == "success" ? "✔" : "✖"
299
+ log [status, item.first.sub("#{first_schema}.", ""), display_message(result)].compact.join(" ")
300
+ end
301
+ end
302
+
303
+ options = {start: start, finish: finish}
271
304
  if @options[:debug] || @options[:in_batches]
272
- tables.each(&block)
305
+ options[:in_processes] = 0
273
306
  else
274
- options = {}
275
307
  options[:in_threads] = 4 if windows?
276
- Parallel.each(tables, options, &block)
277
308
  end
309
+
310
+ Parallel.each(tables, **options, &block)
311
+
312
+ raise PgSync::Error, "Synced failed for #{errors} table#{errors == 1 ? nil : "s"}" if errors > 0
313
+ end
314
+
315
+ def display_message(result)
316
+ message = String.new("")
317
+ message << "- #{result[:time]}s" if result[:time]
318
+ message << "(#{result[:message].gsub("\n", " ").strip})" if result[:message]
319
+ message
278
320
  end
279
321
 
280
322
  def pretty_list(items)
@@ -289,11 +331,20 @@ Options:}
289
331
 
290
332
  def log_completed(start_time)
291
333
  time = Time.now - start_time
292
- log "Completed in #{time.round(1)}s"
334
+ message = "Completed in #{time.round(1)}s"
335
+ log self.class.colorize(message, 32) # green
293
336
  end
294
337
 
295
338
  def windows?
296
339
  Gem.win_platform?
297
340
  end
341
+
342
+ def self.colorize(message, color_code)
343
+ if $stderr.tty?
344
+ "\e[#{color_code}m#{message}\e[0m"
345
+ else
346
+ message
347
+ end
348
+ end
298
349
  end
299
350
  end
@@ -1,6 +1,6 @@
1
1
  module PgSync
2
2
  class TableSync
3
- def sync(mutex, config, table, opts, source_url, destination_url, first_schema)
3
+ def sync(config, table, opts, source_url, destination_url)
4
4
  start_time = Time.now
5
5
  source = DataSource.new(source_url, timeout: 0)
6
6
  destination = DataSource.new(destination_url, timeout: 0)
@@ -17,30 +17,32 @@ module PgSync
17
17
  extra_fields = to_fields - from_fields
18
18
  missing_fields = from_fields - to_fields
19
19
 
20
- from_sequences = source.sequences(table, shared_fields)
21
- to_sequences = destination.sequences(table, shared_fields)
20
+ if opts[:no_sequences]
21
+ from_sequences = []
22
+ to_sequences = []
23
+ else
24
+ from_sequences = source.sequences(table, shared_fields)
25
+ to_sequences = destination.sequences(table, shared_fields)
26
+ end
27
+
22
28
  shared_sequences = to_sequences & from_sequences
23
29
  extra_sequences = to_sequences - from_sequences
24
30
  missing_sequences = from_sequences - to_sequences
25
31
 
26
32
  sql_clause = String.new
27
33
 
28
- table_name = table.sub("#{first_schema}.", "")
34
+ if opts[:sql]
35
+ sql_clause << " #{opts[:sql]}"
36
+ end
29
37
 
30
- mutex.synchronize do
31
- log "* Syncing #{table_name}"
32
- if opts[:sql]
33
- log " #{opts[:sql]}"
34
- sql_clause << " #{opts[:sql]}"
35
- end
36
- log " Extra columns: #{extra_fields.join(", ")}" if extra_fields.any?
37
- log " Missing columns: #{missing_fields.join(", ")}" if missing_fields.any?
38
- log " Extra sequences: #{extra_sequences.join(", ")}" if extra_sequences.any?
39
- log " Missing sequences: #{missing_sequences.join(", ")}" if missing_sequences.any?
38
+ notes = []
39
+ notes << "Extra columns: #{extra_fields.join(", ")}" if extra_fields.any?
40
+ notes << "Missing columns: #{missing_fields.join(", ")}" if missing_fields.any?
41
+ notes << "Extra sequences: #{extra_sequences.join(", ")}" if extra_sequences.any?
42
+ notes << "Missing sequences: #{missing_sequences.join(", ")}" if missing_sequences.any?
40
43
 
41
- if shared_fields.empty?
42
- log " No fields to copy"
43
- end
44
+ if shared_fields.empty?
45
+ return {status: "success", message: "No fields to copy"}
44
46
  end
45
47
 
46
48
  if shared_fields.any?
@@ -147,13 +149,19 @@ module PgSync
147
149
  to_connection.exec("SELECT setval(#{escape(seq)}, #{escape(value)})")
148
150
  end
149
151
  end
150
- mutex.synchronize do
151
- log "* DONE #{table_name} (#{(Time.now - start_time).round(1)}s)"
152
+
153
+ message = nil
154
+ if notes.any?
155
+ message = notes.join(", ")
152
156
  end
157
+
158
+ {status: "success", message: message, time: (Time.now - start_time).round(1)}
153
159
  ensure
154
160
  source.close
155
161
  destination.close
156
162
  end
163
+ rescue PgSync::Error => e
164
+ {status: "error", message: e.message}
157
165
  end
158
166
 
159
167
  private
@@ -1,3 +1,3 @@
1
1
  module PgSync
2
- VERSION = "0.4.3"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgsync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-28 00:00:00.000000000 Z
11
+ date: 2020-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: multiprocessing
14
+ name: parallel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,47 +25,47 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: parallel
28
+ name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.18.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.18.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: pg
42
+ name: slop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.18.2
47
+ version: 4.2.0
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.18.2
54
+ version: 4.2.0
55
55
  - !ruby/object:Gem::Dependency
56
- name: slop
56
+ name: tty-spinner
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 4.2.0
61
+ version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 4.2.0
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.0.3
148
+ rubygems_version: 3.1.2
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Sync Postgres data between databases