IndexedDBにはまってます。(その3)

IndexedDBにはまってます。(その2)ではindexedDBを操作して任意のデータベースを開き(生成して)、閉じて、削除するまでをWebブラウザ(Google Chromeを利用。)のコンソール上で実行して紹介しました。

今回は空っぽのデータベースにスプレッドシートのシートにあたるオブジェクトストアを生成して、合わせて最初のキーを登録するくだりを紹介します。

いままでのところopen()メソッドの第二パラメータであるバージョンについては空のまま利用しています。

今回もデータベースが存在しないことを想定して紹介を進めますのでデータベース名’DB_name’だけがパラメータになります。

const openRequest = indexedDB.open('DB_name');

確実にアップグレード(onupgradeneeded)イベントが発火し、そのイベントを受けてデータベースの「準備」枠が処理されます。(その2)にもあった、つぎのコード内の(データベースの準備処理)にあたる部分にコードを加えていきます。

openRequest.addEventListener('upgradeneeded', (event) => {
	const db = event.target.result;
	const tx = event.target.transaction;
	console.log(`UPGRADE_DB_VER_>${db.version}_OLD_>${event.oldVersion}_NEW_>${event.newVersion}`);
	(データベースの準備処理)
}, false);

アップグレードイベントが発火した時点で’DB_name’という名前のデータベースは生成されますが、もちろん、空っぽの真っさらです。「オブジェクト記憶領域」が0になってます。

Webブラウザのアプリケーション画面にある’DB_name’の情報

オブジェクトストアを作ります。

本当に何も無いので、スプレッドシート(エクセルとか)のシートにあたるものを一つでも作らないと利用できません。シートにあたるものはオブジェクトストアと呼ばれてます。

ここでは、DB_nameデータベースが生成された直後の(データベースの準備処理)の部分で、createObjectStore()メソッドを使ってindexedDBのオブジェクトストアを生成します。

もちろん、データベース生成後に改めてオブジェクトストアを生成することもできます。その場合はopen()メソッドの第二パラメータに現バージョン値より大きな整数値を設定して実行すれば生成できます。

つぎのコードのように、現在バージョンが1と想定されますから2と入れます。

const openRequest = indexedDB.open('DB_name', 2);

データベース’DB_name’の生成と同時にオブジェクトストアを生成する。

上のアップグレードイベント受けて「準備」処理するコード内の冒頭で、定数dbに代入したデータベースオブジェクトのcreateObjectStore()メソッドを参照してオブジェクトストアの生成を行います。

つぎのコードのように、第一パラメータにオブジェクトストアの名前(ここでは’Store_name’)と第二パラメータのオプション指定オブジェクト(ここではStore_option)を渡します。

objectStore = db.createObjectStore('Store_name', Store_option);

‘Store_name’という名前のオブジェクトストアが生成されてそのオブジェクトがobjectStoreに代入されます。(オブジェクトが重複してまわりくどいですが耐えます。)

この時にオプションStore_optionを省略してもオブジェクトストアは作られて利用できるのですが、ちゃんとオプションオブジェクトの内容と利用について理解をしておきましょう。(実は私自身がちゃんと理解しているか不安です。)

オプションオブジェクトは、つぎのようなオブジェクト(連想配列)です。

Store_option = {keyPath:文字列もしくはその配列, autoIncrement:真偽値};

まずはこのオプション指定の何たるかを知る必要があります。

ここら辺からKEYの話題です。

データベースのオブジェクトスアには複数のデータレコードが蓄積されます。それらを取り扱うためには、参照の糸口となるインデックスキーを設定します。

データベースでは複数のインデックスキーが設定されて複合的に運用されます。

インデックスキーには、配列、文字列、Dateオブジェクト、数値(0以下の数や小数も含む浮動小数点数値のこと)を値にできるそうです。配列は入れ子も大丈夫そうです。(全てについて確認してません。)

そしてインデックスキーの中でもオブジェクトストアを一貫して利用するため、特別扱いの初期インデックスキーがあります。オブジェクトストアを生成する際にそれを設定するチャンスがあり、このオプションオブジェクト内のパラメータが必要になります。

(ここ閑話ですが、重要です。)

このインデックスキーは主キーと呼ばれたりしていますが、カーソル(まだ話題にしてません。)の操作でprimaryKeyとして値が参照できます。

そしてこのprimaryKeyは、IndexedDBで一意に運用されます。データベースに格納されたレコード群の中で唯一の値(名前)が保証されるのです。

ほかのインデックスキーは一意でない指定の運用も可能です。ですからprimaryKeyは、まさに「KEY」と呼べるインデクスキーなのです。

はまってシリーズでは、この初期インデックスキーをプライマリキー(主キー)と呼ぶことにします。そうでないキーはインデックスキー(補キー)と呼ぶことにします。

まだまだ、インデックスキー(補キー)登録の紹介は当分の先になりそうですが…。

閑話休題、

オプション(ここではStore_option)オブジェクトを省略すると、keyPathは空でautoIncrementが偽(false)として、勝手にオプションの指定がされます。

そしてStore_optionを指定する場合、keyPathは空(省略やnull、undefined)か、文字列もしくはその配列を指定できます。autoIncrementは真偽値が指定できますが、省略すると偽となります。

keyPathが空でああればout-of-lineというモードでプライマリキー(主キー)が運用されます。また、この指定には制約があり、autoIncrementの真偽指定が偽でなくてはなりません。

実はout-of-lineモードは検証をしていないので今のところ理解の範疇外です。

(ここも閑話です。)

ところで、参考にしたサイトではkeyPathが空でautoIncrementが真という例が載っていました。私の検証ではエラーが発生して動作しませんでした。もしかしたら旧バージョンのIndexedDBの1系では大丈夫だったかもしれません。

