外部ライブラリに頼りたくない
NimでMySQLに接続する際は標準ライブラリのdb_mysql
を使用するのが一般的である(と思う)。しかし、db_mysql
(正確には依存先のmysql
)はMySQLクライアント用ライブラリを動的リンクするため、(ビルド環境ではなく)実行環境に下表のライブラリを用意する必要があるが、手間がかかるだけでなく外部ライブラリの変更に弱い。
そこで外部ライブラリに依存しない方法をとりたい。
OS | 必要ライブラリ |
---|---|
Linux | libmysqlclient.so |
Windows | libmysql.dll(参考) |
MacOS | libmysqlclient.dylib |
備考:Nimでは外部ライブラリのインターフェースとデータ構造をnimファイル内で定義する必要があるのだが、現在のNimのmysql
ライブラリとMySQLクライアントライブラリのインターフェースデータ構造に差異がある。これはMySQLライブラリのインターフェースデータ構造はバージョン毎に微妙に異なるのでNimのFFIの仕組みとは相性が悪いのではと感じるにも関わらずNim側でデータ構造を必要以上に細かく定義しているせいだと思われる。
Golangはどうしてる?
Golangでは標準ライブラリとして各DB共通インターフェースdatabase/sql
を用意し、実装は各Driverライブラリに任されているが、MySQL用Driverの中で最もスタンダードである(と思う)go-sql-driver/mysqlでは全てGolangで実装されていて外部ライブラリを必要としない。
じゃあNimでも書けばいいじゃん
ということで、NimでMySQL接続用のライブラリを書いてみようと思ったが、まずはどんなプロトコルでMySQLとやり取りするのかを調べないといけない。
そこで以下のページでプロトコルの概要を調べるとTCPを使いMySQLのClient/Server Protocolに則って通信すれば良いと分かった。
NimでTCP通信するにはnet
かasyncnet
を使えばいいらしいのだが今回は簡単のためにnet
を使うことにした。
あとはMySQLの公式リファレンスを見たり、良く分からないところはgo-sql-driver/mysqlを参考にしたりしてコードを書いてみる。
とりあえずはMySQLに接続できた
認証方法がmysql_native_password
にしか対応できてないcaching_sha2_password
にも対応できたけどOpenSSLを使ったのでpureでなくなった(MySQL8.0ではデフォルトがcaching_sha2_password
になった)とか色々あるけど、LinuxとWindowsでMySQLに接続してSQLを実行することに成功した。(Macは持ってない)
接続用ライブラリ
サンプル
version: '3.8'
services:
db:
image: mysql:8.0.23
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
restart: always
ports:
- "3306:3306"
environment:
MYSQL_RANDOM_ROOT_PASSWORD: 1
MYSQL_DATABASE: test
MYSQL_USER: nim
MYSQL_PASSWORD: nim
import mysql_connector
proc main()=
var db = db_open("127.0.0.1:3306", "nim", "nim", "test")
defer: db.db_close()
let drop_sql =
sql"""DROP TABLE IF EXISTS `user`"""
let create_sql = sql"""
CREATE TABLE IF NOT EXISTS `user` (
`id` int NOT NULL,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"""
db.exec(drop_sql)
db.exec(create_sql)
let insert_args =
@[
@["1","Tom"],
@["2","Jay"],
@["3","Ann"]
]
for insert_arg in insert_args:
db.exec(sql"INSERT INTO user VALUES (?, ?)", insert_arg)
let rows = db.get_all_rows(sql"SELECT * FROM user WHERE id >= ? ORDER BY id", 2)
echo rows
main()
@[@["2", "Jay"], @["3", "Ann"]]
道のりは遠い
正常系で動くものはできたが、実運用に使えるレベルにはないのでこれから勉強しながらエラーハンドリングやいろんな機能追加をできたらいいな。