PostgreSQL 빠른 시작
이 페이지에는 Node >=22.18.0과 Docker가 필요합니다. Node 22.18.0은 TypeScript 예제를 바로 실행합니다.
1. 데이터베이스 시작하기
섹션 제목: “1. 데이터베이스 시작하기”로컬 PostgreSQL 서버를 시작하고, 예제가 읽는 테이블 두 개를 만드세요. 이 테이블이 있는 서버가 이미 있다면 DATABASE_URL을 설정하고 이 단계를 건너뛰세요.
docker run -d --name braid-postgres -e POSTGRES_PASSWORD=braid -e POSTGRES_DB=braid -p 5432:5432 postgres:17until docker exec braid-postgres psql -h 127.0.0.1 -U postgres -d braid -c "SELECT 1" >/dev/null 2>&1; do sleep 1; donedocker exec -i braid-postgres psql -U postgres -d braid <<'SQL'CREATE TABLE users (id serial PRIMARY KEY, name text NOT NULL);INSERT INTO users (name) VALUES ('Ada'), ('Grace');CREATE TABLE accounts (id serial PRIMARY KEY, email text NOT NULL);INSERT INTO accounts (email) VALUES ('ada@example.com');SQLexport DATABASE_URL=postgres://postgres:braid@localhost:5432/braid2. 프로젝트 만들기
섹션 제목: “2. 프로젝트 만들기”SQLBraid 런타임 파사드, PostgreSQL 방언 패키지, pg 드라이버를 함께 설치하세요.
mkdir braid-postgres-app && cd braid-postgres-apppnpm init --init-type modulepnpm add sqlbraid @sqlbraid/postgres pgmkdir src물리 클라이언트에 직접 연결하기
섹션 제목: “물리 클라이언트에 직접 연결하기”직접 연결 팩토리는 연결된 pg.Client나 pg.PoolClient를 받습니다. pg.Pool은 받지 않습니다. 이 코드를 src/direct.ts로 저장하고 node src/direct.ts로 실행하세요.
import { Client } from "pg";import { createPgDatabase } from "@sqlbraid/postgres/pg";import { sql } from "@sqlbraid/postgres";
interface UserRow { id: string; name: string;}
const client = new Client({ connectionString: process.env.DATABASE_URL });await client.connect();const db = createPgDatabase(client);
try { const users = await db.all<UserRow>(sql.rows<UserRow>` SELECT id, name FROM users ORDER BY id `); console.log(users);} finally { await client.end();}풀 기반 데이터베이스
섹션 제목: “풀 기반 데이터베이스”애플리케이션이 pg.Pool을 쓴다면 풀 팩토리를 쓰세요. 이 코드를 src/pool.ts로 저장하고 node src/pool.ts로 실행하세요.
import { Pool } from "pg";import { createPgPoolDatabase } from "@sqlbraid/postgres/pg";import { sql } from "@sqlbraid/postgres";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });const db = createPgPoolDatabase(pool);const accountId = 1;
try { const account = await db.one(sql.rows<{ id: string; email: string }>` SELECT id, email FROM accounts WHERE id = ${accountId} `); console.log(account);} finally { await pool.end();}작업을 마쳤다면 docker rm -f braid-postgres로 서버를 멈추세요.
풀을 쓰는 루트 작업은 커넥션 리스 하나를 획득하고, 물리 I/O를 실행하고, 리스를 반환한 다음, 메모리로 읽은 결과를 매핑합니다. 커넥션을 고정하는 명시적인 경계는 트랜잭션입니다. 트랜잭션 안에서는 모든 작업에 콜백 핸들을 쓰세요. 풀 종료는 애플리케이션이 맡습니다.
pg 바인딩 어댑터는 논리적인 RenderedStatement를 받아 텍스트·위치 기반 $1, $2, … 플레이스홀더와 순서가 있는 값 배열로 바꿉니다. 이 단계는 드라이버가 맡으며, 순수한 바인딩 설명 이후, 리스 획득 이전에 일어납니다. 플레이스홀더 표기는 PostgreSQL 방언이 정하지 않습니다. 어댑터는 드라이버가 맡는 단순 재사용 방식을 보고합니다. pg에는 파라미터별 타입 옵션이 없으므로, 어댑터는 모든 파라미터 힌트를 I/O 전에 거부합니다. postgres.js와 PGlite 어댑터는 힌트를 적용합니다. 파라미터 타입 힌트를 보세요.
이 경계에 대한 자세한 내용은 직접 연결과 풀, 트랜잭션을 보세요.
스트리밍과 루틴
섹션 제목: “스트리밍과 루틴”애플리케이션이 db.stream()을 쓸 때만 pg-cursor를 설치하세요.
pnpm add pg-cursor일반 쿼리에는 이 피어 의존성이 필요 없습니다.
- PostgreSQL 스트리밍은 커서로 묶음 단위로 읽습니다. 이 기능이 없으면
BRAID_STREAM_UNSUPPORTED를 보고합니다. - 중단하면 진행 중인 묶음 읽기를 포함해 물리
Client.end()가 끝날 때까지 기다린 뒤 커넥션을 폐기합니다. - 풀은 그 커넥션을 새것으로 바꿉니다. 직접 클라이언트는 직접 교체해야 합니다.
- 중단 가능한 스트림을 쓰려면 직접 만든 래퍼가
end()를 제공해야 합니다.
refcursor OUT·INOUT 파라미터가 있는 루틴에는 postgresParameter.refcursor()를 쓰세요. 루틴은 기존 db.tx(async (tx) => tx.call(query)) 범위 안에서 호출합니다. SQLBraid는 트랜잭션의 포털을 가져와 닫고, 스칼라 output에서 빼고, 그 행을 resultSets로 반환합니다. 숨은 트랜잭션은 만들지 않습니다.
pg 표현 방식 프로필
섹션 제목: “pg 표현 방식 프로필”기본 pg 프로필은 pg-lossless-text입니다. 드라이버가 텍스트를 줄 수 있으면 JSON과 날짜·시간 값을 텍스트로 유지합니다. @sqlbraid/postgres는 typePolicyForProfile({ json, temporal })과 변경할 수 없는 representationProfiles 설명 객체를 export합니다. 그래서 런타임과 코드 생성이 정확히 같은 정책을 쓸 수 있습니다.
import { typePolicyForProfile } from "@sqlbraid/postgres/pg";import { generateModels } from "@sqlbraid/codegen";
const typePolicy = typePolicyForProfile({ json: "text", temporal: "text" });const generated = generateModels(snapshot, { typePolicy });{ json: "native", temporal: "native" }는 별도의 pg-native 호환 프로필을 고릅니다. 여기서 네이티브란 node-postgres의 OID별 기본 파서 동작을 뜻합니다. 모든 날짜·시간 타입이 Date가 된다는 뜻은 아닙니다. 직접 만든 pg-types 파서는 또 다른 프로필이며, 고유한 원시 값 근거가 필요합니다.
| 값 | 드라이버 원시 값 / SQLBraid 정규 출력 | 정확도 경계 |
|---|---|---|
int2 / int4 / int8 / oid |
드라이버에 따라 다름 → string |
정확한 출력은 정규 텍스트이며, number나 bigint가 되지 않습니다. |
numeric / decimal |
텍스트 → string |
JavaScript number는 정확한 값으로 인정하지 않습니다. |
float4 / float8 |
number → number |
근사 이진 값입니다. 무손실 텍스트 읽기 프로필에서는 SHOW extra_float_digits가 양수여야 합니다. |
money |
미지원 | PostgreSQL의 로캘 형식 텍스트는 정규 숫자 값이 아닙니다. 형식을 명시한 변환을 직접 쓰세요. |
json / jsonb |
텍스트 → string, 네이티브 → unknown |
파싱된 최상위 값은 문자열, 숫자, 불리언, null, 배열, 객체일 수 있습니다. 네이티브에서는 중첩 숫자의 정확도를 보장하지 않습니다. |
date / timestamp / timestamptz |
텍스트 → string, 네이티브 → Date |
네이티브 모드에서도 time/timetz는 텍스트로 남고, interval은 unknown입니다. |
bytea |
Buffer |
바이트를 그대로 유지하거나 명시적으로 인코딩하세요. |
uuid |
string | 필요하면 애플리케이션 스키마에서 형식을 검증하세요. |
- 문서화된 프로필이 왕복 정확도를 증명하는 경우, 정확한 문자열 입력은 텍스트·위치 기반 바인딩 경로로 지원됩니다.
- 일반
undefined바인딩은 획득 전에BRAID_BIND_VALUE_UNSUPPORTED로 실패합니다.null은 SQLNULL입니다. - 배열, 도메인, 범위, 다중 범위, 복합 타입은 분류되지 않은 컨테이너입니다. 원소 타입이 정확한 스칼라여도 마찬가지입니다.
db.environment()는 선택한 프로필(예: pg-lossless-text)과 TypePolicy의 id·hash를 반환합니다. 서버의 extra_float_digits 설정을 확인하지만 설정값 자체는 노출하지 않습니다. 값이 0보다 크면 numeric.approximate-float를 guaranteed로 보고합니다. 그렇지 않으면 pg.extra-float-digits 조건이 붙은 guarded로 보고합니다. 테스트된 데이터베이스·드라이버·프로필·런타임 조합은 런타임·드라이버 지원 매트릭스에서 확인하세요.
pg-cursor가 네이티브 풀(pull) 스트림을 제공합니다. 피어 의존성이 없으면BRAID_STREAM_UNSUPPORTED가 납니다.- 루틴 refcursor는 기존 트랜잭션이 필요하며, 메모리로 읽은 결과 집합이 됩니다.
- 벌크는 어댑터가 증명한 네이티브 또는 준비된 문장 방식을 씁니다. SQL을 고쳐 쓰지 않습니다.
RETURNING을 포함한 네이티브 PostgreSQL SQL은 바뀌지 않고 그대로 전달됩니다. SQLBraid가 PostgreSQL 문법을 지원한다는 뜻은 아닙니다.