銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

Node.js+Sqlite(npm sqlite3)のeachメソッドは中断できない

f:id:takamints:20170829222928p:plain
photo credit: Skakerman 39/52 - Signs via photopin (license)

8月初旬に急遽「C++で画像処理(オブジェクトトラッキング)」ってなプロジェクト(しかもOpenCVを使わないという骨太方針w)のヘルプに駆り出されて、それまでやってた「Node.js+sqlite3」の悩みどころや困りどころを、今やスッカリ忘れそうになっているので、思い出しつつ書いておきます。

[改訂第4版]SQLポケットリファレンス
朝井 淳
技術評論社
売り上げランキング: 95,176

sqlite3のeachメソッドは中断できない

表題と同じですけど大切なことなので2回・・・

ええ、クエリ結果セットから一件ずつレコードを読むための Database#each または、 Statement#each メソッドなんですが、 どちらも公式ドキュメントに「今のところ中断できない」と書いてあって実質的に使えないです。

Database#each

Database#each(sql, [param, …], [callback], [complete])

Runs the SQL query with the specified parameters and calls the callback once for each result row. The function returns the Database object to allow for function chaining. The parameters are the same as the Database#run function, with the following differences:

The signature of the callback is function(err, row) {}. If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.

After all row callbacks were called, the completion callback will be called if present. The first argument is an error object, and the second argument is the number of retrieved rows. If you specify only one function, it will be treated as row callback, if you specify two, the first (== second to last) function will be the row callback, the last function will be the completion callback.

If you know that a query only returns a very limited number of rows, it might be more convenient to use Database#all to retrieve all rows at once.

There is currently no way to abort execution.

これ見落としていて、えらい目に遭いました。まさかこんなしれっと書いてあるなんて。

Statement#each(↓)なんて、なぜだかビックリマーク付きですよ。

Statement#each

Statement#each([param, …], [callback], [complete])

Binds parameters, executes the statement and calls the callback for each result row. The function returns the Statement object to allow for function chaining. The parameters are the same as the Statement#run function, with the following differences:

The signature of the callback is function(err, row) {}. If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.

After all row callbacks were called, the completion callback will be called if present. The first argument is an error object, and the second argument is the number of retrieved rows. If you specify only one function, it will be treated as row callback, if you specify two, the first (== second to last) function will be the row callback, the last function will be the completion callback.

Like with Statement#run, the statement will not be finalized after executing this function.

If you know that a query only returns a very limited number of rows, it might be more convenient to use Statement#all to retrieve all rows at once.

There is currently no way to abort execution!

結局 all と同じですからー

通常この手のメソッドは、SQLを発行してフェッチした行を順次1行ずつコールバックで通知してくれて、 「もう十分。これ以上必要ない」って時に中断したいわけですが、それが(今のところは)できないってこと。 つまり結局最後まで読みきらないといけないってことで、それなら Database#allStatement#allと同じですよね。

LIMIT使えばいいじゃない / allだけで行けるじゃない?

結局、同じようなことをするためには、SQLでLIMIT句を使って、allメソッドでページングしながら、複数行を繰り返し取ってくるしか手はありませんでした。

結果は大体同じですけど、SQLを複数回発行することになるので、パフォーマンスはちょっと低下すると思います。

LIMIT句をパラメタライズしてStatementを用意しておけば、あまり気にならないとは思いますが。

ま、巨大な結果セットを返さないようにSQLを変更できるなら、それを優先する必要がありますね。

ここぞとコントリビュートしなさいよ

オープンソースなんだから「使えねー」とか文句ばっか言ってないで、フォークして対応してプルリク投げればよいのですが、、、

コレぐらいバカでかいプロジェクトだとちょっと躊躇しますね(笑)

今のところ画像処理方面でアレなんで(言い訳)、またそのうち・・・。

github.com