Skip to content

تم تصميم الواجهة لتكون بسيطة وفعالة، باستخدام قوالب النصوص الموسومة للاستعلامات وتقديم ميزات مثل تجميع الاتصالات والمعاملات والعبارات المحضرة.

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

// PostgreSQL (افتراضي)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

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

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

الميزات

  • قوالب نصوص موسومة للحماية من حقن SQL
  • المعاملات
  • معلمات مسماة وموضعية
  • تجميع الاتصالات
  • دعم BigInt
  • دعم مصادقة SASL (SCRAM-SHA-256) و MD5 والنص العادي
  • مهلات الاتصال
  • إرجاع الصفوف ككائنات بيانات أو مصفوفات من مصفوفات أو Buffer
  • دعم البروتوكول الثنائي يجعلها أسرع
  • دعم TLS (وضع المصادقة)
  • تكوين تلقائي مع متغير البيئة

دعم قاعدة البيانات

يوفر Bun.SQL واجهة برمجة تطبيقات موحدة لأنظمة قواعد بيانات متعددة:

PostgreSQL

يُستخدم PostgreSQL عندما:

  • لا تطابق سلسلة الاتصال أنماط SQLite أو MySQL (هي المحول الافتراضي)
  • تستخدم سلسلة الاتصال صراحةً بروتوكولات postgres:// أو postgresql://
  • لم يتم توفير سلسلة اتصال وتشير متغيرات البيئة إلى PostgreSQL
db.ts
ts
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+:

db.ts
ts
import { SQL } from "bun";

// اتصال MySQL
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // بروتوكول mysql2 يعمل أيضًا

// استخدام كائن الخيارات
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// يعمل مع المعلمات - يستخدم عبارات محضرة تلقائيًا
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// المعاملات تعمل نفس PostgreSQL
await mysql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});

// إدخالات مجمعة
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;

تنسيقات سلاسل اتصال MySQL">

تقبل MySQL تنسيقات URL مختلفة لسلاسل الاتصال:

ts
// بروتوكول mysql:// القياسي
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // المنفذ الافتراضي 3306

// بروتوكول mysql2:// (توافق مع حزمة 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، ويوفر نفس واجهة قالب النص الموسوم:

ts
import { SQL } from "bun";

// قاعدة بيانات في الذاكرة
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// قاعدة بيانات قائمة على الملفات
const sql1 = new SQL("sqlite://myapp.db");

// استخدام كائن الخيارات
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// لأسماء الملفات البسيطة، حدد المحول صراحةً
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });

تنسيقات سلاسل اتصال SQLite">

تقبل SQLite تنسيقات URL مختلفة لسلاسل الاتصال:

ts
// بروتوكول sqlite:// القياسي
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // بدون شرطات

// بروتوكول file:// (معترف به أيضًا كـ SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// قاعدة بيانات خاصة في الذاكرة
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 تدعم خيارات تكوين إضافية:

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

  // خيارات خاصة بـ SQLite
  readonly: false, // فتح في وضع القراءة فقط
  create: true, // إنشاء قاعدة البيانات إذا لم تكن موجودة
  readwrite: true, // فتح للقراءة والكتابة

  // خيارات Bun:sqlite إضافية
  strict: true, // تمكين الوضع الصارم
  safeIntegers: false, // استخدام أرقام JavaScript للأعداد الصحيحة
});

يتم تحليل معلمات الاستعلام في URL لتعيين هذه الخيارات:

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (افتراضي)

إدراج البيانات

يمكنك تمرير قيم JavaScript مباشرة إلى قالب نص SQL وسيتم التعامل مع الهروب نيابةً عنك.

ts
import { sql } from "bun";

// إدراج أساسي مع قيم مباشرة
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// استخدام المساعد الكائن لصيغة أنظف
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// يتوسع إلى: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')

الإدراج المجمع

يمكنك أيضًا تمرير مصفوفات من الكائنات إلى قالب نص SQL وسيتم توسيعها إلى عبارة INSERT INTO ... VALUES ....

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

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

اختيار الأعمدة للإدراج

يمكنك استخدام sql(object, ...string) لاختيار الأعمدة المراد إدراجها. يجب تعريف كل من الأعمدة على الكائن.

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

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// يدرج فقط أعمدة name و email، متجاهلاً الحقول الأخرى

نتائج الاستعلام

بشكل افتراضي، يعيد عميل SQL في Bun نتائج الاستعلام كمصفوفات من الكائنات، حيث يمثل كل كائن صفًا بأسماء الأعمدة ك مفاتيح. ومع ذلك، هناك حالات قد ترغب فيها في البيانات بتنسيق مختلف. يوفر العميل طريقتين إضافيتين لهذا الغرض.

تنسيق sql``.values()

