Indexed DB 適当メモ

Top » 2015-09-26

JavaScriptのデータベースAPI。TypeScriptの書き方をしている部分がある。内容は間違っていることがある。全てのブラウザが仕様書通りに動作するとは限らない。

IDBFactory

データベースは複数生成できる。HTMLのあるオリジン(ドメイン名)と異なるオリジンに作成されたデータベースへはアクセス出来ない。

indexedDB.open()とバージョン

データベース自体を初めて生成するときは次のようにするのがオススメ

ver DB_NAME = "db1", OS_NAME = "store1";
var db;//IDBDatabaseインスタンスをキャッシュする
var req = indexedDB.open(DB_NAME, 2);//IDBOpenDBRequestが入る
req.onupgradeneeded = function(ev) {
    var db = this.result;
    if(ev.oldVersion <= 1){
        var store = db.createObjectStore(OS_NAME,{autoIncrement:true});
        store.createIndex( /**/ );
        store.createIndex( /**/ );
        store.add({ /**/ });
        store.add({ /**/ });
    }
};
req.onsuccess = function() {
    //成功
    db = this.result;
};
req.onerror = function() {
    //エラー
};

ポイント

もうずっと使わない時はdb.close()を呼んで接続を閉じる。

データベース IDBDatabase

IDBOpenDBRequest

onupgradeneeded: (ev: IDBVersionChangeEvent) => any;
イベントハンドラ。resultにIDBDatabaseインスタンスが格納される
onblocked: (ev: Event) => any;
イベントハンドラ。

IDBDatabase 主な機能

transaction(storeNames: string | string[], mode?: string): IDBTransaction;
トランザクション開始
  • storeNames 読み書きしたい複数のオブジェクトストア名。指定する個数は少ないほうがパフォーマンスが良いらしい。
  • mode "readwrite"か"readonly" 省略すると"readonly"と同じ
objectStoreNames: DOMStringList;

このデータベースに作成済みのオブジェクトストア名の一覧

  • .objectStoreNames.length 個数
  • .objectStoreNames[i] i番目の名前
  • .objectStoreNames.contains("name") "name"を含んでいるか判定
createObjectStore(name: string, optionalParameters?: IDBObjectStoreParameters): IDBObjectStore;
このデータベースにオブジェクトストアを生成する"versionchange"トランザクション時にしか実行できない。
deleteObjectStore(name: string): void;
このデータベースからオブジェクトストアを1つ削除する"versionchange"トランザクション時にしか実行できない。
close(): void;
データベース接続を閉じて、他のインスタンスがバージョンアップできるようにする。
onabort: (ev: Event) => any;
イベントハンドラ。
onerror: (ev: ErrorEvent) => any;
イベントハンドラ。

トランザクション IDBTransaction

トランザクションに対してリクエストを追加することでデータベースを操作する。トランザクションに追加されたリクエストは非同期で追加順に処理される。リクエストが全て成功するとコミットされる。リクエストが1個失敗するとロールバックされる。

トランザクションは3つのモードがある

例 store1だけ読み書きできるようにする

var tx = db.transaction("store1","readwrite");
var store1 = tx.objectStore("store1");

例 全てのオブジェクトストアを読めるようにする

var tx = db.transaction(db.objectStoreNames);
var store1 = tx.objectStore("store1");
var store2 = tx.objectStore("store2");

IDBTransaction 主な機能

objectStore(name:string):IDBObjectStore;
オブジェクトストアを一つ取得する
abort():void;
ロールバックしてonabortを呼ぶ
oncomplete: (ev: Event) => any;
イベントハンドラ。全てのリクエストが成功した後に1回呼ばれる
onabort: (ev: any) => any;
イベントハンドラ。少なくとも一つのリクエストでエラーが起きてロールバックするとき1回呼ばれる
onerror: (ev: ErrorEvent) => any;
イベントハンドラ。

リクエスト IDBRequest

result: any;

onsuccess後にリクエストよって様々な種類の結果が格納される

  • IDBObjectStore
    • .add,.put : プライマリキー
    • .delete,.clear : undefined
    • .count : 件数
    • .get : レコード か undefined
    • .openCursor : IDBCursorWithValue か null
  • IDBIndex
    • .count : 件数
    • .get : レコード か undefined
    • .getKey : プライマリキー か undefined
    • .openCursor : IDBCursorWithValue か null
    • .openKeyCursor : IDBCursor か null
  • IDBCursor
    • .update : プライマリキー
    • .delete : undefined
  • IDBFactory
    • .open : IDBDatabase
    • .deleteDatabase : undefined
onsuccess: (ev: Event) => any
イベントハンドラ。リクエストが成功しresultに結果が格納される
onerror: (ev: ErrorEvent) => any

