Интерфейс разработан для простоты и производительности, используя тегированные шаблонные литералы для запросов и предлагая такие функции, как пул соединений, транзакции и подготовленные выражения.
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 Auth (SCRAM-SHA-256), MD5 и Clear Text
- Таймауты соединений
- Возврат строк как объектов данных, массивов массивов или Buffer
- Поддержка бинарного протокола для большей скорости
- Поддержка TLS (и режима аутентификации)
- Автоматическая конфигурация с переменной окружения
Поддержка баз данных
Bun.SQL предоставляет унифицированный API для нескольких систем баз данных:
PostgreSQL
PostgreSQL используется, когда:
- Строка подключения не соответствует шаблонам SQLite или MySQL (это адаптер по умолчанию)
- Строка подключения явно использует протоколы
postgres://илиpostgresql:// - Строка подключения не предоставлена и переменные окружения указывают на PostgreSQL
import { sql } from "bun";
// Использует PostgreSQL, если DATABASE_URL не установлен или является URL PostgreSQL
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:// (совместимость с пакетом npm mysql2)
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 поддерживают:
- Подготовленные выражения: Автоматически создаются для параметризованных запросов с кэшированием выражений
- Бинарный протокол: Для лучшей производительности с подготовленными выражениями и точной обработкой типов
- Несколько наборов результатов: Поддержка хранимых процедур, возвращающих несколько наборов результатов
- Плагины аутентификации: Поддержка mysql_native_password, caching_sha2_password (по умолчанию MySQL 8.0) и sha256_password
- Подключения SSL/TLS: Настраиваемые режимы SSL, аналогичные PostgreSQL
- Атрибуты подключения: Информация о клиенте, отправляемая на сервер для мониторинга
- Конвейеризация запросов: Выполнение нескольких подготовленных выражений без ожидания ответов
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"`) требуют явного указания `{ adapter: "sqlite" }` во избежание неоднозначности с PostgreSQL.Возможности, специфичные для 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, игнорируя другие поляРезультаты запроса
По умолчанию SQL-клиент Bun возвращает результаты запроса как массивы объектов, где каждый объект представляет строку с именами столбцов в качестве ключей. Однако могут быть случаи, когда вам нужны данные в другом формате. Клиент предоставляет два дополнительных метода для этой цели.
Формат 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
Общая потребность в приложениях баз данных — возможность динамически konstruировать запросы на основе условий времени выполнения. 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 создает литералы массивов PostgreSQL из массивов JavaScript:
// Создание литералов массивов для 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 поддерживает два типа запросов: "простые" и "расширенные". Простые запросы могут содержать несколько выражений, но не поддерживают параметры, в то время как расширенные запросы (по умолчанию) поддерживают параметры, но позволяют только одно выражение.
Для выполнения нескольких выражений в одном запросе используйте sql``.simple():
// Несколько выражений в одном запросе
await sql`
SELECT 1;
SELECT 2;
`.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]);Выполнение и отмена запросов
SQL в Bun ленивый, что означает, что он начнет выполняться только при ожидании или выполнении с .execute(). Вы можете отменить запрос, который в настоящее время выполняется, вызвав метод cancel() на объекте запроса.
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;Переменные окружения базы данных
Параметры подключения sql можно настроить с помощью переменных окружения. Клиент проверяет эти переменные в определенном порядке приоритета и автоматически определяет тип базы данных на основе формата строки подключения.
Автоматическое определение базы данных
При использовании Bun.sql() без аргументов или new SQL() со строкой подключения адаптер автоматически определяется на основе формата URL:
Автоматическое определение MySQL
MySQL автоматически выбирается, когда строка подключения соответствует этим шаблонам:
mysql://...- URL протокола MySQLmysql2://...- URL протокола MySQL2 (псевдоним совместимости)
// Все они используют 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://...- URL протокола SQLitesqlite:...- Протокол SQLite без слешейfile://...- URL протокола файлаfile:...- Протокол файла без слешей
// Все они используют 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:
# 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
# Или любой URL, который не соответствует шаблонам MySQL или SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.jsПеременные окружения MySQL
Подключения MySQL можно настроить через переменные окружения:
# Основной URL подключения (проверяется первым)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"
# Альтернатива: DATABASE_URL с протоколом MySQL
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 | (пусто) | Основной URL подключения MySQL |
TLS_MYSQL_DATABASE_URL | (пусто) | URL подключения с SSL/TLS |
Переменные окружения PostgreSQL
Следующие переменные окружения можно использовать для определения подключения PostgreSQL:
| Переменная окружения | Описание |
|---|---|
POSTGRES_URL | Основной URL подключения PostgreSQL |
DATABASE_URL | Альтернативный URL подключения (авто) |
PGURL | Альтернативный URL подключения |
PG_URL | Альтернативный URL подключения |
TLS_POSTGRES_DATABASE_URL | URL подключения с SSL/TLS |
TLS_DATABASE_URL | Альтернативный URL подключения с SSL/TLS |
Если URL подключения не предоставлен, система проверяет следующие отдельные параметры:
| Переменная окружения | Резервные переменные | Значение по умолчанию | Описание |
|---|---|---|---|
PGHOST | - | localhost | Хост базы данных |
PGPORT | - | 5432 | Порт базы данных |
PGUSERNAME | PGUSER, USER, USERNAME | postgres | Пользователь БД |
PGPASSWORD | - | (пусто) | Пароль базы данных |
PGDATABASE | - | имя пользователя | Имя базы данных |
Переменные окружения SQLite
Подключения SQLite можно настроить через DATABASE_URL, когда он содержит URL, совместимый с SQLite:
# Все они распознаются как 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, // Использовать BigInt для целых чисел, превышающих диапазон числа JS
// Обратные вызовы
onconnect: client => {
console.log("База данных SQLite открыта");
},
onclose: client => {
console.log("База данных SQLite закрыта");
},
});Примечания к подключению SQLite">
- Пул подключений: SQLite не использует пул подключений, так как это база данных на основе файлов. Каждый экземпляр
SQLпредставляет одно подключение. - Транзакции: SQLite поддерживает вложенные транзакции через точки сохранения, аналогично PostgreSQL.
- Одновременный доступ: SQLite обрабатывает одновременный доступ через блокировку файлов. Используйте режим WAL для лучшей конкурентности.
- Базы данных в памяти: Использование
:memory:создает временную базу данных, которая существует только в течение времени жизни подключения.
Динамические пароли
Когда клиентам нужно использовать альтернативные схемы аутентификации, такие как токены доступа или подключения к базам данных с вращающимися паролями, предоставьте синхронную или асинхронную функцию, которая будет разрешать значение динамического пароля во время подключения.
import { SQL } from "bun";
const sql = new SQL(url, {
// Другая конфигурация подключения
...
// Функция пароля для пользователя базы данных
password: async () => await signer.getAuthToken(),
});Возможности, специфичные для SQLite
Выполнение запроса
SQLite выполняет запросы синхронно, в отличие от 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`;
// Транзакция автоматически фиксируется, если не выброшено ошибок
// Откатывается, если происходит какая-либо ошибка
});Также можно конвейеризировать запросы в транзакции, если необходимо, вернув массив с запросами из функции обратного вызова:
await sql.begin(async tx => {
return [
tx`INSERT INTO users (name) VALUES (${"Alice"})`,
tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
];
});Точки сохранения
Точки сохранения в SQL создают промежуточные контрольные точки внутри транзакции, позволяя частичные откаты без влияния на всю операцию. Они полезны в сложных транзакциях, позволяя восстановление после ошибок и поддержание согласованных результатов.
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("Откат к точке сохранения");
}
});
// Продолжить транзакцию, даже если точка сохранения откатилась
await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});Распределенные транзакции
Двухфазная фиксация (2PC) — это протокол распределенной транзакции, где Фаза 1 имеет координатора, подготавливающего узлы, обеспечивая запись данных и готовность к фиксации, в то время как Фаза 2 завершает фиксацию или откат узлов на основе решения координатора. Этот процесс обеспечивает долговечность данных и правильное управление блокировками.
В PostgreSQL и MySQL распределенные транзакции сохраняются за пределами их исходного сеанса, позволяя привилегированным пользователям или координаторам зафиксировать или откатить их позже. Это поддерживает надежные распределенные транзакции, процессы восстановления и административные операции.
Каждая система базы данных реализует распределенные транзакции по-разному:
PostgreSQL нативно поддерживает их через подготовленные транзакции, в то время как MySQL использует XA-транзакции.
Если какие-либо исключения происходят во время распределенной транзакции и не перехвачены, система автоматически откатит все изменения. Когда все идет нормально, вы сохраняете возможность зафиксировать или откатить транзакцию позже.
// Начать распределенную транзакцию
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 рекомендуется для лучшей безопасности. Проверьте Документацию по SASL-аутентификации Postgres для получения дополнительной информации.
Обзор режимов 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 | Самый безопасный режим. Проверяет сертификат и соответствие имени хоста. Защищает от недоверенных сертификатов. |
Использование со строками подключения
Режим 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");Пул подключений
SQL-клиент Bun автоматически управляет пулом подключений, который представляет собой пул подключений к базе данных, которые повторно используются для нескольких запросов. Это помогает уменьшить накладные расходы на установление и закрытие подключений для каждого запроса, а также помогает управлять количеством одновременных подключений к базе данных.
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`;
} // Автоматически освобождаетсяПодготовленные выражения
По умолчанию SQL-клиент Bun автоматически создает именованные подготовленные выражения для запросов, где можно сделать вывод, что запрос статический. Это обеспечивает лучшую производительность. Однако вы можете изменить это поведение, установив prepare: false в опциях подключения:
const sql = new SQL({
// ... другие опции ...
prepare: false, // Отключить сохранение именованных подготовленных выражений на сервере
});Когда установлено prepare: false:
Запросы все еще выполняются с использованием "расширенного" протокола, но они выполняются с использованием безымянных подготовленных выражений, безымянное подготовленное выражение длится только до тех пор, пока не будет выдано следующее выражение 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 | Ошибка ввода-вывода диска |
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
SQL-клиент Bun включает специальную обработку для больших чисел, превышающих диапазон 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" 12345BigInt вместо строк
Если вам нужны большие числа как BigInt вместо строк, вы можете включить это, установив опцию bigint в true при инициализации SQL-клиента:
const sql = new SQL({
bigint: true,
});
const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777nПлан развития
Еще есть некоторые вещи, которые мы еще не завершили.
- Предварительная загрузка подключений через флаг CLI
--db-preconnect - Преобразования имен столбцов (например,
snake_caseвcamelCase). Это в основном заблокировано на реализации изменения регистра в C++ с использованиемWTF::Stringот WebKit. - Преобразования типов столбцов
Возможности, специфичные для баз данных
Методы аутентификации
MySQL поддерживает несколько плагинов аутентификации, которые автоматически согласовываются:
mysql_native_password- Традиционная аутентификация MySQL, широко совместимаcaching_sha2_password- По умолчанию в MySQL 8.0+, более безопасна с обменом ключами RSAsha256_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();Наборы символов и сравнения
Bun.SQL автоматически использует набор символов utf8mb4 для подключений MySQL, обеспечивая полную поддержку Unicode, включая эмодзи. Это рекомендуемый набор символов для современных приложений MySQL.
Атрибуты подключения
Bun автоматически отправляет информацию о клиенте в MySQL для лучшего мониторинга:
// Эти атрибуты отправляются автоматически:
// _client_name: "Bun"
// _client_version: <версия bun>
// Вы можете увидеть их в performance_schema.session_connect_attrs MySQLОбработка типов
Типы 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 | Объект Date JavaScript |
| 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 | BIT(1) в MySQL |
| 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
// Получение ID вставки после INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // LAST_INSERT_ID() MySQL
// Обработка затронутых строк
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: Он установлен по умолчанию и обрабатывает все символы Unicode
Часто задаваемые вопросы
Почему это `Bun.sql`, а не `Bun.postgres`?
План состоял в том, чтобы добавить больше драйверов баз данных в будущем. Теперь с добавлением поддержки MySQL этот унифицированный API поддерживает PostgreSQL, MySQL и SQLite.
Как узнать, какой адаптер базы данных используется?
Адаптер автоматически определяется из строки подключения:
- URL, начинающиеся с `mysql://` или `mysql2://`, используют MySQL
- URL, соответствующие шаблонам SQLite (`:memory:`, `sqlite://`, `file://`), используют SQLite
- Все остальное по умолчанию использует PostgreSQL
Поддерживаются ли хранимые процедуры MySQL?">
Да, хранимые процедуры полностью поддерживаются, включая параметры OUT и несколько наборов результатов:
```ts
// Вызов хранимой процедуры
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Получение параметра OUT
const outParam = await mysql`SELECT @total_orders as total`;
```
Можно ли использовать синтаксис SQL, специфичный для 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}`;
```
Почему не просто использовать существующую библиотеку?
Пакеты npm, такие как postgres.js, pg и node-postgres, также можно использовать в Bun. Они отличные варианты.
Две причины:
- Мы считаем, что для разработчиков проще иметь драйвер базы данных, встроенный в Bun. Время, которое вы тратите на выбор библиотек, — это время, которое вы могли бы потратить на создание своего приложения.
- Мы используем некоторые внутренние части движка JavaScriptCore, чтобы быстрее создавать объекты, что было бы сложно реализовать в библиотеке
Благодарности
Огромная благодарность @porsager за postgres.js за вдохновение для интерфейса API.