데이터데이터베이스
env.db
워커가 보는 데이터베이스 API 는 넷입니다. exec · tryExec · session · identity.
export interface Db {
exec(sql: string, params?: unknown[]): Promise<unknown[]>;
tryExec(sql: string, params?: unknown[]): Promise<TryExecResult>;
session(): Promise<DbSession>;
identity(): Promise<{ project: string; epoch: number }>;
}exec
성공하면 행 배열을, 실패하면 던집니다.
const rows = await env.db.exec(
"insert into posts (title) values ($1) returning id",
["hello"],
);파라미터는 $1 부터입니다. 문자열을 이어 붙이지 마세요 — 두 번째 인자가 바인딩입니다.
tryExec
던지지 않고 결과를 돌려줍니다. SQLSTATE 를 코드로 읽는 유일한 방법입니다.
const r = await env.db.tryExec("insert into users (email) values ($1)", [email]);
if (!r.ok) {
if (r.error.code === "23505") return new Response("이미 있는 이메일입니다", { status: 409 });
throw new Error(r.error.message);
}성공한 결과의 모양입니다.
{
ok: true,
columns: [{ name: "id", typeOid: 23 }],
values: [[1]], // 위치 배열. 열 이름이 겹쳐도 셀을 잃지 않습니다
rows: [{ id: 1 }],
commandTag: "SELECT 1",
transactionStatus: "I",
}실패한 결과입니다.
{
ok: false,
error: { code: "23505", message: "…", detail: null, hint: null, position: null, severity: "ERROR" },
}exec 이 던지는 Error 에는 .code 가 없습니다. 던진 객체의 커스텀 필드는 워커 경계를 넘지 못하기 때문입니다. SQLSTATE 는 메시지 앞에 [42P01] 처럼 붙여 두었지만, 프로그램이 읽을 계약은 tryExec(...).error.code 하나입니다. @runlot/pg 를 쓰시면 err.code 가 정상으로 옵니다 — 그쪽은 워커 안의 JS 라 경계를 넘지 않습니다.session
BEGIN … COMMIT 처럼 여러 문장이 한 세션에 있어야 할 때 씁니다.
const s = await env.db.session();
try {
await s.exec("begin");
await s.exec("update accounts set balance = balance - $1 where id = $2", [100, 1]);
await s.exec("update accounts set balance = balance + $1 where id = $2", [100, 2]);
await s.exec("commit");
} finally {
await s.close();
}close() 는 멱등이고, 열려 있던 블록을 롤백합니다. 커밋은 여러분이 COMMIT 을 보내는 것이지 close 의 부수 효과가 아닙니다. 자세한 것은 세션 에 있습니다.
identity
지금 어느 프로젝트의 몇 번째 세대인지 알려 줍니다. 디버깅과 로그에 씁니다.
const { project, epoch } = await env.db.identity();값이 옮겨지는 규칙
조용한 손실이 없는 것이 규칙입니다.
| 타입 | JS 로 |
|---|---|
int2 int4 float4 float8 | number |
int8 numeric | string — number 로 조용히 반올림하지 않습니다 |
bool | boolean |
text varchar uuid | string |
bytea | ArrayBuffer |
json jsonb | 파싱한 값. 실패하면 원문 문자열을 보존합니다 |
timestamp timestamptz date | string — 타임존과 정밀도를 잃지 않습니다 |
| 배열·도메인·확장 타입 | { text, typeOid } 또는 문자열 |
표에 없는 타입은 문자열로 흘립니다. base 타입을 추측하지 않습니다.
@runlot/pg 는 이 위에 node-postgres 의 기본 파서를 얹습니다. 그래서 그쪽에서는
timestamptz 와 date 가 Date 객체로 옵니다. 두 표면의 날짜 타입이 다릅니다.