イベントハンドラ。リクエストが失敗

オブジェクトストア IDBObjectStore

オブジェクトストアは一つのデータベースに対して複数生成できる。

レコードとキー

レコードの事を value とも呼ぶらしい。レコードとする型は次のいずれかに限られる。

ただしキーとするフィールドは次の型に限られる。

プライマリキーがこれらの型でなければトランザクションが失敗する。インデックスキーがこれらの型でなければインデックスに載らない。

キーの大小比較のアルゴリズム

オブジェクトストアとインデックスは、自動でこの規則でキーをソートしてレコードを保持する

indexedDB.cmp(a,b)で試せる。

キーパス keyPath

keyPath:"image.name.length"のようにピリオド区切りで深い階層のオブジェクトのフィールドにアクセスできる。配列内は指定不可。

複数のフィールドを集めて配列にしたものをキーとする機能もある。

keyPath value 結果key
"a" {a:1} 1 キーが配列ではない例
"a" {a:[1,2,3]} [1,2,3] キーが配列である例
["a","b","c"] {a:1,b:2,c:3} [1,2,3] キーが配列である例

オブジェクトの階層だけではなく次のクラス特有のフィールドにもアクセスできる。

オブジェクトストア生成時の5通りの設定

.createObjectStore(), .deleteObjectStore()は"versionchange"トランザクション時にしか実行できない。

interface IDBDatabase {
    createObjectStore(name: string, optionalParameters?: IDBObjectStoreParameters): IDBObjectStore;
}
interface IDBObjectStoreParameters {
    keyPath?: string | string[];
    autoIncrement?: boolean;
}

var store = db.createObjectStore("store1");

out-of-line keysモードで生成する。.add()と.put()の第2引数は必須

store.add("AAA",100);//プライマリキーは100
store.put("BBB",100);//書き換える
//store.add("CCC",100);//不可 add()でキー重複不可

var store = db.createObjectStore("store1",{autoIncrement:true});

out-of-line keysモードで生成する。.add()と.put()の第2引数は省略可能で、省略した場合自動的にプライマリキーに重複しない整数が割り当てられる。

store.add("AAA");//プライマリキーは1になる
store.put("BBB");//プライマリキーは2になる
store.put("CCC",1);//明示も可 "AAA"が"CCC"に書き換わる

var store = db.createObjectStore("store1",{keyPath:"id"});

in-line keysモードで生成する。.add()と.put()の第2引数は指定不可。挿入するレコードにはキーとするフィールドが必須

store.add({id:1,title:"AAA"});//プライマリキーは1
store.put({id:1,title:"BBB"});//書き換える
//store.add({id:1,title:"CCC"});//不可 add()でid重複不可
//store.add({title:"DDD"});//不可 idが必要

var store = db.createObjectStore("store1",{keyPath:"id",autoIncrement:true});

in-line keysモードで生成する。.add()と.put()の第2引数は指定不可。挿入するレコードにはキーとするフィールドは省略可能で、省略した場合自動的にプライマリキーに重複しない整数が割り当てられ、レコードにこのフィールドを持たされる。

store.add({id:1,title:"AAA"});プライマリキーは1
store.put({id:1,title:"BBB"});//書き換える
//store.add({id:1,title:"CCC"});//不可 add()でid重複不可
store.add({title:"DDD"});//{id:2,title:"DDD"}が挿入される

var store = db.createObjectStore("store1",{keyPath:["x","y"]});

in-line keysモードで生成する。.add()と.put()の第2引数は指定不可。複数のフィールドをその順で配列に集めたものがプライマリキーとなる。レコードにはこれらのフィールドが全て必須

store.add({x:2,y:2});//プライマリキーは[2,2]
store.add({x:3,y:4});//プライマリキーは[3,4]
store.add({x:4,y:4});//プライマリキーは[4,4]
//store.add({x:4,y:4});//不可
//store.add({x:5});//不可

IDBObjectStore 主な機能

リクエストを追加するメソッド

put(value: any, key?: any): IDBRequest;
レコードを1つ更新する。指定したプライマリキーがこのオブジェクトストアに既存ならばレコードを上書きし、無ければ挿入する。onsuccess後にresultにプライマリキーが入る。
add(value: any, key?: any): IDBRequest;
レコードを1つ挿入する。指定したプライマリキーがこのオブジェクトストアに既存ならばトランザクションが失敗する。onsuccess後にresultにプライマリキーが入る。
get(key: any): IDBRequest;
レコードを1件を取得する。onsuccess後にresultにレコードかundefinedが入る。
  • 引数がIDBKeyRangeのときは指定範囲のうち最も若い1件。
  • それ以外の時はプライマリキーが一致する1つのレコード。
