Skip to content

이 인터페이스는 쿼리에 태그付き 템플릿 리터럴을 사용하고 연결 풀링, 트랜잭션, 준비된 문 (prepared statements) 과 같은 기능을 제공하도록 설계되어 간단하고 성능이 뛰어납니다.

ts
import { sql, SQL } from "bun";

// PostgreSQL (기본값)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

// MySQL 사용
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
  SELECT * FROM users 
  WHERE active = ${true}
`;

// SQLite 사용
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
  SELECT * FROM users 
  WHERE active = ${1}
`;

기능

  • SQL 인젝션으로부터 보호하는 태그付き 템플릿 리터럴
  • 트랜잭션
  • 이름付き 및 위치 파라미터
  • 연결 풀링
  • BigInt 지원
  • SASL 인증 지원 (SCRAM-SHA-256), MD5 및 Clear Text
  • 연결 타임아웃
  • 행을 데이터 객체, 배열의 배열 또는 Buffer 로 반환
  • 더 빠른 속도를 위한 바이너리 프로토콜 지원
  • TLS 지원 (및 인증 모드)
  • 환경 변수를 사용한 자동 구성

데이터베이스 지원

Bun.SQL 은 여러 데이터베이스 시스템에 대한 통합 API 를 제공합니다:

PostgreSQL

PostgreSQL 은 다음 경우에 사용됩니다:

  • 연결 문자열이 SQLite 또는 MySQL 패턴과 일치하지 않는 경우 (폴백 어댑터)
  • 연결 문자열이 명시적으로 postgres:// 또는 postgresql:// 프로토콜을 사용하는 경우
  • 연결 문자열이 제공되지 않았고 환경 변수가 PostgreSQL 을 가리키는 경우
ts
import { sql } from "bun";
// DATABASE_URL 이 설정되지 않았거나 PostgreSQL URL 인 경우 사용
await sql`SELECT ...`;

import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;

MySQL

MySQL 지원은 Bun.SQL 에 내장되어 있으며, MySQL 5.7+ 및 MySQL 8.0+ 와 완전히 호환되는 동일한 태그付き 템플릿 리터럴 인터페이스를 제공합니다:

ts
import { SQL } from "bun";

// MySQL 연결
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2 프로토콜도 작동

// 옵션 객체 사용
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// 파라미터와 함께 작동 - 자동으로 준비된 문 사용
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 트랜잭션은 PostgreSQL 과 동일하게 작동
await mysql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});

// 대량 삽입
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;

MySQL 연결 문자열 형식">

MySQL 은 연결 문자열에 다양한 URL 형식을 허용합니다:

ts
// 표준 mysql:// 프로토콜
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // 기본 포트 3306

// mysql2:// 프로토콜 (mysql2 npm 패키지와의 호환성)
new SQL("mysql2://user:pass@localhost:3306/database");

// 쿼리 파라미터 포함
new SQL("mysql://user:pass@localhost/db?ssl=true");

// Unix 소켓 연결
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");

MySQL 전용 기능">

MySQL 데이터베이스는 다음을 지원합니다:

  • 준비된 문 (Prepared statements): 문 캐싱과 함께 파라미터화된 쿼리에 대해 자동으로 생성됨
  • 바이너리 프로토콜: 준비된 문과 정확한 타입 처리를 위한 더 나은 성능
  • 여러 결과 집합: 여러 결과 집합을 반환하는 저장 프로시저 지원
  • 인증 플러그인: mysql_native_password, caching_sha2_password(MySQL 8.0 기본), sha256_password 지원
  • SSL/TLS 연결: PostgreSQL 과 유사하게 구성 가능한 SSL 모드
  • 연결 속성: 모니터링을 위해 서버에 전송되는 클라이언트 정보
  • 쿼리 파이프라이닝: 응답을 기다리지 않고 여러 준비된 문 실행

SQLite

SQLite 지원은 Bun.SQL 에 내장되어 있으며, 동일한 태그付き 템플릿 리터럴 인터페이스를 제공합니다:

ts
import { SQL } from "bun";

// 인메모리 데이터베이스
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// 파일 기반 데이터베이스
const sql1 = new SQL("sqlite://myapp.db");

// 옵션 객체 사용
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// 간단한 파일명의 경우, 모호성을 피하기 위해 어댑터를 명시적으로 지정
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });

SQLite 연결 문자열 형식">

SQLite 는 연결 문자열에 다양한 URL 형식을 허용합니다:

ts
// 표준 sqlite:// 프로토콜
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // 슬래시 없음

// file:// 프로토콜 (SQLite 로도 인식됨)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// 특수 :memory: 데이터베이스
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// 상대 및 절대 경로
new SQL("sqlite://./local.db"); // 현재 디렉터리 기준
new SQL("sqlite://../parent/db.db"); // 부모 디렉터리
new SQL("sqlite:///absolute/path.db"); // 절대 경로

// 쿼리 파라미터 포함
new SQL("sqlite://data.db?mode=ro"); // 읽기 전용 모드
new SQL("sqlite://data.db?mode=rw"); // 읽기 - 쓰기 모드 (생성 없음)
new SQL("sqlite://data.db?mode=rwc"); // 읽기 - 쓰기 - 생성 모드 (기본값)
<Note>
프로토콜이 없는 간단한 파일명 (예: `"myapp.db"`) 은 PostgreSQL 과의 모호성을 피하기 위해 `{ adapter: "sqlite" }` 를 명시적으로 지정해야 합니다.
</Note>

SQLite 전용 옵션">

SQLite 데이터베이스는 추가 구성 옵션을 지원합니다:

ts
const sql = new SQL({
  adapter: "sqlite",
  filename: "app.db",

  // SQLite 전용 옵션
  readonly: false, // 읽기 전용 모드로 열기
  create: true, // 데이터베이스가 없으면 생성
  readwrite: true, // 읽기 및 쓰기용으로 열기

  // 추가 Bun:sqlite 옵션
  strict: true, // 엄격 모드 활성화
  safeIntegers: false, // 정수에 JavaScript 숫자 사용
});

URL 의 쿼리 파라미터는 이러한 옵션을 설정하기 위해 파싱됩니다:

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (기본값)

데이터 삽입

JavaScript 값을 SQL 템플릿 리터럴에 직접 전달할 수 있으며 이스케이프는 자동으로 처리됩니다.

ts
import { sql } from "bun";

// 직접 값을 사용한 기본 삽입
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// 더 깔끔한 구문을 위한 객체 헬퍼 사용
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// 다음과 같이 확장됨: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')

대량 삽입

객체 배열을 SQL 템플릿 리터럴에 전달할 수도 있으며 INSERT INTO ... VALUES ... 문으로 확장됩니다.

ts
const users = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
  { name: "Charlie", email: "charlie@example.com" },
];

await sql`INSERT INTO users ${sql(users)}`;

삽입할 열 선택

sql(object, ...string) 을 사용하여 삽입할 열을 선택할 수 있습니다. 각 열은 객체에 정의되어 있어야 합니다.

ts
const user = {
  name: "Alice",
  email: "alice@example.com",
  age: 25,
};

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// name 과 email 열만 삽입하고 다른 필드는 무시

쿼리 결과

기본적으로 Bun 의 SQL 클라이언트는 쿼리 결과를 객체 배열로 반환하며, 각 객체는 행을 나타내고 열 이름이 키입니다. 그러나 데이터를 다른 형식으로 원할 수 있습니다. 클라이언트는 이를 위해 두 가지 추가 메서드를 제공합니다.

sql``.values() 형식

sql``.values() 메서드는 행을 객체 대신 값의 배열로 반환합니다. 각 행은 쿼리의 열 순서와 동일한 값이 있는 배열이 됩니다.

ts
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);

이는 다음과 같은 결과를 반환합니다:

ts
[
  ["Alice", "alice@example.com"],
  ["Bob", "bob@example.com"],
];

sql``.values() 는 쿼리 결과에 중복된 열 이름이 반환되는 경우에 특히 유용합니다. 객체 (기본값) 를 사용할 때 마지막 열 이름이 객체의 키로 사용되므로 중복된 열 이름은 서로 덮어쓰지만 sql``.values() 를 사용할 때는 각 열이 배열에 있으므로 인덱스로 중복된 열의 값에 액세스할 수 있습니다.

sql``.raw() 형식

.raw() 메서드는 행을 Buffer 객체의 배열로 반환합니다. 이는 바이너리 데이터를 다루거나 성능상의 이유로 유용할 수 있습니다.

ts
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]

SQL 조각

런타임 조건에 따라 쿼리를 동적으로 구성하는 것은 데이터베이스 애플리케이션에서 일반적으로 필요합니다. Bun 은 SQL 인젝션 위험 없이 이를 안전하게 수행하는 방법을 제공합니다.

동적 테이블 이름

테이블이나 스키마를 동적으로 참조해야 할 때는 적절한 이스케이프를 보장하기 위해 sql() 헬퍼를 사용하세요:

ts
// 동적으로 테이블을 안전하게 참조
await sql`SELECT * FROM ${sql("users")}`;

// 스키마 한정자와 함께
await sql`SELECT * FROM ${sql("public.users")}`;

조건부 쿼리

sql() 헬퍼를 사용하여 조건부 절이 있는 쿼리를 구축할 수 있습니다. 이를 통해 애플리케이션의 요구 사항에 따라 유연한 쿼리를 생성할 수 있습니다:

ts
// 선택적 WHERE 절
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

업데이트의 동적 열

sql(object, ...string) 을 사용하여 업데이트할 열을 선택할 수 있습니다. 각 열은 객체에 정의되어 있어야 합니다. 열이 지정되지 않으면 모든 키가 행 업데이트에 사용됩니다.

ts
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// 객체의 모든 키를 사용하여 행 업데이트
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

동적 값 및 where in

값 목록도 동적으로 생성할 수 있어 where in 쿼리도 간단합니다. 선택적으로 객체 배열을 전달하고 사용할 키를 지정할 수 있습니다.

ts
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;

sql.array 헬퍼

sql.array 헬퍼는 JavaScript 배열에서 PostgreSQL 배열 리터럴을 생성합니다:

ts
// PostgreSQL 을 위한 배열 리터럴 생성
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// 생성됨: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// 숫자 배열에서도 작동
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// 생성됨: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

NOTE

`sql.array` 는 PostgreSQL 전용입니다. 다차원 배열과 NULL 요소는 아직 지원되지 않을 수 있습니다.

sql``.simple()

PostgreSQL 와이어 프로토콜은 "simple" 과 "extended" 의 두 가지 쿼리 유형을 지원합니다. Simple 쿼리는 여러 문을 포함할 수 있지만 파라미터를 지원하지 않는 반면, extended 쿼리 (기본값) 는 파라미터를 지원하지만 한 번에 하나의 문만 허용합니다.

단일 쿼리에서 여러 문을 실행하려면 sql``.simple() 를 사용하세요:

ts
// 한 쿼리에서 여러 문
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

Simple 쿼리는 데이터베이스 마이그레이션 및 설정 스크립트에 유용합니다.

Simple 쿼리는 파라미터 (${value}) 를 사용할 수 없습니다. 파라미터가 필요한 경우 쿼리를 별도의 문으로 분할해야 합니다.

파일의 쿼리

