あちこちに散らばるDBからCSV吐き出ししたい

使用者がある程度柔軟にSQLを変えられる何かが欲しいという事で作成。
WSHJScriptはなかなか例が少ないので、何かの役に立てばと思い晒しておきます。
一応現場で動いていますが、危なっかしいかもしれません。

使用者に用意してもらうもの

2つ。

  1. 接続先情報
  2. SQL

例えば、以下の様に準備します。


接続先情報(ファイル名は、ここではnode.jsとして、confフォルダに置くものとします。)

{
    "driver" : "{SQL Server}",
    "commandTimeOut" : 514,
    "uid" : "user",
    "pwd" : "pass",
    "sql" : "sql\\nodeで指定なし.sql",
    "node":
    [
        {"name" : "AAA" , "server" : "XXX.XXX.XXX.XXX"},
        {"name" : "BBB" , "server" : "XXX.XXX.XXX.XXX"},
        {"name" : "CCC" , "server" : "XXX.XXX.XXX.XXX"},
        {"name" : "DDD" , "server" : "XXX.XXX.XXX.XXX", "sql" : "sql\\DDD用.sql"},
        {"name" : "EEE" , "server" : "XXX.XXX.XXX.XXX"},
        {"name" : "FFF" , "server" : "XXX.XXX.XXX.XXX", "sql" : "select * from FFF"},
        {"name" : "GGG" , "server" : "XXX.XXX.XXX.XXX", "sql" : "sql\\GGG用.sql"}
    ]
}

nodeの中に個別で設定を書けば、そのnodeではそっちを優先します。
その他、設定は適時変更して下さい。(serverのXXX.XXX.XXX.XXXはIPです。コンピュータ名でも大丈夫かも。)


SQLはあくまでCSV吐き出しに使うだけなのでselect限定にして下さい。
ファイルで用意するか、接続先設定の中に直接書きます。
(.sqlで終わっている場合はSQLファイル、それ以外はSQL文そのままとみなします。)

実行するもの

JScriptです。
適当なファイル名で保存して下さい。(ここでは、getter.jsとしておきます。)

/////////////////////////////////////
// 読み込む設定ファイルをここへ    //
// 設定ファイルは、実行ファイル    //
// からの相対Pathで指定して下さい。//
/////////////////////////////////////

var myConfig = "conf\\node.js";

// prototype.jsのClass
var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

// Main
var Main = Class.create();
Main.prototype = {
    initialize : function(confName) {
        this.confName = confName;
    },
    execute : function() {
        var dao = new Dao(this.confName);
        var nodes = dao.getNodeNames();
        // nodeで設定された宛先それぞれにSQLを発行して、
        // 発行後にcallback関数を呼んでもらう
        for(var i = 0, len = nodes.length; i < len; i++) {
            dao.executeTo(nodes[i], toCsv);
        }
    }
}

// adoで使う定数。
// この変数名でしかうまく検索ヒットしないから書いておく。
var adClipString = 2;

// Stringの拡張
String.prototype.endsWith = function(suffix) {
    return (suffix.length > this.length) ? 
        false : 
        this.lastIndexOf(suffix) == (this.length - suffix.length);
}

function echo(str) {
    WScript.Echo(str);
}

// Wsh特有の部分を隠蔽するクラス
var WshObject = Class.create();
WshObject.prototype = {
    initialize : function() {
        this.fs = new ActiveXObject("Scripting.FileSystemObject");
        this.shell = new ActiveXObject("WScript.Shell");
        this.ado = new ActiveXObject("ADODB.Connection");
    },
    getAdoObject : function() {
        return this.ado;
    },
    getCurrentPath : function() {
        return this.shell.currentDirectory;
    },
    getParentPath : function() {
        return this.fs.getParentFolderName(WScript.ScriptFullName);
    },
    getFileSystem : function() {
        return this.fs;
    }
}