delete(key: any): IDBRequest;

レコードを0個以上削除する。

  • 引数がIDBKeyRangeのときは指定範囲全て。
  • それ以外の時はプライマリキーが一致する1つのレコード。
clear(): IDBRequest;
このオブジェクトストアの全てのレコードを削除する。autoIncrementのカウンタはリセットされない。
count(key?: any): IDBRequest;
プライマリキーの件数を数える。onsuccess後にresultに件数が入る。
openCursor(range?: any, direction?: string): IDBRequest;
カーソルアクセス。onsuccess後にresultにカーソルが入る
  • 引数を省略すると全て。
  • 引数がIDBKeyRangeのときは指定範囲。
  • それ以外の時は一致。

メソッド

index(name: string): IDBIndex;
インデックスを一つ取得する
indexNames: DOMStringList;

このオブジェクトストアに作成済みのインデックス名一覧

  • .indexNames.length 個数
  • .indexNames[i] i番目の名前
  • .indexNames.contains("name") "name"を含んでいるか判定
createIndex(name: string, keyPath: string, optionalParameters?: IDBIndexParameters): IDBIndex;
このオブジェクトストアにインデックスを作成する。"versionchange"トランザクション時にしか実行できない。
deleteIndex(indexName: string): void;

このオブジェクトストアからインデックスを削除する。"versionchange"トランザクション時にしか実行できない。

インデックス IDBIndex

インデックスは、指定のフィールド名でソートしたレコードの順を持つ機能。例えば次のようなコードを実行すると

var store = db.createObjectStore("store1",{keyPath:"id",autoIncrement:true});
var index = store.createIndex("index1","a");
store.add({a:50});
store.add({a:30});
store.add({a:30});
store.add({a:40});
store.add({a:20});

オブジェクトストアの様子とstore.openCursor(null, ???)での走査順。

primaryKey value "next" "prev"
1 {id:1,a:50} 最初
2 {id:2,a:30}
3 {id:3,a:30}
4 {id:3,a:40}
5 {id:4,a:20} 最初

インデックスの様子とindex.openCursor(null, ???)での走査順。

key primaryKey "next" "prev" "nextunique" "prevunique"
20 5 最初 最初
30 2
30 3 飛び 飛び
40 4
50 1 最初 最初

インデックスは一つのオブジェクトストアに対して複数生成できる。createIndex(), deleteIndex()は"versionchange"トランザクション時にしか実行できない。

interface IDBObjectStore {
    createIndex(indexName: string, keyPath: string | string[], optionalParameters?: IDBIndexParameters): IDBIndex;
}
interface IDBIndexParameters {
    unique?: boolean;
    multiEntry?: boolean;
}

インデックス生成時の6通りの設定。

var index = store.createIndex("index1","name");
var index = store.createIndex("index1","name",{multiEntry:true});
var index = store.createIndex("index1",["x","y"]);
var index = store.createIndex("index1","name",{unique:true});
var index = store.createIndex("index1","name",{unique:true,multiEntry:true});
var index = store.createIndex("index1",["x","y"],{unique:true});

配列keyPath

オブジェクトストアのそれと同様。multiEntry:trueと配列keyPathは併用できない。

multiEntry

multiEntry:trueかつインデックスキーが配列の場合、配列内の全ての要素が個別にインデックス化される。multiEntry:trueと配列keyPathは併用できない。

タグ付けの機能が実現できる。

例えば次のようなコードを実行すると

var store:IDBObjectStore = db.createObjectStore("store1",{keyPath:"id",autoIncrement:true});
var index:IDBIndex = store.createIndex("index1","a",{multiEntry:true});
store.add({a:91});
store.add({a:[52,82]});
store.add({a:[53,82,82]});
store.add({a:[[1,4]]});

オブジェクトストアの様子とstore.openCursor(null, ???)での走査順。

primaryKey value "next" "prev"
1 {id:1,a:91} 最初
2 {id:2,a:[52,82]}
3 {id:3,a:[53,82,82]}
4 {id:4,a:[[1,4]]} 最初

インデックスの様子とindex.openCursor(null, ???)での走査順。

key primaryKey "next" "prev" "nextunique" "prevunique"
52 2 最初 最初
53 3
82 2
82 3 飛び 飛び
91 1
[1,4] 4 最初 最初

一つのレコードが何回も走査される。

unique制約

プライマリキーと同等の制約をインデックスキーに持たせる。unique:trueでインデックス作成済みなら、キーを重複させようとするとトランザクションが失敗する。キーに重複があるのに.createIndex()する場合エラーが起きる。