sql.file 메서드를 사용하여 파일에서 쿼리를 읽고 실행할 수 있습니다. 파일에 $1, $2 등이 포함되어 있으면 쿼리에 파라미터를 전달할 수 있습니다. 파라미터가 사용되지 않으면 파일당 여러 명령을 실행할 수 있습니다.

ts
const result = await sql.file("query.sql", [1, 2, 3]);

안전하지 않은 쿼리

sql.unsafe 함수를 사용하여 원시 SQL 문자열을 실행할 수 있습니다. 사용자 입력을 이스케이프하지 않으므로 주의해서 사용하세요. 파라미터가 사용되지 않으면 쿼리당 여러 명령을 실행할 수 있습니다.

ts
// 파라미터 없이 여러 명령
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// 파라미터 사용 (하나의 명령만 허용됨)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

쿼리 실행 및 취소

Bun 의 SQL 은 지연적이므로 await 되거나 .execute() 로 실행될 때까지 실행되지 않습니다. 쿼리 객체에서 cancel() 메서드를 호출하여 현재 실행 중인 쿼리를 취소할 수 있습니다.

ts
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;

데이터베이스 환경 변수

sql 연결 파라미터는 환경 변수를 사용하여 구성할 수 있습니다. 클라이언트는 이러한 변수를 특정 우선 순위 순서로 확인하며 연결 문자열 형식에 따라 데이터베이스 유형을 자동으로 감지합니다.

자동 데이터베이스 감지

Bun.sql() 을 인수 없이 또는 new SQL() 을 연결 문자열과 함께 사용할 때 어댑터는 URL 형식에 따라 자동으로 감지됩니다:

MySQL 자동 감지

연결 문자열이 다음 패턴과 일치할 때 MySQL 이 자동으로 선택됩니다:

  • mysql://... - MySQL 프로토콜 URL
  • mysql2://... - MySQL2 프로토콜 URL (호환성 별칭)
ts
// 이들 모두 MySQL 을 자동으로 사용 (어댑터 불필요)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// DATABASE_URL 환경 변수와 함께 작동
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

SQLite 자동 감지

연결 문자열이 다음 패턴과 일치할 때 SQLite 이 자동으로 선택됩니다:

  • :memory: - 인메모리 데이터베이스
  • sqlite://... - SQLite 프로토콜 URL
  • sqlite:... - 슬래시 없는 SQLite 프로토콜
  • file://... - 파일 프로토콜 URL
  • file:... - 슬래시 없는 파일 프로토콜
ts
// 이들 모두 SQLite 을 자동으로 사용 (어댑터 불필요)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// DATABASE_URL 환경 변수와 함께 작동
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.js

PostgreSQL 자동 감지

PostgreSQL 은 MySQL 이나 SQLite 패턴과 일치하지 않는 연결 문자열에 대한 기본값입니다:

bash
# 이러한 패턴에 대해 PostgreSQL 이 감지됨
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# 또는 MySQL 이나 SQLite 패턴과 일치하지 않는 URL
DATABASE_URL="localhost:5432/mydb" bun run app.js

MySQL 환경 변수

MySQL 연결은 환경 변수를 통해 구성할 수 있습니다:

bash
# 기본 연결 URL (먼저 확인됨)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# 대안: MySQL 프로토콜이 있는 DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

연결 URL 이 제공되지 않으면 MySQL 은 다음 개별 파라미터를 확인합니다:

환경 변수기본값설명
MYSQL_HOSTlocalhost데이터베이스 호스트
MYSQL_PORT3306데이터베이스 포트
MYSQL_USERroot데이터베이스 사용자
MYSQL_PASSWORD(비어있음)데이터베이스 비밀번호
MYSQL_DATABASEmysql데이터베이스 이름
MYSQL_URL(비어있음)MySQL 기본 연결 URL
TLS_MYSQL_DATABASE_URL(비어있음)SSL/TLS 활성화 URL

PostgreSQL 환경 변수

다음 환경 변수는 PostgreSQL 연결을 정의하는 데 사용할 수 있습니다:

환경 변수설명
POSTGRES_URLPostgreSQL 기본 연결 URL
DATABASE_URL대안 연결 URL (자동 감지)
PGURL대안 연결 URL
PG_URL대안 연결 URL
TLS_POSTGRES_DATABASE_URLSSL/TLS 활성화 연결 URL
TLS_DATABASE_URL대안 SSL/TLS 활성화 연결 URL

연결 URL 이 제공되지 않으면 시스템은 다음 개별 파라미터를 확인합니다:

환경 변수대체 변수기본값설명
PGHOST-localhost데이터베이스 호스트
PGPORT-5432데이터베이스 포트
PGUSERNAMEPGUSER, USER, USERNAMEpostgres데이터베이스 사용자
PGPASSWORD-(비어있음)데이터베이스 비밀번호
PGDATABASE-사용자명데이터베이스 이름

SQLite 환경 변수

SQLite 연결은 DATABASE_URL 이 SQLite 호환 URL 을 포함할 때 구성할 수 있습니다:

bash
# 이들 모두 SQLite 로 인식됨
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

참고: PostgreSQL 전용 환경 변수 (POSTGRES_URL, PGHOST 등) 는 SQLite 사용 시 무시됩니다.


런타임 사전 연결

Bun 은 애플리케이션 코드가 실행되기 전에 데이터베이스 연결을 설정하여 성능을 향상시키기 위해 시작 시 PostgreSQL 에 사전 연결할 수 있습니다. 이는 첫 번째 데이터베이스 쿼리에서 연결 지연 시간을 줄이는 데 유용합니다.

bash
# PostgreSQL 사전 연결 활성화
bun --sql-preconnect index.js

# DATABASE_URL 환경 변수와 함께 작동
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# 다른 런타임 플래그와 결합 가능
bun --sql-preconnect --hot index.js