ترجع دالة sql``.values() الصفوف كمصفوفات من القيم بدلاً من الكائنات. يصبح كل صف مصفوفة حيث تكون القيم بنفس ترتيب الأعمدة في استعلامك.

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

هذا يرجع شيئًا مثل:

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

sql``.values() مفيد بشكل خاص إذا تم إرجاع أسماء أعمدة مكررة في نتائج الاستعلام. عند استخدام الكائنات (افتراضي)، يتم استخدام اسم العمود الأخير كمفتاح في الكائن، مما يعني أن أسماء الأعمدة المكررة تكتب فوق بعضها البعض - ولكن عند استخدام sql``.values()، يكون كل عمود موجودًا في المصفوفة حتى تتمكن من الوصول إلى قيم الأعمدة المكررة حسب الفهرس.

تنسيق sql``.raw()

ترجع دالة .raw() الصفوف كمصفوفات من كائنات Buffer. يمكن أن يكون هذا مفيدًا للعمل مع البيانات الثنائية أو لأسباب الأداء.

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

أجزاء SQL

حاجة شائعة في تطبيقات قواعد البيانات هي القدرة على إنشاء استعلامات ديناميكيًا بناءً على ظروف وقت التشغيل. يوفر Bun طرقًا آمنة للقيام بذلك بدون خطر حقن SQL.

أسماء الجداول الديناميكية

عندما تحتاج إلى الإشارة إلى الجداول أو المخططات ديناميكيًا، استخدم المساعد sql() لضمان الهروب المناسب:

ts
// الإشارة إلى الجداول بأمان ديناميكيًا
await sql`SELECT * FROM ${sql("users")}`;

// مع تأهيل المخطط
await sql`SELECT * FROM ${sql("public.users")}`;

استعلامات مشروطة

يمكنك استخدام المساعد sql() لبناء استعلامات مع بنود مشروطة. هذا يسمح لك بإنشاء استعلامات مرنة تتكيف مع احتياجات تطبيقك:

ts
// بنود WHERE اختيارية
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

أعمدة ديناميكية في التحديثات

يمكنك استخدام sql(object, ...string) لاختيار الأعمدة المراد تحديثها. يجب تعريف كل من الأعمدة على الكائن. إذا لم يتم إعلام الأعمدة، فسيتم استخدام جميع المفاتيح لتحديث الصف.

ts
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// يستخدم جميع المفاتيح من الكائن لتحديث الصف
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

قيم ديناميكية و where in

يمكن أيضًا إنشاء قوائم القيم ديناميكيًا، مما يجعل استعلامات where in بسيطة أيضًا. يمكنك اختيارياً تمرير مصفوفة من الكائنات وإعلام المفتاح المراد استخدامه لإنشاء القائمة.

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

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

المساعد sql.array

ينشئ المساعد sql.array حرفيات مصفوفة PostgreSQL من مصفوفات JavaScript:

ts
// إنشاء حرفيات مصفوفة لـ PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// يولد: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// يعمل مع المصفوفات الرقمية أيضًا
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// يولد: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

NOTE

`sql.array` خاص بـ PostgreSQL فقط. المصفوفات متعددة الأبعاد وعناصر NULL قد لا تكون مدعومة بعد.

sql``.simple()

يدعم بروتوكول سلك PostgreSQL نوعين من الاستعلامات: "simple" و "extended". يمكن أن تحتوي الاستعلامات البسيطة على عبارات متعددة لكنها لا تدعم المعلمات، بينما تدعم الاستعلامات الموسعة (افتراضي) المعلمات لكنها تسمح بعبارة واحدة فقط.

لتشغيل عبارات متعددة في استعلام واحد، استخدم sql``.simple():

ts
// عبارات متعددة في استعلام واحد
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

الاستعلامات البسيطة غالبًا ما تكون مفيدة لتهجيرات قاعدة البيانات ونصوص الإعداد.

لاحظ أن الاستعلامات البسيطة لا يمكنها استخدام المعلمات (${value}). إذا كنت بحاجة إلى معلمات، يجب تقسيم استعلامك إلى عبارات منفصلة.

استعلامات في ملفات

يمكنك استخدام دالة sql.file لقراءة استعلام من ملف وتنفيذه، إذا كان الملف يتضمن $1 أو $2 أو غيرها، يمكنك تمرير معلمات إلى الاستعلام. إذا لم يتم استخدام معلمات، يمكنه تنفيذ أوامر متعددة لكل ملف.

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

استعلامات غير آمنة

يمكنك استخدام دالة sql.unsafe لتنفيذ سلاسل SQL الخام. استخدم هذا بحذر، لأنه لن يهرب من إدخال المستخدم. يُسمح بتنفيذ أكثر من أمر واحد لكل استعلام إذا لم يتم استخدام معلمات.

ts
// أوامر متعددة بدون معلمات
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// استخدام المعلمات (يُسمح بأمر واحد فقط)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

تنفيذ وإلغاء الاستعلامات

