IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Google Gears の機能のおさらいと HTML5「Client-side DB」の相互運用を考える。Client-side DB に未来はあるか?

さきほど Client-side database storage について書きましたが

そこで、

Client-side database storage は今は Safari からしか使うことができません
しかし、 Google Gears を使ってすべてのブラウザに実装することは可能だと思います。(すでにある?)
その辺りも少し調べてみたいです。

Safari 3.1 に実装された「Client-side database storage (SQL API)」とは何か? - IT戦記

と書いてから、少しだけ Google Gears が持っているクライアントサイドデータベースについて調べてみました。

Google Gears とは

まず、 Google Gears について少し触れておきます。
Google Gears はブラウザの機能を拡張するためのもので、以下の機能を提供します。

  • オフライン時にアクセスできるようにローカルにサーバのリソースをキャッシュする
  • クライアントサイドデータベースを持つ
  • スレッド機能

このような機能を提供しています。
また、 Google GearsFlash Player のようにユーザにインストールさせる必要があります。

Google Gears はオフラインアプリケーションのためのもの?

Google Gears はオフライン時にウェブアプリケーションを使うためのもの」と言われていたりします。
しかし、今回改めて Google Gears の機能を調べてみて、この認識は間違っているのではないかと思い直しました。

Google Gears は普通のウェブアプリケーションに対してもかなり有効

今まではソーシャルでないウェブアプリケーションであってもサーバがストレージを持たなければなりませんでした。
Google Gears を使うことでローカルのデータはローカルに保存する、という当たり前のことができるようになります。
さらにいうと、サーバで一切動的な処理をしない html と js だけで完結したウェブアプリケーションを作ることができるようになります。
とくに、帯域の限られているモバイルの分野で真価を発揮しそうな気がしています。
(でも、正直いうとマルチでバイスの時代なのでストレージはサーバにあったほうが便利だとは思うのですが、帯域の問題、容量の問題があるので、過渡的なものとして使えるのではないかというのが正直なところだったりします。)

Google Gears のクライアントサイドデータベースの機能

と、いろいろ書きましたが、そんなことはどうでもいいですね。
とりあえず、 Google Gears のクライアントサイドデータベース機能をみていきましょう。

データベースを作る、開く

var db = google.gears.factory.create('beta.database');
db.open('database-test');

SQL を実行する

以下のような感じです

取得系
var rs = db.execute('SELECT * FROM hoge');

// result set を next 関数と isValidRow 関数で回す
var rows = [];
while(rs.isValidRow()) {
  var row = {};
  for (var i = 0; i < rs.fieldCount(); i ++) {
    row[rs.fieldName(i)] = rs.field(i);
  }
  rs.next();
}
rs.close();

rs.fieldByName というのもある。

更新系
// プレースホルダはこんな感じ
db.execute('INSERT INTO hoge VALUES(NULL, ?, ?)', ['hoge', 'hogehoge']);

// database で last insert id を取る
var id = db.lastInsertRowId;

rows affected は取れないっぽい?

データベースを閉じる

db.close();

ドキュメントには not require と書いてある。

トランザクション

db.execute('BEGIN');
try {
  // ここに処理を書く
}
catch(e) {
  db.execute('ROLLBACK');
  throw e;
}
db.execute('COMMIT');

HTML5 と比較してみる

コードの比較

以下に、同じことをする HTML5Google Gears を書いてみた

// HTM5
var db = openDatabase()

// Gears
var db = google.gears.factory.create('beta.database');
db.open('database-test');

// ----

// HTML5
db.transaction(function(tx) {
  tx.executeSql('SELECT * FROM hoge', [], function(tx, rs) {
    for (var i = 0; i < rs.rows.length; i ++) {
      var row = rs.rows.item(i);
      for (var n in row) {
        row[n]
      }
    }
  });
});

// Gears
db.execute('BEGIN');
try {
  try {
    var rs = db.execute('SELECT * FROM hoge');
    var rows = [];
    while(rs.isValidRow()) {
      for (var i = 0; i < rs.fieldCount(); i ++) {
        rs.field(i);
      }
      rs.next();
    }
  }
  finally {
    rs.close();
  }
}
catch(e) {
  db.execute('ROLLBACK');
  throw e;
}
db.execute('COMMIT');

// ----