--sql-preconnect 플래그는 시작 시 구성된 환경 변수를 사용하여 PostgreSQL 연결을 자동으로 설정합니다. 연결이 실패하더라도 애플리케이션이 충돌하지는 않으며 오류는 우아하게 처리됩니다.


연결 옵션

SQL 생성자에 옵션을 전달하여 데이터베이스 연결을 수동으로 구성할 수 있습니다. 옵션은 데이터베이스 어댑터에 따라 다릅니다:

MySQL 옵션

ts
import { SQL } from "bun";

const sql = new SQL({
  // 옵션 객체 사용 시 MySQL 에 필수
  adapter: "mysql",

  // 연결 세부 정보
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Unix 소켓 연결 (hostname/port 대체)
  // socket: "/var/run/mysqld/mysqld.sock",

  // 연결 풀 설정
  max: 20, // 풀의 최대 연결 수 (기본값: 10)
  idleTimeout: 30, // 30 초 후 유휴 연결 닫기
  maxLifetime: 0, // 연결 수명 (초) (0 = 영구)
  connectionTimeout: 30, // 새 연결 설정 시 타임아웃

  // SSL/TLS 옵션
  ssl: "prefer", // 또는 "disable", "require", "verify-ca", "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // 콜백
  onconnect: client => {
    console.log("MySQL 에 연결됨");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("MySQL 연결 오류:", err);
    } else {
      console.log("MySQL 연결 닫힘");
    }
  },
});

PostgreSQL 옵션

ts
import { SQL } from "bun";

const sql = new SQL({
  // 연결 세부 정보 (PostgreSQL 은 자동으로 감지됨)
  url: "postgres://user:pass@localhost:5432/dbname",

  // 대안 연결 파라미터
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // 연결 풀 설정
  max: 20, // 풀의 최대 연결 수
  idleTimeout: 30, // 30 초 후 유휴 연결 닫기
  maxLifetime: 0, // 연결 수명 (초) (0 = 영구)
  connectionTimeout: 30, // 새 연결 설정 시 타임아웃

  // SSL/TLS 옵션
  tls: true,
  // tls: {
  //   rejectUnauthorized: true,
  //   requestCert: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  //   checkServerIdentity(hostname, cert) {
  //     ...
  //   },
  // },

  // 콜백
  onconnect: client => {
    console.log("PostgreSQL 에 연결됨");
  },
  onclose: client => {
    console.log("PostgreSQL 연결 닫힘");
  },
});

SQLite 옵션

ts
import { SQL } from "bun";

const sql = new SQL({
  // SQLite 에 필수
  adapter: "sqlite",
  filename: "./data/app.db", // 인메모리 데이터베이스는 ":memory:"

  // SQLite 전용 액세스 모드
  readonly: false, // 읽기 전용 모드로 열기
  create: true, // 데이터베이스가 없으면 생성
  readwrite: true, // 읽기 및 쓰기 작업 허용

  // SQLite 데이터 처리
  strict: true, // 더 나은 타입 안전성을 위한 엄격 모드 활성화
  safeIntegers: false, // JS 숫자 범위를 초과하는 정수에 BigInt 사용

  // 콜백
  onconnect: client => {
    console.log("SQLite 데이터베이스 열림");
  },
  onclose: client => {
    console.log("SQLite 데이터베이스 닫힘");
  },
});

SQLite 연결 참고">

  • 연결 풀링: SQLite 는 파일 기반 데이터베이스이므로 연결 풀링을 사용하지 않습니다. 각 SQL 인스턴스는 단일 연결을 나타냅니다.
  • 트랜잭션: SQLite 는 PostgreSQL 과 유사하게 savepoint 를 통해 중첩 트랜잭션을 지원합니다.
  • 동시 액세스: SQLite 는 파일 잠금을 통해 동시 액세스를 처리합니다. 더 나은 동시성을 위해 WAL 모드를 사용하세요.
  • 메모리 데이터베이스: :memory: 를 사용하면 연결 수명 동안만 존재하는 임시 데이터베이스가 생성됩니다.

동적 비밀번호

클라이언트가 액세스 토큰이나 회전하는 비밀번호가 있는 데이터베이스에 연결하는 것과 같은 대체 인증 방식을 사용해야 할 때 연결 시점에 동적 비밀번호 값을 해결하는 동기 또는 비동기 함수를 제공하세요.

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // 다른 연결 구성
  ...
  // 데이터베이스 사용자의 비밀번호 함수
  password: async () => await signer.getAuthToken(),
});

SQLite 전용 기능

쿼리 실행

SQLite 는 비동기 I/O 를 사용하는 PostgreSQL 과 달리 쿼리를 동기적으로 실행합니다. 그러나 API 는 Promise 를 사용하여 일관되게 유지됩니다:

ts
const sqlite = new SQL("sqlite://app.db");

// PostgreSQL 과 동일하게 작동하지만 내부적으로 동기적으로 실행됨
const users = await sqlite`SELECT * FROM users`;

// 파라미터는 동일하게 작동
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

SQLite 프라그마

PRAGMA 문을 사용하여 SQLite 동작을 구성할 수 있습니다:

ts
const sqlite = new SQL("sqlite://app.db");

// 외래 키 활성화
await sqlite`PRAGMA foreign_keys = ON`;

// 더 나은 동시성을 위해 저널 모드를 WAL 로 설정
await sqlite`PRAGMA journal_mode = WAL`;

// 무결성 확인
const integrity = await sqlite`PRAGMA integrity_check`;

데이터 타입 차이

SQLite 는 PostgreSQL 보다 더 유연한 타입 시스템을 가지고 있습니다:

