MDBにあるテーブルを全てCSV形式で出力する

MDBにあるテーブルをADOでつないで検索し、
CSV形式でファイルに出力します。
るびまのソースを参考にしています。)


処理を続けるとメモリがどんどん増大してしまい、
メモリ不足で落ちてしまうので、プログラム自体を再帰(?)呼び出しして対応してみました。


Cygwinで実行するとうまく行くのですが
Windowsコマンドプロンプトから実行すると
rubyのプロセスがガンガン増えて行ってしまい、メモリ不足に陥ります orz


そもそも、なぜメモリをガンガン使っていくのかを調べ切れていません・・・。
GC.start してみたり、コネクションを都度切ったりしてみましたが駄目でした。
再帰してみたらうまくいったって感じです。


access-mdb.rb

require 'win32ole'
require 'optparse'

module Recordset
  def  field
    self.Fields.Item(field).Value
  end

  def = field,value
    self.Fields.Item(field).Value = value
  end

  def each_record
    if self.EOF or self.BOF
      return 
    end
    self.MoveFirst
    until self.EOF or self.BOF
      yield self
      self.MoveNext
    end
  end
end

def extract(table_name , connection)
  start_time = Time.new
  # 出力ファイル名
  file_name = table_name + ".txt"
  # 実行するSQL
  sql = "select * from #{table_name}"
  # SQL実行
  rs = connection.Execute(sql)
  # 特異メソッドを追加
  rs.extend Recordset
  # フィールドの一覧を取得する
  fields = rs.Fields
  # フィールド名を出力
  # Type
  # 3   : 
  # 131 : int
  # 202 : char
  # 203 : varchar
  #
  #for field in fields
  #  p field.Name + " : " + field.Type.to_s
  #end
  count = 1
  open("#{file_name}" , "w") {|file|
    print file_name + " "
    rs.each_record {|record|
      print_count(count)
      list = Array.new
      for field in fields
        value = record[field.Name]
        if value.kind_of?(String)
          # nil が来る場合もある
          value = value ? value.strip : ""
          # 数値以外は,と改行をエスケープしてダブルクォートで囲む
          # ダブルクォートはもう一つダブルクォートを付けてエスケープする
          value = "\"" + value.gsub("\"" , "\"\"").gsub("\n" , "") + "\""
        end
        list.push(value)
      end
      tmp = list.join(",")
      #puts count.to_s + " => " + tmp
      count += 1
      file.puts tmp + "\n" 
    }
  }
  # レコード数 : 処理時間
  print ".(#{count} : #{Time.new - start_time}[s])"
end

def get_table_list(connection) 
  # カタログの生成
  catalog= WIN32OLE.new("ADOX.Catalog")
  catalog.ActiveConnection = connection
  # テーブルの一覧を取得する
  tables = catalog.Tables
  # 返却
  return tables
end

def create_connection(file_name)
  connection= WIN32OLE.new("ADODB.Connection")
  connstr = "DRIVER={Microsoft Access Driver (*.mdb)};Dbq=#{file_name}"
  connection.Open connstr
  return connection
end

def print_count(count)
  # ログ出力
  if(count % 5000 == 0)
    print count
    $stdout.flush
  elsif(count % 500 == 0)
    print "|"
    $stdout.flush
  elsif(count % 100 == 0) 
    print "."
    $stdout.flush
  end
end

# 引数を格納するマップ
ProgramConfig = Hash.new
# 引数の判定
opts = OptionParser.new
opts.on("-t table_name") {|val| ProgramConfig[:t] = val}
opts.on("-i count") {|val| ProgramConfig[:i] = val}
opts.parse!(ARGV)

file_name = "mdbのファイル名"
# コネクションの生成
connection = create_connection(file_name)
# テーブルの一覧を取得する
tables = get_table_list(connection)
# テーブルごとにファイルを生成していく
i = 0
tables_count = tables.Count 
for i in 0...tables_count
  # Item(i) の戻り値は Table
  table_name = tables.Item(i).Name
  # 開始対象のテーブル名が指定されている場合
  if ProgramConfig[:t] && ProgramConfig[:t] > table_name
    next
  elsif ProgramConfig[:i] && ProgramConfig[:i].to_i > i
    next
  end
  # MSys始まりのテーブルは無視
  if table_name =~ /^[^(MSys)]/
    # ファイルに抽出する
    print "#{i} "
    extract(table_name , connection)
    break;
  end
end

# コネクションを閉じる
connection.close


puts " (#{i} / #{tables_count - 1})"
if i < tables_count - 1
  # 再帰(?)呼び出し
  exec("ruby access-mdb.rb -i #{i + 1}")
else
  # プログラム終了
  puts "The program ended."
end


2006.07.12 追記:CSVCVSって書いていたのを直しました・・・。