dashmarkを作りながらいろいろとハマっていること

[-]:dashmarkWebKit SQL APIHTML5 client-side database storage)とCSS Animationにベタベタに依存したアプリケーションですが、似たようなことをやっている人が極端に少ないことと、私自身がJavaScriptにもSQLにも、プログラミングそのものにも経験と知識が少ないためにしょうもないところでハマってしまう。
ソースは[-]:dashmarkの先にある[-]:dashmark pagedevelopment versionに上がっているものがすべてなので、興味がある人はぜひ見てほしい……とても恥ずかしいけど。

今までにハマってしまったところを備忘録として記載しておきます。

非同期処理

WebKit SQLの処理は非同期に行われるため、検索などの結果を利用する処理にちょっとした工夫が必要、っていうかSQLの結果はいつ帰ってくるかわからないし複数同時に走っているような状態を想定しないと、存在しないDOMを先に消しにいったりしてちょっと面倒なことになる。
ただ、非同期に処理が行われることを、私は楽しんでいる。[-]:dashmarkインクリメンタルサーチなんて非同期処理でないとインタラクションが悪くて使い物にならなくなるところだったけど、そもそも[-]:dashmarkSQL APICSS Animationという非同期処理に依存するつもりで作っているので、楽しみながら作ることができている。

バグか仕様か……

例えば、以下のような処理は期待する結果が得られない。私のJavaScript理解が大いに間違っている可能性もあるけれど。
TABLE Aからtextを持ってきてTABLE Bの同じuidのある行に移植するようなとき、はじめは下のように書いてしまったが、すべてのrow['text']が同じ値になってしまう。
SQLのキューに積む段階で、var rowの中身がすべて同じものになってしまっているらしい。私の理解が間違っていると信じたい。ひょっとしたら、SQL statementの仕様かもしれない。


db.transaction( function(tx){
tx.executeSql( "SELECT uid, text FROM [TABLE A]",[],
function(tx, result){
for (var i = 0; i < result.rows.length; ++i){
var row = result.rows.item(i);
tx.executeSql( "UPDATE [TABLE B] SET text = ? where uid = ?",[ row['text'], i])
}
}),
function(tx, error){})
)
});

結局、この処理は文字列で固めて別の関数に処理を渡すことでとりあえず回避しているのだけど、本来不要な関数が増えてしまっている。


db.transaction( function(tx){
tx.executeSql( "SELECT uid, text FROM [TABLE A]",[],
function(tx, result){
for (var i = 0; i < result.rows.length; ++i){
var row = result.rows.item(i);
var text_string = row['text'];
updateText(text_string);
}
}),
function(tx, error){})
)
});

某氏より、以下のようにしてみては?とアドバイスを頂いた。だが、これでもrowStringがすべてresult.rows.item(result.rows.length)['text']、つまり最後のiで取得された値になってしまう。困ったものだ。


db.transaction( function(tx){
tx.executeSql( "SELECT uid, text FROM [TABLE A]",[],
function(tx, result){
for (var i = 0; i < result.rows.length; ++i){
var row = result.rows.item(i);
var rowString = row['text'];
tx.executeSql( "UPDATE [TABLE B] SET text = ? where uid = ?",[ rowString, i])
}
}),
function(tx, error){})
)
});

人間が読まない通しナンバーのid

SQL入門みたいなチュートリアルを読んでいると必ず登場するのがPRIMARY NUMBERを使ったシリアルナンバー処理。これは悪。伝票番号のように人間が後から読まないidなら、通しナンバーは絶対につけてはいけない。私も初期のバージョンで通しナンバーをつけてしまい、大きな失敗をしてしまった。
通しナンバーをidに使うデメリットはいくつかあるけれど、私がぶつかったのは下の二つ。

  1. 欠番の登場
  2. 処理の衝突

データを削除すると必ず登場する欠番。欠番が出てきた時点で「通し」ナンバーであるメリットのほとんどが失われ、ただの「数字で構成される文字列」に成り下がる。
もう一つは上でも挙げた非同期処理にまつわる衝突の問題で、こちらは深刻だ。
新規idの取得と、データの挿入が一文のクエリで表現できない処理がある場合、idの取得とデータ挿入の間に他のSQLが実行される可能性がある。それが「最大のidを持つrowからデータを抽出する」ような処理だった場合、抜き出すべき列にはnullしか入っていない。そりゃ、nullが帰ってきたら再帰的に有為なデータが採れるようにする処理を書くのは可能かもしれないけど不毛だ。
WebKit SQL APIは私が使う範囲でまともに処理が動いている間はデータベースのロックが発生しないけれど、nullの参照やresultを拾って行う処理が重かったりするとロックしてしまう。一度ロックしたデータベースはいつ復帰するのかわからないので、衝突しない、しにくい設計にしておかないと、扱うデータが多くなってきたときにハマるような気がする。

というわけで、なんちゃって関数ではあるけれどguidでidを振って、前回のデータベースのバージョンアップの際に通しナンバーのidを捨てることができた。

もしも、何らかの理由で通しナンバーが取得したければ、以下のようにして取得できる_ROWID_とviewで結合すればいい。


db.transaction( function(tx){
tx.executeSql(
"SELECT count() FROM PRIMARY_SCOPE LIMIT 1",[],
function(tx, result){
"SELECT _ROWID_ FROM [TABLE NAME]",[],
function(tx, result){
for(var i = 0; i < result.rows.length; ++i){
var serial_text = result.rows.item(i)['_ROWID_'];
}
}),
function(tx, error){})
)
});

まだまだたくさん

ほとんどは私の理解が足りないために躓いているのだろうけれど、気付いたことがあったらまた書きます。