こうこく
作 ▸
改 ▸

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

今回は次の処理を行うプログラムを書いた。

  • テーブル pagesintroduction カラムを追加する。
  • テーブル pagespublic が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() をネストさせたりしなきゃいけないと思う。ここらへんはマニュアルでちょっと触れられていた。

Control Flow · mapbox/node-sqlite3 Wiki

この記事に何かあればこちらまで (非公開)