Bun's SQL كسول، مما يعني أنه لن يبدأ التنفيذ إلا عند الانتظار أو التنفيذ مع .execute(). يمكنك إلغاء استعلام يتم تنفيذه حاليًا عن طريق استدعاء دالة cancel() على كائن الاستعلام.

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

متغيرات بيئة قاعدة البيانات

يمكن تكوين معلمات اتصال sql باستخدام متغيرات البيئة. يتحقق العميل من هذه المتغيرات بترتيب محدد من الأسبقية ويكتشف تلقائيًا نوع قاعدة البيانات بناءً على تنسيق سلسلة الاتصال.

الاكتشاف التلقائي لقاعدة البيانات

عند استخدام Bun.sql() بدون وسيطات أو new SQL() مع سلسلة اتصال، يتم اكتشاف المحول تلقائيًا بناءً على تنسيق URL:

الاكتشاف التلقائي لـ MySQL

يتم اختيار MySQL تلقائيًا عندما تطابق سلسلة الاتصال هذه الأنماط:

  • mysql://... - عناوين URL بروتوكول MySQL
  • mysql2://... - عناوين URL بروتوكول MySQL2 (اسم مستعار للتوافق)
ts
// كل هذه تستخدم MySQL تلقائيًا (بدون محول مطلوب)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// يعمل مع متغير البيئة DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

الاكتشاف التلقائي لـ SQLite

يتم اختيار SQLite تلقائيًا عندما تطابق سلسلة الاتصال هذه الأنماط:

  • :memory: - قاعدة بيانات في الذاكرة
  • sqlite://... - عناوين URL بروتوكول SQLite
  • sqlite:... - بروتوكول SQLite بدون شرطات
  • file://... - عناوين URL بروتوكول الملفات
  • file:... - بروتوكول الملفات بدون شرطات
ts
// كل هذه تستخدم SQLite تلقائيًا (بدون محول مطلوب)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// يعمل مع متغير البيئة DATABASE_URL
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.js

الاكتشاف التلقائي لـ PostgreSQL

PostgreSQL هو الافتراضي لسلاسل الاتصال التي لا تطابق أنماط MySQL أو SQLite:

bash
# يتم اكتشاف PostgreSQL لهذه الأنماط
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# أو أي URL لا يطابق أنماط MySQL أو SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.js

متغيرات بيئة MySQL

يمكن تكوين اتصالات MySQL عبر متغيرات البيئة:

bash
# 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_HOSTlocalhostمضيف قاعدة البيانات
MYSQL_PORT3306منفذ قاعدة البيانات
MYSQL_USERrootمستخدم قاعدة البيانات
MYSQL_PASSWORD(فارغ)كلمة مرور قاعدة البيانات
MYSQL_DATABASEmysqlاسم قاعدة البيانات
MYSQL_URL(فارغ)URL الاتصال الأساسي لـ MySQL
TLS_MYSQL_DATABASE_URL(فارغ)URL اتصال SSL/TLS

متغيرات بيئة PostgreSQL

يمكن استخدام متغيرات البيئة التالية لتعريف اتصال PostgreSQL:

متغير البيئةالوصف
POSTGRES_URLURL الاتصال الأساسي لـ PostgreSQL
DATABASE_URLURL اتصال بديل (يتم اكتشافه تلقائيًا)
PGURLURL اتصال بديل
PG_URLURL اتصال بديل
TLS_POSTGRES_DATABASE_URLURL اتصال SSL/TLS
TLS_DATABASE_URLURL اتصال SSL/TLS بديل

إذا لم يتم توفير URL اتصال، يتحقق النظام من المعلمات الفردية التالية:

متغير البيئةالمتغيرات البديلةالقيمة الافتراضيةالوصف
PGHOST-localhostمضيف قاعدة البيانات
PGPORT-5432منفذ قاعدة البيانات
PGUSERNAMEPGUSER، USER، USERNAMEpostgresمستخدم قاعدة البيانات
PGPASSWORD-(فارغ)كلمة مرور قاعدة البيانات
PGDATABASE-usernameاسم قاعدة البيانات

متغيرات بيئة SQLite

يمكن تكوين اتصالات SQLite عبر DATABASE_URL عندما يحتوي على URL متوافق مع SQLite:

bash
// كل هذه معترف بها كـ SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

ملاحظة: يتم تجاهل متغيرات البيئة الخاصة بـ PostgreSQL (POSTGRES_URL، PGHOST، إلخ) عند استخدام SQLite.


الاتصال المسبق في وقت التشغيل

يمكن لـ Bun الاتصال المسبق بـ PostgreSQL عند بدء التشغيل لتحسين الأداء من خلال إنشاء اتصالات قاعدة البيانات قبل تشغيل كود التطبيق الخاص بك. هذا مفيد لتقليل زمن انتقال الاتصال في استعلام قاعدة البيانات الأول.

