Go 言語で SQLite の拡張ライブラリを作る

Go 言語で SQLite の拡張ライブラリが作れるライブラリを発見したので、さっそく試してみた。

https://github.com/riyaz-ali/sqlite

（最後のコミットが 2023 年 8 月になっているのが若干気になる。）

今回は SQLite の標準でサポートされていないキャメルケースに変換する `camelCase` 関数を実装してみます。
`camelCase` 関数の実装は [xargs](https://github.com/huandu/xstrings) というライブラリの `ToCamelCase` 関数に委譲しました。

# 手順

WSL(Ubuntu 22.04) で実施。
※ Windows 11 でもできたので、記事の最後に PowerShell での手順も記載しています。

```bash:bash
sudo apt update
sudo apt install -y golang-go sqlite3

mkdir sqlite-my-extensions
cd sqlite-my-extensions

go mod init sqlite-my-extensions
go get -u go.riyazali.net/sqlite
go get github.com/huandu/xstrings

curl -OL https://raw.githubusercontent.com/riyaz-ali/sqlite/master/_examples/Makefile

mkdir camelCase
touch camelCase/camelCase.go
```

ではい、 camelCase.go を実装していきます。
[_examples](https://github.com/riyaz-ali/sqlite/tree/master/_examples) ディレクトリ内に関数の実装例があるので、今回は `upper` 関数をまねて作りました。

```go:camelCase.go
package main

import (
	"go.riyazali.net/sqlite"
    "github.com/huandu/xstrings"
)

// CamelCase implements a custom CamelCase(...) scalar sql function
type CamelCase struct{}

func (m *CamelCase) Args() int           { return 1 }
func (m *CamelCase) Deterministic() bool { return true }
func (m *CamelCase) Apply(ctx *sqlite.Context, values ...sqlite.Value) {
	ctx.ResultText(xstrings.ToCamelCase(values[0].Text()))
}

func init() {
	sqlite.Register(func(api *sqlite.ExtensionApi) (sqlite.ErrorCode, error) {
		if err := api.CreateFunction("camelCase", &CamelCase{}); err != nil {
			return sqlite.SQLITE_ERROR, err
		}
		return sqlite.SQLITE_OK, nil
	})
}

func main() {}
```

```bash:bash
make
#=> camelCase.so ファイルが作成される。

sqlite3
```

SQLite で load し、ちゃんと動くかテストします。

```sql:SQLite Command Line Shell
.load ./camelCase

.mode table

WITH
  parameterized_tests(word, expected) AS (
    VALUES ('http_server', 'httpServer'),
           ('_camel_case', '_camelCase'),
           ('no_https', 'noHttps'),
           ('_complex__case_', '_complex_Case_'),
           (' complex -case ', ' complex Case '),
           ('all', 'all'),
           ('GOLANG_IS_GREAT', 'golangIsGreat'),
           ('GOLANG', 'golang')
)
SELECT
  word,
  CASE camelCase(word) WHEN expected
    THEN 'Passed' ELSE 'Failed'
  END AS result
  FROM paralized_tests
;

-- +-----------------+--------+
-- |      word       | result |
-- +-----------------+--------+
-- | http_server     | Passed |
-- | _camel_case     | Passed |
-- | no_https        | Passed |
-- | _complex__case_ | Passed |
-- |  complex -case  | Passed |
-- | all             | Passed |
-- | GOLANG_IS_GREAT | Passed |
-- | GOLANG          | Passed |
-- +-----------------+--------+
```

手軽すぎる。

# 実装した関数の説明

簡単に実装時に使用した関数の説明を表にしました。
詳しくは、[godoc](https://pkg.go.dev/go.riyazali.net/sqlite) で確認できます。

| 関数 | 説明 |
|:---|:---|
|`Args() int`|関数が受け取れる引数の数を返します。|
|`Deterministic() bool`|同じ引数が与えられた場合、常に同じ値を返すかの真偽値を返します。|
|`Apply(*Context, ...Value)`|関数の実際の中身の処理を実装します。単一の値を返す関数は必須です。|
|`sqlite.Register`|上記 3 つの関数を実装した構造体とその関数名を登録します。init関数内で実装するので、パッケージの import 時に実行されます。|

# Windows 版（PowerShell）

go プロジェクトとソースコードの作成手順は割愛。

```powershell:PowerShell Console
scoop install sqlite3 gcc go
$env:CGO_ENABLED=1

go build -buildmode=c-shared -o camelCase.so sqlite-my-extensions/camelCase
```

```sql:SQLite Command Line Shell
.load camelCase.so

-- 後の確認手順は一緒。
```