Node.jsでSQLite3にトランザクション張って列追加したり一括更新したり
SQLite3のとあるテーブルをデータ移行したかったので、Node.jsの sqlite3
で書いてみた。
[ Node.js v10.15.3 / sqlite3@4.0.9]
sqlite3
の公式マニュアルは↓
Home · mapbox/node-sqlite3 Wiki
以下コマンドで sqlite3
をインストール。
npm install sqlite3
今回は次の処理を行うプログラムを書いた。
- テーブル
pages
にintroduction
カラムを追加する。 - テーブル
pages
のpublic
が1である全てのレコードについて、content
カラムの値を---
で分割し、前半をintroduction
カラムに、後半をcontent
カラムにセットしてUPDATEする。
なお、ここでは content
に ---
が含まれなかったり複数含まれていたりはしないものとする。
以下スクリプト。
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('C:\\develop\\sqlite_batch\\database.sqlite', (err) => {
if (err) {
return console.log('※データベース接続時にエラー', err);
}
});
// クエリを同期的に実行する
db.serialize(() => {
// トランザクション開始
db.run('BEGIN TRANSACTION');
// pagesにintroduction列 (text型) を追加
db.run('ALTER TABLE pages ADD COLUMN introduction text');
// pagesのpublic=1である全てのレコードを取得し、1行ずつ処理
db.each('SELECT * FROM pages WHERE public = ?', [1], (err, row) => {
if (err) {
return console.log('※レコードの取得時にエラー', err);
}
// pagesをid指定でUPDATE
const arr = row.content.split('---');
const introduction = arr[0].trim();
const content = arr[1].trim();
db.run('UPDATE pages SET introduction = ?, content = ? WHERE page_id = ?', [
introduction,
content,
row.page_id
]);
console.log('★page_id =', row.page_id, '完了');
}, (err, count) => {
// each終了時にコミット
if (err) {
return console.log('※eachの終了時にエラー', err);
}
db.run('COMMIT');
console.log('★コミット 処理件数 ->', count);
// データベース接続解除 (ここに書く理由は備考参照)
db.close((err) => {
if (err) {
return console.log('※close時にエラー', err);
}
console.log('★終了');
});
});
});
特にロールバック書いてないのは、コミットしなければロールバックされるから。
備考
db.serialize()
のコールバック直下に書いた操作は同期的に実行されるけど、あくまで直下に書いたものしかシリアライズされない。つまり↑のプログラムだと、db.each()
のコールバック内に書いたUPDATE文はシリアライズされない。
そのせいか db.close()
を db.serialize()
の外に書いたら Error: SQLITE_MISUSE: Database handle is closed
エラーが出た。たぶん、シリアライズされたクエリが終了した時点でデータベースが閉じられてしまい、UPDATE実行時にコケたんだと思う。
よって、db.each()
の処理が全て完了してからデータベースを閉じてもらうために、ここでは db.each()
の最後の引数のコールバック内で db.close()
した。
もっと複雑な処理を書くなら、db.serialize()
をネストさせたりしなきゃいけないと思う。ここらへんはマニュアルでちょっと触れられていた。