Denoでsqlcを使う
はじめに
Denoで以下の組み合わせを試してみたので内容をまとめておきます
前提
以下のバージョンで動作確認しています
セットアップ
sqlc.yamlversion: "2"
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: postgresql
codegen:
- out: generated
plugin: ts
options:
runtime: node
driver: postgres
plugins:
- name: ts
wasm:
url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.3.wasm
sha256: 287df8f6cc06377d67ad5ba02c9e0f00c585509881434d15ea8bd9fc751a9368
重要なのは以下の部分です
yaml plugins:
- name: ts
wasm:
url: https://downloads.sqlc.dev/plugin/sqlc-gen-typescript_0.1.3.wasm
sha256: 287df8f6cc06377d67ad5ba02c9e0f00c585509881434d15ea8bd9fc751a9368
yamlsql:
- schema: "schema.sql"
queries: "query.sql"
engine: postgresql
codegen:
- out: generated
plugin: ts
options:
runtime: node
driver: postgres
sqlcがコードの生成に利用するスキーマの定義は
schema.sql
、
SQLは
query.sql
からそれぞれ読み込まれます
2.
schema.sql
を用意します。このファイルに
DDLを記述します
sqlCREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL
);
3.
psqlなどで
schema.sql
を実行して、テーブルを用意しておきます
shell$ psql -f schema.sql "$DATABASE_URL"
4.
query.sql
を用意します。このファイルに
DMLを記述します
query.sql-- name: ListUsers :many
SELECT * FROM users ORDER BY id;
-- name: FindUserByID :one
SELECT * FROM users WHERE id = $1 LIMIT 1;
-- name: InsertUser :one
INSERT INTO users (name) VALUES ($1) RETURNING *;
5.
sqlc generate
を実行すると、
generated
ディレクトリに
TypeScriptファイルが生成されます
shell$ sqlc generate
$ ls generated
query_sql.ts
以下が生成されるファイルのイメージです
generated/query_sql.tsimport { Sql } from "postgres";
export const listUsersQuery = `-- name: ListUsers :many
SELECT id, name FROM users ORDER BY id`;
export interface ListUsersRow {
id: string;
name: string;
}
export async function listUsers(sql: Sql): Promise<ListUsersRow[]> {
return (await sql.unsafe(listUsersQuery, []).values()).map(row => ({
id: row[0],
name: row[1]
}));
}
export const findUserByIDQuery = `-- name: FindUserByID :one
SELECT id, name FROM users WHERE id = $1 LIMIT 1`;
export interface FindUserByIDArgs {
id: string;
}
export interface FindUserByIDRow {
id: string;
name: string;
}
export async function findUserByID(sql: Sql, args: FindUserByIDArgs): Promise<FindUserByIDRow | null> {
const rows = await sql.unsafe(findUserByIDQuery, [args.id]).values();
if (rows.length !== 1) {
return null;
}
const row = rows[0];
return {
id: row[0],
name: row[1]
};
}
export const insertUserQuery = `-- name: InsertUser :one
INSERT INTO users (name) VALUES ($1) RETURNING id, name`;
export interface InsertUserArgs {
name: string;
}
export interface InsertUserRow {
id: string;
name: string;
}
export async function insertUser(sql: Sql, args: InsertUserArgs): Promise<InsertUserRow | null> {
const rows = await sql.unsafe(insertUserQuery, [args.name]).values();
if (rows.length !== 1) {
return null;
}
const row = rows[0];
return {
id: row[0],
name: row[1]
};
}
deno.json{
"imports": {
"postgres": "npm:postgres@3.4.4"
},
"exclude": ["generated/query_sql.ts"]
}
以下の設定により、
postgres
というspecifierが
npm:postgres@3.4.4
として解釈されます (
npmレジストリから
Postgres.jsの
v3.4.4
が読み込まれます)
これにより、
sqlcによって生成された
generated/query_sql
などで以下の読み込みが解決できます
typescriptimport postgres from "postgres";
deno.json "exclude": ["generated/query_sql.ts"]
7. main.ts
を用意します
main.tsimport assert from "node:assert/strict";
import postgres from "postgres";
import { findUserByID, insertUser, listUsers } from "./generated/query_sql.ts";
// DATABASE_URL環境変数を読み込みます
// DATABASE_URLには以下のような値を設定しておきます (<username>〜<database>までの各値は適宜置き換えが必要です)
// ```
// DATABASE_URL=postgres://<username>:<password>@<host>:<port>/<database>
// ```
const databaseURL = Deno.env.get("DATABASE_URL");
assert(databaseURL, "`DATABASE_URL` is required");
// Postgres.jsとsqlcで自動生成されたコードを使い、クエリを実行します
const sql = postgres(databaseURL);
try {
const foo = await insertUser(sql, { name: "foo" });
assert.equal(foo.name, "foo");
const found = await findUserByID(sql, { id: foo.id });
assert.deepEqual(foo, found);
const users = await listUsers(sql);
assert(Array.isArray(users));
assert.deepEqual(users.at(-1), foo);
} finally {
// コネクションを閉じます
await sql.end();
}
8. main.ts
を実行します (特にエラーがでなければOK🙆♂️)
shell# DATABASE_URL環境変数が設定されている必要があるためご注意
$ deno run --allow-env --allow-net main.ts
本格的にやるなら
スキーマの管理について
このページでは単純な方法を紹介しましたが、本格的に利用するのであれば以下の方法なども検討するとよいかもしれません
sqlcは
dbmateなどの様々なマイグレーションツールにも対応しているため、これらのマイグレーションファイルを利用する
動的なクエリの生成について
補足
関連ページ