失敗する例

IDBIndex 主な機能

リクエストを追加するメソッド

openCursor(range?: any, direction?: string): IDBRequest;
openKeyCursor(range?: any, direction?: string): IDBRequest;
カーソルアクセス。onsuccess後にresultにカーソルが入る。.openKeyCursor()はresult.valueがない
get(key: any): IDBRequest;
レコードを1件を取得する。onsuccess後に.resultにレコードが入る。
  • 引数がIDBKeyRangeのときは指定範囲のうち最も若い1件。
  • それ以外の時は一致する最も若い1件。
getKey(key: any): IDBRequest;
レコードを1件を取得する。onsuccess後に.resultにレコードのプライマリキーが入る。
  • 引数がIDBKeyRangeのときは指定範囲のうち最も若い1件。
  • それ以外の時は一致する最も若い1件。
count(key?: any): IDBRequest;

インデックスキーの件数を数える。onsuccess後にresultに件数が入る。

  • 引数を省略すると全て。
  • 引数がIDBKeyRangeのときは指定範囲。
  • それ以外の時は一致。

カーソル IDBCursor,IDBCursorWithValue

オブジェクトストアかインデックスに対してキーの範囲を1つ指定してレコードを順番に複数取り出したい時はカーソルを使う。

interface IDBObjectStore {
    openCursor(range?: any, direction?: string): IDBRequest;
}
interface IDBIndex {
    openCursor(range?: any, direction?: string): IDBRequest;
    openKeyCursor(range?: any, direction?: string): IDBRequest;
}

例 オブジェクトストアの全てのレコードを列挙する

store.openCursor().onsuccess = function(){
    var cursor = this.result;
    if(cursor){
        console.log(JSON.stringify(cursor.value));
        cursor.continue();
    }else{
        console.log("終わり");
    }
};
tx.oncomplete = function(){
    console.log("終わり2");
}:

IDBCursor 主な機能

フィールド

key: any;
カーソルが指すレコードのインデックスキー。オブジェクトストアからカーソルを生成した場合はプライマリキーと同義
primaryKey: any;
カーソルが指すレコードのプライマリキー
value: any;
カーソルが指すレコード

メソッド

continue(key?: any): void;
引数を省略するとカーソルを1歩進める。引数にキーを指定すると指定キー以上(directionが"prev"系の時は以下)の最初のキーに飛ぶ。後戻りはできない。advance()とcontinue()は2回呼べない。
advance(count: number): void;
引数の数だけカーソルを進める。advance()とcontinue()は2回呼べない。

リクエストを追加するメソッド

update(value: any): IDBRequest;
カーソルが指すレコードを引数で更新する。in-line keysモードのときプライマリキーのフィールドに.primaryKeyと同じ値を指定しないとトランザクションが失敗する。continue()とadvance()の後に呼べない。
delete(): IDBRequest;
カーソルが指すレコードを削除する。continue()とadvance()の後に呼べない。

注意

update()でカーソルが進む先のレコードを作ると無限ループを起こせる。

var store = db.createObjectStore("store1",{autoIncrement:true});
var index = store.createIndex("index1","x");
store.add({x:123});
var req = store.index("index1").openCursor();
req.onsuccess = function(){
    var cursor = this.result;
    if(cursor != null){
        console.log(cursor.value);
        cursor.update({x:cursor.key+100});
        cursor.continue();
    }
};

キーレンジ IDBKeyRange

キーの範囲を1つ指定する
キーの範囲コード
a ≦ keyIDBKeyRange.lowerBound(a)
a < keyIDBKeyRange.lowerBound(a, true)
key ≦ bIDBKeyRange.upperBound(b)
key < bIDBKeyRange.upperBound(b, true)
a ≦ key ≦ bIDBKeyRange.bound(a, b)
a ≦ key < bIDBKeyRange.bound(a, b, false, true)
a < key ≦ bIDBKeyRange.bound(a, b, true, false)
a < key < bIDBKeyRange.bound(a, b, true, true)
a = keyIDBKeyRange.only(a)

引数にIDBKeyRangeを指定できるメソッドはキーも受け取れるのでIDBKeyRange.only()は不要だと思う。

イベント

addEventListener()でもイベントを登録できる。でも基本的にインスタンスが使い捨てなのでfunction(){}代入のほうが短くて書きやすいと思う。

エラーはDOMと同様に伝播する

リクエストでエラー

  1. 子 IDBRequest.onerror()
  2. IDBTransaction.onerror()
  3. 親 IDBDatabase.onerror()

トランザクションが中止

  1. 子 IDBTransaction.onabort()
  2. 親 IDBDatabase.onabort()
inserted by FC2 system