// HTML5
db.transaction(function(tx) {
  tx.executeSql('INSERT INTO hoge VALUES(NULL, ?, ?)', ['hoge', 'hogehoge'], function(tx, rs) {
    var id = rs.insertId;
  });
});

// Gears
db.execute('BEGIN');
try {
  db.execute('INSERT INTO hoge VALUES(NULL, ?, ?)', ['hoge', 'hogehoge']);
  var id = db.lastInsertRowId;
}
catch(e) {
  db.execute('ROLLBACK');
  throw e;
}
db.execute('COMMIT');

うん、ほとんど同じことができる。

セキュリティポリシーの比較

Google Gears のドキュメントを見ると、ここにも「same origin security policy」と書かれている。
つまり、 HTML5 の「Client-side database storage」と同じポリシーであることが分かる。
ということは、データベースの有効範囲も同じということである。

どうやら、

相互運用は可能そうである。

というわけで

Google GearsHTML5 ラッパーみたいなものがないか調べてみた。

すごくいいのがあった

http://attic.glazkov.com/player/
しかし、ここのページはものすごく不親切で、ソースへのリンクも使い方も何もないです。
で、いろいろ調べてソースを発見しました。
ソース
ソースを読んだ感想ですが、すごくよくできていると思います。
しかし、これを使うには結構めんどくさい手順が必要そうだったので、また今度エントリーにします。

さらに調べてみると

なんと Google Gears 本家でも HTML5 のほうの API に合わせようという動きがあるようです!
Google Code Archive - Long-term storage for Google Code Project Hosting.
ということは、待っていればそのうち Google Gears をインストールするだけで HTML5 Client-side database storage が使えるようになる!
ということですね!すばらしい!

まとめ

とにかく、現状ラッパーをかませば Google GearsHTML5 Client-side database storage が使えて、
待っていれば Google Gears 本家の正式な API として HTML5 Client-side database storage 実装されそう。。ということである。
これは、かなり未来があるんじゃないか。という気がしてきました。
みなさんも Client-side DB を触ってみてはいかがでしょうか?

Safari 3.1 に実装された「Client-side database storage (SQL API)」とは何か?

はじめに

Safari 3.1 には Client-side database storage (SQL API とも呼ばれています。)という新しい仕様が実装されました。
というわけで、この新しい API について色々調べたことを簡単にまとめておきます。

Client-side database storage が使えるブラウザ

2008 年 03 月 27 日現在では、 Safari 系のブラウザのみです。

Client-side database storage とは

Selectors API とは HTML5 で定義された仕様です。詳細に関してはこちらをどうぞ。
簡単に説明すると JavaScript 内でリレーショナルデータベースを使えるということです。
もっと簡単にイメージするために、実際のコードを示すとこんな感じです。

var db = openDatabase('mydb', '1.0');

// トランザクションの開始
db.transaction(function(tx) {
  // テーブルを作る
  tx.executeSql('CREATE TABLE IF NOT EXIST link (id INTEGER PRIMARY KEY, href TEXT, title TEXT)');

  // リンクを抽出
  var xresult = document.evaluate('//a', document, null, 7, null);
  for (var i = 0; i < xresult.snapshotLength; i ++) {
    var elmLink = xresult.snapshotItem(i);

    // テーブルに挿入
    tx.executeSql('INSERT INTO link VALUES(NULL, ?, ?)', [elmLink.href, elmLink.textContent])
  }
});

このように JavaScript から SQL を実行してデータベースを使うことができるのです。
簡単ですね!

データベースの保存場所と保存期間と共有範囲

保存場所

データは、クライアントサイドに保存されます。
サーバにあるデータベースとは一切関係ないので誤解しないように

保存期間

このデータベース内の情報はリロードしてもブラウザを再起動しても失われません。
永続 Cookie のような感じです。

共有範囲

このデータベースは、同じドメイン*1で有効です。
つまり、

  • http://www.example.com/hoge/hoge
    • http://www.example.com/hoge/fuga は同じデータベースを見ています。
    • http://www.example.com/fuga/fuga は同じデータベースを見ています。
    • http://www.example.com:80/ は同じデータベースを見ています。
    • http://example.com/hoge/hoge は違うデータベースを見ています。
    • http://sub.example.com/hoge/hoge は違うデータベースを見ています。
    • https://www.example.com/hoge/hoge は違うデータベースを見ています。
    • http://www.example.org/hoge/hoge は違うデータベースを見ています。