閑話休題、

keyPathに文字列もしくはその配列の指定があれば、in-of-lineというモードでモードでプライマリキー(主キー)が運用されます。レコード内にプライマリキー(主キー)の情報が含まれて運用が進みます。

指定が文字列であれば’key’などインデックスの名前で指定できます。また’index.key’などとして階層で表記(名前は’key’ってことですよね?)もできます。

データレコードオブジェクトにおいては、つぎのようにオブジェクトの階層概念にて参照することになります。

{
	index:{
		key:値,
	},
}

配列であれば[‘key’, ‘date’]と名前の配列になり、また[‘index.key’, ‘date.create’]と階層表記でも指定できます。

ただし、この指定には制約があり、keyPathが空の場合と同様にautoIncrementの真偽指定が偽でなくてはなりません。(autoIncrementの真偽指定の制約については、どこかに記載があるのかも知れませんが、私はエラーにぶち当たって知ることになりました。)

じゃあ、autoIncrementの真偽値は何かと言うとキージェネレータのオンオフです。

自動的にプライマリキー(主キー)に必ず一意な連番を振ってくれる機能です。確認したところでは1から始まる正の整数で、意図的に代入することもできますが必ず一意になるように動作します。(すみません。検証はまだ不十分です。)

それ故でしょうか?autoIncrementが真になると、keyPathの指定が空であることもしくは配列であることを受け付けてくれません。

配列は一意が重複する必然がないことで理解できるのですが、空の場合に関しては、ちょっと仕様の齟齬を感じてしまいます。どうやらIndexedDBは、keyPathの指定によるin-of-lineモードとして、プライマリキー(主キー)を明示的に運用することを本分としているように感じます。

さてはトランザクションです。

データベースは完璧な動作が求められます。よって何かに付けてイベントを受け取って確認をするのですが、さらにデータベースへの操作が重複しても間違いなく動作が成される仕組みとして「トランザクション」があります。

お金の流れなどもこのトランザクションが働いていて、一連の作業のどこかで間違いが発生しようものなら、作業全てを中断(abort)してしまうように作られています。部分に発生する失敗(error)よりも強力なのです。

このことから、トランザクションでは操作の終了は成功(success)でなく完遂(complete)になります。(と、私の理解はこんなもの。)

createObjectStore()メソッドについては、そのトランザクションで動作の完遂が確認がされます。

つぎは、定数objectStoreに対してトランザクションの完遂イベント(oncomplete)を受けてオブジェクトストア生成完遂処理を書き込む枠になるコードです。

objectStore.transaction.addEventListener('complete', (event)=>{
	if(event.target.objectStoreNames.contains('Store_name')) {
		console.log(`'Store_name'が出来上がりました。`);
	}
}, false);

eventオブジェクトからオブジェクトストア名の一覧オブジェクト(DOMStringList)を参照して、本当に’Store_name’のオブジェクトストアがあるのかを確認してからコンソールにメッセージ出力してます。

つぎの2点は、定数objectStoreに対してトランザクションの、中断イベント(onabort)と失敗イベント(onerror)を受てそれぞれの処理を書き込む枠になるコードです。これらはコンソールにエラーメッセージを出力してます。

objectStore.transaction.addEventListener('abort', (event)=>{
	console.error(`トランザクションが中断しました。`);
}, false);

objectStore.transaction.addEventListener('error', (event)=>{
	console.error(`トランザクションでエラーが発生しました。`);
}, false);

ここまで紹介したコードをまとめて掲載します。これをコンソールにコピー&ペーストしてエンタキーを押せば、本投稿のくだりが実行できます。

const openRequest = indexedDB.open('DB_name');

openRequest.addEventListener('upgradeneeded', (event) => {
	const db = event.target.result;
	const tx = event.target.transaction;
	console.log(`UPGRADE_DB_VER_>${db.version}_OLD_>${event.oldVersion}_NEW_>${event.newVersion}`);

	const Store_option = {keyPath:['key', 'date'], autoIncrement:false};

	objectStore = db.createObjectStore('Store_name', Store_option);

	objectStore.transaction.addEventListener('complete', (event)=>{
		if(event.target.objectStoreNames.contains('Store_name')) {
			console.log(`'Store_name'が出来上がりました。`);
		}
	}, false);

	objectStore.transaction.addEventListener('abort', (event)=>{
		console.error(`トランザクションが中断しました。`);
	}, false);

	objectStore.transaction.addEventListener('error', (event)=>{
		console.error(`トランザクションでエラーが発生しました。`);
	}, false);

}, false);

openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開きました。`);
	db.close();
}, false);

openRequest.addEventListener('error', (event)=>{
	console.error(`データベースの開始が失敗しました。`);
}, false);
Webブラウザのコンソールでの実行画面

つぎのようにコンソールに出力されます。

UPGRADE_DB_VER_>1_OLD_>0_NEW_>1
'Store_name'が出来上がりました。
データベースを開きました。

二行目と三行目の順番の通り、トランザクションの完遂イベントが発火してから、成功イベントが発火しています。

同じコンソール画面のアプリケーション項目でindexedDB直下にDB_nameデータベース、そしてその直下にStore_nameオブジェクトストアが生成されます。

そして’key’と’date’のキー(キーパス[“key”,”date”])が登録されていることが確認できます。私がプライマリキー(主キー)と呼ぶものです。

Webブラウザのアプリケーション画面にある’Store_name’とプライマリキー(’key’と’date’)

確認ができたら、(その2)にあったデータベースの削除のソースコードを利用してちゃっちゃと削除してしまいましょう。

閑話が多くなりました。これもデータベース利用が複雑であるためと考えます。

次回(その4)では、この生成したオブジェクトストアの削除を紹介する予定です。うー、道のり長すぎー。そかさ。