// ファイル周り
var File = Class.create();
File.prototype = {
    FOR_READING : 1,   //読み込み
    FOR_WRITING : 2,   //書きこみ(上書きモード)
    FOR_APPENDING : 8, //書きこみ(追記モード)
    initialize : function(fileName) {
        this.wsh = new WshObject();
        this.fs = this.wsh.getFileSystem();
        this.fileName = fileName;
        this.file = this.fs.buildPath(this.wsh.getParentPath(), this.fileName);
    },
    openForRead : function() {
        return this.fs.openTextFile(this.file, this.FOR_READING);
    },
    openForWrite : function() {
        return this.fs.openTextFile(this.file, this.FOR_WRITING);
    },
    openForAppend : function() {
        return this.fs.openTextFile(this.file, this.FOR_APPENDING);
    },
    readAll : function() {
        var reader = this.openForRead();
        var str = reader.readAll();
        reader.close();
        return str;
    },
    create : function() {
        this.fs.createTextFile(this.file);
    },
    write : function(str) {
        var writer = this.openForWrite();
        writer.write(str);
        writer.close();
    },
    append : function(str) {
        var writer = this.openForAppend();
        writer.write(str);
        writer.close();
    },
    writeLine : function(str) {
        var writer = this.openForWrite();
        writer.writeLine(str);
        writer.close();
    },
    appendLine : function(str) {
        var writer = this.openForAppend();
        writer.writeLine(str);
        writer.close();
    }
}

// 設定ファイルまわり
var Config = Class.create();
Config.prototype = {
    initialize : function(fileName) {
        this.file = new File(fileName);
        eval("this.conf = " + this.file.readAll());
    },
    // Configファイルからデータを取得して値を返す。
    // keyが2つ(ネスト)の場合、引数は["key1","key2"]
    get : function(key) {
        var keys = (key instanceof Array) ? key : [key];
        var value;
        var keyStr = new String();
        for(var i = 0; i < keys.length; i++) {
            keyStr = keyStr + "[keys[" + i + "]]";
        }
        eval("value = this.conf" + keyStr);
        return value;
    },
    getAll : function() {
        return this.conf;
    }
}