という感じになります。
いわゆる「Same origin policy」というやつですね。

関数の具体的な使い方

Client-side database storage を使うには以下の三つの種類の関数を使います。

  • openDatabase
    • データベースを作る、開く
  • transaction, changeVersion
  • executeSql
    • SQL を実行する

では、ひとつひとつ見ていきましょう

openDatabase

openDatabase 関数はデータベースを開く関数で、データベースがない場合はデータベースを作ります。(あってもなくても開くってことです。)
この関数は、グローバル関数として使うことができます。

// 以下のように
//     第一引数にデータベースの名前、
//     第二引数にデータベースのバージョン名(自分でかってに決める、適当でいい)
// を与えると、データベースを開いて、
//     Database オブジェクト
// を返します
var db = openDatabase('testdb', '1.0');

// 既に testdb というデータベースが(同じドメイン内のデータベースとして)存在している場合で、
// バージョンが違う場合は例外(エラー)が投げられます。
db = openDatabase('testdb', '2.0'); // error!

また、データベースの容量はブラウザ側で制限されている可能性があります(実装依存)。
その場合は以下のように書きます。

// 第三引数に何かしらのメッセージ
// 第四引数に使いたい容量(バイト数)
var db = openDatabase('testdb', '1.0', '100MB 使わせてください><', 100000000);
transaction, changeVersion

transaction 関数は openDatabase 関数で取得した Database オブジェクトの関数(メソッド)です。
トランザクションを開くことができます。

// 第一引数に関数を渡します。
// すると、
//     トランザクションが開始され、
//     関数が実行され
//     関数が終了するとトランザクションが終了します。
db.transaction(function(tx) { // 関数の第一引数に SQLTransaction オブジェクトが渡される

  // この関数内で、
  // SQLTransaction オブジェクトを使ってデータベースを操作する
  tx.executeSql('INSERT INTO hoge VALUES(NULL, "hoge")');

});

ここで、注意しなければならないことがあります。
それは、 transaction 関数に渡された関数は非同期に呼び出されるということです。
たとえば、

var data = 'hoge';

db.transaction(function(tx) {
  // ここで data は 'fuga' になる
  tx.executeSql('INSERT INTO hoge VALUES(NULL, ?)', [data]);
});

// ここは transaction より前に実行される
data = 'fuga';

イメージ的には setTimeout に渡された関数の挙動と同じですね。
では、transaction が終わった後に処理をしたい場合はどうしたらいいでしょうか。
そんなときは、 transaction 関数に第二引数、第三引数として関数を渡します。
するとトランザクション終了時にそれらの関数を呼び出してくれます。

  • 第二引数は、エラーが発生して、ロールバックした場合に呼び出されます
  • 第三引数は、正常にトランザクションがコミットされた場合に呼び出されます。

以下のような感じです。

tx.transaction(
  function(tx) {/* ...略 */},
  function(error) {
    alert(error.message + 'という訳でロールバックしたよ')
  },
  function() {
    alert('正常に終了したよ'); 
  }
);

また、 transaction 関数の特殊版として changeVersion という関数があります。
これも、 Database オブジェクトの関数(メソッド)です。
データベースのバージョンを変更するために使います。
バージョンとは、 openDatabase の第二引数で指定したものです。

  • 第一引数に、古いバージョン(openDatabase の時に指定したもの)
  • 第二引数に、新しいバージョン

を指定します。
第三引数以降は、 transaction 関数の第一引数以降と同じです。

tx.changeVersion('1.0', '2.0',
  function(tx) {
    // テーブルを追加するなどする
  }, 
  function(error) {
    // エラーが発生した
  },
  function() {
    // 正常終了
  }
);

なぜ、バージョン変更関数とトランザクションが関係あるかというと、バージョンが変わるとその際にいろいろデータの形をかえたりする必要があるためです。
バージョンを変更すると openDatabase 関数の第二引数に与えるべき値も変わります。
ただ、僕が Safari 3.1 で、この関数を試したところ、バージョンを変更したデータベースがオープンできなくなってしまいました。ちょっと調べています(バグ?)

executeSql

executeSql 関数は transaction 関数に渡した関数に渡される SQLTransaction オブジェクトの関数(メソッド)です。
第一引数に SQL を渡すことで SQL を実行することができます。
こんな感じです。

