gamma_replication 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/gamma_replication +4 -0
- data/lib/gamma_replication/command/base_replication.rb +99 -8
- data/lib/gamma_replication/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39c63ef1a8906600d9df6b25658cbd4a1b8674be75cb6b5d3d870419f9e5f75e
|
|
4
|
+
data.tar.gz: ad13bc1e0d79976532f368cd555e59cb89300c920743f35d145b7a928a588ae9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a5aa663b77e7354ff500e7fcb3fe936b103edf466442e8e44b9e31297da5c6dd4eec0ba12472f195490f710ab6d3dbd3a351311e2676d1edd104b73b396752f
|
|
7
|
+
data.tar.gz: bc597b21d63e0135ffa154116db12acf4578678db85ab9ef090df30e7247d078e1907460f377c4baa0111317ae9dde8e0bbc5ab9e57cbcda68a9288074e5ab6f
|
data/exe/gamma_replication
CHANGED
|
@@ -12,6 +12,8 @@ class GammaReplicationCLI < Thor
|
|
|
12
12
|
option :data, aliases: "-d", desc: "Table Sync Settings yaml", required: true
|
|
13
13
|
option :hook_dir, aliases: "-h", desc: "Hook script directory", default: "."
|
|
14
14
|
option :maxwell_config, aliases: "-m", desc: "Maxwell configuration file path", required: true
|
|
15
|
+
option :enable_stats, type: :boolean, desc: "Enable statistics output (default: true)"
|
|
16
|
+
option :stats_interval, type: :numeric, desc: "Statistics output interval in seconds (default: 10800)"
|
|
15
17
|
def start
|
|
16
18
|
GammaReplication::Command::Start.new(options).execute
|
|
17
19
|
end
|
|
@@ -21,6 +23,8 @@ class GammaReplicationCLI < Thor
|
|
|
21
23
|
option :data, aliases: "-d", desc: "Table Sync Settings yaml", required: true
|
|
22
24
|
option :hook_dir, aliases: "-h", desc: "Hook script directory", default: "."
|
|
23
25
|
option :maxwell_config, aliases: "-m", desc: "Maxwell configuration file path", required: true
|
|
26
|
+
option :enable_stats, type: :boolean, desc: "Enable statistics output (default: true)"
|
|
27
|
+
option :stats_interval, type: :numeric, desc: "Statistics output interval in seconds (default: 10800)"
|
|
24
28
|
def dryrun
|
|
25
29
|
GammaReplication::Command::Dryrun.new(options).execute
|
|
26
30
|
end
|
|
@@ -8,6 +8,9 @@ module GammaReplication
|
|
|
8
8
|
setup_database(opts)
|
|
9
9
|
setup_parser(opts)
|
|
10
10
|
setup_maxwell(opts)
|
|
11
|
+
initialize_stats
|
|
12
|
+
@enable_stats = opts[:enable_stats].nil? ? true : opts[:enable_stats]
|
|
13
|
+
@stats_interval = (opts[:stats_interval] || 3 * 60 * 60).to_i # Default: 3 hours in seconds
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def execute
|
|
@@ -18,6 +21,9 @@ module GammaReplication
|
|
|
18
21
|
hash[table.table_name] = table
|
|
19
22
|
end
|
|
20
23
|
before_start if respond_to?(:before_start)
|
|
24
|
+
|
|
25
|
+
setup_stats_reporter if @enable_stats
|
|
26
|
+
|
|
21
27
|
@maxwell_client.start do |data|
|
|
22
28
|
process_maxwell_data(data)
|
|
23
29
|
end
|
|
@@ -25,6 +31,52 @@ module GammaReplication
|
|
|
25
31
|
|
|
26
32
|
private
|
|
27
33
|
|
|
34
|
+
def initialize_stats
|
|
35
|
+
@stats = {
|
|
36
|
+
total: { insert: 0, update: 0, delete: 0 },
|
|
37
|
+
by_table: {}
|
|
38
|
+
}
|
|
39
|
+
@stats_mutex = Mutex.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def setup_stats_reporter
|
|
43
|
+
@stats_thread = Thread.new do
|
|
44
|
+
loop do
|
|
45
|
+
sleep @stats_interval
|
|
46
|
+
output_stats
|
|
47
|
+
reset_stats
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def update_stats(table_name, operation)
|
|
53
|
+
return unless @enable_stats
|
|
54
|
+
|
|
55
|
+
@stats_mutex.synchronize do
|
|
56
|
+
@stats[:total][operation] += 1
|
|
57
|
+
@stats[:by_table][table_name] ||= { insert: 0, update: 0, delete: 0 }
|
|
58
|
+
@stats[:by_table][table_name][operation] += 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reset_stats
|
|
63
|
+
@stats_mutex.synchronize do
|
|
64
|
+
initialize_stats
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def output_stats
|
|
69
|
+
@stats_mutex.synchronize do
|
|
70
|
+
total_stats = "Total[ins:#{@stats[:total][:insert]} | upd:#{@stats[:total][:update]} | del:#{@stats[:total][:delete]}]"
|
|
71
|
+
|
|
72
|
+
table_stats = @stats[:by_table].map do |table, counts|
|
|
73
|
+
"#{table}[ins:#{counts[:insert]} | upd:#{counts[:update]} | del:#{counts[:delete]}]"
|
|
74
|
+
end.join("\n")
|
|
75
|
+
|
|
76
|
+
logger.info("STATS | #{total_stats}\n#{table_stats}")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
28
80
|
def setup_database(opts)
|
|
29
81
|
@database_settings = GammaReplication::DatabaseSettings.new(opts[:settings])
|
|
30
82
|
@in_client = GammaReplication::DatabaseConnector::MysqlConnector.new(@database_settings.in_database)
|
|
@@ -64,22 +116,30 @@ module GammaReplication
|
|
|
64
116
|
table_setting = @table_settings[data["table"]]
|
|
65
117
|
case data["type"]
|
|
66
118
|
when "insert"
|
|
119
|
+
return unless data["data"].present?
|
|
120
|
+
|
|
121
|
+
update_stats(data["table"], :insert)
|
|
67
122
|
process_insert(table_setting, data)
|
|
68
123
|
when "update"
|
|
124
|
+
return unless data["data"].present?
|
|
125
|
+
|
|
126
|
+
update_stats(data["table"], :update)
|
|
69
127
|
process_update(table_setting, data)
|
|
70
128
|
when "delete"
|
|
129
|
+
return unless data["old"].present?
|
|
130
|
+
|
|
131
|
+
update_stats(data["table"], :delete)
|
|
71
132
|
process_delete(table_setting, data)
|
|
72
133
|
end
|
|
73
134
|
rescue StandardError => e
|
|
74
|
-
logger.error(e)
|
|
75
|
-
# Nothing
|
|
135
|
+
logger.error("Error processing data: #{data["table"]} - #{e.message}")
|
|
76
136
|
end
|
|
77
137
|
|
|
78
138
|
def process_insert(table_setting, data)
|
|
79
139
|
record = data["data"]
|
|
80
140
|
processed_record = apply_hooks(table_setting, record)
|
|
81
141
|
|
|
82
|
-
columns = processed_record.keys
|
|
142
|
+
columns = processed_record.keys.map { |k| "`#{k}`" }
|
|
83
143
|
values = processed_record.values.map { |v| format_value(v) }
|
|
84
144
|
|
|
85
145
|
query = "INSERT INTO #{table_setting.table_name} (#{columns.join(",")}) VALUES (#{values.join(",")})"
|
|
@@ -91,7 +151,7 @@ module GammaReplication
|
|
|
91
151
|
old_record = data["old"]
|
|
92
152
|
processed_record = apply_hooks(table_setting, record)
|
|
93
153
|
|
|
94
|
-
set_clause = processed_record.map { |k, v| "
|
|
154
|
+
set_clause = processed_record.map { |k, v| "`#{k}` = #{format_value(v)}" }.join(",")
|
|
95
155
|
where_clause = build_where_clause(old_record, record, table_setting.primary_key)
|
|
96
156
|
|
|
97
157
|
query = "UPDATE #{table_setting.table_name} SET #{set_clause} WHERE #{where_clause}"
|
|
@@ -100,8 +160,9 @@ module GammaReplication
|
|
|
100
160
|
|
|
101
161
|
def process_delete(table_setting, data)
|
|
102
162
|
old_record = data["old"]
|
|
103
|
-
|
|
163
|
+
return unless old_record.present? && old_record[table_setting.primary_key].present?
|
|
104
164
|
|
|
165
|
+
where_clause = build_where_clause(old_record, nil, table_setting.primary_key)
|
|
105
166
|
query = "DELETE FROM #{table_setting.table_name} WHERE #{where_clause}"
|
|
106
167
|
execute_query(query)
|
|
107
168
|
end
|
|
@@ -116,9 +177,12 @@ module GammaReplication
|
|
|
116
177
|
|
|
117
178
|
def build_where_clause(old_record, new_record, primary_key)
|
|
118
179
|
if old_record.present? && old_record[primary_key].present?
|
|
119
|
-
"
|
|
180
|
+
"`#{primary_key}` = #{format_value(old_record[primary_key])}"
|
|
181
|
+
elsif new_record.present? && new_record[primary_key].present?
|
|
182
|
+
"`#{primary_key}` = #{format_value(new_record[primary_key])}"
|
|
120
183
|
else
|
|
121
|
-
"#{
|
|
184
|
+
logger.error("Primary key not found. old_record: #{old_record.inspect}, new_record: #{new_record.inspect}")
|
|
185
|
+
raise "Primary key '#{primary_key}' not found in record"
|
|
122
186
|
end
|
|
123
187
|
end
|
|
124
188
|
|
|
@@ -131,7 +195,34 @@ module GammaReplication
|
|
|
131
195
|
when Time
|
|
132
196
|
"'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
|
133
197
|
else
|
|
134
|
-
|
|
198
|
+
if json_column?(value)
|
|
199
|
+
sanitized_value = sanitize_json(value)
|
|
200
|
+
"'#{@out_client.client.escape(sanitized_value)}'"
|
|
201
|
+
else
|
|
202
|
+
"'#{@out_client.client.escape(value.to_s)}'"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def json_column?(value)
|
|
208
|
+
value.is_a?(String) && (value.start_with?("{") || value.start_with?("["))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def sanitize_json(value)
|
|
212
|
+
JSON.parse(value)
|
|
213
|
+
value
|
|
214
|
+
rescue JSON::ParserError => e
|
|
215
|
+
sanitized = value.gsub(/([{,]\s*)(\w+)(\s*:)/) do
|
|
216
|
+
"#{::Regexp.last_match(1)}\"#{::Regexp.last_match(2)}\"#{::Regexp.last_match(3)}"
|
|
217
|
+
end
|
|
218
|
+
sanitized = sanitized.gsub(/:\s*([^",\s\d\[\]{}-].*?)(,|\}|$)/) do
|
|
219
|
+
": \"#{::Regexp.last_match(1)}\"#{::Regexp.last_match(2)}"
|
|
220
|
+
end
|
|
221
|
+
begin
|
|
222
|
+
JSON.parse(sanitized)
|
|
223
|
+
sanitized
|
|
224
|
+
rescue JSON::ParserError
|
|
225
|
+
value.to_json
|
|
135
226
|
end
|
|
136
227
|
end
|
|
137
228
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gamma_replication
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinsuke Nishio
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-02-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|