// データアクセスまわり
var Dao = Class.create();
Dao.prototype = {
    initialize: function(confName) {
        var wsh = new WshObject();
        this.ado = wsh.getAdoObject();
        if(confName) {
            var myConf = new Config(confName);
            if(myConf.get("driver")) {this.driver = myConf.get("driver");}
            if(myConf.get("uid")) {this.uid = myConf.get("uid");}
            if(myConf.get("pwd")) {this.pwd = myConf.get("pwd");}
            if(myConf.get("sql")) {this.sql = myConf.get("sql");}
            if(myConf.get("commandTimeOut")) {this.timeout = myConf.get("commandTimeOut");}
            if(myConf.get("node")) {this.nodes = myConf.get("node");}
            if(myConf.get("server")) {this.server = myConf.get("server");}
        }
    },
    getOpenString : function() {
        var str = new String();
        str = str 
            + "Driver=" + this.driver + ";"
            + "server=" + this.server + ";"
            + "uid=" + this.uid + ";"
            + "pwd=" + this.pwd;
        return str;
    },
    open : function() {
        this.ado.commandTimeout = this.timeout;
        this.ado.open(this.getOpenString());
    },
    close : function() {
        this.ado.close();
    },
    execute : function(sql) {
        var res = this.ado.execute(sql);
        return res;
    },
    executeTo : function(nodeName, callback) {
        var node = this.nodes[this.getNodeNumber(nodeName)];
        try {
            if(!node) {
                throw("node取得に失敗しました。\nnode:" + nodeName + "の設定を確認して下さい。");
            }
            var nodeSql = node["sql"] || this.sql;
            if(!nodeSql) {
                throw("SQL取得に失敗しました。\nnode:" + nodeName + "のSQL設定を確認して下さい。");
            }
            var dao = this.createDaoByNode(node);
            dao.open();
            var sqlStr = toSqlString(nodeSql);
            var rs = dao.execute(sqlStr);
            if(typeof callback == "function") {
                callback(nodeName, rs);
            }
            dao.close();
        } catch (e) {
            echo("executeTo : " + e);
        }
    },
    createDaoByNode : function(node) {
        try {
            var nodeServer = node["server"] || this.server;
            var nodeDriver = node["driver"] || this.driver;
            var nodeUid = node["uid"] || this.uid;
            var nodePwd = node["pwd"] || this.pwd;
            var nodeTimeOut = node["commandTimeOut"] || this.timeout;
            if(!nodeServer || !nodeDriver || !nodeUid || !nodePwd || !nodeTimeOut) {
                throw("node詳細情報取得に失敗しました。\nnodeの設定を確認して下さい。");
            }
            var dao = new Dao();
            dao.setDriver(nodeDriver);
            dao.setUid(nodeUid);
            dao.setPwd(nodePwd);
            dao.setServer(nodeServer);
            dao.setTimeOut(nodeTimeOut);
            return dao;
        } catch (e) {
            echo("createDaoByNode : " + e);
        }
    },
    getNodeNumber : function(nodeName) {
        var names = this.getNodeNames();
        var num = -1;
        for(var i = 0, len = names.length; i < len; i++) {
            if(nodeName == names[i]) {
                num = i;
                break;
            }
        }
        return num;
    },
    getNodeNames : function() {
        var names = new Array();
        for(var i = 0, len = this.nodes.length; i < len; i++) {
            names[i] = this.nodes[i]["name"];
        }
        return names;
    },
    setDriver : function(driver) {
        this.driver = driver;
    },
    setServer : function(server) {
        this.server = server;
    },
    setSql : function(sql) {
        this.sql = sql;
    },
    setUid : function(uid) {
        this.uid = uid;
    },
    setPwd : function(pwd) {
        this.pwd = pwd;
    },
    setDriver : function(driver) {
        this.driver = driver;
    },
    setNodes : function(nodes) {
        this.nodes = nodes;
    },
    setTimeOut : function(timeout) {
        this.timeout = timeout;
    }
}

// 引数が".sql"で終わるSQLファイルだったら中身を返す。
// ".sql"で終わってなかったら引数をそのまま返す。
function toSqlString(sql) {
    var str = new String();
    if(sql.endsWith(".sql")) {
        str = new File(sql).readAll();
    } else {
        str = sql;
    }
    return str;
}

// レコードセットから列名を取り出してCSVへ
function getHeader(rs) {
    var header = "";
    for(var i = 0, len = rs.fields.count; i < len; i++) {
        var columnName = rs.fields(i).name;
        if(i == 0) {
            header = columnName;
        } else {
            header = header + "," + columnName;
        }
    }
    return header;
}

// 受け取ったレコードをCSVにする
// ファイル名は、{nodeName}.csv
function toCsv(nodeName, rs) {
    var header = getHeader(rs);
    var file = new File(nodeName + ".csv");
    file.create();
    if(!rs.Eof) {
        file.appendLine(header);
    }
    while(!rs.Eof) {
        var strRs = rs.getString(adClipString, 1000, ",", "\r\n", "NULL");
        file.append(strRs);
    }
}

////////////////////////
// ここからメイン処理 //
////////////////////////
var main = new Main(myConfig);
main.execute();


これで準備完了。
上記例で出来あがったものは・・・

getter.js
conf
  L node.js
sql
  L nodeで指定なし.sql
  L DDD用.sql
  L GGG用.sql

です。


実行は、getter.jsのあるフォルダで、

cscript getter.js

とかそんな感じです。
Windows XP の場合、ダブルクリックでも実行できました。


実行すると、各nodeからデータをひっぱってきて、実行ファイルのあるフォルダにnode名.csvで吐き出されます。
ちなみに、データが1件も無い場合、0byteのファイルになります。