pgsync 0.3.9 → 0.4.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 +6 -0
- data/LICENSE.txt +1 -1
- data/exe/pgsync +1 -1
- data/lib/pgsync.rb +8 -15
- data/lib/pgsync/client.rb +22 -11
- data/lib/pgsync/data_source.rb +43 -50
- data/lib/pgsync/table_list.rb +1 -1
- data/lib/pgsync/table_sync.rb +14 -25
- data/lib/pgsync/version.rb +1 -1
- data/pgsync.gemspec +4 -4
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4000209f5f79dad8a7c6f427be2cdcf25abe219e
|
4
|
+
data.tar.gz: 4980413b816f0d0aa150aedf7bbcb1141fb7caad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3122cf01ba1dcda75172bc823507c5db684561797dbdb88be42a93866d3fbfb0147e068ab501d1c7a74f09fd88341f623f61e4a2751183a051ba5cefecbdcbb4
|
7
|
+
data.tar.gz: cb8531e15f2ba2c714d1e022b286efe09208abd4b88c997139a23d6ff583e45c460bae46961735716978dd34b7448f42dc2b8f1cbdb4b06592b0d604e070a219
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/exe/pgsync
CHANGED
data/lib/pgsync.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
require "
|
2
|
-
require "slop"
|
3
|
-
require "uri"
|
1
|
+
require "cgi"
|
4
2
|
require "erb"
|
3
|
+
require "fileutils"
|
4
|
+
require "multiprocessing"
|
5
5
|
require "pg"
|
6
6
|
require "parallel"
|
7
|
-
require "multiprocessing"
|
8
|
-
require "fileutils"
|
9
|
-
require "tempfile"
|
10
|
-
require "cgi"
|
11
|
-
require "shellwords"
|
12
7
|
require "set"
|
8
|
+
require "shellwords"
|
9
|
+
require "slop"
|
10
|
+
require "tempfile"
|
13
11
|
require "thread" # windows only
|
12
|
+
require "uri"
|
13
|
+
require "yaml"
|
14
14
|
|
15
15
|
require "pgsync/client"
|
16
16
|
require "pgsync/data_source"
|
@@ -18,13 +18,6 @@ require "pgsync/table_list"
|
|
18
18
|
require "pgsync/table_sync"
|
19
19
|
require "pgsync/version"
|
20
20
|
|
21
|
-
module URI
|
22
|
-
class POSTGRESQL < Generic
|
23
|
-
DEFAULT_PORT = 5432
|
24
|
-
end
|
25
|
-
@@schemes["POSTGRESQL"] = @@schemes["POSTGRES"] = POSTGRESQL
|
26
|
-
end
|
27
|
-
|
28
21
|
module PgSync
|
29
22
|
class Error < StandardError; end
|
30
23
|
end
|
data/lib/pgsync/client.rb
CHANGED
@@ -2,6 +2,7 @@ module PgSync
|
|
2
2
|
class Client
|
3
3
|
def initialize(args)
|
4
4
|
$stdout.sync = true
|
5
|
+
$stderr.sync = true
|
5
6
|
@exit = false
|
6
7
|
@arguments, @options = parse_args(args)
|
7
8
|
@mutex = windows? ? Mutex.new : MultiProcessing::Mutex.new
|
@@ -12,7 +13,7 @@ module PgSync
|
|
12
13
|
return if @exit
|
13
14
|
|
14
15
|
args, opts = @arguments, @options
|
15
|
-
[:to, :from, :to_safe, :exclude].each do |opt|
|
16
|
+
[:to, :from, :to_safe, :exclude, :schemas].each do |opt|
|
16
17
|
opts[opt] ||= config[opt.to_s]
|
17
18
|
end
|
18
19
|
map_deprecations(args, opts)
|
@@ -41,12 +42,21 @@ module PgSync
|
|
41
42
|
destination = DataSource.new(opts[:to])
|
42
43
|
raise PgSync::Error, "No destination" unless destination.exists?
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
begin
|
46
|
+
# start connections
|
47
|
+
source.host
|
48
|
+
destination.host
|
49
|
+
|
50
|
+
unless opts[:to_safe] || destination.local?
|
51
|
+
raise PgSync::Error, "Danger! Add `to_safe: true` to `.pgsync.yml` if the destination is not localhost or 127.0.0.1"
|
52
|
+
end
|
47
53
|
|
48
|
-
|
49
|
-
|
54
|
+
print_description("From", source)
|
55
|
+
print_description("To", destination)
|
56
|
+
ensure
|
57
|
+
source.close
|
58
|
+
destination.close
|
59
|
+
end
|
50
60
|
|
51
61
|
tables = nil
|
52
62
|
begin
|
@@ -82,7 +92,7 @@ module PgSync
|
|
82
92
|
confirm_tables_exist(destination, tables, "destination")
|
83
93
|
|
84
94
|
in_parallel(tables) do |table, table_opts|
|
85
|
-
TableSync.new.
|
95
|
+
TableSync.new.sync(@mutex, config, table, opts.merge(table_opts), source.url, destination.url, source.search_path.find { |sp| sp != "pg_catalog" })
|
86
96
|
end
|
87
97
|
end
|
88
98
|
|
@@ -90,14 +100,14 @@ module PgSync
|
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
93
|
-
def confirm_tables_exist(
|
103
|
+
def confirm_tables_exist(data_source, tables, description)
|
94
104
|
tables.keys.each do |table|
|
95
|
-
unless
|
105
|
+
unless data_source.table_exists?(table)
|
96
106
|
raise PgSync::Error, "Table does not exist in #{description}: #{table}"
|
97
107
|
end
|
98
108
|
end
|
99
109
|
ensure
|
100
|
-
|
110
|
+
data_source.close
|
101
111
|
end
|
102
112
|
|
103
113
|
def map_deprecations(args, opts)
|
@@ -215,7 +225,8 @@ Options:}
|
|
215
225
|
end
|
216
226
|
|
217
227
|
def print_description(prefix, source)
|
218
|
-
|
228
|
+
location = " on #{source.host}:#{source.port}" if source.host
|
229
|
+
log "#{prefix}: #{source.dbname}#{location}"
|
219
230
|
end
|
220
231
|
|
221
232
|
def search_tree(file)
|
data/lib/pgsync/data_source.rb
CHANGED
@@ -2,8 +2,9 @@ module PgSync
|
|
2
2
|
class DataSource
|
3
3
|
attr_reader :url
|
4
4
|
|
5
|
-
def initialize(source)
|
5
|
+
def initialize(source, timeout: 3)
|
6
6
|
@url = resolve_url(source)
|
7
|
+
@timeout = timeout
|
7
8
|
end
|
8
9
|
|
9
10
|
def exists?
|
@@ -11,45 +12,31 @@ module PgSync
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def local?
|
14
|
-
%w(localhost 127.0.0.1).include?(
|
15
|
+
!host || %w(localhost 127.0.0.1).include?(host)
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
@
|
19
|
-
uri = URI.parse(@url)
|
20
|
-
uri.scheme ||= "postgres"
|
21
|
-
uri.host ||= "localhost"
|
22
|
-
uri.port ||= 5432
|
23
|
-
uri.path = "/#{uri.path}" if uri.path && uri.path[0] != "/"
|
24
|
-
uri
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def schema
|
29
|
-
@schema ||= CGI.parse(uri.query.to_s)["schema"][0]
|
18
|
+
def host
|
19
|
+
@host ||= conninfo[:host]
|
30
20
|
end
|
31
21
|
|
32
|
-
def
|
33
|
-
|
34
|
-
execute(query).map { |row| "#{row["schemaname"]}.#{row["tablename"]}" }
|
22
|
+
def port
|
23
|
+
@port ||= conninfo[:port]
|
35
24
|
end
|
36
25
|
|
37
|
-
def
|
38
|
-
|
39
|
-
execute(query, table.split(".", 2)).size > 0
|
26
|
+
def dbname
|
27
|
+
@dbname ||= conninfo[:dbname]
|
40
28
|
end
|
41
29
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
30
|
+
# gets visible tables
|
31
|
+
def tables
|
32
|
+
@tables ||= begin
|
33
|
+
query = "SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('information_schema', 'pg_catalog') ORDER BY 1, 2"
|
34
|
+
execute(query).map { |row| "#{row["table_schema"]}.#{row["table_name"]}" }
|
46
35
|
end
|
47
36
|
end
|
48
37
|
|
49
|
-
def
|
50
|
-
|
51
|
-
uri.query = nil
|
52
|
-
uri.to_s
|
38
|
+
def table_exists?(table)
|
39
|
+
table_set.include?(table)
|
53
40
|
end
|
54
41
|
|
55
42
|
def columns(table)
|
@@ -98,38 +85,40 @@ module PgSync
|
|
98
85
|
row && row["attname"]
|
99
86
|
end
|
100
87
|
|
101
|
-
# borrowed from
|
102
|
-
# ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver
|
103
88
|
def conn
|
104
89
|
@conn ||= begin
|
105
90
|
begin
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
connect_timeout: 3
|
114
|
-
}.reject { |_, value| value.to_s.empty? }
|
115
|
-
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a?(String) }
|
116
|
-
conn = PG::Connection.new(config)
|
91
|
+
ENV["PGCONNECT_TIMEOUT"] ||= @timeout.to_s
|
92
|
+
if @url =~ /\Apostgres(ql)?:\/\//
|
93
|
+
config = @url
|
94
|
+
else
|
95
|
+
config = {dbname: @url}
|
96
|
+
end
|
97
|
+
PG::Connection.new(config)
|
117
98
|
rescue PG::ConnectionBad => e
|
118
|
-
log
|
119
99
|
raise PgSync::Error, e.message
|
100
|
+
rescue URI::InvalidURIError
|
101
|
+
raise PgSync::Error, "Invalid connection string"
|
120
102
|
end
|
121
103
|
end
|
122
104
|
end
|
123
105
|
|
106
|
+
def close
|
107
|
+
if @conn
|
108
|
+
@conn.close
|
109
|
+
@conn = nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
124
113
|
def dump_command(tables)
|
125
114
|
tables = tables.keys.map { |t| "-t #{Shellwords.escape(quote_ident_full(t))}" }.join(" ")
|
126
|
-
|
115
|
+
"pg_dump -Fc --verbose --schema-only --no-owner --no-acl #{tables} -d #{@url}"
|
127
116
|
end
|
128
117
|
|
129
118
|
def restore_command
|
130
119
|
psql_version = Gem::Version.new(`psql --version`.lines[0].chomp.split(" ")[-1].sub(/beta\d/, ""))
|
131
120
|
if_exists = psql_version >= Gem::Version.new("9.4.0")
|
132
|
-
|
121
|
+
"pg_restore --verbose --no-owner --no-acl --clean #{if_exists ? "--if-exists" : nil} -d #{@url}"
|
133
122
|
end
|
134
123
|
|
135
124
|
def fully_resolve_tables(tables)
|
@@ -143,11 +132,19 @@ module PgSync
|
|
143
132
|
end
|
144
133
|
|
145
134
|
def search_path
|
146
|
-
execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
|
135
|
+
@search_path ||= execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
|
147
136
|
end
|
148
137
|
|
149
138
|
private
|
150
139
|
|
140
|
+
def table_set
|
141
|
+
@table_set ||= Set.new(tables)
|
142
|
+
end
|
143
|
+
|
144
|
+
def conninfo
|
145
|
+
@conninfo ||= conn.conninfo_hash
|
146
|
+
end
|
147
|
+
|
151
148
|
def quote_ident_full(ident)
|
152
149
|
ident.split(".", 2).map { |v| quote_ident(v) }.join(".")
|
153
150
|
end
|
@@ -156,10 +153,6 @@ module PgSync
|
|
156
153
|
conn.exec_params(query, params).to_a
|
157
154
|
end
|
158
155
|
|
159
|
-
def log(message = nil)
|
160
|
-
$stderr.puts message
|
161
|
-
end
|
162
|
-
|
163
156
|
def quote_ident(value)
|
164
157
|
PG::Connection.quote_ident(value)
|
165
158
|
end
|
data/lib/pgsync/table_list.rb
CHANGED
@@ -53,7 +53,7 @@ module PgSync
|
|
53
53
|
|
54
54
|
tabs = source.tables
|
55
55
|
unless opts[:all_schemas]
|
56
|
-
schemas = Set.new(opts[:schemas] ? to_arr(opts[:schemas]) :
|
56
|
+
schemas = Set.new(opts[:schemas] ? to_arr(opts[:schemas]) : source.search_path)
|
57
57
|
tabs.select! { |t| schemas.include?(t.split(".", 2)[0]) }
|
58
58
|
end
|
59
59
|
|
data/lib/pgsync/table_sync.rb
CHANGED
@@ -1,24 +1,14 @@
|
|
1
1
|
module PgSync
|
2
2
|
class TableSync
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
mutex.synchronize do
|
10
|
-
log "* DONE #{table} (#{time.round(1)}s)"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def sync(mutex, config, table, opts, source_url, destination_url)
|
15
|
-
source = DataSource.new(source_url)
|
16
|
-
destination = DataSource.new(destination_url)
|
17
|
-
|
18
|
-
from_connection = source.conn
|
19
|
-
to_connection = destination.conn
|
3
|
+
def sync(mutex, config, table, opts, source_url, destination_url, first_schema)
|
4
|
+
start_time = Time.now
|
5
|
+
source = DataSource.new(source_url, timeout: 0)
|
6
|
+
destination = DataSource.new(destination_url, timeout: 0)
|
20
7
|
|
21
8
|
begin
|
9
|
+
from_connection = source.conn
|
10
|
+
to_connection = destination.conn
|
11
|
+
|
22
12
|
bad_fields = opts[:no_rules] ? [] : config["data_rules"]
|
23
13
|
|
24
14
|
from_fields = source.columns(table)
|
@@ -35,8 +25,10 @@ module PgSync
|
|
35
25
|
|
36
26
|
sql_clause = String.new
|
37
27
|
|
28
|
+
table_name = table.sub("#{first_schema}.", "")
|
29
|
+
|
38
30
|
mutex.synchronize do
|
39
|
-
log "* Syncing #{
|
31
|
+
log "* Syncing #{table_name}"
|
40
32
|
if opts[:sql]
|
41
33
|
log " #{opts[:sql]}"
|
42
34
|
sql_clause << " #{opts[:sql]}"
|
@@ -93,7 +85,7 @@ module PgSync
|
|
93
85
|
batch_copy_to_command = "COPY (SELECT #{copy_fields} FROM #{quote_ident_full(table)}#{batch_sql_clause}) TO STDOUT"
|
94
86
|
to_connection.copy_data "COPY #{quote_ident_full(table)} (#{fields}) FROM STDIN" do
|
95
87
|
from_connection.copy_data batch_copy_to_command do
|
96
|
-
while row = from_connection.get_copy_data
|
88
|
+
while (row = from_connection.get_copy_data)
|
97
89
|
to_connection.put_copy_data(row)
|
98
90
|
end
|
99
91
|
end
|
@@ -157,6 +149,9 @@ module PgSync
|
|
157
149
|
to_connection.exec("SELECT setval(#{escape(seq)}, #{escape(value)})")
|
158
150
|
end
|
159
151
|
end
|
152
|
+
mutex.synchronize do
|
153
|
+
log "* DONE #{table_name} (#{(Time.now - start_time).round(1)}s)"
|
154
|
+
end
|
160
155
|
ensure
|
161
156
|
source.close
|
162
157
|
destination.close
|
@@ -205,12 +200,6 @@ module PgSync
|
|
205
200
|
end
|
206
201
|
end
|
207
202
|
|
208
|
-
def benchmark
|
209
|
-
start_time = Time.now
|
210
|
-
yield
|
211
|
-
Time.now - start_time
|
212
|
-
end
|
213
|
-
|
214
203
|
def log(message = nil)
|
215
204
|
$stderr.puts message
|
216
205
|
end
|
data/lib/pgsync/version.rb
CHANGED
data/pgsync.gemspec
CHANGED
@@ -18,12 +18,12 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "slop", ">= 4.2.0"
|
22
|
-
spec.add_dependency "pg"
|
23
|
-
spec.add_dependency "parallel"
|
24
21
|
spec.add_dependency "multiprocessing"
|
22
|
+
spec.add_dependency "parallel"
|
23
|
+
spec.add_dependency "pg"
|
24
|
+
spec.add_dependency "slop", ">= 4.2.0"
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler"
|
27
|
-
spec.add_development_dependency "rake"
|
28
27
|
spec.add_development_dependency "minitest"
|
28
|
+
spec.add_development_dependency "rake"
|
29
29
|
end
|
metadata
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgsync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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: 2018-
|
11
|
+
date: 2018-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: multiprocessing
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: parallel
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: pg
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: slop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 4.2.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:
|
68
|
+
version: 4.2.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: minitest
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|