cornucopiaとは (2023-05-26)
SQLからRustのコードを生成して安全にデータベース操作ができる。恐らくGoの
sqlc と同じ感じなんだと思う。
というかcornucopiaって何よ
1
[the cornucopia] 【ギリシャ神話】 豊饒(ほうじよう)の角 《幼時の Zeus 神に授乳したと伝えられるやぎの角》.
2
可算名詞 豊饒の角の装飾 《角の中に花・果物・穀類を盛った形で,物の豊かな象徴》.
3
[a cornucopia] 豊富 〔of〕.
a cornucopia of good things to eat たくさんのおいしい食物.
4
可算名詞 円錐形の容器.
resources
準備 (2023-05-26)
(2023-05-26)
Rustのプロジェクトを作成
cargo new --bin cornucopia-example
(2023-05-26)
PostgreSQLのサーバーを適当に建てる
version : "3"
services :
db :
image : postgres:13.3
environment :
POSTGRES_USER : test
POSTGRES_PASSWORD : password
ports :
- 5432:5432
volumes :
- postgres:/var/lib/postgresql
volumes :
postgres :
(2023-05-26)
cornucopiaにはmigration機能などはついていない。
まず最初にデータベースのスキーマを作る必要がある。
ここでは
atlas を使って適当にスキーマを決める
schema "public" {}
table "users" {
schema = schema. public
column "id" {
null = false
type = int
identity {
generated = ALWAYS
start = 10
increment = 10
}
}
primary_key {
columns = [ column . id ]
}
column "name" {
null = false
type = text
}
}
そして適用
atlas schema apply --url "postgresql://test:password@localhost:5432/test?sslmode=disable" --to "file://schema.hcl"
使い方 (2023-05-26)
ここから実際にcornucopiaを使う
クエリの作成 (2023-05-26)
Rustプロジェクトのルートにqueries
フォルダを作り、そこにSQLを置く
例:
--! insert_user
INSERT INTO users( name )
VALUES (: name );
インストール (2023-05-26)
cliをインストール
生成
cornucopiaコマンドでRustのファイルを生成できる。ちなみにcornucopiaがpostgresのdockerコンテナを勝手に作るようにもできるみたい。
cornucopia live "postgresql://test:password@localhost:5432/test"
rustfmt --edition 2021 ./src/cornucopia.rs
このコマンドでは実際のデータベースに接続することで存在しないテーブルにアクセスしようとした際などにエラーを出してくれる。とても便利。
src/cornucopia.rs
が生成される。先のSQLではこのようなファイルが得られる。
// This file was generated with `cornucopia`. Do not modify.
#[allow(clippy::all, clippy::pedantic)]
#[allow(unused_variables)]
#[allow(unused_imports)]
#[allow(dead_code)]
pub mod types {}
#[allow(clippy::all, clippy::pedantic)]
#[allow(unused_variables)]
#[allow(unused_imports)]
#[allow(dead_code)]
pub mod queries {
pub mod user {
use cornucopia_async :: GenericClient ;
use futures;
use futures ::{ StreamExt , TryStreamExt };
pub fn insert_user () -> InsertUserStmt {
InsertUserStmt ( cornucopia_async :: private :: Stmt :: new (
"INSERT INTO users(name)
VALUES ($1)" ,
))
}
pub struct InsertUserStmt ( cornucopia_async :: private :: Stmt );
impl InsertUserStmt {
pub async fn bind <' a , C : GenericClient , T1 : cornucopia_async :: StringSql >(
&' a mut self ,
client : &' a C ,
name : &' a T1 ,
) -> Result < u64 , tokio_postgres :: Error > {
let stmt = self . 0. prepare ( client ). await ?;
client . execute ( stmt , &[ name ]). await
}
}
}
}
とてもわかりやすい
使用方法 (2023-05-26)
この生成されたファイルの依存をいろいろ追加する。
cargo add tokio cornucopia_async futures tokio_postgres
なおデフォルトではtokio_postgresを用いた非同期コードが生成されるが同期コードも生成できるみたい。
あとは生成された関数を呼び出すだけ。
use cornucopia :: queries :: user ::insert_user;
use tokio_postgres ::{ Error , NoTls };
mod cornucopia;
#[tokio::main]
async fn main () -> Result <(), Error > {
let ( client , connection ) = tokio_postgres :: connect (
"host=localhost port=5432 user=test password=password" ,
NoTls ,
)
. await ?;
tokio :: spawn ( async move {
if let Err ( e ) = connection . await {
eprintln! ( "connection error: {}" , e );
}
});
insert_user (). bind (& client , & "me" ). await . unwrap ();
Ok (())
}
良かったところ (2023-05-26)
今までRustでRDBを扱う方法を色々さがしてきて
ORM: まだあまり成熟してない感じだった(個人的にはprisma-client-rust
が一番よかったと感じたが依存が重すぎなのと色々不安定だった)
sqlx: 確かにSQLを書けばマクロで型が補完されるのはすごいが開発中もデータベースの状態を気にしないといけないし何よりマクロは辛い
などの問題を感じていたがcornucopiaはデータベースに接続するのはコードを生成するときだけだし生成されたコードも普通のRustファイルで見やすいのがとても良い。
あとbind()
以外にもparams()
関数が用意されていてパラメータを構造体で作成できるのも嬉しい。
改善されてほしいところ (2023-05-26)
まだ色々機能が足りてない感じがする
PostgreSQLにしか対応してない
まあこれは仕方ないのかなと思いつつもsqliteとかで使えたらとてもいいなと