ts
// SQLite 는 5 가지 저장 클래스를 사용합니다: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite 는 타입에 대해 더 관대합니다
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- 문자열로 숫자 저장 가능
    value NUMERIC,    -- 정수, 실수 또는 문자열 저장 가능
    blob BLOB         -- 바이너리 데이터
  )
`;

// JavaScript 값은 자동으로 변환됨
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

트랜잭션

새 트랜잭션을 시작하려면 sql.begin 을 사용하세요. 이 메서드는 PostgreSQL 과 SQLite 모두에서 작동합니다. PostgreSQL 의 경우 풀에서 전용 연결을 예약합니다. SQLite 의 경우 단일 연결에서 트랜잭션을 시작합니다.

BEGIN 명령은 지정한 모든 선택적 구성과 함께 자동으로 전송됩니다. 트랜잭션 중에 오류가 발생하면 프로세스가 원활하게 계속되도록 ROLLBACK 이 트리거됩니다.

기본 트랜잭션

ts
await sql.begin(async tx => {
  // 이 함수의 모든 쿼리는 트랜잭션에서 실행됨
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // 오류가 throw 되지 않으면 트랜잭션이 자동으로 커밋됨
  // 오류가 발생하면 롤백됨
});

필요한 경우 콜백 함수에서 쿼리가 있는 배열을 반환하여 트랜잭션에서 요청을 파이프라이닝할 수도 있습니다:

ts
await sql.begin(async tx => {
  return [
    tx`INSERT INTO users (name) VALUES (${"Alice"})`,
    tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
  ];
});

Savepoint

SQL 의 savepoint 는 트랜잭션 내에서 중간 체크포인트를 생성하여 전체 작업에 영향을 주지 않고 부분 롤백을 가능하게 합니다. 복잡한 트랜잭션에서 유용하며 오류 복구와 일관된 결과 유지를 가능하게 합니다.

ts
await sql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;

  await tx.savepoint(async sp => {
    // 이 부분은 별도로 롤백될 수 있음
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("savepoint 로 롤백");
    }
  });

  // savepoint 가 롤백되더라도 트랜잭션 계속
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

분산 트랜잭션

2 단계 커밋 (2PC) 은 분산 트랜잭션 프로토콜로, 1 단계에서는 코디네이터가 데이터를 쓰고 커밋할 준비가 되었는지 확인하여 노드를 준비하고, 2 단계에서는 코디네이터의 결정에 따라 노드가 커밋하거나 롤백하여 최종화합니다. 이 프로세스는 데이터 내구성과 적절한 잠금 관리를 보장합니다.

PostgreSQL 과 MySQL 에서 분산 트랜잭션은 원래 세션을 넘어 지속되며, 권한이 있는 사용자나 코디네이터가 나중에 커밋하거나 롤백할 수 있습니다. 이는 강력한 분산 트랜잭션, 복구 프로세스 및 관리 작업을 지원합니다.

분산 트랜잭션 중에 예외가 발생하고 catch 되지 않으면 시스템은 자동으로 모든 변경 사항을 롤백합니다. 모든 것이 정상적으로 진행되면 나중에 트랜잭션을 커밋하거나 롤백할 수 있는 유연성을 유지합니다.

ts
// 분산 트랜잭션 시작
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// 나중에 커밋 또는 롤백
await sql.commitDistributed("tx1");
// 또는
await sql.rollbackDistributed("tx1");

인증

Bun 은 SCRAM-SHA-256(SASL), MD5 및 Clear Text 인증을 지원합니다. 더 나은 보안을 위해 SASL 을 권장합니다. 자세한 내용은 Postgres SASL Authentication 을 참조하세요.

SSL 모드 개요

PostgreSQL 은 보안 연결 설정 방식을 제어하는 다양한 SSL/TLS 모드를 지원합니다. 이러한 모드는 연결 시 동작과 수행되는 인증서 확인 수준을 결정합니다.

ts
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL 모드설명
disableSSL/TLS 사용 안 함. 서버가 SSL 을 요구하면 연결이 실패합니다.
prefer먼저 SSL 을 시도하고 SSL 이 실패하면 비 SSL 로 폴백합니다. 지정되지 않은 경우 기본 모드입니다.
require인증서 확인 없이 SSL 을 요구합니다. SSL 을 설정할 수 없으면 실패합니다.
verify-ca서버 인증서가 신뢰할 수 있는 CA 에 의해 서명되었는지 확인합니다. 확인이 실패하면 실패합니다.
verify-full가장 안전한 모드입니다. 인증서와 호스트 이름 일치를 확인합니다. 신뢰할 수 없는 인증서와 MITM 공격으로부터 보호합니다.

연결 문자열과 함께 사용

SSL 모드는 연결 문자열에서도 지정할 수 있습니다:

ts
// prefer 모드 사용
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// verify-full 모드 사용
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");

연결 풀링

Bun 의 SQL 클라이언트는 연결 풀을 자동으로 관리합니다. 이는 여러 쿼리에 재사용되는 데이터베이스 연결 풀로, 각 쿼리에 대한 연결 설정 및 닫기의 오버헤드를 줄이고 데이터베이스에 대한 동시 연결 수를 관리하는 데 도움이 됩니다.

ts
const sql = new SQL({
  // 풀 구성
  max: 20, // 최대 20 개 동시 연결
  idleTimeout: 30, // 30 초 후 유휴 연결 닫기
  maxLifetime: 3600, // 최대 연결 수명 1 시간
  connectionTimeout: 10, // 연결 타임아웃 10 초
});

쿼리가 만들어질 때까지 연결이 생성되지 않습니다.

ts
const sql = Bun.SQL(); // 연결이 생성되지 않음

await sql`...`; // 풀이 max 에 도달할 때까지 시작됨 (가능하다면), 첫 번째 사용 가능한 연결이 사용됨
await sql`...`; // 이전 연결이 재사용됨

// 이제 두 개의 연결이 동시에 사용됨
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // 모든 쿼리가 완료될 때까지 기다리고 풀의 모든 연결 닫기
await sql.close({ timeout: 5 }); // 5 초 기다린 후 풀의 모든 연결 닫기
await sql.close({ timeout: 0 }); // 즉시 풀의 모든 연결 닫기

예약된 연결

Bun 을 사용하면 풀에서 연결을 예약하고 단일 연결을 래핑하는 클라이언트를 반환할 수 있습니다. 이는 격리된 연결에서 쿼리를 실행하는 데 사용할 수 있습니다.

ts
// 풀에서 전용 연결 가져오기
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // 중요: 연결을 풀로 반환
  reserved.release();
}

// 또는 Symbol.dispose 사용
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // 자동으로 해제됨

준비된 문

기본적으로 Bun 의 SQL 클라이언트는 쿼리가 정적이라고 추론할 수 있는 쿼리에 대해 이름付き 준비된 문을 자동으로 생성합니다. 이는 더 나은 성능을 제공합니다. 그러나 연결 옵션에서 prepare: false 를 설정하여 이 동작을 변경할 수 있습니다:

ts
const sql = new SQL({
  // ... 다른 옵션 ...
  prepare: false, // 서버에서 이름付き 준비된 문 지속 비활성화
});

prepare: false 가 설정되면:

쿼리는 여전히 "extended" 프로토콜을 사용하여 실행되지만 이름 없는 준비된 문 을 사용하여 실행됩니다. 이름 없는 준비된 문은 대상으로 이름 없는 문을 지정하는 다음 Parse 문이 발행될 때까지 지속됩니다.

  • 파라미터 바인딩은 여전히 SQL 인젝션으로부터 안전합니다
  • 각 쿼리는 서버에서 처음부터 파싱되고 계획됩니다
  • 쿼리는 파이프라이닝 되지 않습니다

prepare: false 를 사용하려는 경우:

  • 트랜잭션 모드에서 PGBouncer 사용 (PGBouncer 1.21.0 이후 적절히 구성되면 프로토콜 수준 이름付き 준비된 문이 지원됨)
  • 쿼리 실행 계획 디버깅
  • 쿼리 계획을 자주 재생성해야 하는 동적 SQL 작업
  • 쿼리당 여러 명령이 지원되지 않음 (sql``.simple() 사용 제외)

준비된 문을 비활성화하면 다른 파라미터로 자주 실행되는 쿼리의 성능에 영향을 미칠 수 있습니다. 서버가 각 쿼리를 처음부터 파싱하고 계획해야 하기 때문입니다.


오류 처리

클라이언트는 다양한 실패 시나리오에 대해 타입화된 오류를 제공합니다. 오류는 데이터베이스별이며 기본 오류 클래스를 확장합니다:

오류 클래스

ts
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // PostgreSQL 전용 오류
    console.log(error.code); // PostgreSQL 오류 코드
    console.log(error.detail); // 자세한 오류 메시지
    console.log(error.hint); // PostgreSQL 의 유용한 힌트
  } else if (error instanceof SQL.SQLiteError) {
    // SQLite 전용 오류
    console.log(error.code); // SQLite 오류 코드 (예: "SQLITE_CONSTRAINT")
    console.log(error.errno); // SQLite 오류 번호
    console.log(error.byteOffset); // SQL 문의 바이트 오프셋 (사용 가능한 경우)
  } else if (error instanceof SQL.SQLError) {
    // 일반 SQL 오류 (기본 클래스)
    console.log(error.message);
  }
}

PostgreSQL 전용 오류 코드">

PostgreSQL 연결 오류

연결 오류설명
ERR_POSTGRES_CONNECTION_CLOSED연결이 종료되거나 설정되지 않음
ERR_POSTGRES_CONNECTION_TIMEOUT타임아웃 기간 내에 연결 설정 실패
ERR_POSTGRES_IDLE_TIMEOUT비활성으로 인해 연결 닫힘
ERR_POSTGRES_LIFETIME_TIMEOUT연결이 최대 수명을 초과
ERR_POSTGRES_TLS_NOT_AVAILABLESSL/TLS 연결을 사용할 수 없음
ERR_POSTGRES_TLS_UPGRADE_FAILED연결을 SSL/TLS 로 업그레이드 실패

인증 오류

인증 오류설명
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2비밀번호 인증 실패
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD서버가 알 수 없는 인증 방법 요청
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD서버가 지원되지 않는 인증 방법 요청
ERR_POSTGRES_INVALID_SERVER_KEY인증 중 잘못된 서버 키
ERR_POSTGRES_INVALID_SERVER_SIGNATURE잘못된 서버 서명
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64잘못된 SASL 서명 인코딩
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL 서명 확인 실패

쿼리 오류

쿼리 오류설명
ERR_POSTGRES_SYNTAX_ERROR잘못된 SQL 구문 (SyntaxError 확장)
ERR_POSTGRES_SERVER_ERRORPostgreSQL 서버의 일반 오류
ERR_POSTGRES_INVALID_QUERY_BINDING잘못된 파라미터 바인딩
ERR_POSTGRES_QUERY_CANCELLED쿼리가 취소됨
ERR_POSTGRES_NOT_TAGGED_CALL쿼리가 태그付き 호출 없이 호출됨

데이터 타입 오류

데이터 타입 오류설명
ERR_POSTGRES_INVALID_BINARY_DATA잘못된 바이너리 데이터 형식
ERR_POSTGRES_INVALID_BYTE_SEQUENCE잘못된 바이트 시퀀스
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING인코딩 오류
ERR_POSTGRES_INVALID_CHARACTER데이터의 잘못된 문자
ERR_POSTGRES_OVERFLOW숫자 오버플로우
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT지원되지 않는 바이너리 형식
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE지원되지 않는 정수 크기
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET다차원 배열은 아직 지원되지 않음
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET배열의 NULL 값은 아직 지원되지 않음

프로토콜 오류

프로토콜 오류설명
ERR_POSTGRES_EXPECTED_REQUEST클라이언트 요청 예상
ERR_POSTGRES_EXPECTED_STATEMENT준비된 문 예상
ERR_POSTGRES_INVALID_BACKEND_KEY_DATA잘못된 백엔드 키 데이터
ERR_POSTGRES_INVALID_MESSAGE잘못된 프로토콜 메시지
ERR_POSTGRES_INVALID_MESSAGE_LENGTH잘못된 메시지 길이
ERR_POSTGRES_UNEXPECTED_MESSAGE예상치 못한 메시지 유형

트랜잭션 오류

트랜잭션 오류설명
ERR_POSTGRES_UNSAFE_TRANSACTION안전하지 않은 트랜잭션 작업 감지
ERR_POSTGRES_INVALID_TRANSACTION_STATE잘못된 트랜잭션 상태

SQLite 전용 오류

SQLite 오류는 SQLite 의 표준 오류 코드에 해당하는 오류 코드와 번호를 제공합니다:

일반 SQLite 오류 코드">

오류 코드errno설명
SQLITE_CONSTRAINT19제약 위반 (UNIQUE, CHECK, NOT NULL 등)
SQLITE_BUSY5데이터베이스가 잠김
SQLITE_LOCKED6데이터베이스의 테이블이 잠김
SQLITE_READONLY8읽기 전용 데이터베이스에 쓰기 시도
SQLITE_IOERR10디스크 I/O 오류
SQLITE_CORRUPT11데이터베이스 디스크 이미지가 손상됨
SQLITE_FULL13데이터베이스 또는 디스크가 가득 참
SQLITE_CANTOPEN14데이터베이스 파일을 열 수 없음
SQLITE_PROTOCOL15데이터베이스 잠금 프로토콜 오류
SQLITE_SCHEMA17데이터베이스 스키마가 변경됨
SQLITE_TOOBIG18문자열 또는 BLOB 이 크기 제한을 초과
SQLITE_MISMATCH20데이터 타입 불일치
SQLITE_MISUSE21라이브러리가 잘못 사용됨
SQLITE_AUTH23인증 거부

오류 처리 예제:

ts
const sqlite = new SQL("sqlite://app.db");

try {
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // 중복 ID
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("제약 위반:", error.message);
      // 고유 제약 위반 처리
    }
  }
}

숫자 및 BigInt

Bun 의 SQL 클라이언트는 53 비트 정수 범위를 초과하는 큰 숫자에 대한 특별한 처리를 포함합니다. 작동 방식은 다음과 같습니다:

ts
import { sql } from "bun";

const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;

console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345

문자열 대신 BigInt

큰 숫자를 문자열 대신 BigInt 로 필요로 할 때는 SQL 클라이언트를 초기화할 때 bigint 옵션을 true 로 설정하여 활성화할 수 있습니다:

ts
const sql = new SQL({
  bigint: true,
});

const [{ x }] = await sql`SELECT 9223372036854777 as x`;

console.log(typeof x, x); // "bigint" 9223372036854777n

로드맵

아직 완료되지 않은 몇 가지 사항이 있습니다.

  • --db-preconnect Bun CLI 플래그를 통한 연결 사전 로딩
  • 열 이름 변환 (예: snake_case 에서 camelCase 로). 이는 대부분 C++ 에서 WebKit 의 WTF::String 를 사용하여 대소문자를 변경하는 유니코드 인식 구현이 막혀 있습니다.
  • 열 타입 변환

데이터베이스별 기능

인증 방법

MySQL 은 서버에 의해 자동으로 협상되는 여러 인증 플러그인을 지원합니다:

  • mysql_native_password - 전통적인 MySQL 인증, 널리 호환됨
  • caching_sha2_password - MySQL 8.0+ 의 기본값, RSA 키 교환으로 더 안전함
  • sha256_password - SHA-256 기반 인증

클라이언트는 서버에서 요청할 때 인증 플러그인 전환을 자동으로 처리하며, 비 SSL 연결에서도 안전한 비밀번호 교환을 포함합니다.

준비된 문 및 성능

MySQL 은 모든 파라미터화된 쿼리에 대해 서버 측 준비된 문을 사용합니다:

ts
// 이는 자동으로 서버에서 준비된 문을 생성합니다
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 준비된 문은 동일한 쿼리에 대해 캐시되고 재사용됨
for (const id of userIds) {
  // 동일한 준비된 문이 재사용됨
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// 쿼리 파이프라이닝 - 여러 문이 응답을 기다리지 않고 전송됨
const [users, orders, products] = await Promise.all([
  mysql`SELECT * FROM users WHERE active = ${true}`,
  mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
  mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);

여러 결과 집합

MySQL 은 다중 문 쿼리에서 여러 결과 집합을 반환할 수 있습니다:

ts
const mysql = new SQL("mysql://user:pass@localhost/mydb");

// simple() 메서드를 사용한 다중 문 쿼리
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

문자 세트 및 Collation

Bun.SQL 은 이모지를 포함한 전체 유니코드 지원을 보장하기 위해 MySQL 연결에 utf8mb4 문자 세트를 자동으로 사용합니다. 이는 현대 MySQL 애플리케이션에 권장되는 문자 세트입니다.

연결 속성

Bun 은 더 나은 모니터링을 위해 MySQL 에 클라이언트 정보를 자동으로 전송합니다:

ts
// 이러한 속성은 자동으로 전송됨:
// _client_name: "Bun"
// _client_version: <bun 버전>
// MySQL 의 performance_schema.session_connect_attrs 에서 확인할 수 있음

타입 처리

MySQL 타입은 자동으로 JavaScript 타입으로 변환됩니다:

MySQL 타입JavaScript 타입참고
INT, TINYINT, MEDIUMINTnumber안전한 정수 범위 내
BIGINTstring, number 또는 BigInti32/u32 크기에 맞으면 number, 그렇지 않으면 string 또는 BigInt (bigint 옵션에 따라)
DECIMAL, NUMERICstring정밀도 유지를 위해
FLOAT, DOUBLEnumber
DATEDateJavaScript Date 객체
DATETIME, TIMESTAMPDate타임존 처리 포함
TIMEnumber총 마이크로초
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringBLOB 타입은 TEXT 타입의 별칭
JSONobject/array자동으로 파싱됨
BIT(1)booleanMySQL 의 BIT(1)
GEOMETRYstring지오메트리 데이터

PostgreSQL 과의 차이점

API 는 통합되지만 몇 가지 동작 차이가 있습니다:

  1. 파라미터 플레이스홀더: MySQL 은 내부적으로 ? 를 사용하지만 Bun 은 자동으로 $1, $2 스타일로 변환합니다
  2. RETURNING 절: MySQL 은 RETURNING 을 지원하지 않음; result.lastInsertRowid 또는 별도 SELECT 사용
  3. 배열 타입: MySQL 은 PostgreSQL 과 같은 네이티브 배열 타입이 없습니다

MySQL 전용 기능

아직 LOAD DATA INFILE 지원을 구현하지 않았습니다

PostgreSQL 전용 기능

아직 다음을 구현하지 않았습니다:

  • COPY 지원
  • LISTEN 지원
  • NOTIFY 지원

또한 다음과 같은 덜 일반적인 기능도 구현하지 않았습니다:

  • GSSAPI 인증
  • SCRAM-SHA-256-PLUS 지원
  • Point 및 PostGIS 타입
  • 모든 다차원 정수 배열 타입 (몇 가지 타입만 지원됨)

일반적인 패턴 및 모범 사례

MySQL 결과 집합 작업

ts
// INSERT 후 삽입 ID 가져오기
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL 의 LAST_INSERT_ID()

// 영향받은 행 처리
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // 업데이트된 행 수

// MySQL 전용 함수 사용
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

MySQL 오류 처리

ts
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("중복 항목 감지됨");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("액세스 거부됨");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("데이터베이스가 존재하지 않음");
  }
  // MySQL 오류 코드는 mysql/mysql2 패키지와 호환됨
}

MySQL 성능 팁

  1. 연결 풀링 사용: 작업 부하에 따라 적절한 max 풀 크기 설정
  2. 준비된 문 활성화: 기본적으로 활성화되어 있으며 성능 향상
  3. 대량 작업에 트랜잭션 사용: 관련 쿼리를 트랜잭션으로 그룹화
  4. 적절히 인덱싱: MySQL 은 쿼리 성능을 위해 인덱스에 크게 의존
  5. utf8mb4 문자 세트 사용: 기본적으로 설정되어 있으며 모든 유니코드 문자 처리

자주 묻는 질문

왜 이것이 `Bun.sql` 이지 `Bun.postgres` 인가요?
	미래에 더 많은 데이터베이스 드라이버를 추가할 계획이었습니다. 이제 MySQL 지원이 추가되어 이 통합 API 는 PostgreSQL, MySQL 및 SQLite 를 지원합니다.
어떤 데이터베이스 어댑터가 사용되는지 어떻게 알 수 있나요?
	어댑터는 연결 문자열에서 자동으로 감지됩니다:

	- `mysql://` 또는 `mysql2://` 로 시작하는 URL 은 MySQL 사용
	- SQLite 패턴 (`:memory:`, `sqlite://`, `file://`) 과 일치하는 URL 은 SQLite 사용
	- 그 외는 모두 PostgreSQL 이 기본값입니다

