Guest User

Untitled

a guest
May 23rd, 2018
405
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.46 KB | None | 0 0
  1. #!/usr/bin/env ruby
  2.  
  3. require 'rubygems'
  4. require 'mysql'
  5. require 'cooloptions'
  6. require 'log4r'
  7. require 'strscan'
  8.  
  9. include Log4r
  10.  
  11. # Date time method for parsing mysql datetime
  12. module 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.  
  75. end
  76.  
  77. # ===========
  78. # = Methods =
  79. # ===========
  80.  
  81. # gets the schema of the source table
  82. def 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
  98. end
  99.  
  100. # takes the slightly modified hash from a mysql result and creates a table schema
  101. # this ignores keys other than the pk
  102. def 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
  110. end
  111.  
  112. # takes the slightly modified hash from a mysql result and creates a table schema
  113. # this ignores keys other than the pk
  114. def 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
  127. end
  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
  131. def 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
  138. end
  139.  
  140. # grabs the rows from the src db
  141. def 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
  176. end
  177.  
  178. # inserts rows and returns the rows to delete
  179. def 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
  215. end
  216.  
  217. # copy rows from src table to dest table
  218. def 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
  226. end
  227.  
  228. # copies and then deletes rows in chunks of num_rows size
  229. def 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
  294. end
  295.  
  296. def 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
  305. end
  306.  
  307. def 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
  318. end
  319.  
  320. def 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
  330. end
  331.  
  332. def 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.  
  345. end
  346.  
  347. def get_fields(table_struct)
  348. fields = []
  349. table_struct.each do | row |
  350. fields.push << row[:field]
  351. end
  352.  
  353. fields
  354. end
  355.  
  356. def 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
  364. end
  365.  
  366. # makes a template for sql row inserts (based on the current schema)
  367. def 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
  380. end
  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
  384. def 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
  395. end
  396.  
  397. # get the field type (useful to determine if a time field is datetime or timestamp/integer)
  398. def 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
  409. end
  410.  
  411. # optimize table
  412. def 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
  419. end
  420.  
  421. # just pass a sql command and this does it all and returns the result
  422. def 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
  440. end
  441.  
  442. # returns true if the global database connection is available
  443. def 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
  449. end
  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
  452. def 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
  506. end
  507.  
  508. # close the database connection
  509. def 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.  
  518. end
  519.  
  520. def 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
  535. end
  536.  
  537. def 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.
  540. Note: Time zones are ignored in all dates and comparisons are based on >= start and < end, so the start is inclusive and ends is not.
  541. New 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
  632. end
  633.  
  634. # ======================
  635. # = Constants and more =
  636. # ======================
  637.  
  638. # constants
  639. DEFAULT_MAX_RECORDS = 0
  640. DEFAULT_RECORDS_PER_TRANSACTION = 10
  641. DEFAULT_TIME_LAPSE = 0
  642. DEFAULT_SLEEP_TIME = 5
  643. DEFAULT_LOG_LEVEL = 2
  644.  
  645. MYSQL_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
  646. PRINTABLE_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
  647. SECONDS_IN_WEEK = 604800
  648. SCRIPT_RUN_TIME = Time.now
  649. FIELD_NAME_TYPE = ['int', 'datetime'] # this is for the future, to automatically pull out the field name
  650.  
  651. # parse command line options
  652. OPTIONS = parse_command_line_options()
  653.  
  654. TABLE_NAME = ARGV.shift || nil
  655. NEW_TABLE_NAME = OPTIONS.table_name.is_a?(String) ? OPTIONS.table_name : (!TABLE_NAME.nil? ? "#{TABLE_NAME}_#{SCRIPT_RUN_TIME.strftime("%Y%m%d")}" : nil)
  656. FIELD_NAME = ARGV.shift || nil
  657. START_TIME = OPTIONS.start
  658. TIME_LAPSE = OPTIONS.lapse
  659. END_TIME = OPTIONS.end
  660. MAX_RECORDS = OPTIONS.max_records
  661. ROWS_PER_TRANSACTION = OPTIONS.records_per_transaction
  662. SLEEP_TIME = OPTIONS.wait_time
  663. LOGGER = Logger.new('log')
  664. LOGGER.level = determine_log_level(OPTIONS.output_log_level)
  665. if OPTIONS.output_log_level > 0
  666. LOGGER.outputters = Outputter.stdout
  667. end
  668.  
  669. DBHOST = OPTIONS.db_host
  670. DBUSER = OPTIONS.user
  671. DBPASS = OPTIONS.password
  672. DBNAME = OPTIONS.name
  673. DBWAIT = 43200
  674.  
  675.  
  676. $global_db_conn = nil
  677.  
  678.  
  679. # ========
  680. # = Main =
  681. # ========
  682.  
  683. if !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}`."
  696. end
Add Comment
Please, Sign In to add comment