تم تصميم الواجهة لتكون بسيطة وفعالة، باستخدام قوالب النصوص الموسومة للاستعلامات وتقديم ميزات مثل تجميع الاتصالات والمعاملات والعبارات المحضرة.
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 والنص العادي
- مهلات الاتصال
- إرجاع الصفوف ككائنات بيانات أو مصفوفات من مصفوفات أو Buffer
- دعم البروتوكول الثنائي يجعلها أسرع
- دعم TLS (وضع المصادقة)
- تكوين تلقائي مع متغير البيئة
دعم قاعدة البيانات
يوفر Bun.SQL واجهة برمجة تطبيقات موحدة لأنظمة قواعد بيانات متعددة:
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");
// قاعدة بيانات خاصة في الذاكرة
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
حاجة شائعة في تطبيقات قواعد البيانات هي القدرة على إنشاء استعلامات ديناميكيًا بناءً على ظروف وقت التشغيل. يوفر 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 نوعين من الاستعلامات: "simple" و "extended". يمكن أن تحتوي الاستعلامات البسيطة على عبارات متعددة لكنها لا تدعم المعلمات، بينما تدعم الاستعلامات الموسعة (افتراضي) المعلمات لكنها تسمح بعبارة واحدة فقط.
لتشغيل عبارات متعددة في استعلام واحد، استخدم 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]);تنفيذ وإلغاء الاستعلامات
Bun's SQL كسول، مما يعني أنه لن يبدأ التنفيذ إلا عند الانتظار أو التنفيذ مع .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 | - | username | اسم قاعدة البيانات |
متغيرات بيئة 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 التي تستخدم I/O غير متزامن. ومع ذلك، تظل واجهة برمجة التطبيقات متسقة باستخدام الوعود:
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}`;Pragmas لـ 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 والنص العادي. يُوصى بـ 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 | أكثر الأوضاع أمانًا. يتحقق من تطابق الشهادة واسم المضيف. يحمي من الشهادات غير الموثوقة وهجمات 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");تجميع الاتصالات
يدير عميل SQL في Bun تلقائيًا تجمع اتصالات، وهو تجمع من اتصالات قاعدة البيانات التي يعاد استخدامها لاستعلامات متعددة. هذا يساعد على تقليل نفقات إنشاء وإغلاق الاتصالات لكل استعلام، ويساعد أيضًا على إدارة عدد الاتصالات المتزامنة بقاعدة البيانات.
const sql = new SQL({
// تكوين التجمع
max: 20, // الحد الأقصى 20 اتصال متزامن
idleTimeout: 30, // إغلاق الاتصالات الخاملة بعد 30 ثانية
maxLifetime: 3600, // الحد الأقصى لعمر الاتصال ساعة واحدة
connectionTimeout: 10, // مهلة الاتصال 10 ثوانٍ
});لن يتم إجراء أي اتصال حتى يتم إجراء استعلام.
const sql = Bun.SQL(); // لا يتم إنشاء أي اتصال
await sql`...`; // يبدأ التجمع حتى الوصول إلى الحد الأقصى (إذا أمكن)، يتم استخدام أول اتصال متاح
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:
لا تزال الاستعلامات تُنفذ باستخدام بروتوكول "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')`; // معرف مكرر
} 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خارطة الطريق
لا تزال هناك بعض الأشياء التي لم ننتهِ منها بعد.
- تحميل الاتصال عبر علم
--db-preconnectفي سطر أوامر Bun - تحويلات أسماء الأعمدة (مثل
snake_caseإلىcamelCase). هذا معطل في الغالب على تنفيذ مدرك لـ unicode لتغيير الحالة في 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 و BLOB و LONG BLOB | string | أنواع BLOB هي اسم مستعار لأنواع TEXT |
| JSON | object/array | يتم تحليلها تلقائيًا |
| BIT(1) | boolean | BIT(1) في MySQL |
| GEOMETRY | string | بيانات الهندسة |
الفروق عن PostgreSQL
على الرغم من توحيد واجهة برمجة التطبيقات، هناك بعض الفروق السلوكية:
- عناصر نائبة للمعلمات: تستخدم 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
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، تدعم واجهة برمجة التطبيقات الموحدة هذه 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؟">
نعم، يمكنك استخدام أي صيغة خاصة بـ 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. الوقت الذي تقضيه في التسوق للمكتبات هو الوقت الذي كان يمكنك فيه بناء تطبيقك.
- نستفيد من بعض internals محرك JavaScriptCore لجعله أسرع لإنشاء كائنات سيكون من الصعب تنفيذها في مكتبة
الشكر
شكر كبير لـ @porsager على postgres.js للإلهام لواجهة واجهة برمجة التطبيقات.