gamma_replication 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b68929290d56de956de9597cfbdb9c6c7e2574f26489b5b55fc0223aa7d3c6ad
4
- data.tar.gz: 550aa21f4800a9da12f9d4f7b5bb4565a999321623d700cb74ae360bac461ae5
3
+ metadata.gz: 39c63ef1a8906600d9df6b25658cbd4a1b8674be75cb6b5d3d870419f9e5f75e
4
+ data.tar.gz: ad13bc1e0d79976532f368cd555e59cb89300c920743f35d145b7a928a588ae9
5
5
  SHA512:
6
- metadata.gz: 73720919700ce8c4158bee20a58f375fe5a160ba1e44aa0f4348ed3f7ba0ba60d75c3224563579c65c5616dabd4f764b997c936dee029c12a2bade898f410028
7
- data.tar.gz: 10cf80dc382210270080adf30148a7556938bb164633bce1336484f3fdf22be571622ab711e2289aefc7486b8d1c3ea54e12932a117af77540eda7ba73045023
6
+ metadata.gz: 6a5aa663b77e7354ff500e7fcb3fe936b103edf466442e8e44b9e31297da5c6dd4eec0ba12472f195490f710ab6d3dbd3a351311e2676d1edd104b73b396752f
7
+ data.tar.gz: bc597b21d63e0135ffa154116db12acf4578678db85ab9ef090df30e7247d078e1907460f377c4baa0111317ae9dde8e0bbc5ab9e57cbcda68a9288074e5ab6f
@@ -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,15 +116,23 @@ 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)
@@ -100,8 +160,9 @@ module GammaReplication
100
160
 
101
161
  def process_delete(table_setting, data)
102
162
  old_record = data["old"]
103
- where_clause = build_where_clause(old_record, nil, table_setting.primary_key)
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
@@ -120,7 +181,8 @@ module GammaReplication
120
181
  elsif new_record.present? && new_record[primary_key].present?
121
182
  "`#{primary_key}` = #{format_value(new_record[primary_key])}"
122
183
  else
123
- raise "Primary key not found in record. old_record: #{old_record.inspect}, new_record: #{new_record.inspect}"
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"
124
186
  end
125
187
  end
126
188
 
@@ -133,7 +195,34 @@ module GammaReplication
133
195
  when Time
134
196
  "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
135
197
  else
136
- "'#{@out_client.client.escape(value.to_s)}'"
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
137
226
  end
138
227
  end
139
228
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GammaReplication
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
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.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-01-22 00:00:00.000000000 Z
11
+ date: 2025-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport