#!/usr/bin/env ruby

# msqldump.rb written by itacchi (DATE Ken) 2000-
# That's sample program for Ruby/mSQL.
# msqldump.rb is a `msqldump' clone.

require 'getopts'
require 'msql'


class OptionError < StandardError; end

class MsqlDump

  def initialize(host, database, verbose=false)
    @verbose = verbose
    connect(host, database)
  end
  attr_accessor :verbose, :with_column

  def dump(table=nil, where_clause=nil, table_only=false, with_column=false)
    target_tables = []

    if table
      target_tables.push table
    else
      @client.list_tables.each_row do |table|
        target_tables.push table[0]
      end
    end

    target_tables.each do |table|
      get_table_structure(table)
      dump_table(table, where_clause, with_column) unless table_only
    end

  end


  def dump_end
    disconect
    print "\n"
  end


  private
  def connect(host, database)
    STDERR.printf "Connecting to %s...\n", host ? host : "localhost" if @verbose
    @client = Msql::connect host

    STDERR.print "Selecting data base #{database}...\n" if @verbose
    @client.select_db database
  end


  def disconect
    STDERR.print "Disconnecting from #{@client.get_host}...\n" if @verbose
    @client.close
  end


  def dump_table(table, where_clause, with_column=false)
    STDERR.print "Sending SELECT query...\n" if @verbose
    printf "\n#\n# Dumping data for table '#{table}'\n#\n\n"

    if where_clause
      query = "SELECT * FROM #{table} where #{where_clause}"
    else
      query = "SELECT * FROM #{table} "
    end

    @client.query query
    res = @client.store_result

    STDERR.print "Retrieved #{res.num_rows} rows. Processing...\n" if @verbose

    res.each_row {|row|
       printf "INSERT INTO #{table} %s VALUES (", with_column ? @column_def : ""
       res.field_seek 0

       res.each_field_with_index {|field, i|
         print "," if i != 0
         if row[i]
           case field.type
           when MsqlField::CHAR_TYPE, MsqlField::TEXT_TYPE,
                MsqlField::DATE_TYPE, MsqlField::TIME_TYPE
             print "\'#{row[i].gsub(/\\{1,2}/, '\\\\')}\'"
           else
             print "#{row[i]}"
           end
         else
           print "NULL"
         end
       }

      print ")\\g\n"
    }

  end


  def get_table_structure(table)
    STDERR.print "Retrieving table structure for table #{table}...\n" if @verbose

  # table
    table_res = @client.list_fields(table)
    print "\n#\n# Table structure for table '#{table}'\n#\n"
    print "CREATE TABLE #{table} (\n"

    @column_def = "("
    table_res.each_field_with_index do |field, i|
      break if field.type > MsqlField::LAST_REAL_TYPE
      if i != 0
        @column_def += ", "
        print ",\n"
      end
      @column_def += field.name
      print "  #{field}"
    end
    print "\n) \\g\n\n"
    @column_def += ")"

    table_res.field_seek 0

  # index
    table_res.each_field do |field|
      next if field.type != MsqlField::IDX_TYPE

      index_res = @client.list_index table, field.name
      row = index_res.fetch_row

      type = row[0] == "avl" ? "" : row[0]

      printf "CREATE %s %s INDEX %s ON %s (",
        field.is_unique? ? "UNIQUE" : "",
        type, field.name, table

      index_res.each_row_with_index do |row, i|
        print (i == 0 ? "\n" : ",\n")
        print "\t#{row[0]}"
      end

      print "\n) \\g\n\n"
    end


  # sequence
    if seq = @client.get_sequence_info(table)
      print "CREATE SEQUENCE ON #{table} STEP #{seq.step} VALUE #{seq.value} \\g\n\n"
    end
  end

end




# print usage
def usage
  print "\n\nusage: \t%s [-h host] [-f conf] [-v] [-t] [-c] [-w where_clause] database [table]\n\n"
  print "\tProduce an ASCII dump of a database table or an entire database\n\n"
end



# --- main process

# parse option
opts = getopts("vtc", "h:", "f:", "w:")

# parse database name and table name
database = ARGV.shift
table    = ARGV.shift


verbose = $OPT_v
host    = $OPT_h
conf    = $OPT_f
where   = $OPT_w
is_only_table_structure   = $OPT_t
is_insert_with_column_def = $OPT_c


begin

  # illegal option or no database
  raise OptionError unless opts and database


  # too many args...
  raise OptionError if ARGV.size != 0


  # When option -f exists, load specified config file.
  if conf
    Msql::load_config_file(conf)
  end

  # print header message
  printf "#\n# mSQL Dump  (requires mSQL 2.0 Beta 5 or newer)\n#\n"
  printf "# Host: %s    Database: %s\n", host ? host : "localhost", database
  printf "#--------------------------------------------------------\n\n"

  # create dumper instance
  dump = MsqlDump.new host, database, verbose

  # dump database
  dump.dump table, where, is_only_table_structure, is_insert_with_column_def

  # end process
  dump.dump_end

rescue OptionError
  usage
end
