Indexed Database API について

Indexed Database API(以下、indexedDB)について、これまで追いかけてきた情報をとりまとめたので公開します。

indexedDBは当初は仕様が固まっておらず、サンプルコードも当然のように動かなかったので(今も動きませんが…)、検証するにはかなりハードな状況でした。最近になってどうにか動くようになってきたので、@komasshu さんと色々やり取りしながら一通りの動作を確認しました。

現時点で利用できるブラウザは Chrome 9 以降 または Firefox 4 beta 8 以降となります。まだまだ仕様は動いていますので、検証の際は、なるべく最新の開発版を使うことをおすすめします。本エントリーでは、Chrome 9 beta 、Firefox 4 beta 8 にて検証します。また、資料は、2011年1月20日時点の W3C Editor's Draft を参照しています。


1. Indexed Database API とは
indexedDBは、ブラウザが持つ NoSQL ベースのデータベースです。オリジン(Origin*1)ごとに固有の領域を持ち、複数のデータベースを保持することができます。


2. 基本的な使い方
(1) データベースの作成(接続)
現時点では indexedDB(IDBFactory)にベンダープリフィックスを付けてアクセスします。Chrome であれば、webkitIndexedDB。Firefoxであれば moz_indexedDB(beta8)、または mozIndexedDB(beta9)です。


サンプルコード1

//Chrome、Firefoxのベンダープリフィックス対応
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.moz_indexedDB;

//データベース作成(接続)
var db = null;
var req = indexedDB.open("library");

//成功時コールバック
req.onsuccess = function(evt) {
    db = evt.result;
};

//失敗時コールバック
req.onerror = function(err) {
    alert(err.code + ":" + err.message);
};

データベース操作の結果は、リクエスト(IDBRequest)のコールバックに登録することになります。


(2) オブジェクトストア・インデックスの作成
オブジェクトストアは、データを格納するための入れ物です。RDBMSのテーブルに相当します。オブジェクトストア、及びインデックスの作成と削除はデータベースのバージョン変更時にしか行うことができませんので、注意が必要です。


サンプルコード2

//DBのバージョン変更
var verReq = db.setVersion("1.0");
verReq.onsuccess = function(evt) {

    //オブジェクトストア作成(in-line key として isbn を指定)
    var store = db.createObjectStore("books", "isbn", false);

    //Chromeの場合、以下のように記載
    //var store = db.createObjectStore("books", {"keyPath": "isbn"}, false);

    //インデックス作成
    store.createIndex("name", false);
};

オブジェクトストアには、各データを一意に識別するキーが必要です。キーは、createObjectStore の第2引数で指定します。キーには、2種類あり、キーを指定した場合の in-line key 、省略した場合の out-of-line key があります。キーの種類、及び第3引数の autoIncrement の値でデータ追加時の動作が異なります。


・in-line key
データ内のプロパティをキーとします。データ追加時には、該当プロパティに値が存在する必要があります。
autoIncrement が true の場合、該当プロパティは存在しなくても問題ありません。


・out-of-line key
データの外にキーを持ちます。データ追加時には、別途キーを渡す必要があります。
autoIncrement が true の場合、別途キーを渡す必要はありません。


(3) データの追加・削除
オブジェクトストアに格納できるデータは、その名の通りオブジェクト(structured clone*2)です。データはトランザクションを経由して操作します。


サンプルコード3

//Chromeのベンダープリフィックス対応
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

//データ作成
var book = {
    name: "徹底解説 HTML5 APIガイドブック コミュニケーション系API編",
    description: "AjaxやHTTPで高速なリアルタイム通信アプリケーション開発を可能にする新APIの仕様と使い方を詳細に解説。",
    author: "小松 健作",
    isbn: "4798028215"
};

//トランザクションの開始とオブジェクトストアの取得
var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books");

//データ追加
store.add(book);    //putでも同様の結果が得られる


//データ削除
store.delete("4798028215");

オブジェクトストア作成時に、isbn を in-line key に指定しているので、データ作成時に値をセットした isbn が key になります。ただし Chrome では、まだ in-line key の指定が無視されるので、コメントのように記述しないと動作しません。createObjectStoreに第2引数に{"keyPath": "isbn"}のように指定すると動作するようです。

有効なキーは、string、date、long、float です。null 及び floatの Inifinite は有効なキーですが、NaN は無効です。また、undefined は許可されていません。


(4) データの検索・変更
データの検索方法には、1件だけ取得する get と 複数取得するための openCursor があり、データを変更したい場合、openCursor で取得してから行います。