MySQL 저장 프로시저가 지원되나요?">
	예, 저장 프로시저는 OUT 파라미터와 여러 결과 집합을 포함하여 완전히 지원됩니다:

	```ts
	// 저장 프로시저 호출
	const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

	// OUT 파라미터 가져오기
	const outParam = await mysql`SELECT @total_orders as total`;
	```

MySQL 전용 SQL 구문을 사용할 수 있나요?">
	예, MySQL 전용 구문을 자유롭게 사용할 수 있습니다:

	```ts
	// MySQL 전용 구문 작동
	await mysql`SET @user_id = ${userId}`;
	await mysql`SHOW TABLES`;
	await mysql`DESCRIBE users`;
	await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
	```

왜 기존 라이브러리를 사용하지 않나요?

postgres.js, pg 및 node-postgres 와 같은 npm 패키지도 Bun 에서 사용할 수 있습니다. 훌륭한 옵션입니다.

두 가지 이유가 있습니다:

  1. 개발자가 Bun 에 내장된 데이터베이스 드라이버를 사용하는 것이 더 간단하다고 생각합니다. 라이브러리 쇼핑에 보내는 시간은 앱을 구축하는 데 사용할 수 있는 시간입니다.
  2. 라이브러리로 구현하기 어려운 객체를 생성하기 위해 JavaScriptCore 엔진 내부를 활용하여 더 빠르게 만듭니다

크레딧

API 인터페이스에 영감을 준 @porsagerpostgres.js 에 큰 감사를 드립니다.

Bun by www.bunjs.com.cn 편집