pgsync 0.5.2 → 0.6.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 +4 -4
- data/CHANGELOG.md +37 -0
- data/LICENSE.txt +1 -1
- data/README.md +121 -38
- data/config.yml +4 -0
- data/exe/pgsync +0 -4
- data/lib/pgsync.rb +5 -1
- data/lib/pgsync/client.rb +54 -52
- data/lib/pgsync/data_source.rb +78 -80
- data/lib/pgsync/init.rb +48 -10
- data/lib/pgsync/schema_sync.rb +83 -0
- data/lib/pgsync/sync.rb +98 -175
- data/lib/pgsync/table.rb +28 -0
- data/lib/pgsync/table_sync.rb +167 -219
- data/lib/pgsync/task.rb +315 -0
- data/lib/pgsync/task_resolver.rb +235 -0
- data/lib/pgsync/utils.rb +64 -24
- data/lib/pgsync/version.rb +1 -1
- metadata +8 -5
- data/lib/pgsync/table_list.rb +0 -143
data/lib/pgsync/utils.rb
CHANGED
@@ -1,46 +1,86 @@
|
|
1
1
|
module PgSync
|
2
2
|
module Utils
|
3
|
+
COLOR_CODES = {
|
4
|
+
red: 31,
|
5
|
+
green: 32,
|
6
|
+
yellow: 33
|
7
|
+
}
|
8
|
+
|
3
9
|
def log(message = nil)
|
4
|
-
|
10
|
+
output.puts message
|
5
11
|
end
|
6
12
|
|
7
|
-
def colorize(message,
|
8
|
-
if
|
9
|
-
"\e[#{
|
13
|
+
def colorize(message, color)
|
14
|
+
if output.tty?
|
15
|
+
"\e[#{COLOR_CODES[color]}m#{message}\e[0m"
|
10
16
|
else
|
11
17
|
message
|
12
18
|
end
|
13
19
|
end
|
14
20
|
|
15
|
-
def
|
16
|
-
|
21
|
+
def warning(message)
|
22
|
+
log colorize(message, :yellow)
|
23
|
+
end
|
24
|
+
|
25
|
+
def deprecated(message)
|
26
|
+
warning "[DEPRECATED] #{message}"
|
27
|
+
end
|
17
28
|
|
18
|
-
|
19
|
-
|
20
|
-
if @options[:db]
|
21
|
-
db_config_file(@options[:db])
|
22
|
-
else
|
23
|
-
@options[:config] || ".pgsync.yml"
|
24
|
-
end
|
25
|
-
)
|
29
|
+
def output
|
30
|
+
$stderr
|
26
31
|
end
|
27
32
|
|
28
33
|
def db_config_file(db)
|
29
|
-
return unless db
|
30
34
|
".pgsync-#{db}.yml"
|
31
35
|
end
|
32
36
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
absolute_file = File.join(path, file)
|
38
|
-
if File.exist?(absolute_file)
|
39
|
-
break absolute_file
|
37
|
+
def confirm_tables_exist(data_source, tasks, description)
|
38
|
+
tasks.map(&:table).each do |table|
|
39
|
+
unless data_source.table_exists?(table)
|
40
|
+
raise Error, "Table not found in #{description}: #{table}"
|
40
41
|
end
|
41
|
-
path = File.dirname(path)
|
42
|
-
break if path == "/"
|
43
42
|
end
|
44
43
|
end
|
44
|
+
|
45
|
+
def first_schema
|
46
|
+
@first_schema ||= source.search_path.find { |sp| sp != "pg_catalog" }
|
47
|
+
end
|
48
|
+
|
49
|
+
def task_name(task)
|
50
|
+
friendly_name(task.table)
|
51
|
+
end
|
52
|
+
|
53
|
+
def friendly_name(table)
|
54
|
+
if table.schema == first_schema
|
55
|
+
table.name
|
56
|
+
else
|
57
|
+
table.full_name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def quote_ident_full(ident)
|
62
|
+
if ident.is_a?(Table)
|
63
|
+
[quote_ident(ident.schema), quote_ident(ident.name)].join(".")
|
64
|
+
else # temp table names are strings
|
65
|
+
quote_ident(ident)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def quote_ident(value)
|
70
|
+
PG::Connection.quote_ident(value)
|
71
|
+
end
|
72
|
+
|
73
|
+
def escape(value)
|
74
|
+
if value.is_a?(String)
|
75
|
+
"'#{quote_string(value)}'"
|
76
|
+
else
|
77
|
+
value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# activerecord
|
82
|
+
def quote_string(s)
|
83
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''")
|
84
|
+
end
|
45
85
|
end
|
46
86
|
end
|
data/lib/pgsync/version.rb
CHANGED
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
|
+
version: 0.6.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: 2020-
|
11
|
+
date: 2020-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parallel
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 4.
|
47
|
+
version: 4.8.1
|
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: 4.
|
54
|
+
version: 4.8.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: tty-spinner
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,9 +124,12 @@ files:
|
|
124
124
|
- lib/pgsync/client.rb
|
125
125
|
- lib/pgsync/data_source.rb
|
126
126
|
- lib/pgsync/init.rb
|
127
|
+
- lib/pgsync/schema_sync.rb
|
127
128
|
- lib/pgsync/sync.rb
|
128
|
-
- lib/pgsync/
|
129
|
+
- lib/pgsync/table.rb
|
129
130
|
- lib/pgsync/table_sync.rb
|
131
|
+
- lib/pgsync/task.rb
|
132
|
+
- lib/pgsync/task_resolver.rb
|
130
133
|
- lib/pgsync/utils.rb
|
131
134
|
- lib/pgsync/version.rb
|
132
135
|
homepage: https://github.com/ankane/pgsync
|
data/lib/pgsync/table_list.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
module PgSync
|
2
|
-
class TableList
|
3
|
-
include Utils
|
4
|
-
|
5
|
-
attr_reader :args, :opts, :source, :config
|
6
|
-
|
7
|
-
def initialize(args, options, source, config)
|
8
|
-
@args = args
|
9
|
-
@opts = options
|
10
|
-
@source = source
|
11
|
-
@config = config
|
12
|
-
end
|
13
|
-
|
14
|
-
def tables
|
15
|
-
tables = nil
|
16
|
-
|
17
|
-
if opts[:groups]
|
18
|
-
tables ||= Hash.new { |hash, key| hash[key] = {} }
|
19
|
-
specified_groups = to_arr(opts[:groups])
|
20
|
-
specified_groups.map do |tag|
|
21
|
-
group, id = tag.split(":", 2)
|
22
|
-
if (t = (config["groups"] || {})[group])
|
23
|
-
add_tables(tables, t, id, args[1])
|
24
|
-
else
|
25
|
-
raise Error, "Group not found: #{group}"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
if opts[:tables]
|
31
|
-
tables ||= Hash.new { |hash, key| hash[key] = {} }
|
32
|
-
to_arr(opts[:tables]).each do |tag|
|
33
|
-
table, id = tag.split(":", 2)
|
34
|
-
raise Error, "Cannot use parameters with tables" if id
|
35
|
-
add_table(tables, table, id, args[1])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
if args[0]
|
40
|
-
# could be a group, table, or mix
|
41
|
-
tables ||= Hash.new { |hash, key| hash[key] = {} }
|
42
|
-
specified_groups = to_arr(args[0])
|
43
|
-
specified_groups.map do |tag|
|
44
|
-
group, id = tag.split(":", 2)
|
45
|
-
if (t = (config["groups"] || {})[group])
|
46
|
-
add_tables(tables, t, id, args[1])
|
47
|
-
else
|
48
|
-
raise Error, "Cannot use parameters with tables" if id
|
49
|
-
add_table(tables, group, id, args[1])
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
tables ||= begin
|
55
|
-
exclude = to_arr(opts[:exclude])
|
56
|
-
exclude = source.fully_resolve_tables(exclude).keys if exclude.any?
|
57
|
-
|
58
|
-
tabs = source.tables
|
59
|
-
unless opts[:all_schemas]
|
60
|
-
schemas = Set.new(opts[:schemas] ? to_arr(opts[:schemas]) : source.search_path)
|
61
|
-
tabs.select! { |t| schemas.include?(t.split(".", 2)[0]) }
|
62
|
-
end
|
63
|
-
|
64
|
-
Hash[(tabs - exclude).map { |k| [k, {}] }]
|
65
|
-
end
|
66
|
-
|
67
|
-
source.fully_resolve_tables(tables)
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def to_arr(value)
|
73
|
-
if value.is_a?(Array)
|
74
|
-
value
|
75
|
-
else
|
76
|
-
# Split by commas, but don't use commas inside double quotes
|
77
|
-
# https://stackoverflow.com/questions/21105360/regex-find-comma-not-inside-quotes
|
78
|
-
value.to_s.split(/(?!\B"[^"]*),(?![^"]*"\B)/)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def add_tables(tables, t, id, boom)
|
83
|
-
# if id
|
84
|
-
# # TODO show group name and value
|
85
|
-
# log colorize("`pgsync group:value` is deprecated and will have a different function in 0.6.0.", 33) # yellow
|
86
|
-
# log colorize("Use `pgsync group --var 1=value` instead.", 33) # yellow
|
87
|
-
# end
|
88
|
-
|
89
|
-
t.each do |table|
|
90
|
-
sql = nil
|
91
|
-
if table.is_a?(Array)
|
92
|
-
table, sql = table
|
93
|
-
end
|
94
|
-
add_table(tables, table, id, boom || sql)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def add_table(tables, table, id, boom, wildcard = false)
|
99
|
-
if table.include?("*") && !wildcard
|
100
|
-
regex = Regexp.new('\A' + Regexp.escape(table).gsub('\*','[^\.]*') + '\z')
|
101
|
-
t2 = source.tables.select { |t| regex.match(t) }
|
102
|
-
t2.each do |tab|
|
103
|
-
add_table(tables, tab, id, boom, true)
|
104
|
-
end
|
105
|
-
else
|
106
|
-
tables[table] = {}
|
107
|
-
if boom
|
108
|
-
sql = boom.dup
|
109
|
-
# vars must match \w
|
110
|
-
missing_vars = sql.scan(/{\w+}/).map { |v| v[1..-2] }
|
111
|
-
|
112
|
-
vars = {}
|
113
|
-
|
114
|
-
# legacy
|
115
|
-
if id
|
116
|
-
vars["id"] = cast(id)
|
117
|
-
vars["1"] = cast(id)
|
118
|
-
end
|
119
|
-
|
120
|
-
# opts[:var].each do |value|
|
121
|
-
# k, v = value.split("=", 2)
|
122
|
-
# vars[k] = v
|
123
|
-
# end
|
124
|
-
|
125
|
-
sql = boom.dup
|
126
|
-
vars.each do |k, v|
|
127
|
-
# only sub if in var list
|
128
|
-
sql.gsub!("{#{k}}", cast(v)) if missing_vars.delete(k)
|
129
|
-
end
|
130
|
-
|
131
|
-
raise Error, "Missing variables: #{missing_vars.uniq.join(", ")}" if missing_vars.any?
|
132
|
-
|
133
|
-
tables[table][:sql] = sql
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# TODO quote vars in next major version
|
139
|
-
def cast(value)
|
140
|
-
value.to_s.gsub(/\A\"|\"\z/, '')
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|