bash
# تمكين الاتصال المسبق لـ PostgreSQL
bun --sql-preconnect index.js

// يعمل مع متغير البيئة DATABASE_URL
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

// يمكن دمجه مع أعلام وقت التشغيل الأخرى
bun --sql-preconnect --hot index.js

سيقوم العلم --sql-preconnect تلقائيًا بإنشاء اتصال PostgreSQL باستخدام متغيرات البيئة المكونة عند بدء التشغيل. إذا فشل الاتصال، لن يتعطل تطبيقك - سيتم التعامل مع الخطأ بسلاسة.


خيارات الاتصال

يمكنك تكوين اتصال قاعدة البيانات يدويًا عن طريق تمرير خيارات إلى منشئ SQL. تختلف الخيارات اعتمادًا على محول قاعدة البيانات:

خيارات MySQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // مطلوب لـ MySQL عند استخدام كائن الخيارات
  adapter: "mysql",

  // تفاصيل الاتصال
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // اتصال مقبس Unix (بديل لـ hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // إعدادات تجمع الاتصالات
  max: 20, // الحد الأقصى للاتصالات في التجمع (افتراضي: 10)
  idleTimeout: 30, // إغلاق الاتصالات الخاملة بعد 30 ثانية
  maxLifetime: 0, // عمر الاتصال بالثواني (0 = للأبد)
  connectionTimeout: 30, // مهلة عند إنشاء اتصالات جديدة

  // خيارات SSL/TLS
  ssl: "prefer", // أو "disable" أو "require" أو "verify-ca" أو "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // ردود الاستدعاء
  onconnect: client => {
    console.log("تم الاتصال بـ MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("خطأ اتصال MySQL:", err);
    } else {
      console.log("تم إغلاق اتصال MySQL");
    }
  },
});

خيارات PostgreSQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // تفاصيل الاتصال (يتم اكتشاف المحول تلقائيًا كـ PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // معلمات اتصال بديلة
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // إعدادات تجمع الاتصالات
  max: 20, // الحد الأقصى للاتصالات في التجمع
  idleTimeout: 30, // إغلاق الاتصالات الخاملة بعد 30 ثانية
  maxLifetime: 0, // عمر الاتصال بالثواني (0 = للأبد)
  connectionTimeout: 30, // مهلة عند إنشاء اتصالات جديدة

  // خيارات SSL/TLS
  tls: true,
  // tls: {
  //   rejectUnauthorized: true,
  //   requestCert: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  //   checkServerIdentity(hostname, cert) {
  //     ...
  //   },
  // },

  // ردود الاستدعاء
  onconnect: client => {
    console.log("تم الاتصال بـ PostgreSQL");
  },
  onclose: client => {
    console.log("تم إغلاق اتصال PostgreSQL");
  },
});

خيارات SQLite

ts
import { SQL } from "bun";

const sql = new SQL({
  // مطلوب لـ SQLite
  adapter: "sqlite",
  filename: "./data/app.db", // أو ":memory:" لقاعدة بيانات في الذاكرة

  // أوضاع الوصول الخاصة بـ SQLite
  readonly: false, // فتح في وضع القراءة فقط
  create: true, // إنشاء قاعدة البيانات إذا لم تكن موجودة
  readwrite: true, // السماح بعمليات القراءة والكتابة

  // معالجة بيانات SQLite
  strict: true, // تمكين الوضع الصارم لسلامة أنواع أفضل
  safeIntegers: false, // استخدام BigInt للأعداد الصحيحة التي تتجاوز نطاق أرقام JS

  // ردود الاستدعاء
  onconnect: client => {
    console.log("تم فتح قاعدة بيانات SQLite");
  },
  onclose: client => {
    console.log("تم إغلاق قاعدة بيانات SQLite");
  },
});

ملاحظات اتصال SQLite">

  • تجميع الاتصالات: لا يستخدم SQLite تجميع الاتصالات لأنه قاعدة بيانات قائمة على الملفات. كل مثيل SQL يمثل اتصالاً واحدًا.
  • المعاملات: تدعم SQLite المعاملات المتداخلة من خلال نقاط الحفظ، مشابهة لـ PostgreSQL.
  • الوصول المتزامن: تتعامل SQLite مع الوصول المتزامن من خلال قفل الملفات. استخدم وضع WAL لتحسين التزامن.
  • قواعد بيانات الذاكرة: استخدام :memory: ينشئ قاعدة بيانات مؤقتة توجد فقط لعمر الاتصال.

كلمات المرور الديناميكية

عندما يحتاج العملاء إلى استخدام مخططات مصادقة بديلة مثل رموز الوصول أو الاتصالات بقواعد بيانات بكلمات مرور دوارة، قدم دالة متزامنة أو غير متزامنة ستحل قيمة كلمة المرور الديناميكية في وقت الاتصال.

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // تكوين الاتصال الآخر
  ...
  // دالة كلمة المرور لمستخدم قاعدة البيانات
  password: async () => await signer.getAuthToken(),
});

