Open9
「sqlc で D1 向けの TypeScript コードを生成する」という記事を書くためのメモ
sqlc とは
- Go で書かれた SQL コンパイルして型チェックをしてくれ、Go のコードを生成してくれるツール
- PostgreSQL や MySQL 、SQLite に対応している
- プラグインを Wasm で書くことができる
- sqlc が wasmtime を内蔵している
- Python や Kotlin のコードも出力ができる
Cloudflare D1 とは
- Cloudflare がアルファ版として提供している Cloudflare Workers で利用できる組み込み SQLite
- 未実装あり
- 制限あり
sqlc-gen-ts-d1 とは
- orisano が開発している sqlc プラグイン
- Go で書かれており tinygo で Wasm 化している
- Cloudflare D1 向けの TypeScript コードを出力できる
作者の orisano は sqlc 自体にかなり貢献をしている、sqlc チョットデキル 。
仕様
自分が Gist に公開していたのを参考にしてくれている。実際に生成されるコードを見て貰うのが早い。
-
https://github.com/voluntas/sqlc-gen-ts-d1-spec
- Cloudflare Worker で sqlc-gen-typescript-d1 をテストするリポジトリ
-
https://github.com/voluntas/sqlc-gen-ts-d1-spec/blob/main/db/schema.sql
- スキーマ
-
https://github.com/voluntas/sqlc-gen-ts-d1-spec/tree/main/db/query
- クエリ
-
https://github.com/voluntas/sqlc-gen-ts-d1-spec/blob/main/src/gen/sqlc/models.ts
- 生成された Go の Struct
-
https://github.com/voluntas/sqlc-gen-ts-d1-spec/blob/main/src/gen/sqlc/querier.ts
- 生成された Go のコード
sqlc.embed
- sqlc.embed は sqlc 1.18.0 で入った account.* と org.* のときに、うまいことまとめてくれる機能
-- name: GetOrgAccount :one
SELECT sqlc.embed(account),
sqlc.embed(org)
FROM account
JOIN org_account ON account.pk = org_account.account_pk
JOIN org ON org_account.org_pk = org.pk
WHERE org.id = @org_id
AND account.id = @account_id;
const getOrgAccountQuery = `-- name: GetOrgAccount :one
SELECT account.pk AS account_pk, account.id AS account_id, account.display_name AS account_display_name, account.email AS account_email,
org.pk AS org_pk, org.id AS org_id, org.display_name AS org_display_name
FROM account
JOIN org_account ON account.pk = org_account.account_pk
JOIN org ON org_account.org_pk = org.pk
WHERE org.id = ?1
AND account.id = ?2`;
export type GetOrgAccountParams = {
orgId: string;
accountId: string;
};
export type GetOrgAccountRow = {
account: Account;
org: Org;
};
type RawGetOrgAccountRow = {
account_pk: number;
account_id: string;
account_display_name: string;
account_email: string | null;
org_pk: number;
org_id: string;
org_display_name: string;
};
export async function getOrgAccount(
d1: D1Database,
args: GetOrgAccountParams
): Promise<GetOrgAccountRow | null> {
return await d1
.prepare(getOrgAccountQuery)
.bind(args.orgId, args.accountId)
.first<RawGetOrgAccountRow | null>()
.then((raw: RawGetOrgAccountRow | null) => raw ? {
account: {
pk: raw.account_pk,
id: raw.account_id,
displayName: raw.account_display_name,
email: raw.account_email,
},
org: {
pk: raw.org_pk,
id: raw.org_id,
displayName: raw.org_display_name,
},
} : null);
}
戻り値
- 基本的に D1Result が戻ってくる
const listAccountsQuery = `-- name: ListAccounts :many
SELECT pk, id, display_name, email
FROM account`;
export type ListAccountsRow = {
pk: number;
id: string;
displayName: string;
email: string | null;
};
type RawListAccountsRow = {
pk: number;
id: string;
display_name: string;
email: string | null;
};
export async function listAccounts(
d1: D1Database
): Promise<D1Result<ListAccountsRow>> {
return await d1
.prepare(listAccountsQuery)
.all<RawListAccountsRow>()
.then((r: D1Result<RawListAccountsRow>) => { return {
...r,
results: r.results ? r.results.map((raw: RawListAccountsRow) => { return {
pk: raw.pk,
id: raw.id,
displayName: raw.display_name,
email: raw.email,
}}) : undefined,
}});
}
D1 の仕様
- run() は sqlc でいう exec 向け
- first() は sqlc でいう one 向け
- all() は sqlc でいう many 向け
バッチ
- env.D1_TEST.batch
sqlc-gen-ts-d1 の使い方
sqlc の最新版をインストールする。 サクッと入る。
$ go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
sqlc.yaml
version: "2"
plugins:
- name: typescript-d1
wasm:
url: "https://github.com/orisano/sqlc-gen-typescript-d1/releases/download/v0.0.0-a/sqlc-gen-typescript-d1.wasm"
sha256: "4a3ac1e4faeefbb8d85d2153ec82e6977db1aee6ddab641511c0bcdabd0a872f"
sql:
- schema: db/schema.sql
queries: db/query/
engine: sqlite
codegen:
- out: src/gen/sqlc
plugin: typescript-d1
options: workers-types=2022-11-30
sqlc-gen-ts-d1 自前ビルド
バイナリ公開してくれてるので、ビルドしなくて良い
- tinygo が必要
- brew install tinygo
$ git clone https://github.com/orisano/sqlc-gen-ts-d1
$ cd sqlc-gen-ts-d1
$ make
- bin/sqlc-gen-ts-d1.wasm
- bin/ 以下にバイナリとハッシュが生成される
- bin/sqlc-gen-ts-d1.wasm.sha256
- このハッシュを sqlc.json に設定する
テスト
sqlc を使うと、SQL 自体のテストを書きやすくなるので、 D1 でも実現したい。ただ Cloudflare Workers は特殊な環境なので、まずは @miniflare/d1
を利用してテストが書きたい。
実際にテストできるのでどうぞ。
D1
d1 create
- spam という DB を作る
$ pnpm wrangler d1 create spam --experimental-backend --location apac
-
--location apac
- Asia Pacific アピール (ヒント)
-
--experimental-backend true
Options:
--location A hint for the primary location of the new DB. Options:
weur: Western Europe
eeur: Eastern Europe
apac: Asia Pacific
wnam: Western North America
enam: Eastern North America
[string]
--experimental-backend Use new experimental DB backend [boolean] [default: false]
batch
現在は実験的で仕様を入れてみてる
既存のクエリー実行用に生成された関数で .batch() を呼び出すことで batch 実行時に評価されるようにした。この書き方が良いかどうかはわからないが、わかりやすくするための仕組み。
env.D1.batch([db.deleteAccount(..).batch(), db.createAccount(..).batcn()]);
そもそも batch 自体が特殊。早く TX 来てほしい。