サンプルコード4 〜 データの検索

//トランザクションの開始とオブジェクトストアの取得
var store = db.transaction().objectStore("books");

//Firefox4の場合、transactionの引数の省略ができないのでnullを入れておく
//var store = db.transaction(null).objectStore("books");

//get
var req = store.get("4798028215");
req.onsuccess = function(evt) {
    var value = evt.result;
    console.log(value);
};

//openCursor、インデックス経由で取得
var req = store.index("name").openCursor("徹底解説 HTML5 APIガイドブック コミュニケーション系API編");
req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {
        console.log(cursor.value);

        //Firefox4の場合、index経由だとvalueにkeyが入っているので再度検索
        //var getReq = store.get(key).onsuccess = function(getEvt){
        //    console.log(getEvt.result);
        //};
        cursor.continue();
    }
};

get の結果に複数のデータがある場合は、最初の1件のみ取得できます。また、インデックスを利用する場合も get 、openCorsor 等、同様のメソッドがあるので同じ方法で取得することができます。


サンプルコード5 〜 データの変更

//Chromeのベンダープリフィックス対応
var IDBKeyRange= window.IDBKeyRange|| window.webkitIDBKeyRange;

//トランザクションの開始とオブジェクトストアの取得(READ_WRITE)
var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books");

//キーを範囲指定するにはIDBKeyRangeを使う。
var req = store.openCursor(IDBKeyRange.lowerBound("4844329278", false));

req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {
        //description変更、changedフラグ追加
        var book = cursor.value;
        book.description = "updated";        
        book.changed = true;

        //データ変更反映
        cursor.update(book);
        cursor.continue();
    }
};

データの変更を行う場合は、トランザクションを READ_WRITE で開始します。カーソルを使ってデータを取得後、変更し、同じくカーソルの update メソッドを介して変更を反映します。プロパティの追加なども問題なくできます。


(5) その他 Tips
・データのソート
オブジェクトストア内のデータは、キーの昇順で格納されています。そのため、昇順で取得する必要があれば、そのまま取得します。降順で取得したければ、openCursor 時の第2引数で、IDBCursor.PREV を指定します。インデックスでも同様です。

//Chromeでの実行例
var req = db.transaction().objectStore("books").index("name").getCursor("", webkitIDBCursor.PREV);
req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {

        //降順にデータ取得
        console.log(cursor.value);
        cursor.continue();
    }
};

応用として、最少値、最大値を取得したい場合は、対象のプロパティにインデックスを作成して、最初の1件を取得すれば求められます。

キーの順序は、string、date、longとfloatとなっており、longとfloatはその数値で比較されます。


トランザクションのコミットとロールバック
トランザクションのコミットは、別のトランザクションが開始されるか、トランザクションが発生したメソッドが終了すれば、暗黙的にコミットされます。ロールバックはエラーやタイムアウトが発生した場合には自動的にロールバックされますが、明示的に指定したい場合は、abort を使います。

//トランザクション開始
var tx = db.transaction([], IDBTransaction.READ_WRITE);

/*
 複数のデータ操作
*/

//トランザクションの中断とロールバック
tx.abort();


データベースのバージョン変更時は、リクエストの result に入っているトランザクションを使ってロールバックします。

//トランザクション開始
var req = db.setVersion("1.1");
req.onsuccess = function(evt) {
    var tx = evt.result;

    /*
     スキーマの変更など
     */

    //トランザクションの中断とロールバック
    tx.abort();
};


3. 簡易API一覧
IDBFactory

メソッド・プロパティ 説明 コメント
open(name) nameで指定されたDBを作成する。
既に存在していればDBに接続する。
以前あった第2引数のdescriptionは
なくなりました
deleteDatabase(name) nameで指定されたDBを削除する。 - - 未実装

IDBRequest

メソッド・プロパティ 説明 コメント
onsuccess リクエスト成功時コールバック
onerror リクエスト失敗時コールバック
readyState リクエストの状態
LOADING: 実行中
DONE: 完了

IDBDatabase

