· 8 years ago · May 24, 2018, 04:12 AM
1#!/usr/bin/env ruby
2
3require 'rubygems'
4require 'mysql'
5require 'cooloptions'
6require 'log4r'
7require 'strscan'
8
9include Log4r
10
11# Date time method for parsing mysql datetime
12module DateTimeImport
13
14 PRINTABLE_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
15 MYSQL_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
16 LOGGER = Logger.new('log')
17 LOGGER.level = DEBUG
18 LOGGER.outputters = Outputter.stdout
19
20 # Detects time in unix time stamp or mysql datetime format
21 # and creates a ruby Time object
22 def DateTimeImport.convert_time(time)
23 case time.to_s
24
25 # unix time
26 when /^\d+$/
27 LOGGER.info "Using unix time format for #{time.to_s}"
28 get_time_from_unixtime(time.to_s)
29
30 # mysql datetime
31 when /^(\d{2,4})-(\d{1,2})-(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/
32 LOGGER.info "Using mysql datetime format for #{time.to_s}"
33 get_time_from_mysql_datetime(time.to_s)
34 else
35 nil
36 end
37 end
38
39 # convert a time from unix timestamp to ruby Time obj
40 def DateTimeImport.get_time_from_unixtime(unixtime)
41 rubytime = nil
42
43 begin
44 rubytime = Time.at(unixtime.to_i)
45 rescue TypeError => te
46 rubytime = nil
47 LOGGER.error "Error in type conversion from unix time format."
48 end
49
50 rubytime
51 end
52
53 # Convert a time from mysql datetime to ruby time object
54 def DateTimeImport.get_time_from_mysql_datetime(mysql_datetime)
55 datetime_parser = StringScanner.new(mysql_datetime)
56 datetime_parser.scan(/^(\d{2,4})-(\d{1,2})-(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/)
57 year=datetime_parser[1]
58 month=datetime_parser[2]
59 day=datetime_parser[3]
60 hour=datetime_parser[4]
61 min=datetime_parser[5]
62 sec=datetime_parser[6]
63 rubytime = nil
64
65 begin
66 rubytime = Time.mktime(year, month, day, hour, min, sec, 0)
67 rescue ArgumentError => ae
68 LOGGER.error "Error converting time format."
69 rubytime = nil
70 end
71
72 rubytime
73 end
74
75end
76
77# ===========
78# = Methods =
79# ===========
80
81# gets the schema of the source table
82def get_schema_struct(table_name)
83 dbres = do_sql_command("DESC #{table_name};")
84
85 dbstruct = []
86
87 if(dbres) then
88 dbres.each_hash do | row |
89 dbstruct_hash = {}
90 row.each {|key, val|
91 dbstruct_hash[key.downcase.to_sym] = val
92 }
93 dbstruct << dbstruct_hash
94 end
95 end
96
97 dbstruct
98end
99
100# takes the slightly modified hash from a mysql result and creates a table schema
101# this ignores keys other than the pk
102def get_pkey_fields(table_struct)
103 pkeys = []
104
105 table_struct.each do | row |
106 pkeys << row[:field] if row[:key] == 'PRI'
107 end
108
109 pkeys
110end
111
112# takes the slightly modified hash from a mysql result and creates a table schema
113# this ignores keys other than the pk
114def get_schema_sql(table_struct, table_name = NEW_TABLE_NAME)
115 dbstruct = []
116 pkeys = []
117
118 table_struct.each do | row |
119 dbstruct << "`#{row[:field]}` #{row[:type]} #{(!row[:default].nil? && row[:default] != '' ) ? "default '#{row[:default]}'" : ''} #{row[:null] == 'NO' ? 'NOT NULL' : ''}"
120 pkeys << "`#{row[:field]}`" if row[:key] == 'PRI'
121 end
122
123 dbstruct << "PRIMARY KEY (%s)" % [pkeys.join(', ')]
124 dbstring = "CREATE TABLE `%s` (\n\t%s\n)" % [table_name, dbstruct.join(",\n\t")]
125
126 dbstring
127end
128
129# take given a set of keys and a row return a hash for the primary key index of this row,
130# this means whatever this returns can be used to lookup the row
131def make_key_hash_for_row(keys, row)
132 key_hash = {}
133 keys.each {|k|
134 key_hash[k] = row[k]
135 }
136
137 key_hash
138end
139
140# grabs the rows from the src db
141def grab_rows(field, src_table_name = TABLE_NAME, num_rows = ROWS_PER_TRANSACTION)
142 LOGGER.info "Creating select statement based on field `#{field[:name]}` (#{field[:type]})"
143
144 if !(field[:type] =~ /int/).nil?
145 LOGGER.info "Using integer type for select."
146 sql = "SELECT * FROM `%s` WHERE `%s` >= '%s' AND `%s` < '%s' ORDER BY `%s` LIMIT %s;" % [ Mysql::escape_string(src_table_name),
147 Mysql::escape_string(field[:name]),
148 Mysql::escape_string(field[:min].to_i.to_s),
149 Mysql::escape_string(field[:name]),
150 Mysql::escape_string(field[:max].to_i.to_s),
151 Mysql::escape_string(field[:name]),
152 num_rows]
153 elsif !(field[:type] =~ /datetime/).nil?
154 LOGGER.info "Using datetime type for select."
155 sql = "SELECT * FROM `%s` WHERE `%s` >= '%s' AND `%s` < '%s' ORDER BY `%s` LIMIT %s;" % [ Mysql::escape_string(src_table_name),
156 Mysql::escape_string(field[:name]),
157 Mysql::escape_string(field[:min].strftime(MYSQL_DATETIME_FORMAT)),
158 Mysql::escape_string(field[:name]),
159 Mysql::escape_string(field[:max].strftime(MYSQL_DATETIME_FORMAT)),
160 Mysql::escape_string(field[:name]),
161 num_rows]
162 else
163 LOGGER.info "Using default type for select, this isn't expected."
164 sql = "SELECT * FROM `%s` WHERE `%s` >= '%s' AND `%s` < '%s' ORDER BY `%s` LIMIT %s;" % [ Mysql::escape_string(src_table_name),
165 Mysql::escape_string(field[:name]),
166 Mysql::escape_string(field[:min]),
167 Mysql::escape_string(field[:name]),
168 Mysql::escape_string(field[:max]),
169 Mysql::escape_string(field[:name]),
170 num_rows]
171 end
172
173 LOGGER.debug "SQL: #{sql}"
174 dbres = do_sql_command(sql)
175 dbres
176end
177
178# inserts rows and returns the rows to delete
179def insert_rows(rows, field, table_struct, dest_table_name = NEW_TABLE_NAME)
180 fields = get_fields(table_struct)
181 insert_tmplt = row_sql_insert(dest_table_name, table_struct)
182 primary_keys = get_pkey_fields(table_struct)
183 errs = []
184 row_action_data = []
185 del_keys = []
186
187 if (rows) then
188 rows.each_hash do | row |
189 row_action_data << {
190 :sql_insert => make_sql_insert_row(fields, insert_tmplt, row),
191 :key => make_key_hash_for_row(primary_keys, row)
192 }
193 end
194 end
195
196 row_action_data.each { |row|
197 begin
198 dbres = do_sql_command(row[:sql_insert])
199 if dbres.nil?
200 del_keys << row[:key]
201 end
202 rescue Mysql::Error
203 if !($! =~ /^Duplicate entry .* for key/).nil?
204 # i'll consider a duplicate entry okay for a delete
205 LOGGER.warn "Database error! Duplicate key found on insert, marking for deletion anyway, moving on: #{$!}"
206 del_keys << row[:key]
207 else
208 #errs << "Database error, moving on: #{$!}"
209 LOGGER.error "Database error, not sure what, moving on: #{$!}"
210 end
211 end
212 }
213
214 del_keys
215end
216
217# copy rows from src table to dest table
218def copy_rows( field,
219 table_struct,
220 src_table_name = TABLE_NAME,
221 dest_table_name = NEW_TABLE_NAME,
222 num_rows = ROWS_PER_TRANSACTION)
223 rows = grab_rows(field, src_table_name, num_rows)
224 keys_for_delete = insert_rows(rows, field, table_struct, dest_table_name)
225 keys_for_delete
226end
227
228# copies and then deletes rows in chunks of num_rows size
229def move_rows( field,
230 table_struct,
231 src_table_name = TABLE_NAME,
232 dest_table_name = NEW_TABLE_NAME,
233 num_rows = ROWS_PER_TRANSACTION,
234 max_rows = MAX_RECORDS,
235 sleepy_time = SLEEP_TIME)
236 iteration = 1
237 count = 0
238
239 # prime the pump vars for loop
240 keys_for_delete = []
241
242 if max_rows != 0 && max_rows < num_rows
243 LOGGER.info "Adjusting per row transaction due to maximum row limit. This move will only require one transaction."
244 upper_bound = max_rows
245 num_rows = max_rows
246 else
247 upper_bound = num_rows
248 end
249
250 lower_bound = 1
251 max_iterations = max_rows == 0 ? 0 : (max_rows.to_f/num_rows.to_f).ceil
252 remaining_rows = max_rows % num_rows
253
254 while ((iteration == 1 || !keys_for_delete.empty?) && (max_iterations == 0 || iteration <= max_iterations)) do
255 # sleep if we need another iteration
256 if iteration > 1
257 LOGGER.info "Sleeping for #{sleepy_time}"
258 sleep sleepy_time
259 end
260
261 LOGGER.debug "upper_bound: #{upper_bound}\nlower_bound: #{lower_bound}\nnum_rows: #{num_rows}\niteration: #{iteration}\nmax_rows: #{max_rows}\nmax_rows: #{max_iterations}"
262
263 LOGGER.info "Starting move transactions iteration #{iteration} (records #{lower_bound} to #{upper_bound})"
264
265 LOGGER.info "Copying up to #{num_rows} rows..."
266 keys_for_delete = copy_rows(field, table_struct, src_table_name, dest_table_name, num_rows)
267 LOGGER.info "...done"
268
269 if keys_for_delete.size > 0
270 count += keys_for_delete.size
271 LOGGER.info "Deleting #{keys_for_delete.size} rows..."
272 delete_rows(keys_for_delete, src_table_name)
273 LOGGER.info "...done\n"
274 else
275 LOGGER.info "No rows to delete. This could be a problem, but should just mean that it's last iteration."
276 end
277
278 # do calculations for next iterations
279 iteration += 1
280
281 upper_bound = (iteration * num_rows)
282 lower_bound = upper_bound - num_rows + 1
283
284 # this is the last iteration
285 if remaining_rows != 0 && iteration == max_iterations
286 LOGGER.info "Last iteration, only a partial per transaction is needed."
287 num_rows = remaining_rows
288 upper_bound = max_rows
289 end
290
291 end
292
293 count
294end
295
296def row_sql_delete(src_table_name)
297 sql = <<-EOF
298 DELETE FROM `#{src_table_name}`
299 WHERE
300 %s
301 LIMIT 1;
302 EOF
303
304 sql
305end
306
307def make_where_clause(keys)
308 clauses = []
309 keys.each { |key|
310 tmp_clause = []
311 key.each { |i, val|
312 tmp_clause << "`#{i}` = '#{Mysql::escape_string(val)}'"
313 }
314 clauses << tmp_clause.join(' AND ')
315 }
316
317 clauses
318end
319
320def make_sql_delete_rows(src_table_name, keys)
321 whereless_sql = row_sql_delete(src_table_name)
322 where_clauses = make_where_clause(keys)
323
324 sql_deletes = []
325 where_clauses.each {|where|
326 sql_deletes << whereless_sql % where
327 }
328
329 sql_deletes
330end
331
332def delete_rows(keys, src_table_name = TABLE_NAME)
333 errs = []
334 sql_delete_statements = make_sql_delete_rows(src_table_name, keys)
335
336 sql_delete_statements.each{ |sql|
337 LOGGER.debug "DELETE SQL: #{sql}"
338 begin
339 dbres = do_sql_command(sql)
340 rescue Mysql::Error
341 LOGGER.error "Database error, moving on: #{$!}"
342 end
343 }
344
345end
346
347def get_fields(table_struct)
348 fields = []
349 table_struct.each do | row |
350 fields.push << row[:field]
351 end
352
353 fields
354end
355
356def make_sql_insert_row(field_names, row_template, row)
357 values = []
358 field_names.each { |field|
359 values << Mysql::escape_string(row[field])
360 }
361 sql = row_template % values
362
363 sql
364end
365
366# makes a template for sql row inserts (based on the current schema)
367def row_sql_insert(table_name, table_struct)
368 fields = get_fields(table_struct)
369
370 sql = <<-EOF
371 INSERT INTO `#{DBNAME}`.`#{table_name}` (
372 #{fields.collect { |f| "`#{f}`" }.join(", ")}
373 )
374 VALUES (
375 #{fields.collect { |f| "'%s'" }.join(", ")}
376 );
377 EOF
378
379 sql
380end
381
382# creates the new table (really just wrapps the do sql method, but make its readable)
383# that new method kind of makes this useless
384def create_new_table(table_sql)
385 begin
386 do_sql_command(table_sql)
387 LOGGER.info "Successfully reated new table: \n#{table_sql}\n"
388 rescue Mysql::Error
389 if ($!.to_s =~ /^Table .* already exists$/) == 0
390 LOGGER.warn "Database error, duplicate table is okay, moving on: #{$!}"
391 else
392 LOGGER.error "Database error, moving on: #{$!}"
393 end
394 end
395end
396
397# get the field type (useful to determine if a time field is datetime or timestamp/integer)
398def get_field_datatype(table_struct, field_name = FIELD_NAME)
399 datatype = nil
400
401 table_struct.each { |row|
402 break if !datatype.nil?
403 if row[:field] == field_name
404 datatype = row[:type]
405 end
406 }
407
408 datatype
409end
410
411# optimize table
412def optimize_table(table)
413 result = do_sql_command("OPTIMIZE TABLE `#{Mysql::escape_string(table)}`")
414 if !result['Msg_type'].nil? && !result['Msg_text'].nil?
415 {:type => result['Msg_type'], :text => result['Msg_text']}
416 else
417 nil
418 end
419end
420
421# just pass a sql command and this does it all and returns the result
422def do_sql_command(sql, use_global_connection = true, close_connection = false)
423 begin
424
425 if use_global_connection
426 dbconn = open_conn
427 else
428 dbconn = open_conn(false)
429 end
430
431 dbres = dbconn.query(sql)
432
433 rescue Mysql::Error => err
434 raise err
435 ensure
436 close_conn(dbconn) if close_connection
437 end
438
439 dbres
440end
441
442# returns true if the global database connection is available
443def is_global_db_connected?
444 if defined?($global_db_conn).nil? || $global_db_conn.class != Mysql || $global_db_conn.stat == 'MySQL server has gone away'
445 return false
446 end
447
448 return true
449end
450
451# opens a database conneciton with the set params, uses a global connection by default but can create a new connection and return that instead
452def open_conn(global = true, set_wait = true, host = DBHOST, user = DBUSER, pass = DBPASS, name = DBNAME)
453 LOGGER.debug "Opening db connection"
454 conn = nil
455
456 #use global connection
457 if global
458 LOGGER.debug "Using global database connection"
459
460 # global connection is already defined
461 if is_global_db_connected?
462 LOGGER.debug "Global connection is set, just giving it back"
463 conn = $global_db_conn
464
465 # global connection is not defined or not set
466 else
467
468 # open new global connection
469 LOGGER.debug "Global connection isn't defined or isn't set; attempting to reconnect..."
470 begin
471 $global_db_conn = Mysql::new(host, user, pass, name)
472 conn = $global_db_conn
473
474 if set_wait && defined?(DBWAIT)
475 LOGGER.debug "Settign wait time for db connection to #{DBWAIT}"
476 sql = "SET SESSION WAIT_TIMEOUT = #{DBWAIT};"
477 dbres = conn.query(sql)
478 end
479
480 rescue Mysql::Error => err
481 LOGGER.error "Error making db connection: #{$1}"
482 raise err
483 end
484 end
485
486 # don't use global connection
487 else
488 LOGGER.debug "Not using global connection, creating anew..."
489 # open new global connection
490
491 begin
492 conn = Mysql::new(host, user, pass, name)
493
494 if set_wait && defined?(DBWAIT)
495 LOGGER.debug "Settign wait time for db connection to #{DBWAIT}"
496 sql = "SET SESSION WAIT_TIMEOUT = #{DBWAIT};"
497 dbres = conn.query(sql)
498 end
499 rescue Mysql::Error => err
500 LOGGER.error "Error making db connection: #{$1}"
501 raise err
502 end
503 end
504
505 conn
506end
507
508# close the database connection
509def close_conn(conn)
510 LOGGER.debug "Closing db connection"
511
512 begin
513 conn.close if conn
514 rescue Mysql::Error => err
515 LOGGER.error "Error closing db connection: #{$1}"
516 end
517
518end
519
520def determine_log_level(level)
521 case level
522 when 1
523 DEBUG
524 when 2
525 INFO
526 when 3
527 WARN
528 when 4
529 ERROR
530 when 5
531 FATAL
532 else
533 FATAL
534 end
535end
536
537def parse_command_line_options
538 options = CoolOptions.parse!("[options] <table name> <field name>") do |o|
539 o.desc %q(Archive a database table in small bite size portions.
540Note: Time zones are ignored in all dates and comparisons are based on >= start and < end, so the start is inclusive and ends is not.
541New table schemas ignore the "extra" params. This is because auto_increment fields could interfere with adding records and keeping referential integrity.
542)
543 o.on "db-host DATABASE_HOST", "Database host name"
544 o.on "name DATABASE_NAME", "Database name"
545 o.on "user DATABASE_USER", "Database user"
546 o.on "password DATABASE_PASS", "Database password"
547
548 o.on "default", "Start now and run for an hour (previous hours worth of records) (ignore start, end and lapse params)", false
549 o.on "start TIME", "Start time (mysql or epoch). A lower time bound. ex 2007-10-01 16:00:00", ''
550 o.on "end TIME", "End time (mysql or epoch). An upper time bound. ex 2007-10-01 17:00:00", ''
551 o.on "lapse SECONDS", "Time lapse from start in seconds.", DEFAULT_TIME_LAPSE
552 o.on "max-records NUMBER", "Number of records to copy. 0 = all", DEFAULT_MAX_RECORDS
553 o.on "records-per-transaction NUMBER", "Number of records to grab per transaction", DEFAULT_RECORDS_PER_TRANSACTION
554 o.on "wait-time SECONDS", "Number of seconds to sleep between move transactions", DEFAULT_SLEEP_TIME
555 o.on "output-log-level NUMBER", "Log level, 0-5 (off, debug, info, warn, error, fatal)", DEFAULT_LOG_LEVEL
556 o.on "table-name DEST_TABLE_NAME", "Destination table name. Default table name is <table_name>_<datestamp>.", false
557
558 o.after do |r|
559
560 o.error("Error: Max records value must be an integer numeric value.") if (r.max_records =~ /^\d+$/).nil?
561 o.error("Error: Per transaction value must be an integer numeric value.") if (r.records_per_transaction =~ /^\d+$/).nil?
562 o.error("Error: Wait time must be an integer numeric value.") if (r.wait_time =~ /^\d+$/).nil?
563 o.error("Error: Log level must be a numeric value 1-5") if (r.output_log_level =~ /^\d+$/).nil? || (r.output_log_level.to_i > 5) || (r.output_log_level.to_i < 0)
564
565 r.max_records = r.max_records.to_i
566 r.wait_time = r.wait_time.to_i
567 r.records_per_transaction = r.records_per_transaction.to_i
568 r.output_log_level = r.output_log_level.to_i
569
570 # generally test purposes, these defaults could be changed:
571 if r.default
572 r.end = SCRIPT_RUN_TIME #now
573 r.lapse = 60 # one minute
574 r.start = SCRIPT_RUN_TIME - r.lapse
575
576 # start and end were specified
577 elsif (r.start != '' && r.end != '' && r.lapse == 0)
578 r.start = DateTimeImport.convert_time(r.start)
579 r.end = DateTimeImport.convert_time(r.end)
580
581 o.error("Error: Start date format is invalid. For best results, use YYYY-MM-DD HH:MM:SS") if r.start.nil?
582 o.error("Error: End date format is invalid. For best results, use YYYY-MM-DD HH:MM:SS") if r.end.nil?
583 o.error("Error: Start date must be priaor to end date.") if r.start >= r.end
584
585 r.lapse = (r.end - r.start).to_i
586
587 # if you aren't using the default you have to specify a start or end
588 elsif (r.start != '' && r.end != '' && r.lapse != 0)
589 o.error("Error: Please specify either start/end, start/lapse, end/lapse options")
590
591 elsif (r.start == '' && r.end == '')
592 o.error("Error: Start or end must be specified.")
593
594 # we know the start and end both aren't empty, so check the other options
595 # this means the start was specified and the lapse was nil
596 elsif (r.lapse == 0 && r.start != '')
597 o.error("Error: A lapse or end time must be specified")
598
599 # this means the end was specified, but no start or lapse
600 elsif (r.lapse == 0 && r.end != '')
601 o.error("Error: A lapse or start time must be specified")
602
603 # using start and lapse
604 elsif (r.start != '' && r.lapse != 0)
605 o.error("Error: Lapse must be an integer value") if (r.lapse.to_s =~ /^[0-9]+$/).nil?
606
607 r.lapse = r.lapse.to_i
608 r.start = DateTimeImport.convert_time(r.start)
609 r.end = (r.start + r.lapse)
610
611 o.error("Error: Start date format is invalid. For best results, use 'YYYY-MM-DD HH:MM:SS' or a unix time integer value.") if r.start.nil?
612 o.error("Error: End date format is invalid. For best results, use 'YYYY-MM-DD HH:MM:SS' or a unix time integer value.") if r.end.nil?
613 o.error("Error: Start date must be priaor to end date.") if r.start >= r.end
614
615 # using end and lapse
616 elsif (r.end != '' && r.lapse != 0)
617 o.error("Error: Lapse must be an integer value") if (r.lapse.to_s =~ /^[0-9]+$/).nil?
618
619 r.lapse = r.lapse.to_i
620 r.end = DateTimeImport.convert_time(r.end)
621 r.start = r.end - r.lapse
622
623 o.error("Error: Start date format is invalid. For best results, use 'YYYY-MM-DD HH:MM:SS' or a unix time integer value.") if r.start.nil?
624 o.error("Error: End date format is invalid. For best results, use 'YYYY-MM-DD HH:MM:SS' or a unix time integer value.") if r.end.nil?
625 o.error("Error: Start date must be priaor to end date.") if r.start >= r.end
626 end
627
628 end
629 end
630
631 options
632end
633
634# ======================
635# = Constants and more =
636# ======================
637
638# constants
639DEFAULT_MAX_RECORDS = 0
640DEFAULT_RECORDS_PER_TRANSACTION = 10
641DEFAULT_TIME_LAPSE = 0
642DEFAULT_SLEEP_TIME = 5
643DEFAULT_LOG_LEVEL = 2
644
645MYSQL_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
646PRINTABLE_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
647SECONDS_IN_WEEK = 604800
648SCRIPT_RUN_TIME = Time.now
649FIELD_NAME_TYPE = ['int', 'datetime'] # this is for the future, to automatically pull out the field name
650
651# parse command line options
652OPTIONS = parse_command_line_options()
653
654TABLE_NAME = ARGV.shift || nil
655NEW_TABLE_NAME = OPTIONS.table_name.is_a?(String) ? OPTIONS.table_name : (!TABLE_NAME.nil? ? "#{TABLE_NAME}_#{SCRIPT_RUN_TIME.strftime("%Y%m%d")}" : nil)
656FIELD_NAME = ARGV.shift || nil
657START_TIME = OPTIONS.start
658TIME_LAPSE = OPTIONS.lapse
659END_TIME = OPTIONS.end
660MAX_RECORDS = OPTIONS.max_records
661ROWS_PER_TRANSACTION = OPTIONS.records_per_transaction
662SLEEP_TIME = OPTIONS.wait_time
663LOGGER = Logger.new('log')
664LOGGER.level = determine_log_level(OPTIONS.output_log_level)
665if OPTIONS.output_log_level > 0
666 LOGGER.outputters = Outputter.stdout
667end
668
669DBHOST = OPTIONS.db_host
670DBUSER = OPTIONS.user
671DBPASS = OPTIONS.password
672DBNAME = OPTIONS.name
673DBWAIT = 43200
674
675
676$global_db_conn = nil
677
678
679# ========
680# = Main =
681# ========
682
683if !TABLE_NAME.nil?
684 LOGGER.info "Starting..."
685 LOGGER.info "Records from #{START_TIME.strftime(PRINTABLE_DATETIME_FORMAT)} to #{END_TIME.strftime(PRINTABLE_DATETIME_FORMAT)} are being moved from #{TABLE_NAME} to #{NEW_TABLE_NAME}"
686 LOGGER.info "This is a period of #{TIME_LAPSE} seconds, but could be interrupted at the limit of #{MAX_RECORDS} records"
687 LOGGER.info "Note: Time zones are ignored and comparisons are based on >= start and < end, so the start is inclusive and ends is not."
688
689 table_struct_hash = get_schema_struct(TABLE_NAME)
690 create_new_table(get_schema_sql(table_struct_hash))
691 field_data = {:name => FIELD_NAME, :min => START_TIME, :max => END_TIME, :type => get_field_datatype(table_struct_hash)}
692 move_row_count = move_rows(field_data, table_struct_hash)
693 close_conn($global_db_conn)
694 LOGGER.info "Moved #{move_row_count} rows."
695 LOGGER.info "You should probably run OPTIMIZE TABLE `#{TABLE_NAME}`."
696end