Denoでsqlcを使う
はじめに
Denoで以下の組み合わせを試してみたので内容をまとめておきます 前提
以下のバージョンで動作確認しています
セットアップ
code:sqlc.yaml
version: "2"
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: postgresql
codegen:
- out: generated
plugin: ts
options:
runtime: node
driver: postgres
plugins:
- name: ts
wasm:
sha256: 287df8f6cc06377d67ad5ba02c9e0f00c585509881434d15ea8bd9fc751a9368
重要なのは以下の部分です
code:yaml
plugins:
- name: ts
wasm:
sha256: 287df8f6cc06377d67ad5ba02c9e0f00c585509881434d15ea8bd9fc751a9368
code:yaml
sql:
- 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を記述します code:sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL
);
3. psqlなどでschema.sqlを実行して、テーブルを用意しておきます code:shell
$ psql -f schema.sql "$DATABASE_URL"
4. query.sqlを用意します。このファイルにDMLを記述します code: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ファイルが生成されます code:shell
$ sqlc generate
$ ls generated
query_sql.ts
以下が生成されるファイルのイメージです
code:generated/query_sql.ts
import { 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 => ({
}));
}
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;
}
return {
};
}
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;
}
return {
};
}
code:deno.json
{
"imports": {
"postgres": "npm:postgres@3.4.4"
},
}
以下の設定により、postgresというspecifierがnpm:postgres@3.4.4として解釈されます (npmレジストリからPostgres.jsのv3.4.4が読み込まれます) code:deno.json
"postgres": "npm:postgres@3.4.4"
これにより、sqlcによって生成されたgenerated/query_sqlなどで以下の読み込みが解決できます code:typescript
import postgres from "postgres";
code:deno.json
7. main.tsを用意します
code:main.ts
import 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🙆♂️)
code:shell
# DATABASE_URL環境変数が設定されている必要があるためご注意
$ deno run --allow-env --allow-net main.ts
本格的にやるなら
スキーマの管理について
このページでは単純な方法を紹介しましたが、本格的に利用するのであれば以下の方法なども検討するとよいかもしれません
sqlcはdbmateなどの様々なマイグレーションツールにも対応しているため、これらのマイグレーションファイルを利用する 動的なクエリの生成について
補足
関連ページ