メソッド・プロパティ 説明 コメント
name DB名
objectStoreNames DBに格納されている全オブジェクトストアの名前
version DBのバージョン
close() DB接続を閉じる。 -
createObjectStore(name, keyPath, autoIncrement) nameで指定されたオブジェクトストアを作成する。 ChromekeyPath を{"keyPath": "keyPath"}のようにオブジェクトで渡します。
deleteObjectStore(name) nameで指定されたオブジェクトストアを削除する。
setVersion(version) DBバージョンを更新する。 以下のメソッドは、バージョン更新のコールバックでしか実行できない。
createObjectStore
deleteObjectStore
createIndex
deleteIndex
transaction(storeNames, mode, timeout) storeNamesで指定されたオブジェクトストア(デフォルト:全オブジェクトストア)を対象としてトランザクションを開始する。
modeトランザクションの種類を指定する。
READ_ONLY: 読込専用
READ_WRITE: 書込可能
VERSION_CHANGE: バージョン更新
(デフォルト:READ_ONLY)
timeout
ミリ秒単位(デフォルト:ブラウザ固有)
引数はすべてオプションだが、Firefox は transaction() のような記述ができない。ChromeFirefoxともにtimeoutは無視される。

IDBObjectStore

メソッド・プロパティ 説明 コメント
name オブジェクトストア名
keyPath キーのプロパティ名
indexNames オブジェクトストアに設定されたインデックス名
put(value, key) オブジェクトストアにデータを追加する。keyは、オブジェクトストア作成時に、キーとキーの自動生成を指定しない場合に必須となる。
add(value, key) putと同様。
delete(key) keyリテラルのキー値)のデータを削除する。
get(key) keyリテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 ChromeはIDBKeyRangeでの指定ができない。
clear() すべてのデータを削除する。keyは -
openCursor(range, direction) rangeリテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor)
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
rangeについて、ChromeはIDBKeyRangeでの指定ができない。逆にFirefoxでは、リテラルのキー値での指定ができない。
createIndex(name, keyPath, unique) keyPathで指定したプロパティを対象とするインデックスを作成する。uniqueで一意性を指定する。
index(name) インデックスを取得する。
deleteIndex(indexName) インデックスを削除する。

IDBIndex

メソッド・プロパティ 説明 コメント
name インデックス名
storeName インデックスの対象とするオブジェクトストア名
keyPath インデックスの対象とするプロパティ名
unique 一意であるか
get(key) keyリテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。
getKey(key) getと同様だが、取得対象がキーとなる。
openCursor(range, direction) rangeリテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor)
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。
openKeyCursor(range, direction) openCursorと同様だが、取得対象がキーとなる。

IDBCursor

メソッド・プロパティ 説明 コメント
direction 走査方向
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
key カーソルを開くときに使用したキー
value カーソルの現ポジションのデータ Firefoxの場合、インデックス経由では、キーとなる。
continue(key) カーソルの現ポジションを次の走査方向へ進める。イベントが再発生し、カーソルの現ポジションが更新される。keyリテラルのキー値または、IDBKeyRange)を指定した場合、該当するキーまでカーソルを進める。 ChromeFirefoxともにkeyの指定は受け付けない。
delete() カーソルの現ポジションのデータを削除する。
update(value) カーソルの現ポジションのデータをvalueで更新する。 -

IDBTransaction

メソッド・プロパティ 説明 コメント
mode トランザクションの種類
READ_ONLY: 読込専用
READ_WRITE: 書込可能
VERSION_CHANGE: バージョン更新
以前あった、SNAPSHOT_READはなくなりました。
db 接続しているデータベース。
objectStore(name) トランザクションの対象となるオブジェクトストアの中からnameで指定されたオブジェクトストアを取得する。
abort() トランザクションを中断し、ロールバックする。
onabort トランザクション中断時のコールバック
oncomplete トランザクション完了時のコールバック
ontimeout タイムアウト時のコールバック - -


4. おわりに
indexDB は、まだまだ実装途中です。バージョンが変われば、そのままのコードでは動かなくなる可能性がかなり高いので、本格的なアプリを作れるようになるには、もう少し時間がかかるでしょう。それでも是非、試してみたいという方は、本エントリーを参考にして頂ければ幸いです。もちろん、(自称)html5-developers-jp indexedDB担当としては、今後もメンテナンスしていきたいと思いますので、ご指摘、ご質問等あればコメントでもMLでもツイッターでもお気軽にどうぞ!


まだまだ書くこともたくさんありますが(手元のサンプルコードもリファクタリングして公開しないといけないですし…)、まずはこのぐらいで。
@komasshu さんのこてさきAjax indexedDB を触ってみたも合わせてどうぞ

*1:origin … プロトコルドメイン、ポートからなる、Webサイトを指し示す固有の情報。URLとは似て非なるもの。

*2:structured clone … オブジェクトから、関数と循環参照などを排除したもの。単純なリテラル、Date、Object、Arrayを含む。