ميزات خاصة بـ SQLite

تنفيذ الاستعلامات

تنفذ SQLite الاستعلامات بشكل متزامن، على عكس PostgreSQL التي تستخدم I/O غير متزامن. ومع ذلك، تظل واجهة برمجة التطبيقات متسقة باستخدام الوعود:

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

// يعمل نفس PostgreSQL، لكن ينفذ بشكل متزامن تحت الغطاء
const users = await sqlite`SELECT * FROM users`;

// المعلمات تعمل بشكل مطابق
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

Pragmas لـ SQLite

يمكنك استخدام عبارات PRAGMA لتكوين سلوك SQLite:

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

// تمكين المفاتيح الخارجية
await sqlite`PRAGMA foreign_keys = ON`;

// تعيين وضع السجل إلى WAL لتحسين التزامن
await sqlite`PRAGMA journal_mode = WAL`;

// التحقق من السلامة
const integrity = await sqlite`PRAGMA integrity_check`;

فروق أنواع البيانات

لدى SQLite نظام أنواع أكثر مرونة من PostgreSQL:

ts
// تخزن SQLite البيانات في 5 فئات تخزين: NULL و INTEGER و REAL و TEXT و BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite أكثر تساهلاً مع الأنواع
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- يمكن تخزين الأرقام كسلاسل
    value NUMERIC,    -- يمكن تخزين الأعداد الصحيحة أو الحقيقية أو النص
    blob BLOB         -- البيانات الثنائية
  )
