A Day in the Life

2009-10-07

JavaScript 版の SQL::Abstract ぽいのを作った

WebDatabase で SQLite 使うんで、JavaScript で SQL 生成するのに作った。Ruby の ActiveRecord の SQL 生成方法より、SQL::Abstract のほうがしっくりくるんだよなー。S2JDBC のような Fluent Interface ぽいのも作ってみたけどしっくりこなく、結局 SQL::Abstract が単純明快で個人的にはわかりやすいんだよな。

WebDatabase の executeSql は引数に SQL 文と、プレースホルダーに bind する配列をとるので、Perl の SQL::Abstract とほぼ同じ引数、戻り値になっている。

where 句作る

var sql = new SQLAbstract;
 var res = sql.where(['user = ? AND status = ?', 'nadeko', 'completed']);
 // res[0] #=> 'WHERE user = ? AND status = ?'
 // res[1] #=> ['nadeko', 'completed']

名前つきプレースホルダーも使える

res = sql.where(['user = :user AND status = :status', {
        user: 'nadeko',
        status: 'completed'
 }]);
 // res[0] #=> 'WHERE user = ? AND status = ?'
 // res[1] #=> ['nadeko', 'completed']

Hash を引数にとれる

res = sql.where({
     user: 'nadeko',
     status: 'completed'
 });
 // res[0] #=> 'WHERE user = ? AND status = ?'
 // res[1] #=> ['nadeko', 'completed']

select。limit/offset/group などもかける

res = sql.select('table', '*', {
     user: null,
     status: 'completed'
 }, {
     limit: 20,
     offset: 10
 });
 // res[0] => 'select * from table WHERE user IS NULL AND status = ? LIMIT ? OFFSET ?'
 // res[1] => ['completed', 20, 10]

insert/update/delete

res = sql.insert('table', {
     user: 'nadeko',
     status: 'completed'
 });
 // res[0] => 'insert into table (user, status) values (?, ?)'
 // res[1] =>  ['nadeko', 'completed']

 res = sql.update('table', {
     user: 'nadeko',
     status: 'completed'
 });
 // res[0] => 'update table3 SET user = ?, status = ?'
 // res[1] =>  ['nadeko', 'completed']

 res = sql.update('table', {
     user: 'nadeko',
     status: 'completed'
 });
 // res[0] => 'update table3 SET user = ?, status = ?'
 // res[1] =>  ['nadeko', 'completed']

 res = sql.deleteSql('table', {
    id: 3
 });
 // res[0] => 'delete from table WHERE id = ?'
 // res[1] =>  [3]

その他いろいろは test/test.js をみてください。テストは、Safari4 などの openDatabase の実装があるなら、それを使って Syntax チェックもします。

Deferred.prototype.e

JSDeferred 使っていて、非同期時の例外をきちんと error() でキャッチするのではなく、ログに表示したいとき

Deferred.prototype.e = function() {
    this.error(function(e) {
        console.log('catch deferred error: ' + e);
        return e;
    });
};

とか定義しておくと

d.next(foo).e();

とするだけで便利、と思ったけどいっそ以下のようにしちゃって、問答無用で表示の方が開発時は便利だな。

Deferred.prototype._fire = function (okng, value) {
    var next = "ok";
    try {
        value = this.callback[okng].call(this, value);
    } catch (e) {
        next  = "ng";
        if (Deferred.debug) console.log('DeferredError: ' + e);
        value = e;
    }
    if (value instanceof Deferred) {
        value._next = this._next;
    } else {
        if (this._next) this._next._fire(next, value);
    }
    return this;
}

Deferred.debug = true;