table_syncer 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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]
@@ -1,7 +1,7 @@
1
1
  module TableSyncer
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
4
+ MINOR = 3
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
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
- example_db_1 = {:host => '127.0.0.1', :user => 'root', :password => '', :db => 'wilkboar_ties'}
28
- example_db_2 = {:host => '127.0.0.1', :user => 'root', :password => '', :db => 'new_database'}
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
- all_database_names = ['example_db_1', 'example_db_2'] # just used so that the --help command is more useful and can print out the database connection names available
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
- # setup defaults -- it will use these databases by default unless you specify otherwise on the command line:
33
- db_from_name = 'example_db_1'
34
- db_to_name = 'example_db_2'
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 -- \nnote that it doesnt yet do indices and you need to still pass --commit for it to do the structure change. 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") do
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
- else
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}").use_result
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}").use_result
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}").use_result rescue nil
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}").use_result
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};").use_result
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}").use_result
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 [these 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 the following columns: #{apparently_lacking.inspect}\n\n
242
- you might get away with dropping the old table and letting it be recreated -- that might also add the right indices\n\n
243
- #{all_indices.inspect}\n != \n#{all_existing_indices.inspect}"
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 (yes/no)?"
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
- ssh_port = db_from_info[:ssh_port] || db_to_info[:ssh_port]
290
- ssh_local_to_port = db_from_info[:ssh_local_to_port] || db_to_info[:ssh_local_to_port] || 3306
291
- ssh_user = db_from_info[:ssh_user] || db_to_info[:ssh_user]
292
- ssh_local_to_host = db_from_info[:ssh_local_to_host] || db_to_info[:ssh_local_to_host] || 'localhost'
293
- ssh_host = db_from_info[:ssh_host] || db_to_info[:ssh_host]
294
- command = "ssh -N #{ssh_port ? '-p ' + ssh_port.to_s : nil} -L 4000:#{ssh_local_to_host}:#{ssh_local_to_port} #{ssh_user}@#{ssh_host} \n" # NOTE DOES NOT YET ALLOW FOR TWO SSH DB's
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").use_result
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").use_result
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.use_result
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.use_result
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.use_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
- require 'pp'
430
- pp "got sql result", result
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.2.0</a>
36
+ <a href="http://rubyforge.org/projects/table_syncer" class="numbers">0.3.0</a>
37
37
  </div>
38
38
  <h1>&amp;#x2192; &#8216;table_syncer&#8217;</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.2.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-09-30 00:00:00 -06:00
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.7.0
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.txt
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.0
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