pgsync 0.5.1 → 0.6.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 +4 -4
- data/CHANGELOG.md +40 -0
- data/LICENSE.txt +1 -1
- data/README.md +84 -41
- data/config.yml +5 -4
- data/exe/pgsync +3 -11
- data/lib/pgsync.rb +8 -5
- data/lib/pgsync/client.rb +60 -332
- data/lib/pgsync/data_source.rb +78 -77
- data/lib/pgsync/init.rb +61 -0
- data/lib/pgsync/schema_sync.rb +83 -0
- data/lib/pgsync/sync.rb +162 -0
- data/lib/pgsync/table.rb +28 -0
- data/lib/pgsync/table_sync.rb +168 -208
- data/lib/pgsync/task.rb +315 -0
- data/lib/pgsync/task_resolver.rb +235 -0
- data/lib/pgsync/utils.rb +86 -0
- data/lib/pgsync/version.rb +1 -1
- metadata +11 -5
- data/lib/pgsync/table_list.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6c20cf99ebc0961d8699228883f6c2e9c95c9798cfb6f101cb78803df6b1b43
|
4
|
+
data.tar.gz: 7c719442c07e6db1d4704199aa2b95d32e1ba528797b2d21b15be85f2de71d79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e3d735e6e3002e2fcb74de574f63f534472821aca0154c13d9d7ce6e9fa94426239cef875cd33724fec5a6cf2567b5656a80a9d92f89d9678ee6bee2c2ec70c
|
7
|
+
data.tar.gz: 3ab5d416f4c2364644c015d106631898a3615cc671179f8b1193cda5b1e894512fb39906ec1c27570ca9fbd186c9eb19af3afb88eba093bb21e8bc471c28f18b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
## 0.6.0 (2020-06-07)
|
2
|
+
|
3
|
+
- Added messages for different column types and non-deferrable constraints
|
4
|
+
- Added support for wildcards to `--exclude`
|
5
|
+
- Improved `--overwrite` and `--preserve` options for foreign keys
|
6
|
+
- Improved output for schema sync
|
7
|
+
- Fixed `--overwrite` and `--preserve` options for multicolumn primary keys
|
8
|
+
- Fixed output for notices
|
9
|
+
|
10
|
+
Breaking
|
11
|
+
|
12
|
+
- Syncs shared tables instead of raising an error when tables missing in destination
|
13
|
+
- Raise an error when `--config` or `--db` option provided and config not found
|
14
|
+
- Removed deprecated options
|
15
|
+
- Dropped support for Postgres < 9.5
|
16
|
+
|
17
|
+
## 0.5.5 (2020-05-13)
|
18
|
+
|
19
|
+
- Added `--jobs` option
|
20
|
+
- Added `--defer-constraints` option
|
21
|
+
- Added `--disable-user-triggers` option
|
22
|
+
- Added `--disable-integrity` option
|
23
|
+
- Improved error message for older libpq
|
24
|
+
|
25
|
+
## 0.5.4 (2020-05-09)
|
26
|
+
|
27
|
+
- Fixed output for `--in-batches`
|
28
|
+
|
29
|
+
## 0.5.3 (2020-04-03)
|
30
|
+
|
31
|
+
- Improved Postgres error messages
|
32
|
+
- Fixed behavior of wildcard without schema
|
33
|
+
|
34
|
+
## 0.5.2 (2020-03-27)
|
35
|
+
|
36
|
+
- Added `--fail-fast` option
|
37
|
+
- Automatically exclude tables when `--init` run inside Rails app
|
38
|
+
- Improved error message
|
39
|
+
- Fixed typo in error message
|
40
|
+
|
1
41
|
## 0.5.1 (2020-03-26)
|
2
42
|
|
3
43
|
- Fixed Slop warning with Ruby 2.7
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# pgsync
|
2
2
|
|
3
|
-
Sync Postgres
|
3
|
+
Sync data from one Postgres database to another (like `pg_dump`/`pg_restore`). Designed for:
|
4
4
|
|
5
|
-
- **speed** -
|
5
|
+
- **speed** - tables are transferred in parallel
|
6
6
|
- **security** - built-in methods to prevent sensitive data from ever leaving the server
|
7
|
+
- **flexibility** - gracefully handles schema differences, like missing columns and extra columns
|
7
8
|
- **convenience** - sync partial tables, groups of tables, and related records
|
8
9
|
|
9
10
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
@@ -20,6 +21,8 @@ gem install pgsync
|
|
20
21
|
|
21
22
|
This will give you the `pgsync` command. If installation fails, you may need to install [dependencies](#dependencies).
|
22
23
|
|
24
|
+
## Setup
|
25
|
+
|
23
26
|
In your project directory, run:
|
24
27
|
|
25
28
|
```sh
|
@@ -30,20 +33,26 @@ This creates `.pgsync.yml` for you to customize. We recommend checking this into
|
|
30
33
|
|
31
34
|
## How to Use
|
32
35
|
|
36
|
+
First, make sure your schema is set up in both databases. We recommend using a schema migration tool for this, but pgsync also provides a few [convenience methods](#schema). Once that’s done, you’re ready to sync data.
|
37
|
+
|
33
38
|
Sync all tables
|
34
39
|
|
35
40
|
```sh
|
36
41
|
pgsync
|
37
42
|
```
|
38
43
|
|
39
|
-
**Note:** pgsync assumes your schema is already set up on your local machine. See the [schema section](#schema) if that’s not the case.
|
40
|
-
|
41
44
|
Sync specific tables
|
42
45
|
|
43
46
|
```sh
|
44
47
|
pgsync table1,table2
|
45
48
|
```
|
46
49
|
|
50
|
+
Works with wildcards as well
|
51
|
+
|
52
|
+
```sh
|
53
|
+
pgsync "table*"
|
54
|
+
```
|
55
|
+
|
47
56
|
Sync specific rows (existing rows are overwritten)
|
48
57
|
|
49
58
|
```sh
|
@@ -62,13 +71,15 @@ Or truncate them
|
|
62
71
|
pgsync products "where store_id = 1" --truncate
|
63
72
|
```
|
64
73
|
|
65
|
-
|
74
|
+
## Tables
|
75
|
+
|
76
|
+
Exclude specific tables
|
66
77
|
|
67
78
|
```sh
|
68
|
-
pgsync --exclude
|
79
|
+
pgsync --exclude table1,table2
|
69
80
|
```
|
70
81
|
|
71
|
-
|
82
|
+
Add to `.pgsync.yml` to exclude by default
|
72
83
|
|
73
84
|
```yml
|
74
85
|
exclude:
|
@@ -76,15 +87,17 @@ exclude:
|
|
76
87
|
- table2
|
77
88
|
```
|
78
89
|
|
79
|
-
|
90
|
+
Sync tables from all schemas or specific schemas (by default, only the search path is synced)
|
80
91
|
|
81
|
-
```
|
82
|
-
|
83
|
-
|
84
|
-
|
92
|
+
```sh
|
93
|
+
pgsync --all-schemas
|
94
|
+
# or
|
95
|
+
pgsync --schemas public,other
|
96
|
+
# or
|
97
|
+
pgsync public.table1,other.table2
|
85
98
|
```
|
86
99
|
|
87
|
-
|
100
|
+
## Groups
|
88
101
|
|
89
102
|
Define groups in `.pgsync.yml`:
|
90
103
|
|
@@ -101,6 +114,8 @@ And run:
|
|
101
114
|
pgsync group1
|
102
115
|
```
|
103
116
|
|
117
|
+
## Variables
|
118
|
+
|
104
119
|
You can also use groups to sync a specific record and associated records in other tables.
|
105
120
|
|
106
121
|
To get product `123` with its reviews, last 10 coupons, and store, use:
|
@@ -120,18 +135,14 @@ And run:
|
|
120
135
|
pgsync product:123
|
121
136
|
```
|
122
137
|
|
123
|
-
|
138
|
+
## Schema
|
124
139
|
|
125
|
-
|
126
|
-
|
127
|
-
Sync schema before the data
|
140
|
+
Sync schema before the data (this wipes out existing data)
|
128
141
|
|
129
142
|
```sh
|
130
143
|
pgsync --schema-first
|
131
144
|
```
|
132
145
|
|
133
|
-
**Note:** This wipes out existing data
|
134
|
-
|
135
146
|
Specify tables
|
136
147
|
|
137
148
|
```sh
|
@@ -146,13 +157,9 @@ pgsync --schema-only
|
|
146
157
|
|
147
158
|
pgsync does not try to sync Postgres extensions.
|
148
159
|
|
149
|
-
## Data
|
160
|
+
## Sensitive Data
|
150
161
|
|
151
|
-
|
152
|
-
|
153
|
-
## Sensitive Information
|
154
|
-
|
155
|
-
Prevent sensitive information like email addresses from leaving the remote server.
|
162
|
+
Prevent sensitive data like email addresses from leaving the remote server.
|
156
163
|
|
157
164
|
Define rules in `.pgsync.yml`:
|
158
165
|
|
@@ -185,31 +192,45 @@ Options for replacement are:
|
|
185
192
|
- `null`
|
186
193
|
- `untouched`
|
187
194
|
|
188
|
-
Rules starting with `unique_` require the table to have a primary key. `unique_phone` requires a numeric primary key.
|
195
|
+
Rules starting with `unique_` require the table to have a single column primary key. `unique_phone` requires a numeric primary key.
|
189
196
|
|
190
|
-
##
|
197
|
+
## Foreign Keys
|
191
198
|
|
192
|
-
|
199
|
+
Foreign keys can make it difficult to sync data. Three options are:
|
200
|
+
|
201
|
+
1. Manually specify the order of tables
|
202
|
+
2. Use deferrable constraints
|
203
|
+
3. Disable foreign key triggers, which can silently break referential integrity
|
204
|
+
|
205
|
+
When manually specifying the order, use `--jobs 1` so tables are synced one-at-a-time.
|
193
206
|
|
194
207
|
```sh
|
195
|
-
pgsync --
|
208
|
+
pgsync table1,table2,table3 --jobs 1
|
196
209
|
```
|
197
210
|
|
198
|
-
|
211
|
+
If your tables have [deferrable constraints](https://begriffs.com/posts/2017-08-27-deferrable-sql-constraints.html), use:
|
199
212
|
|
200
213
|
```sh
|
201
|
-
pgsync --
|
214
|
+
pgsync --defer-constraints
|
202
215
|
```
|
203
216
|
|
204
|
-
|
217
|
+
To disable foreign key triggers and potentially break referential integrity, use:
|
205
218
|
|
206
|
-
|
219
|
+
```sh
|
220
|
+
pgsync --disable-integrity
|
221
|
+
```
|
207
222
|
|
208
|
-
|
223
|
+
## Triggers
|
224
|
+
|
225
|
+
Disable user triggers with:
|
209
226
|
|
210
|
-
|
227
|
+
```sh
|
228
|
+
pgsync --disable-user-triggers
|
229
|
+
```
|
211
230
|
|
212
|
-
|
231
|
+
## Append-Only Tables
|
232
|
+
|
233
|
+
For extremely large, append-only tables, sync in batches.
|
213
234
|
|
214
235
|
```sh
|
215
236
|
pgsync large_table --in-batches
|
@@ -217,15 +238,31 @@ pgsync large_table --in-batches
|
|
217
238
|
|
218
239
|
The script will resume where it left off when run again, making it great for backfills.
|
219
240
|
|
220
|
-
##
|
241
|
+
## Connection Security
|
242
|
+
|
243
|
+
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.
|
244
|
+
|
245
|
+
## Safety
|
246
|
+
|
247
|
+
To keep you from accidentally overwriting production, the destination is limited to `localhost` or `127.0.0.1` by default.
|
248
|
+
|
249
|
+
To use another host, add `to_safe: true` to your `.pgsync.yml`.
|
221
250
|
|
222
|
-
|
251
|
+
## Multiple Databases
|
252
|
+
|
253
|
+
To use with multiple databases, run:
|
254
|
+
|
255
|
+
```sh
|
256
|
+
pgsync --init db2
|
257
|
+
```
|
258
|
+
|
259
|
+
This creates `.pgsync-db2.yml` for you to edit. Specify a database in commands with:
|
223
260
|
|
224
261
|
```sh
|
225
|
-
pgsync
|
262
|
+
pgsync --db db2
|
226
263
|
```
|
227
264
|
|
228
|
-
##
|
265
|
+
## Other Commands
|
229
266
|
|
230
267
|
Help
|
231
268
|
|
@@ -239,6 +276,12 @@ Version
|
|
239
276
|
pgsync --version
|
240
277
|
```
|
241
278
|
|
279
|
+
List tables
|
280
|
+
|
281
|
+
```sh
|
282
|
+
pgsync --list
|
283
|
+
```
|
284
|
+
|
242
285
|
## Scripts
|
243
286
|
|
244
287
|
Use groups when possible to take advantage of parallelism.
|
@@ -246,7 +289,7 @@ Use groups when possible to take advantage of parallelism.
|
|
246
289
|
For Ruby scripts, you may need to do:
|
247
290
|
|
248
291
|
```rb
|
249
|
-
Bundler.
|
292
|
+
Bundler.with_unbundled_env do
|
250
293
|
system "pgsync ..."
|
251
294
|
end
|
252
295
|
```
|
data/config.yml
CHANGED
@@ -13,16 +13,17 @@ from: $(some_command)?sslmode=require
|
|
13
13
|
to: postgres://localhost:5432/myapp_development
|
14
14
|
|
15
15
|
# exclude tables
|
16
|
-
|
17
|
-
# - schema_migrations
|
18
|
-
# - ar_internal_metadata
|
19
|
-
|
16
|
+
%{exclude}
|
20
17
|
# define groups
|
21
18
|
# groups:
|
22
19
|
# group1:
|
23
20
|
# - table1
|
24
21
|
# - table2
|
25
22
|
|
23
|
+
# sync specific schemas
|
24
|
+
# schemas:
|
25
|
+
# - public
|
26
|
+
|
26
27
|
# protect sensitive information
|
27
28
|
data_rules:
|
28
29
|
email: unique_email
|
data/exe/pgsync
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
#
|
4
|
-
$VERBOSE = nil
|
5
|
-
|
3
|
+
# handle interrupts
|
6
4
|
trap("SIGINT") { abort }
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
PgSync::Client.new(ARGV).perform
|
11
|
-
rescue PgSync::Error => e
|
12
|
-
abort PgSync::Client.colorize(e.message, 31) # red
|
13
|
-
rescue Interrupt
|
14
|
-
abort
|
15
|
-
end
|
6
|
+
require "pgsync"
|
7
|
+
PgSync::Client.start
|
data/lib/pgsync.rb
CHANGED
@@ -5,21 +5,24 @@ require "slop"
|
|
5
5
|
require "tty-spinner"
|
6
6
|
|
7
7
|
# stdlib
|
8
|
-
require "cgi"
|
9
|
-
require "erb"
|
10
|
-
require "fileutils"
|
11
8
|
require "set"
|
12
9
|
require "shellwords"
|
13
10
|
require "tempfile"
|
14
|
-
require "thread" # windows only
|
15
11
|
require "uri"
|
16
12
|
require "yaml"
|
13
|
+
require "open3"
|
17
14
|
|
18
15
|
# modules
|
16
|
+
require "pgsync/utils"
|
19
17
|
require "pgsync/client"
|
20
18
|
require "pgsync/data_source"
|
21
|
-
require "pgsync/
|
19
|
+
require "pgsync/init"
|
20
|
+
require "pgsync/schema_sync"
|
21
|
+
require "pgsync/sync"
|
22
|
+
require "pgsync/table"
|
22
23
|
require "pgsync/table_sync"
|
24
|
+
require "pgsync/task"
|
25
|
+
require "pgsync/task_resolver"
|
23
26
|
require "pgsync/version"
|
24
27
|
|
25
28
|
module PgSync
|
data/lib/pgsync/client.rb
CHANGED
@@ -1,350 +1,78 @@
|
|
1
1
|
module PgSync
|
2
2
|
class Client
|
3
|
+
include Utils
|
4
|
+
|
3
5
|
def initialize(args)
|
4
|
-
|
5
|
-
|
6
|
-
@exit = false
|
7
|
-
@arguments, @options = parse_args(args)
|
6
|
+
@args = args
|
7
|
+
output.sync = true
|
8
8
|
end
|
9
9
|
|
10
|
-
# TODO clean up this mess
|
11
10
|
def perform
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
true
|
27
|
-
end
|
28
|
-
|
29
|
-
protected
|
30
|
-
|
31
|
-
def sync(args, opts)
|
32
|
-
start_time = Time.now
|
33
|
-
|
34
|
-
if args.size > 2
|
35
|
-
raise PgSync::Error, "Usage:\n pgsync [options]"
|
36
|
-
end
|
37
|
-
|
38
|
-
source = DataSource.new(opts[:from])
|
39
|
-
raise PgSync::Error, "No source" unless source.exists?
|
40
|
-
|
41
|
-
destination = DataSource.new(opts[:to])
|
42
|
-
raise PgSync::Error, "No destination" unless destination.exists?
|
43
|
-
|
44
|
-
begin
|
45
|
-
# start connections
|
46
|
-
source.host
|
47
|
-
destination.host
|
48
|
-
|
49
|
-
unless opts[:to_safe] || destination.local?
|
50
|
-
raise PgSync::Error, "Danger! Add `to_safe: true` to `.pgsync.yml` if the destination is not localhost or 127.0.0.1"
|
51
|
-
end
|
52
|
-
|
53
|
-
print_description("From", source)
|
54
|
-
print_description("To", destination)
|
55
|
-
ensure
|
56
|
-
source.close
|
57
|
-
destination.close
|
58
|
-
end
|
59
|
-
|
60
|
-
tables = nil
|
61
|
-
begin
|
62
|
-
tables = TableList.new(args, opts, source, config).tables
|
63
|
-
ensure
|
64
|
-
source.close
|
65
|
-
end
|
66
|
-
|
67
|
-
confirm_tables_exist(source, tables, "source")
|
68
|
-
|
69
|
-
if opts[:list]
|
70
|
-
confirm_tables_exist(destination, tables, "destination")
|
71
|
-
|
72
|
-
list_items =
|
73
|
-
if args[0] == "groups"
|
74
|
-
(config["groups"] || {}).keys
|
75
|
-
else
|
76
|
-
tables.keys
|
77
|
-
end
|
78
|
-
|
79
|
-
pretty_list list_items
|
11
|
+
result = Slop::Parser.new(slop_options).parse(@args)
|
12
|
+
arguments = result.arguments
|
13
|
+
options = result.to_h
|
14
|
+
|
15
|
+
raise Error, "Specify either --db or --config, not both" if options[:db] && options[:config]
|
16
|
+
raise Error, "Cannot use --overwrite with --in-batches" if options[:overwrite] && options[:in_batches]
|
17
|
+
|
18
|
+
if options[:version]
|
19
|
+
log VERSION
|
20
|
+
elsif options[:help]
|
21
|
+
log slop_options
|
22
|
+
elsif options[:init]
|
23
|
+
Init.new(arguments, options).perform
|
80
24
|
else
|
81
|
-
|
82
|
-
if opts[:preserve]
|
83
|
-
raise PgSync::Error, "Cannot use --preserve with --schema-first or --schema-only"
|
84
|
-
end
|
85
|
-
|
86
|
-
log "* Dumping schema"
|
87
|
-
schema_tables = tables if !opts[:all_schemas] || opts[:tables] || opts[:groups] || args[0] || opts[:exclude]
|
88
|
-
sync_schema(source, destination, schema_tables)
|
89
|
-
end
|
90
|
-
|
91
|
-
unless opts[:schema_only]
|
92
|
-
confirm_tables_exist(destination, tables, "destination")
|
93
|
-
|
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)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
log_completed(start_time)
|
25
|
+
Sync.new(arguments, options).perform
|
100
26
|
end
|
27
|
+
rescue => e
|
28
|
+
# Error, PG::ConnectionBad, Slop::Error
|
29
|
+
raise e if options && options[:debug]
|
30
|
+
abort colorize(e.message.strip, :red)
|
101
31
|
end
|
102
32
|
|
103
|
-
def
|
104
|
-
|
105
|
-
unless data_source.table_exists?(table)
|
106
|
-
raise PgSync::Error, "Table does not exist in #{description}: #{table}"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
ensure
|
110
|
-
data_source.close
|
33
|
+
def self.start
|
34
|
+
new(ARGV).perform
|
111
35
|
end
|
112
36
|
|
113
|
-
|
114
|
-
command = args[0]
|
115
|
-
|
116
|
-
case command
|
117
|
-
when "setup"
|
118
|
-
args.shift
|
119
|
-
opts[:init] = true
|
120
|
-
deprecated "Use `psync --init` instead"
|
121
|
-
when "schema"
|
122
|
-
args.shift
|
123
|
-
opts[:schema_only] = true
|
124
|
-
deprecated "Use `psync --schema-only` instead"
|
125
|
-
when "tables"
|
126
|
-
args.shift
|
127
|
-
opts[:tables] = args.shift
|
128
|
-
deprecated "Use `pgsync #{opts[:tables]}` instead"
|
129
|
-
when "groups"
|
130
|
-
args.shift
|
131
|
-
opts[:groups] = args.shift
|
132
|
-
deprecated "Use `pgsync #{opts[:groups]}` instead"
|
133
|
-
end
|
134
|
-
|
135
|
-
if opts[:where]
|
136
|
-
opts[:sql] ||= String.new
|
137
|
-
opts[:sql] << " WHERE #{opts[:where]}"
|
138
|
-
deprecated "Use `\"WHERE #{opts[:where]}\"` instead"
|
139
|
-
end
|
140
|
-
|
141
|
-
if opts[:limit]
|
142
|
-
opts[:sql] ||= String.new
|
143
|
-
opts[:sql] << " LIMIT #{opts[:limit]}"
|
144
|
-
deprecated "Use `\"LIMIT #{opts[:limit]}\"` instead"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def sync_schema(source, destination, tables = nil)
|
149
|
-
dump_command = source.dump_command(tables)
|
150
|
-
restore_command = destination.restore_command
|
151
|
-
unless system("#{dump_command} | #{restore_command}")
|
152
|
-
raise PgSync::Error, "Schema sync returned non-zero exit code"
|
153
|
-
end
|
154
|
-
end
|
37
|
+
protected
|
155
38
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
|
39
|
+
def slop_options
|
40
|
+
o = Slop::Options.new
|
41
|
+
o.banner = %{Usage:
|
42
|
+
pgsync [options]
|
160
43
|
|
161
44
|
Options:}
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
o.on "-h", "--help", "prints help" do
|
194
|
-
log o
|
195
|
-
@exit = true
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
opts_hash = opts.to_hash
|
200
|
-
opts_hash[:init] = opts_hash[:setup] if opts_hash[:setup]
|
201
|
-
|
202
|
-
[opts.arguments, opts_hash]
|
203
|
-
rescue Slop::Error => e
|
204
|
-
raise PgSync::Error, e.message
|
205
|
-
end
|
206
|
-
|
207
|
-
def config
|
208
|
-
@config ||= begin
|
209
|
-
if config_file
|
210
|
-
begin
|
211
|
-
YAML.load_file(config_file) || {}
|
212
|
-
rescue Psych::SyntaxError => e
|
213
|
-
raise PgSync::Error, e.message
|
214
|
-
end
|
215
|
-
else
|
216
|
-
{}
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def setup(config_file)
|
222
|
-
if File.exist?(config_file)
|
223
|
-
raise PgSync::Error, "#{config_file} exists."
|
224
|
-
else
|
225
|
-
FileUtils.cp(File.dirname(__FILE__) + "/../../config.yml", config_file)
|
226
|
-
log "#{config_file} created. Add your database credentials."
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def db_config_file(db)
|
231
|
-
return unless db
|
232
|
-
".pgsync-#{db}.yml"
|
233
|
-
end
|
234
|
-
|
235
|
-
def print_description(prefix, source)
|
236
|
-
location = " on #{source.host}:#{source.port}" if source.host
|
237
|
-
log "#{prefix}: #{source.dbname}#{location}"
|
238
|
-
end
|
239
|
-
|
240
|
-
def search_tree(file)
|
241
|
-
path = Dir.pwd
|
242
|
-
# prevent infinite loop
|
243
|
-
20.times do
|
244
|
-
absolute_file = File.join(path, file)
|
245
|
-
if File.exist?(absolute_file)
|
246
|
-
break absolute_file
|
247
|
-
end
|
248
|
-
path = File.dirname(path)
|
249
|
-
break if path == "/"
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def config_file
|
254
|
-
return @config_file if instance_variable_defined?(:@config_file)
|
255
|
-
|
256
|
-
@config_file =
|
257
|
-
search_tree(
|
258
|
-
if @options[:db]
|
259
|
-
db_config_file(@options[:db])
|
260
|
-
else
|
261
|
-
@options[:config] || ".pgsync.yml"
|
262
|
-
end
|
263
|
-
)
|
264
|
-
end
|
265
|
-
|
266
|
-
def log(message = nil)
|
267
|
-
$stderr.puts message
|
268
|
-
end
|
269
|
-
|
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}
|
304
|
-
if @options[:debug] || @options[:in_batches]
|
305
|
-
options[:in_processes] = 0
|
306
|
-
else
|
307
|
-
options[:in_threads] = 4 if windows?
|
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
|
320
|
-
end
|
321
|
-
|
322
|
-
def pretty_list(items)
|
323
|
-
items.each do |item|
|
324
|
-
log item
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
def deprecated(message)
|
329
|
-
log "[DEPRECATED] #{message}"
|
330
|
-
end
|
331
|
-
|
332
|
-
def log_completed(start_time)
|
333
|
-
time = Time.now - start_time
|
334
|
-
message = "Completed in #{time.round(1)}s"
|
335
|
-
log self.class.colorize(message, 32) # green
|
336
|
-
end
|
337
|
-
|
338
|
-
def windows?
|
339
|
-
Gem.win_platform?
|
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
|
45
|
+
o.string "-d", "--db", "database"
|
46
|
+
o.string "-t", "--tables", "tables to sync"
|
47
|
+
o.string "-g", "--groups", "groups to sync"
|
48
|
+
o.integer "-j", "--jobs", "number of tables to sync at a time"
|
49
|
+
o.string "--schemas", "schemas to sync"
|
50
|
+
o.string "--from", "source"
|
51
|
+
o.string "--to", "destination"
|
52
|
+
o.string "--exclude", "exclude tables"
|
53
|
+
o.string "--config", "config file"
|
54
|
+
o.boolean "--to-safe", "accept danger", default: false
|
55
|
+
o.boolean "--debug", "debug", default: false
|
56
|
+
o.boolean "--list", "list", default: false
|
57
|
+
o.boolean "--overwrite", "overwrite existing rows", default: false, help: false
|
58
|
+
o.boolean "--preserve", "preserve existing rows", default: false
|
59
|
+
o.boolean "--truncate", "truncate existing rows", default: false
|
60
|
+
o.boolean "--schema-first", "schema first", default: false
|
61
|
+
o.boolean "--schema-only", "schema only", default: false
|
62
|
+
o.boolean "--all-schemas", "all schemas", default: false
|
63
|
+
o.boolean "--no-rules", "do not apply data rules", default: false
|
64
|
+
o.boolean "--no-sequences", "do not sync sequences", default: false
|
65
|
+
o.boolean "--init", "init", default: false
|
66
|
+
o.boolean "--in-batches", "in batches", default: false, help: false
|
67
|
+
o.integer "--batch-size", "batch size", default: 10000, help: false
|
68
|
+
o.float "--sleep", "sleep", default: 0, help: false
|
69
|
+
o.boolean "--fail-fast", "stop on the first failed table", default: false
|
70
|
+
o.boolean "--defer-constraints", "defer constraints", default: false
|
71
|
+
o.boolean "--disable-user-triggers", "disable non-system triggers", default: false
|
72
|
+
o.boolean "--disable-integrity", "disable foreign key triggers", default: false
|
73
|
+
o.boolean "-v", "--version", "print the version"
|
74
|
+
o.boolean "-h", "--help", "prints help"
|
75
|
+
o
|
348
76
|
end
|
349
77
|
end
|
350
78
|
end
|