`;

// يتم تحويل قيم JavaScript تلقائيًا
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

المعاملات

لبدء معاملة جديدة، استخدم sql.begin. تعمل هذه الدالة لكل من PostgreSQL و SQLite. بالنسبة لـ PostgreSQL، تحجز اتصالاً مخصصًا من التجمع. بالنسبة لـ SQLite، تبدأ معاملة على الاتصال الواحد.

يتم إرسال أمر BEGIN تلقائيًا، بما في ذلك أي تكوينات اختيارية تحددها. إذا حدث خطأ أثناء المعاملة، يتم تشغيل ROLLBACK لضمان استمرار العملية بسلاسة.

المعاملات الأساسية

ts
await sql.begin(async tx => {
  // جميع الاستعلامات في هذه الدالة تعمل في معاملة
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // المعاملة تلتزم تلقائيًا إذا لم يتم طرح أخطاء
  // ترتد إذا حدث أي خطأ
});

من الممكن أيضًا أنابيب الطلبات في معاملة إذا لزم الأمر عن طريق إرجاع مصفوفة مع استعلامات من دالة الاستدعاء مثل هذا:

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

نقاط الحفظ

تُنشئ نقاط الحفظ في SQL نقاط تفتيش وسيطة داخل معاملة، مما يسمح بالارتدادات الجزئية بدون التأثير على العملية بأكملها. إنها مفيدة في المعاملات المعقدة، مما يسمح باستعادة الأخطاء والحفاظ على نتائج متسقة.

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

  await tx.savepoint(async sp => {
    // هذا الجزء يمكن أن يرتد بشكل منفصل
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("الارتداد إلى نقطة الحفظ");
    }
  });

  // الاستمرار في المعاملة حتى إذا ارتدت نقطة الحفظ
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

المعاملات الموزعة

الالتزام ذو المرحلتين (2PC) هو بروتوكول معاملة موزعة حيث في المرحلة 1 يقوم المنسق بإعداد العقد من خلال ضمان كتابة البيانات والاستعداد للالتزام، بينما تنهي المرحلة 2 مع العقد التي تلتزم أو ترتد بناءً على قرار المنسق. تضمن هذه العملية متانة البيانات والإدارة المناسبة للأقفال.

في PostgreSQL و MySQL، تستمر المعاملات الموزعة بعد جلستها الأصلية، مما يسمح للمستخدمين المتميزين أو المنسقين بالالتزام أو الارتداد منها لاحقًا. هذا يدعم المعاملات الموزعة القوية وعمليات الاستعادة والعمليات الإدارية.

كل نظام قاعدة بيانات ينفذ المعاملات الموزعة بشكل مختلف:

تدعم PostgreSQL محليًا من خلال المعاملات المحضرة، بينما تستخدم MySQL معاملات XA.

إذا حدثت أي استثناءات أثناء المعاملة الموزعة ولم يتم التقاطها، فسيرتد النظام تلقائيًا جميع التغييرات. عندما يسير كل شيء بشكل طبيعي، تحتفظ بالمرونة إما للالتزام أو الارتداد من المعاملة لاحقًا.

ts
// بدء معاملة موزعة
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// لاحقًا، التزام أو ارتداد
await sql.commitDistributed("tx1");
// أو
await sql.rollbackDistributed("tx1");

المصادقة

يدعم Bun مصادقة SCRAM-SHA-256 (SASL) و MD5 والنص العادي. يُوصى بـ SASL لأمان أفضل. راجع مصادقة SASL لـ Postgres للحصول على مزيد من المعلومات.

نظرة عامة على أوضاع SSL

تدعم PostgreSQL أوضاع SSL/TLS مختلفة للتحكم في كيفية إنشاء الاتصالات الآمنة. تحدد هذه الأوضاع السلوك عند الاتصال ومستوى التحقق من الشهادة الذي يتم إجراؤه.

ts
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 في سلاسل الاتصال:

ts
// استخدام وضع prefer
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// استخدام وضع verify-full
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");

تجميع الاتصالات

يدير عميل SQL في Bun تلقائيًا تجمع اتصالات، وهو تجمع من اتصالات قاعدة البيانات التي يعاد استخدامها لاستعلامات متعددة. هذا يساعد على تقليل نفقات إنشاء وإغلاق الاتصالات لكل استعلام، ويساعد أيضًا على إدارة عدد الاتصالات المتزامنة بقاعدة البيانات.

ts
const sql = new SQL({
  // تكوين التجمع
  max: 20, // الحد الأقصى 20 اتصال متزامن
  idleTimeout: 30, // إغلاق الاتصالات الخاملة بعد 30 ثانية
  maxLifetime: 3600, // الحد الأقصى لعمر الاتصال ساعة واحدة
  connectionTimeout: 10, // مهلة الاتصال 10 ثوانٍ
});

لن يتم إجراء أي اتصال حتى يتم إجراء استعلام.

ts
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 حجز اتصال من التجمع، ويرجع عميلًا يغلف الاتصال الواحد. يمكن استخدام هذا لتشغيل استعلامات على اتصال معزول.

ts
// الحصول على اتصال حصري من التجمع
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // مهم: إطلاق الاتصال مرة أخرى إلى التجمع
  reserved.release();
}

// أو استخدام Symbol.dispose
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // يتم إطلاقه تلقائيًا

العبارات المحضرة

بشكل افتراضي، ينشئ عميل SQL في Bun تلقائيًا عبارات محضرة مسماة للاستعلامات حيث يمكن الاستدلال على أن الاستعلام ثابت. هذا يوفر أداءً أفضل. ومع ذلك، يمكنك تغيير هذا السلوك عن طريق تعيين prepare: false في خيارات الاتصال:

ts
const sql = new SQL({
  // ... خيارات أخرى ...
  prepare: false, // تعطيل استمرار العبارات المحضرة المسماة على الخادم
});

عند تعيين prepare: false:

لا تزال الاستعلامات تُنفذ باستخدام بروتوكول "extended"، لكنها تُنفذ باستخدام عبارات محضرة غير مسماة، العبارة المحضرة غير المسماة تستمر فقط حتى يتم إصدار عبارة Parse التالية التي تحدد العبارة غير المسماة كوجهة.

  • لا يزال ربط المعلمات آمنًا ضد حقن SQL
  • يتم تحليل كل استعلام وتخطيطه من الصفر بواسطة الخادم
  • لن يتم أنبوبة الاستعلامات

قد ترغب في استخدام prepare: false عندما:

  • استخدام PGBouncer في وضع المعاملة (على الرغم من أنه منذ PGBouncer 1.21.0، يتم دعم العبارات المحضرة المسماة على مستوى البروتوكول عند تكوينها بشكل صحيح)
  • تصحيح خطط تنفيذ الاستعلام
  • العمل مع SQL ديناميكي حيث تحتاج خطط الاستعلام إلى إعادة إنشائها بشكل متكرر
  • لن يتم دعم أكثر من أمر واحد لكل استعلام (ما لم تستخدم sql``.simple())

لاحظ أن تعطيل العبارات المحضرة قد يؤثر على الأداء للاستعلامات التي تُنفذ بشكل متكرر بمعلمات مختلفة، حيث يحتاج الخادم إلى تحليل وتخطيط كل استعلام من الصفر.


معالجة الأخطاء

يوفر العميل أخطاءً مكتوبة لسيناريوهات الفشل المختلفة. الأخطاء خاصة بقاعدة البيانات وتمتد من فئات الأخطاء الأساسية:

فئات الأخطاء

ts
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // خطأ خاص بـ PostgreSQL
    console.log(error.code); // رمز خطأ PostgreSQL
    console.log(error.detail); // رسالة خطأ مفصلة
    console.log(error.hint); // تلميح مفيد من PostgreSQL
  } else if (error instanceof SQL.SQLiteError) {
    // خطأ خاص بـ SQLite
    console.log(error.code); // رمز خطأ SQLite (مثل "SQLITE_CONSTRAINT")
    console.log(error.errno); // رقم خطأ SQLite
    console.log(error.byteOffset); // إزاحة البايت في عبارة SQL (إذا كانت متاحة)
  } else if (error instanceof SQL.SQLError) {
    // خطأ SQL عام (فئة أساسية)
    console.log(error.message);
  }
}

رموز الأخطاء الخاصة بـ PostgreSQL">

أخطاء اتصال PostgreSQL

أخطاء الاتصالالوصف
ERR_POSTGRES_CONNECTION_CLOSEDتم إنهاء الاتصال أو لم يتم إنشاؤه أبدًا
ERR_POSTGRES_CONNECTION_TIMEOUTفشل في إنشاء الاتصال خلال فترة المهلة
ERR_POSTGRES_IDLE_TIMEOUTتم إغلاق الاتصال بسبب عدم النشاط
ERR_POSTGRES_LIFETIME_TIMEOUTتجاوز الاتصال الحد الأقصى لعمره
ERR_POSTGRES_TLS_NOT_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_CONSTRAINT19انتهاك القيد (UNIQUE أو CHECK أو NOT NULL أو غيرها)
SQLITE_BUSY5قاعدة البيانات مقفلة
SQLITE_LOCKED6الجدول في قاعدة البيانات مقفل
SQLITE_READONLY8محاولة الكتابة إلى قاعدة بيانات للقراءة فقط
SQLITE_IOERR10خطأ I/O في القرص
SQLITE_CORRUPT11صورة قرص قاعدة البيانات تالفة
SQLITE_FULL13قاعدة البيانات أو القرص ممتلئ
SQLITE_CANTOPEN14غير قادر على فتح ملف قاعدة البيانات
SQLITE_PROTOCOL15خطأ في بروتوكول قفل قاعدة البيانات
SQLITE_SCHEMA17تغير مخطط قاعدة البيانات
SQLITE_TOOBIG18السلسلة أو BLOB تتجاوز حد الحجم
SQLITE_MISMATCH20عدم تطابق نوع البيانات
SQLITE_MISUSE21استخدام المكتبة بشكل غير صحيح
SQLITE_AUTH23تم رفض التفويض

مثال على معالجة الأخطاء:

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

try {
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // معرف مكرر
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("انتهاك القيد:", error.message);
      // التعامل مع انتهاك القيد الفريد
    }
  }
}

الأعداد و BigInt

يتضمن عميل SQL في Bun معالجة خاصة للأعداد الكبيرة التي تتجاوز نطاق عدد 53 بت. إليك كيفية عمله:

ts
import { sql } from "bun";

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

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

BigInt بدلاً من السلاسل

إذا كنت بحاجة إلى أعداد كبيرة كـ BigInt بدلاً من السلاسل، يمكنك تمكين ذلك عن طريق تعيين خيار bigint إلى true عند تهيئة عميل SQL:

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

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

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

خارطة الطريق

لا تزال هناك بعض الأشياء التي لم ننتهِ منها بعد.

  • تحميل الاتصال عبر علم --db-preconnect في سطر أوامر Bun
  • تحويلات أسماء الأعمدة (مثل snake_case إلى camelCase). هذا معطل في الغالب على تنفيذ مدرك لـ unicode لتغيير الحالة في C++ باستخدام WTF::String من WebKit.
  • تحويلات أنواع الأعمدة

ميزات خاصة بقاعدة البيانات

طرق المصادقة

تدعم MySQL ملحقات مصادقة متعددة يتم التفاوض عليها تلقائيًا:

  • mysql_native_password - مصادقة MySQL التقليدية، متوافقة على نطاق واسع
  • caching_sha2_password - افتراضي في MySQL 8.0+، أكثر أمانًا مع تبادل مفتاح RSA
  • sha256_password - مصادقة قائمة على SHA-256

يتعامل العميل تلقائيًا مع تبديل ملحق المصادقة عند طلبه من الخادم، بما في ذلك تبادل كلمة المرور الآمن عبر اتصالات غير SSL.

العبارات المحضرة والأداء

تستخدم MySQL عبارات محضرة من جانب الخادم لجميع الاستعلامات المعلمة:

ts
// هذا ينشئ تلقائيًا عبارة محضرة على الخادم
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// يتم تخزين العبارات المحضرة وإعادة استخدامها للاستعلامات المتطابقة
for (const id of userIds) {
  // يعاد استخدام نفس العبارة المحضرة
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// أنابيب الاستعلام - يتم إرسال عبارات متعددة بدون انتظار
const [users, orders, products] = await Promise.all([
  mysql`SELECT * FROM users WHERE active = ${true}`,
  mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
  mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);

مجموعات النتائج المتعددة

يمكن لـ MySQL إرجاع مجموعات نتائج متعددة من استعلامات متعددة العبارات:

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

// استعلامات متعددة العبارات مع دالة simple()
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

مجموعات الأحرف والترتيبات

يستخدم Bun.SQL تلقائيًا مجموعة أحرف utf8mb4 لاتصالات MySQL، مما يضمن دعم Unicode الكامل بما في ذلك الرموز التعبيرية. هذه هي مجموعة الأحرف الموصى بها لتطبيقات MySQL الحديثة.

سمات الاتصال

يرسل Bun تلقائيًا معلومات العميل إلى MySQL لمراقبة أفضل:

ts
// يتم إرسال هذه السمات تلقائيًا:
// _client_name: "Bun"
// _client_version: <إصدار bun>
// يمكنك رؤية هذه في performance_schema.session_connect_attrs في MySQL

معالجة الأنواع

يتم تحويل أنواع MySQL تلقائيًا إلى أنواع JavaScript:

نوع MySQLنوع JavaScriptملاحظات
INT و TINYINT و MEDIUMINTnumberضمن نطاق الأعداد الصحيحة الآمنة
BIGINTstring أو number أو BigIntإذا كانت القيمة تناسب حجم i32/u32 ستكون number وإلا string أو BigInt بناءً على خيار bigint
DECIMAL و NUMERICstringللحفاظ على الدقة
FLOAT و DOUBLEnumber
DATEDateكائن Date في JavaScript
DATETIME و TIMESTAMPDateمع معالجة المنطقة الزمنية
TIMEnumberإجمالي الميكروثواني
YEARnumber
CHAR و VARCHAR و VARSTRING و STRINGstring
TINY TEXT و MEDIUM TEXT و TEXT و LONG TEXTstring
TINY BLOB و MEDIUM BLOB و BLOB و LONG BLOBstringأنواع BLOB هي اسم مستعار لأنواع TEXT
JSONobject/arrayيتم تحليلها تلقائيًا
BIT(1)booleanBIT(1) في MySQL
GEOMETRYstringبيانات الهندسة

الفروق عن PostgreSQL

على الرغم من توحيد واجهة برمجة التطبيقات، هناك بعض الفروق السلوكية:

  1. عناصر نائبة للمعلمات: تستخدم MySQL ? داخليًا لكن Bun يحول نمط $1 و $2 تلقائيًا
  2. بند RETURNING: لا يدعم MySQL RETURNING؛ استخدم result.lastInsertRowid أو SELECT منفصل
  3. أنواع المصفوفات: لا يحتوي MySQL على أنواع مصفوفات أصلية مثل PostgreSQL

ميزات خاصة بـ MySQL

لم ننفذ دعم LOAD DATA INFILE بعد

ميزات خاصة بـ PostgreSQL

لم ننفذ هذه بعد:

  • دعم COPY
  • دعم LISTEN
  • دعم NOTIFY

لم ننفذ أيضًا بعض الميزات الأكثر ندرة مثل:

  • مصادقة GSSAPI
  • دعم SCRAM-SHA-256-PLUS
  • أنواع Point و PostGIS
  • جميع أنواع مصفوفات الأعداد الصحيحة متعددة الأبعاد (يتم دعم نوعين فقط)

الأنماط الشائعة وأفضل الممارسات

العمل مع مجموعات نتائج MySQL

ts
// الحصول على معرف الإدراج بعد INSERT
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

ts
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("تم اكتشاف إدخال مكرر");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("تم رفض الوصول");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("قاعدة البيانات غير موجودة");
  }
  // رموز أخطاء MySQL متوافقة مع حزم mysql/mysql2
}

نصائح الأداء لـ MySQL

  1. استخدم تجميع الاتصالات: عيّن حجم تجمع max مناسب بناءً على حمل العمل الخاص بك
  2. تمكين العبارات المحضرة: مفعلة افتراضيًا وتحسن الأداء
  3. استخدم المعاملات للعمليات المجمعة: جمّع الاستعلامات ذات الصلة في معاملات
  4. استخدم الفهارس بشكل صحيح: تعتمد MySQL بشدة على الفهارس لأداء الاستعلام
  5. استخدم مجموعة أحرف utf8mb4: مضبوطة افتراضيًا وتتعامل مع جميع أحرف 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 أيضًا. إنها خيارات رائعة.

سببان:

  1. نعتقد أنه من الأبسط للمطورين وجود برنامج تشغيل قاعدة بيانات مدمج في Bun. الوقت الذي تقضيه في التسوق للمكتبات هو الوقت الذي كان يمكنك فيه بناء تطبيقك.
  2. نستفيد من بعض internals محرك JavaScriptCore لجعله أسرع لإنشاء كائنات سيكون من الصعب تنفيذها في مكتبة

الشكر

شكر كبير لـ @porsager على postgres.js للإلهام لواجهة واجهة برمجة التطبيقات.

Bun بواسطة www.bunjs.com.cn تحرير