JXAでSQLite3を使う

JXAのObjective-Cブリッジは、[いくつかの標準添付Cライブラリへのアクセス](https://developer.apple.com/library/content/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW18)を提供している。

この記事では、JXAにおいて、[SQLite3のCインターフェイス](https://www.sqlite.org/cintro.html)を介して、SQLite3データベースを操作する方法を説明する。

## オープンおよびクローズ

`sqlite**`型は空の`Ref`で表現できる。`var db = ppDb[0]`はデリファレンス(`sqlite *db = *ppDb`)に相当する操作である。

```javascript
ObjC.import('sqlite3')
var err
var filename = $('~/data.sqlite3').stringByStandardizingPath.js

var ppDb = Ref()
err = $.sqlite3_open(filename, ppDb)
var db = ppDb[0]
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

try {
	/* データベース操作 */
} finally {
	err = $.sqlite3_close(db)
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
}
```

## テーブル作成

`NULL`ポインタは空の`Ref()`で表現できる。

```javascript
sql = 'CREATE TABLE IF NOT EXISTS myTable (id INTEGER PRIMARY KEY, name TEXT); DELETE FROM myTable'
err = $.sqlite3_exec(db, sql, Ref(), Ref(), Ref())
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
```

## レコード挿入

```javascript
// トランザクション
err = $.sqlite3_exec(db,'BEGIN', Ref(), Ref(), Ref())
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

sql = 'INSERT INTO myTable (id, name) VALUES (?, ?)'
ppStmt = Ref()
err = $.sqlite3_prepare_v2(db, sql, -1, ppStmt, Ref())
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
pStmt = ppStmt[0]

try {
	for (var i = 0; i < 100; i++) {
		err = $.sqlite3_reset(pStmt)
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

		err = $.sqlite3_bind_int(pStmt, 1, i)
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

		err = $.sqlite3_bind_text(pStmt, 2, "First " + i + " unique names", -1, -1)
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

		err = $.sqlite3_step(pStmt)
		if (err != $.SQLITE_DONE) throw new Error($.sqlite3_errmsg(db))
	}

	// トランザクション
	err = $.sqlite3_exec(db,'END', Ref(), Ref(), Ref())
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
} finally {
	err = $.sqlite3_finalize(pStmt)
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
}
```

### 注意
型が`const char *`の引数に対しては、JavaScriptの文字列を直接渡すことが可能であるが、渡したポインタが指す領域は当該関数呼び出し終了時に解放される模様。
したがって、`$.sqlite3_bind_text`などの関数を使用する場合、第5引数に`SQLITE_TRANSIENT` (`-1`) を使用し、SQLiteの内部バッファに文字列をコピーしておく必要がある。

## レコード検索

```javascript
sql = 'SELECT id, name FROM myTable'
ppStmt = Ref()
err = $.sqlite3_prepare_v2(db, sql, -1, ppStmt, Ref())
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
pStmt = ppStmt[0]

try {
	while ((err = $.sqlite3_step(pStmt)) == $.SQLITE_ROW) {
		console.log($.sqlite3_column_int(pStmt, 0), $.sqlite3_column_text(pStmt, 1))
	}
	if (err != $.SQLITE_DONE) throw new Error($.sqlite3_errmsg(db))
} finally {
	err = $.sqlite3_finalize(pStmt)
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
}
```

## コード
```javascript
ObjC.import('sqlite3')

var err
var filename = $('~/data.sqlite3').stringByStandardizingPath.js

var ppDb = Ref()
err = $.sqlite3_open(filename, ppDb)
var db = ppDb[0]
if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

try {
	var sql, ppStmt, pStmt
	
	sql = 'CREATE TABLE IF NOT EXISTS myTable (id INTEGER PRIMARY KEY, name TEXT); DELETE FROM myTable'
	err = $.sqlite3_exec(db, sql, Ref(), Ref(), Ref())
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	
	err = $.sqlite3_exec(db,'BEGIN', Ref(), Ref(), Ref())
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

	sql = 'INSERT INTO myTable (id, name) VALUES (?, ?)'
	ppStmt = Ref()
	err = $.sqlite3_prepare_v2(db, sql, -1, ppStmt, Ref())
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	pStmt = ppStmt[0]

	try {
		for (var i = 0; i < 100; i++) {
			err = $.sqlite3_reset(pStmt)
			if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

			err = $.sqlite3_bind_int(pStmt, 1, i)
			if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

			err = $.sqlite3_bind_text(pStmt, 2, "First " + i + " unique names", -1, -1)
			if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))

			err = $.sqlite3_step(pStmt)
			if (err != $.SQLITE_DONE) throw new Error($.sqlite3_errmsg(db))
		}
	
		err = $.sqlite3_exec(db,'END', Ref(), Ref(), Ref())
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	} finally {
		err = $.sqlite3_finalize(pStmt)
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	}

	sql = 'SELECT id, name FROM myTable'
	ppStmt = Ref()
	err = $.sqlite3_prepare_v2(db, sql, -1, ppStmt, Ref())
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	pStmt = ppStmt[0]

	try {
		while ((err = $.sqlite3_step(pStmt)) == $.SQLITE_ROW) {
			console.log($.sqlite3_column_int(pStmt, 0), $.sqlite3_column_text(pStmt, 1))
		}
		if (err != $.SQLITE_DONE) throw new Error($.sqlite3_errmsg(db))
	} finally {
		err = $.sqlite3_finalize(pStmt)
		if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
	}
} finally {
	err = $.sqlite3_close(db)
	if (err != $.SQLITE_OK) throw new Error($.sqlite3_errmsg(db))
}
```

## 結果
```javascript
/* 0 First 0 unique names */
/* 1 First 1 unique names */
...
/* 97 First 97 unique names */
/* 98 First 98 unique names */
/* 99 First 99 unique names */
Result:
0
```