db.transaction(function(tx) {
  tx.executeSql('CREATE TABLE IF NOT EXIST bbs (id INTEGER PRIMARY, title TEXT, content TEXT)');
});

トランザクションの外では実行できません。

var globalTx;
db.transaction(
  function(tx) {
    globalTx = tx;
  },
  function() {},
  function() {
    // ここは、トランザクション終了後に実行されるため
    // 以下は、エラー
    globalTx.executeSql('CREATE TABLE IF NOT EXIST bbs (id INTEGER PRIMARY, title TEXT, content TEXT)');
  }
);

また、プレースホルダ「?」を使うには、第二引数に配列を渡します。

db.transaction(function(tx) {
  tx.executeSql('INSERT INTO bbs VALUES(NULL, ?, ?)', ['hoge', 'hogehoge']);
});

また、結果を使うような場合は、第三引数に関数を渡します。
そうすると、 SQL 実行後に結果を表す SQLResultSet オブジェクトがその関数に渡されます。

db.transaction(function(tx) {
  tx.executeSql('SELECT * FROM bbs', [], function(tx, rs) { // rs が SQLResultSet オブジェクト。 tx は同じ。

    // rs.rows に行のデータが入っている
    for (var i = 0; i < rs.rows.length; i++) {
      var row = rs.rows.item(i);

      // row はカラム名でハッシュになっている
      var title = row.title; // または row['title']
      var content = row.content; // row['content']
    }
  });
});

ここで注意しなければならないのは、このコールバック関数は非同期に呼び出されるということです。
つまり、以下のようなことはできません。

db.transaction(function(tx) {
  var bbsid;
  tx.executeSql('INSERT INTO bbs VALUES(NULL, ?, ?)', ['hoge', 'hogehoge'], function(tx, rs) {
    bbsid = rs.insertId; // insertId には行に挿入したときに自動で決まった id が入っている
  });
  tx.executeSql('INSERT INTO bbs_user_map VALUES(NULL, ?, ?)', [bbsid, userid]); // この時点で bbsid は決まらない
});

なので、以下のようにすれば大丈夫です。

db.transaction(function(tx) {
  var bbsid;
  tx.executeSql('INSERT INTO bbs VALUES(NULL, ?, ?)', ['hoge', 'hogehoge'], function(tx, rs) {
    bbsid = rs.insertId;
    tx.executeSql('INSERT INTO bbs_user_map VALUES(NULL, ?, ?)', [bbsid, userid]); // この時点で bbsid は決まっている
  });
});

また、 SQL でエラーが発生した場合は自動でロールバックされるのですが、エラーを補足してロールバックさせないようにするには、第四引数に関数を渡します。

db.transaction(function(tx) {
  tx.executeSql('INSERT INTO bbs VALUES(NULL, ?, ?)', ['hoge', 'hogehoge'],
    function(tx, rs) {
    },
    function(tx, error) {
      alert(error.message + 'だけどロールバックしない');
    }
  );
});

こんな感じで SQL を実行します。
便利ですね!

Safari での実装

使い方がわかったところで Safari の実装について少し見てみましょう

データベースはどこに保存される?

Mac でしか確認していないのですがデータベースは ~/Library/Safari/Databases ディレクトリに以下のように保存されました。

Databases
|-- Databases.db
|-- http_amachang.art-code.org_0
|   `-- 0000000000000001.db
`-- http_hogehoge.com_0
    |-- 0000000000000006.db
    `-- 0000000000000006.db-journal
エンジンは?

Sqlite 3 のようです。sqlite3 コマンドで中身を見ることができました。

Web Inspector との連携

コンテンツでデータベースをオープンした状態で Web Inspector を開くと以下のようにデータベースを調べることができます。

この画面上からも、直接 SQL が打てるので便利です。

Google Gears

Client-side database storage は今は Safari からしか使うことができません
しかし、 Google Gears を使ってすべてのブラウザに実装することは可能だと思います。(すでにある?)
その辺りも少し調べてみたいです。

まとめ

という訳で、少し突っ込んで Client-side database storage というものを調べてみました。
Client-side database storage かなりおもしろかったです。
モバイルブラウザ上の JavaScript コンテンツ (iPhone 用のアプリ) や、 UserScripit などから利用するととても便利そうだなと思いました。

*1:ここでは単にドメインと書いたが、正確にはプロトコル(スキーム)、ホスト名、ポート番号が等しい範囲内。