table_syncer 0.2.0 → 0.3.0
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.
- data/History.txt +6 -0
- data/lib/table_syncer/version.rb +1 -1
- data/lib/table_syncer.rb +92 -49
- data/website/index.html +1 -1
- metadata +7 -6
data/History.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
==0.3.0 2008-10-15
|
2
|
+
|
3
|
+
* Now uses the more conservative [and slower] query_with_result = true, instead of the faster and potentially scary query_with_result = false -- see http://betterlogic.com/roger/?p=473
|
4
|
+
* Now has a working --extra_sql=SQL
|
5
|
+
* Recommended upgrade.
|
6
|
+
|
1
7
|
== 0.2.0 2009-09-39
|
2
8
|
|
3
9
|
* Now prompts you if you use --commit [so you don't do it on accident]
|
data/lib/table_syncer/version.rb
CHANGED
data/lib/table_syncer.rb
CHANGED
@@ -13,36 +13,50 @@
|
|
13
13
|
# If it doesn't, then run the command output [it will print out the ssh tunnel command appropriate] in a different terminal window
|
14
14
|
|
15
15
|
# 2008 Roger Pack Public Domain
|
16
|
-
# No warranty of any type :)
|
16
|
+
# No warranty expressed or implied of any type :)
|
17
17
|
|
18
18
|
require 'rubygems'
|
19
19
|
begin
|
20
|
-
require 'mysqlplus'
|
20
|
+
require 'mysqlplus' # it's barely faster
|
21
21
|
rescue LoadError
|
22
22
|
require "mysql"
|
23
23
|
end
|
24
24
|
require 'optparse'
|
25
25
|
|
26
26
|
# define some databases and how you connect with them, if foreign
|
27
|
-
|
28
|
-
|
27
|
+
local_db = {:host => '127.0.0.1', :user => 'root', :password => '', :db => 'local_leadgen_dev'}
|
28
|
+
ties_db = {:user => 'db_user_name_ex_root', :password => 'mysql_password_for_that_user', :db => 'wilkboar_ties', :ssh_host => 'my_host.com', :ssh_user => 'ssh_login'} # ssh example -- doesn't take passwords here but attempts to create an ssh tunnel for you to that host.
|
29
29
|
|
30
|
-
|
30
|
+
# I suppose you wouldn't need an ssh_user if you had established the tunnel youself [ex: if you had already created a tunnel on port 9000 to the remote Mysql port]
|
31
|
+
ties_db_no_host_even = {:user => 'db_user_name_ex_root', :password => 'mysql_password_for_that_user', :db => 'wilkboar_ties', :tunnel_local_port_to_use => 9000}
|
32
|
+
# if you have multiple remote hosts then you'll want to assign a different port for each host
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
ties_super_advanced = {:user => 'mysql_user', :password => 'mysql_pass_for_that_user', :ssh_host => 'helpme.com', :ssh_port => 888, :ssh_user => 'ssh_login_name', :ssh_local_to_host => '127.0.0.1 -- change if you want it to connect to a different host "from the remote"', :ssh_local_to_port => '4000 instead of the normal 3306'}
|
35
|
+
# ssh_port is the ssh port [instead of 22] for the remote host
|
36
|
+
|
37
|
+
all_database_names = ['local_db', 'ties_db', 'ties_db_no_host_even', 'ties_super_advanced'] # only used --help command is more useful and can print out the database connection names available -- not necessary
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
# setup defaults -- it will use these databases by default unless you specify otherwise on the command line [or use nil for none]
|
43
|
+
db_from_name = 'local_db' # replace with yours
|
44
|
+
db_to_name = 'local_db'
|
35
45
|
actually_run_queries = false # default to just previewing -- use --commit to actually run it
|
36
46
|
verbose = true
|
47
|
+
default_tables_to_sync = nil # replace with default tables to sync, like ['users']
|
37
48
|
|
38
49
|
|
39
|
-
# now parse incoming options
|
40
50
|
my_options = {}
|
41
51
|
auto_create_ssh_tunnels = true
|
42
|
-
tables_to_sync = nil
|
52
|
+
tables_to_sync = nil # leave as nil--this will be propagated by yours, above, or those passed from the command line
|
43
53
|
# make sure use normal style options, or nothing :)
|
44
54
|
my_options[:skip_the_warning_prompt_for_commit] = false
|
45
55
|
|
56
|
+
# note that ctrl-c doesn't work too well in the middle of this script, but ctrl-z + kill PID does
|
57
|
+
|
58
|
+
# now parse incoming options
|
59
|
+
|
46
60
|
do_structure_sync_only = false
|
47
61
|
|
48
62
|
OptionParser.new do |opts|
|
@@ -52,10 +66,14 @@
|
|
52
66
|
db_from_name = from_name
|
53
67
|
end
|
54
68
|
|
55
|
-
opts.on("-t", "--to=TO", "to database name #{all_database_names.inspect} \n\t\t\t\t\t\tedit databases at #{__FILE__}") do |to_name|
|
69
|
+
opts.on("-t", "--to=TO", "to database name #{all_database_names.inspect} \n\t\t\t\t\t\tedit databases descriptions at #{__FILE__}") do |to_name|
|
56
70
|
db_to_name = to_name
|
57
71
|
end
|
58
72
|
|
73
|
+
opts.on('--verbose') do
|
74
|
+
my_options[:verbose] = true
|
75
|
+
end
|
76
|
+
|
59
77
|
opts.on('-y', 'skip the warning prompt for commit') do
|
60
78
|
my_options[:skip_the_warning_prompt_for_commit] = true
|
61
79
|
end
|
@@ -68,12 +86,10 @@
|
|
68
86
|
end
|
69
87
|
|
70
88
|
opts.on("", "--commit", " tell us whether or not to actually run queries -- the default is to not run queries") do
|
71
|
-
print "DOING IT COMMITTING LIVE QUERIES\n"
|
72
89
|
actually_run_queries = true
|
73
90
|
end
|
74
91
|
|
75
92
|
opts.on("-z", "--extra_sql=STRING", "specify an sql sql string to run after the script ends [in the 'to' database]") do |sql|
|
76
|
-
print "extra sql", sql
|
77
93
|
my_options[:extra_sql] = sql
|
78
94
|
end
|
79
95
|
|
@@ -81,7 +97,7 @@
|
|
81
97
|
verbose = false
|
82
98
|
end
|
83
99
|
|
84
|
-
opts.on('--perform_structure_sync_only', "Do a structure morph for the designated tables --
|
100
|
+
opts.on('--perform_structure_sync_only', "Do a structure morph for the designated tables -- Use it with --tables=STRUCTURE_SYNC_THE_WHOLE_THING to drop any existing tables not found in the origin database and structure sync over any existing tables -- see the docs http://code.google.com/p/ruby-roger-useful-functions/wiki/TableSyncer") do
|
85
101
|
do_structure_sync_only = true
|
86
102
|
end
|
87
103
|
|
@@ -93,11 +109,14 @@
|
|
93
109
|
eval("db_from_info = #{db_from_name}")
|
94
110
|
print "to db: #{db_to_name}\n"
|
95
111
|
eval("db_to_info = #{db_to_name}")
|
96
|
-
|
97
|
-
# some error checking
|
98
|
-
raise if defined?(production) and db_to_name == 'production' and actually_run_queries # I never wanted to commit to one db
|
99
112
|
raise 'missing a database selected?' unless db_to_info and db_from_info
|
100
113
|
|
114
|
+
# skip the necessity for -y if a db is set to :expendable
|
115
|
+
my_options[:skip_the_warning_prompt_for_commit] = true if db_to_info[:expendable]
|
116
|
+
|
117
|
+
# raise if they attempt to commit to a db which has :read_only => true set in its options
|
118
|
+
raise 'attempting to commit to a read only db ' + db_to_name if db_to_info[:read_only] and actually_run_queries
|
119
|
+
|
101
120
|
# custom parse table names they have within the parameters
|
102
121
|
unless tables_to_sync
|
103
122
|
extra_table_args = ARGV.select{|arg| arg[0..0] != '-'}
|
@@ -106,7 +125,9 @@
|
|
106
125
|
for arg in extra_table_args
|
107
126
|
tables_to_sync += Array(arg.split(','))
|
108
127
|
end
|
109
|
-
|
128
|
+
elsif default_tables_to_sync
|
129
|
+
tables_to_sync = default_tables_to_sync
|
130
|
+
else
|
110
131
|
print 'no tables specified! run with --help', "\n"
|
111
132
|
exit
|
112
133
|
end
|
@@ -150,19 +171,19 @@ class Hash
|
|
150
171
|
end
|
151
172
|
end
|
152
173
|
|
153
|
-
def sync_structure(db_to, db_from, table, actually_run_queries)
|
174
|
+
def sync_structure(db_to, db_from, table, actually_run_queries, my_options)
|
154
175
|
print "structure syncing #{table}\n"
|
155
|
-
good_structure = db_from.query("desc #{table}")
|
176
|
+
good_structure = db_from.query("desc #{table}")
|
156
177
|
all_from_columns = {}
|
157
178
|
good_structure.each_hash{|h| all_from_columns[h['Field']] = h }
|
158
179
|
good_structure.free
|
159
180
|
# we basically cheat and just fakely recreate mismatched columns by "modifying them" to match the creation script given by 'show create table x' for that column
|
160
|
-
good_creation_query = db_from.query("show create table #{table}")
|
181
|
+
good_creation_query = db_from.query("show create table #{table}")
|
161
182
|
create_whole_table_script = good_creation_query.fetch_hash['Create Table']
|
162
183
|
good_creation_script = create_whole_table_script.split("\n")
|
163
184
|
good_creation_query.free
|
164
185
|
|
165
|
-
questionable_to_structure = db_to.query("desc #{table}")
|
186
|
+
questionable_to_structure = db_to.query("desc #{table}") rescue nil
|
166
187
|
unless questionable_to_structure
|
167
188
|
if actually_run_queries
|
168
189
|
db_to.query(create_whole_table_script)
|
@@ -170,7 +191,7 @@ end
|
|
170
191
|
print "would have created new table #{table} thus: #{create_whole_table_script}\n"
|
171
192
|
db_to = db_from # fake it that they match so we don't raise any errors for the duration of this method call
|
172
193
|
end
|
173
|
-
questionable_to_structure = db_to.query("desc #{table}")
|
194
|
+
questionable_to_structure = db_to.query("desc #{table}")
|
174
195
|
end
|
175
196
|
|
176
197
|
all_to_columns = {}
|
@@ -222,25 +243,36 @@ end
|
|
222
243
|
db_to.query("ALTER TABLE #{table} DROP #{column_name}") if actually_run_queries
|
223
244
|
end
|
224
245
|
|
225
|
-
indices = db_from.query("show index from #{table};")
|
246
|
+
indices = db_from.query("show index from #{table};")
|
226
247
|
all_indices = []
|
227
248
|
indices.each_hash{|h| h.delete('Cardinality'); all_indices << h } # Cardinality doesn't make a difference...AFAIK
|
228
249
|
indices.free
|
229
250
|
|
230
|
-
existing_indices = db_to.query("show index from #{table}")
|
251
|
+
existing_indices = db_to.query("show index from #{table}")
|
231
252
|
all_existing_indices = []
|
232
253
|
existing_indices.each_hash{|h| h.delete('Cardinality'); all_existing_indices << h }
|
233
254
|
existing_indices.free
|
255
|
+
different = []
|
256
|
+
all_different = all_indices - all_existing_indices #.each{|hash| different << hash['Column_name'] unless existing_indices.include?(hash) }
|
234
257
|
apparently_lacking = all_indices.map{|index| index['Column_name']} - all_existing_indices.map{|index| index['Column_name']}
|
235
258
|
|
259
|
+
|
260
|
+
|
236
261
|
for index in apparently_lacking
|
237
262
|
# ltodo if it looks nice and generic then go ahead and add it
|
238
263
|
end
|
239
264
|
|
265
|
+
|
266
|
+
|
240
267
|
if all_indices != all_existing_indices # this is right
|
241
|
-
print "\n\nWARNING #{table}: you are missing some indexes now or there is some type of discrepancy [
|
242
|
-
|
243
|
-
|
268
|
+
print "\n\nWARNING #{table}: you are missing some indexes now or there is some type of discrepancy [indices aren't handled yet]-- you may want to add them a la\nCREATE INDEX some_name_usually_column_name_here ON #{table} (column_name_here)\n for apparently at least the following missing indices: #{apparently_lacking.inspect}
|
269
|
+
|
270
|
+
you have apparently mismatched indices for: #{all_different.map{|h| h['Column_name']}.inspect}\n\n
|
271
|
+
--you might get away with dropping the old table and letting it be recreated -- that might add the right indices -- run with --verbose to see more info"
|
272
|
+
|
273
|
+
if my_options[:verbose]
|
274
|
+
print "the 'good' one is #{all_indices.inspect}, yours is #{all_existing_indices.inspect}"
|
275
|
+
end
|
244
276
|
end
|
245
277
|
end
|
246
278
|
|
@@ -262,7 +294,7 @@ end
|
|
262
294
|
# ltodo add in the local_to_stuff here
|
263
295
|
|
264
296
|
if actually_run_queries and !my_options[:skip_the_warning_prompt_for_commit]
|
265
|
-
print "Continue (
|
297
|
+
print "Continue (y/n)?"
|
266
298
|
input = gets
|
267
299
|
if !['y', 'yes'].include? input.downcase.strip
|
268
300
|
print "aborting -- you gave me #{input}"
|
@@ -286,19 +318,25 @@ end
|
|
286
318
|
puts "This may mean a tunnel is not working" if e.error.include?('127.0.0.1')
|
287
319
|
# note that, if you do add ssh -> ssh, you may still only need one connection!
|
288
320
|
if db_from_info[:ssh_host] or db_to_info[:ssh_host]
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
321
|
+
|
322
|
+
raise 'two ssh hosts not yet a working feature--request it' if db_from_info[:ssh_host] and db_to_info[:ssh_host] and (db_from_info[:ssh_host] != db_to_info[:ssh_host])
|
323
|
+
ssh_requiring_connection = db_to_info[:ssh_host] ? db_to_info : db_from_info
|
324
|
+
ssh_port = ssh_requiring_connection[:ssh_port]
|
325
|
+
ssh_local_to_port = ssh_requiring_connection[:ssh_local_to_port] || 3306
|
326
|
+
ssh_user = ssh_requiring_connection[:ssh_user]
|
327
|
+
ssh_local_to_host = ssh_requiring_connection[:ssh_local_to_host] || 'localhost'
|
328
|
+
ssh_host = ssh_requiring_connection[:ssh_host]
|
329
|
+
|
330
|
+
local_port_to_use = ssh_requiring_connection[:tunnel_local_port_to_use] || 4000
|
331
|
+
|
332
|
+
command = "ssh -N #{ssh_port ? '-p ' + ssh_port.to_s : nil} -L #{local_port_to_use}:#{ssh_local_to_host}:#{ssh_local_to_port} #{ssh_user}@#{ssh_host} \n" # note that ssh_local_to_port is 'local on the foreign server'
|
295
333
|
if auto_create_ssh_tunnels and !retried
|
296
334
|
print "trying to auto create ssh tunnel via: #{command}\n"
|
297
335
|
Thread.new { system(command) }
|
298
336
|
retried = true # this doesn't actually work :P
|
299
337
|
retry
|
300
338
|
else
|
301
|
-
print "unable to connect to server--try running\n#{command}in another window or try again
|
339
|
+
print "unable to connect to server--try running\n\t#{command}in another window or try again!\n"
|
302
340
|
end
|
303
341
|
end
|
304
342
|
exit
|
@@ -308,19 +346,15 @@ end
|
|
308
346
|
|
309
347
|
summary_information = '' # so we can print it all (again), at the end
|
310
348
|
|
311
|
-
db_to.query_with_result = false
|
312
|
-
db_from.query_with_result = false # allow us to read them from the wire when they are coming in. Save a little CPU time :)
|
313
|
-
|
314
|
-
|
315
349
|
# we need to delete any extra tables if they're there in one and not the other
|
316
350
|
if do_structure_sync_only and tables_to_sync == ['STRUCTURE_SYNC_THE_WHOLE_THING'] or tables_to_sync == ['ALL_TABLES']
|
317
|
-
tables_from = db_from.query("show tables")
|
351
|
+
tables_from = db_from.query("show tables")
|
318
352
|
tables_from_array = []
|
319
353
|
tables_from.each_hash {|h| h.each{|k, v| tables_from_array << v}}
|
320
354
|
tables_from.free
|
321
355
|
tables_to_sync = tables_from_array
|
322
356
|
if tables_to_sync == ['STRUCTURE_SYNC_THE_WHOLE_THING'] # then we want to drop some tables if they exist
|
323
|
-
tables_to = db_to.query("show tables")
|
357
|
+
tables_to = db_to.query("show tables")
|
324
358
|
tables_to_array = []
|
325
359
|
tables_to.each_hash {|h| h.each{|k, v| tables_to_array << v}}
|
326
360
|
tables_to.free
|
@@ -336,13 +370,13 @@ end
|
|
336
370
|
for table in tables_to_sync do
|
337
371
|
print "start #{commit_style} table #{table}" + "**" * 10 + "\n"
|
338
372
|
if do_structure_sync_only
|
339
|
-
sync_structure(db_to, db_from, table, actually_run_queries)
|
373
|
+
sync_structure(db_to, db_from, table, actually_run_queries, my_options)
|
340
374
|
next
|
341
375
|
end
|
342
376
|
|
343
377
|
all_to_keys_not_yet_processed = {}
|
344
378
|
select_all_to = db_to.query("SELECT * FROM #{table}") # could easily be 'select id', as well note this assumes distinct id's! Otherwise we'd need hashes, one at a time, etc. etc.
|
345
|
-
select_all_to = select_all_to
|
379
|
+
select_all_to = select_all_to
|
346
380
|
select_all_to.each_hash { |to_element|
|
347
381
|
if all_to_keys_not_yet_processed[to_element['id']] # duplicated id's are a fringe case and not yet handled! TODO use hashes or somefin' bet-uh
|
348
382
|
raise "\n\n\n\nERROR detected a duplicated id (or the lack of id at all) in #{table} -- aborting [consider clearing [DELETE FROM #{table} in the 'to' database and trying again, if in a pinch]!\n\n\n\n"
|
@@ -351,7 +385,7 @@ end
|
|
351
385
|
}
|
352
386
|
|
353
387
|
res = db_from.query("SELECT * from #{table}")
|
354
|
-
res = res
|
388
|
+
res = res
|
355
389
|
count_updated = 0
|
356
390
|
count_created = 0
|
357
391
|
|
@@ -398,7 +432,7 @@ end
|
|
398
432
|
end
|
399
433
|
double_check_all_query = "select * from #{table} where id IN (#{ids.join(',')})" # this allows us to make sure we don't delete any doubled ones (which would be a weird situation and too odd to handle), and also so we can have a nice verbose 'we are deleting this row' message
|
400
434
|
double_check_result = db_to.query(double_check_all_query)
|
401
|
-
double_check_result = double_check_result
|
435
|
+
double_check_result = double_check_result
|
402
436
|
|
403
437
|
|
404
438
|
victims = {}
|
@@ -425,9 +459,16 @@ end
|
|
425
459
|
end
|
426
460
|
if my_options[:extra_sql] and actually_run_queries
|
427
461
|
print "doing sql #{my_options[:extra_sql]}\n"
|
428
|
-
result = db_to.query my_options[:extra_sql]
|
429
|
-
|
430
|
-
|
462
|
+
result = db_to.query( my_options[:extra_sql])
|
463
|
+
# result will only be a result set if it was a select query
|
464
|
+
|
465
|
+
if result
|
466
|
+
require 'pp'
|
467
|
+
while row = result.fetch_row do
|
468
|
+
pp row
|
469
|
+
end
|
470
|
+
result.free
|
471
|
+
end
|
431
472
|
end
|
432
473
|
db_from.close if db_from
|
433
474
|
db_to.close if db_to
|
@@ -506,3 +547,5 @@ end
|
|
506
547
|
# ltodo: alias for table names :) czs, ps, etc :)
|
507
548
|
|
508
549
|
# we really need to be able to handle many to many: just use a hash + counter based system instead of an id based system
|
550
|
+
# ltodo: use mysql table checksum :)
|
551
|
+
# ltodo: lacks using port besides 3306 locally
|
data/website/index.html
CHANGED
@@ -33,7 +33,7 @@
|
|
33
33
|
<h1>table_syncer</h1>
|
34
34
|
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/table_syncer"; return false'>
|
35
35
|
<p>Get Version</p>
|
36
|
-
<a href="http://rubyforge.org/projects/table_syncer" class="numbers">0.
|
36
|
+
<a href="http://rubyforge.org/projects/table_syncer" class="numbers">0.3.0</a>
|
37
37
|
</div>
|
38
38
|
<h1>&#x2192; ‘table_syncer’</h1>
|
39
39
|
<h2>What</h2>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: table_syncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Pack
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-11-22 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.
|
23
|
+
version: 1.8.0
|
24
24
|
version:
|
25
25
|
description: tool to synchronize data across databases
|
26
26
|
email:
|
@@ -33,6 +33,7 @@ extra_rdoc_files:
|
|
33
33
|
- History.txt
|
34
34
|
- Manifest.txt
|
35
35
|
- PostInstall.txt
|
36
|
+
- README.rdoc
|
36
37
|
- website/index.txt
|
37
38
|
files:
|
38
39
|
- History.txt
|
@@ -72,7 +73,7 @@ post_install_message: |+
|
|
72
73
|
|
73
74
|
rdoc_options:
|
74
75
|
- --main
|
75
|
-
- README.
|
76
|
+
- README.rdoc
|
76
77
|
require_paths:
|
77
78
|
- lib
|
78
79
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -90,10 +91,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
91
|
requirements: []
|
91
92
|
|
92
93
|
rubyforge_project: table-syncer
|
93
|
-
rubygems_version: 1.3.
|
94
|
+
rubygems_version: 1.3.1
|
94
95
|
signing_key:
|
95
96
|
specification_version: 2
|
96
97
|
summary: tool to synchronize data across databases
|
97
98
|
test_files:
|
98
|
-
- test/test_helper.rb
|
99
99
|
- test/test_table_syncer.rb
|
100
|
+
- test/test_helper.rb
|