이 인터페이스는 쿼리에 태그付き 템플릿 리터럴을 사용하고 연결 풀링, 트랜잭션, 준비된 문 (prepared statements) 과 같은 기능을 제공하도록 설계되어 간단하고 성능이 뛰어납니다.
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 을 가리키는 경우
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+ 와 완전히 호환되는 동일한 태그付き 템플릿 리터럴 인터페이스를 제공합니다:
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 형식을 허용합니다:
// 표준 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 에 내장되어 있으며, 동일한 태그付き 템플릿 리터럴 인터페이스를 제공합니다:
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 형식을 허용합니다:
// 표준 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 데이터베이스는 추가 구성 옵션을 지원합니다:
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=ro→readonly: true?mode=rw→readonly: false, create: false?mode=rwc→readonly: false, create: true(기본값)
데이터 삽입
JavaScript 값을 SQL 템플릿 리터럴에 직접 전달할 수 있으며 이스케이프는 자동으로 처리됩니다.
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 ... 문으로 확장됩니다.
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) 을 사용하여 삽입할 열을 선택할 수 있습니다. 각 열은 객체에 정의되어 있어야 합니다.
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() 메서드는 행을 객체 대신 값의 배열로 반환합니다. 각 행은 쿼리의 열 순서와 동일한 값이 있는 배열이 됩니다.
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);이는 다음과 같은 결과를 반환합니다:
[
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
];sql``.values() 는 쿼리 결과에 중복된 열 이름이 반환되는 경우에 특히 유용합니다. 객체 (기본값) 를 사용할 때 마지막 열 이름이 객체의 키로 사용되므로 중복된 열 이름은 서로 덮어쓰지만 sql``.values() 를 사용할 때는 각 열이 배열에 있으므로 인덱스로 중복된 열의 값에 액세스할 수 있습니다.
sql``.raw() 형식
.raw() 메서드는 행을 Buffer 객체의 배열로 반환합니다. 이는 바이너리 데이터를 다루거나 성능상의 이유로 유용할 수 있습니다.
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]SQL 조각
런타임 조건에 따라 쿼리를 동적으로 구성하는 것은 데이터베이스 애플리케이션에서 일반적으로 필요합니다. Bun 은 SQL 인젝션 위험 없이 이를 안전하게 수행하는 방법을 제공합니다.
동적 테이블 이름
테이블이나 스키마를 동적으로 참조해야 할 때는 적절한 이스케이프를 보장하기 위해 sql() 헬퍼를 사용하세요:
// 동적으로 테이블을 안전하게 참조
await sql`SELECT * FROM ${sql("users")}`;
// 스키마 한정자와 함께
await sql`SELECT * FROM ${sql("public.users")}`;조건부 쿼리
sql() 헬퍼를 사용하여 조건부 절이 있는 쿼리를 구축할 수 있습니다. 이를 통해 애플리케이션의 요구 사항에 따라 유연한 쿼리를 생성할 수 있습니다:
// 선택적 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) 을 사용하여 업데이트할 열을 선택할 수 있습니다. 각 열은 객체에 정의되어 있어야 합니다. 열이 지정되지 않으면 모든 키가 행 업데이트에 사용됩니다.
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 쿼리도 간단합니다. 선택적으로 객체 배열을 전달하고 사용할 키를 지정할 수 있습니다.
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 배열 리터럴을 생성합니다:
// 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() 를 사용하세요:
// 한 쿼리에서 여러 문
await sql`
SELECT 1;
SELECT 2;
`.simple();Simple 쿼리는 데이터베이스 마이그레이션 및 설정 스크립트에 유용합니다.
Simple 쿼리는 파라미터 (${value}) 를 사용할 수 없습니다. 파라미터가 필요한 경우 쿼리를 별도의 문으로 분할해야 합니다.
파일의 쿼리
sql.file 메서드를 사용하여 파일에서 쿼리를 읽고 실행할 수 있습니다. 파일에 $1, $2 등이 포함되어 있으면 쿼리에 파라미터를 전달할 수 있습니다. 파라미터가 사용되지 않으면 파일당 여러 명령을 실행할 수 있습니다.
const result = await sql.file("query.sql", [1, 2, 3]);안전하지 않은 쿼리
sql.unsafe 함수를 사용하여 원시 SQL 문자열을 실행할 수 있습니다. 사용자 입력을 이스케이프하지 않으므로 주의해서 사용하세요. 파라미터가 사용되지 않으면 쿼리당 여러 명령을 실행할 수 있습니다.
// 파라미터 없이 여러 명령
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() 메서드를 호출하여 현재 실행 중인 쿼리를 취소할 수 있습니다.
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;데이터베이스 환경 변수
sql 연결 파라미터는 환경 변수를 사용하여 구성할 수 있습니다. 클라이언트는 이러한 변수를 특정 우선 순위 순서로 확인하며 연결 문자열 형식에 따라 데이터베이스 유형을 자동으로 감지합니다.
자동 데이터베이스 감지
Bun.sql() 을 인수 없이 또는 new SQL() 을 연결 문자열과 함께 사용할 때 어댑터는 URL 형식에 따라 자동으로 감지됩니다:
MySQL 자동 감지
연결 문자열이 다음 패턴과 일치할 때 MySQL 이 자동으로 선택됩니다:
mysql://...- MySQL 프로토콜 URLmysql2://...- MySQL2 프로토콜 URL (호환성 별칭)
// 이들 모두 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.jsSQLite 자동 감지
연결 문자열이 다음 패턴과 일치할 때 SQLite 이 자동으로 선택됩니다:
:memory:- 인메모리 데이터베이스sqlite://...- SQLite 프로토콜 URLsqlite:...- 슬래시 없는 SQLite 프로토콜file://...- 파일 프로토콜 URLfile:...- 슬래시 없는 파일 프로토콜
// 이들 모두 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.jsPostgreSQL 자동 감지
PostgreSQL 은 MySQL 이나 SQLite 패턴과 일치하지 않는 연결 문자열에 대한 기본값입니다:
# 이러한 패턴에 대해 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.jsMySQL 환경 변수
MySQL 연결은 환경 변수를 통해 구성할 수 있습니다:
# 기본 연결 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_HOST | localhost | 데이터베이스 호스트 |
MYSQL_PORT | 3306 | 데이터베이스 포트 |
MYSQL_USER | root | 데이터베이스 사용자 |
MYSQL_PASSWORD | (비어있음) | 데이터베이스 비밀번호 |
MYSQL_DATABASE | mysql | 데이터베이스 이름 |
MYSQL_URL | (비어있음) | MySQL 기본 연결 URL |
TLS_MYSQL_DATABASE_URL | (비어있음) | SSL/TLS 활성화 URL |
PostgreSQL 환경 변수
다음 환경 변수는 PostgreSQL 연결을 정의하는 데 사용할 수 있습니다:
| 환경 변수 | 설명 |
|---|---|
POSTGRES_URL | PostgreSQL 기본 연결 URL |
DATABASE_URL | 대안 연결 URL (자동 감지) |
PGURL | 대안 연결 URL |
PG_URL | 대안 연결 URL |
TLS_POSTGRES_DATABASE_URL | SSL/TLS 활성화 연결 URL |
TLS_DATABASE_URL | 대안 SSL/TLS 활성화 연결 URL |
연결 URL 이 제공되지 않으면 시스템은 다음 개별 파라미터를 확인합니다:
| 환경 변수 | 대체 변수 | 기본값 | 설명 |
|---|---|---|---|
PGHOST | - | localhost | 데이터베이스 호스트 |
PGPORT | - | 5432 | 데이터베이스 포트 |
PGUSERNAME | PGUSER, USER, USERNAME | postgres | 데이터베이스 사용자 |
PGPASSWORD | - | (비어있음) | 데이터베이스 비밀번호 |
PGDATABASE | - | 사용자명 | 데이터베이스 이름 |
SQLite 환경 변수
SQLite 연결은 DATABASE_URL 이 SQLite 호환 URL 을 포함할 때 구성할 수 있습니다:
# 이들 모두 SQLite 로 인식됨
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"참고: PostgreSQL 전용 환경 변수 (POSTGRES_URL, PGHOST 등) 는 SQLite 사용 시 무시됩니다.
런타임 사전 연결
Bun 은 애플리케이션 코드가 실행되기 전에 데이터베이스 연결을 설정하여 성능을 향상시키기 위해 시작 시 PostgreSQL 에 사전 연결할 수 있습니다. 이는 첫 번째 데이터베이스 쿼리에서 연결 지연 시간을 줄이는 데 유용합니다.
# 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 옵션
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 옵션
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 옵션
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:를 사용하면 연결 수명 동안만 존재하는 임시 데이터베이스가 생성됩니다.
동적 비밀번호
클라이언트가 액세스 토큰이나 회전하는 비밀번호가 있는 데이터베이스에 연결하는 것과 같은 대체 인증 방식을 사용해야 할 때 연결 시점에 동적 비밀번호 값을 해결하는 동기 또는 비동기 함수를 제공하세요.
import { SQL } from "bun";
const sql = new SQL(url, {
// 다른 연결 구성
...
// 데이터베이스 사용자의 비밀번호 함수
password: async () => await signer.getAuthToken(),
});SQLite 전용 기능
쿼리 실행
SQLite 는 비동기 I/O 를 사용하는 PostgreSQL 과 달리 쿼리를 동기적으로 실행합니다. 그러나 API 는 Promise 를 사용하여 일관되게 유지됩니다:
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 동작을 구성할 수 있습니다:
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 보다 더 유연한 타입 시스템을 가지고 있습니다:
// 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 이 트리거됩니다.
기본 트랜잭션
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 되지 않으면 트랜잭션이 자동으로 커밋됨
// 오류가 발생하면 롤백됨
});필요한 경우 콜백 함수에서 쿼리가 있는 배열을 반환하여 트랜잭션에서 요청을 파이프라이닝할 수도 있습니다:
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 는 트랜잭션 내에서 중간 체크포인트를 생성하여 전체 작업에 영향을 주지 않고 부분 롤백을 가능하게 합니다. 복잡한 트랜잭션에서 유용하며 오류 복구와 일관된 결과 유지를 가능하게 합니다.
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 되지 않으면 시스템은 자동으로 모든 변경 사항을 롤백합니다. 모든 것이 정상적으로 진행되면 나중에 트랜잭션을 커밋하거나 롤백할 수 있는 유연성을 유지합니다.
// 분산 트랜잭션 시작
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 모드를 지원합니다. 이러한 모드는 연결 시 동작과 수행되는 인증서 확인 수준을 결정합니다.
const sql = new SQL({
hostname: "localhost",
username: "user",
password: "password",
ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});| SSL 모드 | 설명 |
|---|---|
disable | SSL/TLS 사용 안 함. 서버가 SSL 을 요구하면 연결이 실패합니다. |
prefer | 먼저 SSL 을 시도하고 SSL 이 실패하면 비 SSL 로 폴백합니다. 지정되지 않은 경우 기본 모드입니다. |
require | 인증서 확인 없이 SSL 을 요구합니다. SSL 을 설정할 수 없으면 실패합니다. |
verify-ca | 서버 인증서가 신뢰할 수 있는 CA 에 의해 서명되었는지 확인합니다. 확인이 실패하면 실패합니다. |
verify-full | 가장 안전한 모드입니다. 인증서와 호스트 이름 일치를 확인합니다. 신뢰할 수 없는 인증서와 MITM 공격으로부터 보호합니다. |
연결 문자열과 함께 사용
SSL 모드는 연결 문자열에서도 지정할 수 있습니다:
// 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 클라이언트는 연결 풀을 자동으로 관리합니다. 이는 여러 쿼리에 재사용되는 데이터베이스 연결 풀로, 각 쿼리에 대한 연결 설정 및 닫기의 오버헤드를 줄이고 데이터베이스에 대한 동시 연결 수를 관리하는 데 도움이 됩니다.
const sql = new SQL({
// 풀 구성
max: 20, // 최대 20 개 동시 연결
idleTimeout: 30, // 30 초 후 유휴 연결 닫기
maxLifetime: 3600, // 최대 연결 수명 1 시간
connectionTimeout: 10, // 연결 타임아웃 10 초
});쿼리가 만들어질 때까지 연결이 생성되지 않습니다.
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 을 사용하면 풀에서 연결을 예약하고 단일 연결을 래핑하는 클라이언트를 반환할 수 있습니다. 이는 격리된 연결에서 쿼리를 실행하는 데 사용할 수 있습니다.
// 풀에서 전용 연결 가져오기
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 를 설정하여 이 동작을 변경할 수 있습니다:
const sql = new SQL({
// ... 다른 옵션 ...
prepare: false, // 서버에서 이름付き 준비된 문 지속 비활성화
});prepare: false 가 설정되면:
쿼리는 여전히 "extended" 프로토콜을 사용하여 실행되지만 이름 없는 준비된 문 을 사용하여 실행됩니다. 이름 없는 준비된 문은 대상으로 이름 없는 문을 지정하는 다음 Parse 문이 발행될 때까지 지속됩니다.
- 파라미터 바인딩은 여전히 SQL 인젝션으로부터 안전합니다
- 각 쿼리는 서버에서 처음부터 파싱되고 계획됩니다
- 쿼리는 파이프라이닝 되지 않습니다
prepare: false 를 사용하려는 경우:
- 트랜잭션 모드에서 PGBouncer 사용 (PGBouncer 1.21.0 이후 적절히 구성되면 프로토콜 수준 이름付き 준비된 문이 지원됨)
- 쿼리 실행 계획 디버깅
- 쿼리 계획을 자주 재생성해야 하는 동적 SQL 작업
- 쿼리당 여러 명령이 지원되지 않음 (
sql``.simple()사용 제외)
준비된 문을 비활성화하면 다른 파라미터로 자주 실행되는 쿼리의 성능에 영향을 미칠 수 있습니다. 서버가 각 쿼리를 처음부터 파싱하고 계획해야 하기 때문입니다.
오류 처리
클라이언트는 다양한 실패 시나리오에 대해 타입화된 오류를 제공합니다. 오류는 데이터베이스별이며 기본 오류 클래스를 확장합니다:
오류 클래스
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_AVAILABLE | SSL/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_MISMATCH | SASL 서명 확인 실패 |
쿼리 오류
| 쿼리 오류 | 설명 |
|---|---|
ERR_POSTGRES_SYNTAX_ERROR | 잘못된 SQL 구문 (SyntaxError 확장) |
ERR_POSTGRES_SERVER_ERROR | PostgreSQL 서버의 일반 오류 |
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_CONSTRAINT | 19 | 제약 위반 (UNIQUE, CHECK, NOT NULL 등) |
SQLITE_BUSY | 5 | 데이터베이스가 잠김 |
SQLITE_LOCKED | 6 | 데이터베이스의 테이블이 잠김 |
SQLITE_READONLY | 8 | 읽기 전용 데이터베이스에 쓰기 시도 |
SQLITE_IOERR | 10 | 디스크 I/O 오류 |
SQLITE_CORRUPT | 11 | 데이터베이스 디스크 이미지가 손상됨 |
SQLITE_FULL | 13 | 데이터베이스 또는 디스크가 가득 참 |
SQLITE_CANTOPEN | 14 | 데이터베이스 파일을 열 수 없음 |
SQLITE_PROTOCOL | 15 | 데이터베이스 잠금 프로토콜 오류 |
SQLITE_SCHEMA | 17 | 데이터베이스 스키마가 변경됨 |
SQLITE_TOOBIG | 18 | 문자열 또는 BLOB 이 크기 제한을 초과 |
SQLITE_MISMATCH | 20 | 데이터 타입 불일치 |
SQLITE_MISUSE | 21 | 라이브러리가 잘못 사용됨 |
SQLITE_AUTH | 23 | 인증 거부 |
오류 처리 예제:
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 비트 정수 범위를 초과하는 큰 숫자에 대한 특별한 처리를 포함합니다. 작동 방식은 다음과 같습니다:
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 로 설정하여 활성화할 수 있습니다:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777n로드맵
아직 완료되지 않은 몇 가지 사항이 있습니다.
--db-preconnectBun 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 은 모든 파라미터화된 쿼리에 대해 서버 측 준비된 문을 사용합니다:
// 이는 자동으로 서버에서 준비된 문을 생성합니다
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 은 다중 문 쿼리에서 여러 결과 집합을 반환할 수 있습니다:
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 에 클라이언트 정보를 자동으로 전송합니다:
// 이러한 속성은 자동으로 전송됨:
// _client_name: "Bun"
// _client_version: <bun 버전>
// MySQL 의 performance_schema.session_connect_attrs 에서 확인할 수 있음타입 처리
MySQL 타입은 자동으로 JavaScript 타입으로 변환됩니다:
| MySQL 타입 | JavaScript 타입 | 참고 |
|---|---|---|
| INT, TINYINT, MEDIUMINT | number | 안전한 정수 범위 내 |
| BIGINT | string, number 또는 BigInt | i32/u32 크기에 맞으면 number, 그렇지 않으면 string 또는 BigInt (bigint 옵션에 따라) |
| DECIMAL, NUMERIC | string | 정밀도 유지를 위해 |
| FLOAT, DOUBLE | number | |
| DATE | Date | JavaScript Date 객체 |
| DATETIME, TIMESTAMP | Date | 타임존 처리 포함 |
| TIME | number | 총 마이크로초 |
| YEAR | number | |
| CHAR, VARCHAR, VARSTRING, STRING | string | |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string | |
| TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOB | string | BLOB 타입은 TEXT 타입의 별칭 |
| JSON | object/array | 자동으로 파싱됨 |
| BIT(1) | boolean | MySQL 의 BIT(1) |
| GEOMETRY | string | 지오메트리 데이터 |
PostgreSQL 과의 차이점
API 는 통합되지만 몇 가지 동작 차이가 있습니다:
- 파라미터 플레이스홀더: MySQL 은 내부적으로
?를 사용하지만 Bun 은 자동으로$1, $2스타일로 변환합니다 - RETURNING 절: MySQL 은 RETURNING 을 지원하지 않음;
result.lastInsertRowid또는 별도 SELECT 사용 - 배열 타입: MySQL 은 PostgreSQL 과 같은 네이티브 배열 타입이 없습니다
MySQL 전용 기능
아직 LOAD DATA INFILE 지원을 구현하지 않았습니다
PostgreSQL 전용 기능
아직 다음을 구현하지 않았습니다:
COPY지원LISTEN지원NOTIFY지원
또한 다음과 같은 덜 일반적인 기능도 구현하지 않았습니다:
- GSSAPI 인증
SCRAM-SHA-256-PLUS지원- Point 및 PostGIS 타입
- 모든 다차원 정수 배열 타입 (몇 가지 타입만 지원됨)
일반적인 패턴 및 모범 사례
MySQL 결과 집합 작업
// 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 오류 처리
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 성능 팁
- 연결 풀링 사용: 작업 부하에 따라 적절한
max풀 크기 설정 - 준비된 문 활성화: 기본적으로 활성화되어 있으며 성능 향상
- 대량 작업에 트랜잭션 사용: 관련 쿼리를 트랜잭션으로 그룹화
- 적절히 인덱싱: MySQL 은 쿼리 성능을 위해 인덱스에 크게 의존
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 에서 사용할 수 있습니다. 훌륭한 옵션입니다.
두 가지 이유가 있습니다:
- 개발자가 Bun 에 내장된 데이터베이스 드라이버를 사용하는 것이 더 간단하다고 생각합니다. 라이브러리 쇼핑에 보내는 시간은 앱을 구축하는 데 사용할 수 있는 시간입니다.
- 라이브러리로 구현하기 어려운 객체를 생성하기 위해 JavaScriptCore 엔진 내부를 활용하여 더 빠르게 만듭니다
크레딧
API 인터페이스에 영감을 준 @porsager 의 postgres.js 에 큰 감사를 드립니다.