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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f8d5362420e8f65878dcb39bfcb945db069d585f
4
- data.tar.gz: a0d4d2b74ca25148f6ddcb2b4bdfa0bea7ef2c99
3
+ metadata.gz: 4000209f5f79dad8a7c6f427be2cdcf25abe219e
4
+ data.tar.gz: 4980413b816f0d0aa150aedf7bbcb1141fb7caad
5
5
  SHA512:
6
- metadata.gz: ed08a53660ea4973932176da8ea2bb1354f457c67dc9dd9cff832a67eb2b785d399f84a1840e21485579c0d595e81657303213d8831ae8ae72cba6cede713dd2
7
- data.tar.gz: 4f56fdbec5b5b93b4ff6ffe781edba94076107203e5f620a5708c3487d8a1cd77e8388a06cf0c9ddbd403afefa06832dd648382dfb3dadde9fc857b83ddea089
6
+ metadata.gz: 3122cf01ba1dcda75172bc823507c5db684561797dbdb88be42a93866d3fbfb0147e068ab501d1c7a74f09fd88341f623f61e4a2751183a051ba5cefecbdcbb4
7
+ data.tar.gz: cb8531e15f2ba2c714d1e022b286efe09208abd4b88c997139a23d6ff583e45c460bae46961735716978dd34b7448f42dc2b8f1cbdb4b06592b0d604e070a219
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.4.0
2
+
3
+ - Sync all schemas in search path by default
4
+ - Added support for socket connections
5
+ - Added support for environment variables
6
+
1
7
  # 0.3.9
2
8
 
3
9
  - Better support for schemas
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Andrew Kane
3
+ Copyright (c) 2015-2018 Andrew Kane
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/exe/pgsync CHANGED
@@ -5,6 +5,6 @@ begin
5
5
  PgSync::Client.new(ARGV).perform
6
6
  rescue PgSync::Error => e
7
7
  abort e.message
8
- rescue Interrupt => e
8
+ rescue Interrupt
9
9
  # do nothing
10
10
  end
data/lib/pgsync.rb CHANGED
@@ -1,16 +1,16 @@
1
- require "yaml"
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
- unless opts[:to_safe] || destination.local?
45
- raise PgSync::Error, "Danger! Add `to_safe: true` to `.pgsync.yml` if the destination is not localhost or 127.0.0.1"
46
- end
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
- print_description("From", source)
49
- print_description("To", destination)
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.sync_with_benchmark(@mutex, config, table, opts.merge(table_opts), source.url, destination.url)
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(destination, tables, description)
103
+ def confirm_tables_exist(data_source, tables, description)
94
104
  tables.keys.each do |table|
95
- unless destination.table_exists?(table)
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
- destination.close
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
- log "#{prefix}: #{source.uri.path.sub(/\A\//, '')} on #{source.uri.host}:#{source.uri.port}"
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)
@@ -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?(uri.host)
15
+ !host || %w(localhost 127.0.0.1).include?(host)
15
16
  end
16
17
 
17
- def uri
18
- @uri ||= begin
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 tables
33
- query = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('information_schema', 'pg_catalog') ORDER BY 1, 2"
34
- execute(query).map { |row| "#{row["schemaname"]}.#{row["tablename"]}" }
22
+ def port
23
+ @port ||= conninfo[:port]
35
24
  end
36
25
 
37
- def table_exists?(table)
38
- query = "SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2"
39
- execute(query, table.split(".", 2)).size > 0
26
+ def dbname
27
+ @dbname ||= conninfo[:dbname]
40
28
  end
41
29
 
42
- def close
43
- if @conn
44
- conn.close
45
- @conn = nil
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 to_url
50
- uri = self.uri.dup
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
- uri_parser = URI::Parser.new
107
- config = {
108
- host: uri.host,
109
- port: uri.port,
110
- dbname: uri.path.sub(/\A\//, ""),
111
- user: uri.user,
112
- password: uri.password,
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
- dump_command = "pg_dump -Fc --verbose --schema-only --no-owner --no-acl #{tables} #{to_url}"
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
- restore_command = "pg_restore --verbose --no-owner --no-acl --clean #{if_exists ? "--if-exists" : nil} -d #{to_url}"
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
@@ -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]) : [source.schema || "public"])
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
 
@@ -1,24 +1,14 @@
1
1
  module PgSync
2
2
  class TableSync
3
- def sync_with_benchmark(mutex, config, table, opts, source_url, destination_url)
4
- time =
5
- benchmark do
6
- sync(mutex, config, table, opts, source_url, destination_url)
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 #{table}"
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
@@ -1,3 +1,3 @@
1
1
  module PgSync
2
- VERSION = "0.3.9"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.9
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-02-27 00:00:00.000000000 Z
11
+ date: 2018-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: slop
14
+ name: multiprocessing
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.0
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: 4.2.0
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: pg
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: parallel
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: multiprocessing
56
+ name: slop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
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: '0'
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: rake
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: minitest
98
+ name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="