دليل كتابة عمليات Firebase SQL Connect باستخدام لغة الاستعلامات البنيوية (SQL) بدلاً من GraphQL.page_type: guideannouncement: >تتوفر لغة الاستعلامات البنيوية (SQL) الأصلية كمعاينة للميزة، مما يعني أنها ليست خاضعة لأي اتفاقية مستوى الخدمة أو سياسة الإيقاف النهائي ويمكن أن تتغير بطرق غير متوافقة مع الأنظمة القديمة. إذا كنت تستخدم هذه الميزة مع الإجراءات أو الدوال المخزّنة التي تنفّذ SQL ديناميكيًا، اتّبِع أفضل ممارسات الأمان الموضّحة في أسفل هذه الصفحة.
Firebase SQL Connect توفّر طرقًا متعدّدة للتفاعل مع قاعدة بياناتك Cloud SQL:
- Native GraphQL: يمكنك تحديد الأنواع في ملف
schema.gqlو SQL Connect تترجم عمليات GraphQL إلى SQL. هذا هو النهج العادي الذي يوفّر كتابة قوية للأنواع وبُنى مفروضة على المخطط. تتناول معظم مستندات SQL Connect خارج هذه الصفحة هذا الخيار. ننصحك باستخدام هذه الطريقة للاستفادة من ميزة الأمان الكامل للأنواع ودعم الأدوات، متى أمكن ذلك. - التوجيه
@view: يمكنك تحديد نوع GraphQL فيschema.gqlاستنادًا إلى عبارة مخصّصةSELECTفي SQL. يفيد ذلك في إنشاء طرق عرض للقراءة فقط ومكتوبة بقوة استنادًا إلى منطق SQL معقّد. يمكن الاستعلام عن هذه الأنواع مثل الأنواع العادية. راجِع@view. - Native SQL: يمكنك تضمين عبارات SQL مباشرةً في العمليات المُسمّاة في
.gqlباستخدام حقول جذر خاصة. يوفّر ذلك أقصى قدر من المرونة والتحكّم المباشر، خاصةً للعمليات التي لا يتيحها GraphQL العادي، أو التي تستفيد من الميزات الخاصة بقاعدة البيانات، أو التي تستخدم إضافات PostgreSQL. على عكس GraphQL والتوجيه@view، لا يوفّر Native SQL ناتجًا مكتوبًا بقوة.
يركّز هذا الدليل على خيار Native SQL.
حالات الاستخدام الشائعة لـ Native SQL
في حين يوفّر Native GraphQL ميزة منع أخطاء الكتابة الكامل، ويقدّم التوجيه @view نتائج مكتوبة بقوة لتقارير SQL للقراءة فقط، يوفّر Native SQL المرونة اللازمة لما يلي:
- إضافات PostgreSQL: يمكنك الاستعلام عن أي إضافة مثبَّتة في PostgreSQL
واستخدامها مباشرةً (مثل
PostGISللبيانات الجغرافية المكانية) بدون الحاجة إلى ربط الأنواع المعقّدة في مخطط GraphQL. - الاستعلامات المعقّدة: يمكنك تنفيذ SQL المعقّد باستخدام عمليات الربط والاستعلامات الفرعية، التجميع والدوال المستندة إلى النطاق والإجراءات المخزّنة.
- معالجة البيانات (DML): يمكنك تنفيذ عمليات
INSERT, UPDATE, DELETEمباشرةً. (ومع ذلك، لا تستخدم Native SQL لأوامر لغة تعريف البيانات (DDL). عليك مواصلة إجراء التعديلات على مستوى المخطط باستخدام GraphQL للحفاظ على مزامنة الخلفية وحِزم SDK التي تم إنشاؤها.) - الميزات الخاصة بقاعدة البيانات: يمكنك استخدام الدوال أو العوامل أو أنواع البيانات الخاصة بـ PostgreSQL.
- تحسين الأداء: يمكنك ضبط عبارات SQL يدويًا للمسارات المهمة.
حقول جذر Native SQL
لكتابة العمليات باستخدام SQL، استخدِم أحد حقول الجذر التالية لأنواع query أو mutation:
حقول query
| الحقل | الوصف |
|---|---|
_select |
ينفّذ استعلام SQL يعرض صفرًا أو أكثر من الصفوف. الوسيطات:
النتائج: صفيف JSON ( |
_selectFirst |
ينفّذ استعلام SQL يُتوقَّع أن يعرض صفرًا أو صفًا واحدًا. الوسيطات:
النتائج: كائن JSON ( |
حقول mutation
| الحقل | الوصف |
|---|---|
_execute |
ينفّذ عبارة لغة معالجة البيانات ( الوسيطات:
النتائج: يتم تجاهل عبارات |
_executeReturning |
ينفّذ عبارة لغة معالجة البيانات باستخدام عبارة الوسيطات:
النتائج: صفيف JSON ( |
_executeReturningFirst |
ينفّذ عبارة لغة معالجة البيانات باستخدام عبارة الوسيطات:
النتائج: كائن JSON ( |
ملاحظات:
- يتم تنفيذ العمليات باستخدام الأذونات الممنوحة لحساب خدمة الـ SQL Connect.
قواعد البنية والقيود
يفرض Native SQL قواعد تحليل صارمة لضمان الأمان ومنع حقن SQL. يُرجى مراعاة القيود التالية:
- التعليقات: استخدِم التعليقات المتعدّدة الأسطر (
/* ... */). يُمنع استخدام التعليقات على مستوى السطر (--) لأنّها يمكن أن تقطع العبارات اللاحقة (مثل فلاتر الأمان) أثناء تسلسل الاستعلام. - المَعلمات: استخدِم المَعلمات الموضعية (
$1و$2) التي تتطابق معparamsترتيب صفيف. لا يتم دعم المَعلمات المُسمّاة ($idو:name). - السلاسل: يتم دعم السلاسل الحرفية الموسّعة (
E'...') والسلاسل التي يتم اقتباسها بالرمز الدولار ($$...$$). لا يتم دعم أحرف Unicode المخصصة لـ PostgreSQL (U&'...') لا يتم دعمها.
المَعلمات في التعليقات
يتجاهل المحلّل كل ما بداخل تعليق متعدّد الأسطر. إذا علّقت على سطر يحتوي على مَعلمة (على سبيل المثال، /* WHERE id = $1 */)، عليك أيضًا إزالة هذه المَعلمة من قائمة params، وإلا ستفشل العملية بسبب الخطأ unused parameter: $1.
اصطلاحات التسمية
عند كتابة Native SQL، أنت تتفاعل مباشرةً مع قاعدة بيانات PostgreSQL، لذا عليك استخدام أسماء قاعدة البيانات الفعلية للجداول والأعمدة. تلقائيًا، تربط
SQL Connect الأسماء في مخطط GraphQL
بـ snake case في قاعدة البيانات، ما لم تخصّص معرّفات
PostgreSQL بشكلٍ صريح باستخدام التوجيهَين
@table(name) و
@col(name).
إذا حدّدت نوعًا بدون توجيهات، يتم ربط أسماء جدول GraphQL وحقوله بمعرّفات snake_case التلقائية في PostgreSQL:
schema.gql |
queries.gql |
|---|---|
|
|
تكون معرّفات PostgreSQL غير حساسة لحالة الأحرف تلقائيًا. إذا كنت تستخدم توجيهات مثل @table أو @col لتحديد اسم يحتوي على أحرف كبيرة أو أحرف مختلطة، عليك وضع هذا المعرّف بين علامتَي اقتباس مزدوجتَين في عبارات SQL.
في المثال التالي، عليك استخدام "UserProfiles" لاسم الجدول و
"profileId" لعمود userId. يتبع الحقل displayName عملية التحويل التلقائية إلى display_name:
schema.gql |
queries.gql |
|---|---|
|
|
أمثلة على استخدام هذه الكلمة في جملة
المثال 1: عبارة `SELECT` أساسية مع وضع اسم مستعار للحقل
يمكنك وضع اسم مستعار لحقل الجذر (على سبيل المثال، movies: _select) لجعل استجابة العميل أوضح (data.movies بدلاً من data._select).
queries.gql:
query GetMoviesByGenre($genre: String!, $limit: Int!) @auth(level: PUBLIC) {
movies: _select(
sql: """
SELECT id, title, release_year, rating
FROM movie
WHERE genre = $1
ORDER BY release_year DESC
LIMIT $2
""",
params: [$genre, $limit]
)
}
بعد تشغيل الاستعلام باستخدام حزمة SDK للعميل، ستظهر النتيجة في data.movies.
المثال 2: عبارة `UPDATE` أساسية
mutations.gql:
mutation UpdateMovieRating(
$movieId: UUID!,
$newRating: Float!
) @auth(level: NO_ACCESS) {
_execute(
sql: """
UPDATE movie
SET rating = $2
WHERE id = $1
""",
params: [$movieId, $newRating]
)
}
بعد تشغيل عملية التعديل باستخدام حزمة SDK للعميل، سيظهر عدد الصفوف المتأثرة في data._execute.
المثال 3: عملية تجميع أساسية
queries.gql:
query GetTotalReviewCount @auth(level: PUBLIC) {
stats: _selectFirst(
sql: "SELECT COUNT(*) as total_reviews FROM \"Reviews\""
)
}
بعد تشغيل الاستعلام باستخدام حزمة SDK للعميل، ستظهر النتيجة في data.stats.total_reviews.
المثال 4: عملية تجميع متقدّمة باستخدام `RANK`
queries.gql:
query GetMoviesRankedByRating @auth(level: PUBLIC) {
_select(
sql: """
SELECT
id,
title,
rating,
RANK() OVER (ORDER BY rating DESC) as rank
FROM movie
WHERE rating IS NOT NULL
LIMIT 20
""",
params: []
)
}
بعد تشغيل الاستعلام باستخدام حزمة SDK للعميل، ستظهر النتيجة في data._select.
المثال 5: عبارة `UPDATE` باستخدام `RETURNING` وسياق المصادقة
mutations.gql:
mutation UpdateMyReviewText(
$movieId: UUID!,
$newText: String!
) @auth(level: USER) {
updatedReview: _executeReturningFirst(
sql: """
UPDATE "Reviews"
SET review_text = $2
WHERE movie_id = $1 AND user_id = $3
RETURNING movie_id, user_id, rating, review_text
""",
params: [$movieId, $newText, {_expr: "auth.uid"}]
)
}
بعد تشغيل عملية التعديل باستخدام حزمة SDK للعميل، ستظهر بيانات المنشور المعدَّلة في data.updatedReview.
المثال 6: عبارة جدول شائعة متقدّمة باستخدام عمليات الإدراج أو التعديل (الحصول على سجلّ أو إنشاؤه بشكلٍ ذري)
يفيد هذا النمط في ضمان توفُّر السجلات التابعة (مثل المستخدمين أو الأفلام) قبل إدراج سجلّ ثانوي (مثل مراجعة)، وكل ذلك في معاملة واحدة لقاعدة البيانات.
mutations.gql:
mutation CreateMovieCTE($movieId: UUID!, $userId: UUID!, $reviewId: UUID!) @auth(level: USER) {
_execute(
sql: """
WITH
new_user AS (
INSERT INTO "user" (id, username)
VALUES ($2, 'Auto-Generated User')
ON CONFLICT (id) DO NOTHING
RETURNING id
),
movie AS (
INSERT INTO movie (id, title, image_url, release_year, genre)
VALUES ($1, 'Auto-Generated Movie', 'https://placeholder.com', 2025, 'Sci-Fi')
ON CONFLICT (id) DO NOTHING
RETURNING id
)
INSERT INTO "Reviews" (id, movie_id, user_id, rating, review_text, review_date)
VALUES (
$3,
$1,
$2,
5,
'Good!',
NOW()
)
""",
params: [$movieId, $userId, $reviewId]
)
}
_executeReturning و_executeReturningFirst يغلّفان استعلامك في عبارة جدول شائعة رئيسية لتنسيق الناتج بتنسيق JSON. لا يسمح PostgreSQL بتضمين عبارة جدول شائعة تعدّل البيانات داخل عبارة أخرى تعدّل البيانات، ما يؤدي إلى فشل الاستعلام.
المثال 7: استخدام إضافات PostgreSQL
يتيح لك Native SQL استخدام إضافات PostgreSQL، مثل PostGIS، بدون الحاجة إلى ربط أنواع الهندسة المعقّدة في مخطط GraphQL أو تغيير الجداول الأساسية.
في هذا المثال، لنفترض أنّ تطبيق المطعم يتضمّن جدولاً يخزّن بيانات الموقع الجغرافي
في عمود JSON للبيانات الوصفية (على سبيل المثال، {"latitude": 37.3688,
"longitude": -122.0363}). إذا فعّلت إضافة PostGIS
، يمكنك
استخدام عوامل JSON العادية في PostgreSQL (->>) لاستخراج هذه القيم أثناء
التنفيذ
وتمريرها إلى دالة PostGIS ST_MakePoint.
query GetNearbyActiveRestaurants(
$userLong: Float!,
$userLat: Float!,
$maxDistanceMeters: Float!
) @auth(level: USER) {
nearby: _select(
sql: """
SELECT
id,
name,
tags,
ST_Distance(
ST_MakePoint(
(metadata->>'longitude')::float,
(metadata->>'latitude')::float
)::geography,
ST_MakePoint($1, $2)::geography
) as distance_meters
FROM restaurant
WHERE active = true
AND metadata ? 'longitude' AND metadata ? 'latitude'
AND ST_DWithin(
ST_MakePoint(
(metadata->>'longitude')::float,
(metadata->>'latitude')::float
)::geography,
ST_MakePoint($1, $2)::geography,
$3
)
ORDER BY distance_meters ASC
LIMIT 10
""",
params: [$userLong, $userLat, $maxDistanceMeters]
)
}
بعد تشغيل الاستعلام باستخدام حزمة SDK للعميل، ستظهر النتيجة في data.nearby.
أفضل ممارسات الأمان: SQL الديناميكي والإجراءات المخزّنة
SQL Connect تُعدّ جميع الإدخالات بشكلٍ آمن على الحد الفاصل بين GraphQL وقاعدة البيانات، ما يحمي استعلامات SQL العادية بالكامل من حقن SQL من الدرجة الأولى. ومع ذلك، إذا كنت تستخدم SQL لاستدعاء إجراءات أو دوال مخزّنة مخصّصة في PostgreSQL تنفّذ SQL ديناميكيًا، عليك التأكّد من أنّ رمز PL/pgSQL الداخلي يعالج هذه المَعلمات بشكلٍ آمن.
إذا كان الإجراء المخزّن يربط مباشرةً إدخالات المستخدمين بسلسلة EXECUTE، فإنّه يتجاوز عملية إعداد المَعلمات ويُنشئ ثغرة أمنية لحقن SQL من الدرجة الثانية:
-- INSECURE: Do not concatenate parameters into dynamic strings!
CREATE OR REPLACE PROCEDURE unsafe_update(user_input TEXT)
LANGUAGE plpgsql AS $$
BEGIN
-- A malicious user_input (e.g., "val'; DROP TABLE users; --")
-- will execute as code.
EXECUTE 'UPDATE target_table SET status = ''' || user_input || '''';
END;
$$;
لتجنُّب ذلك، اتّبِع أفضل الممارسات التالية:
- استخدِم عبارة
USING: عند كتابة SQL ديناميكي في الإجراءات المخزّنة، استخدِم دائمًا عبارةUSINGلربط مَعلمات البيانات بأمان. - استخدِم
format()للمعرّفات: استخدِمformat()مع العلامة%Iلإدراج معرّفات قاعدة البيانات بأمان (مثل أسماء الجداول). - اسمح بالمعرّفات بشكلٍ صارم: لا تسمح لتطبيقات العميل باختيار معرّفات قاعدة البيانات بشكلٍ عشوائي. إذا كان الإجراء يتطلّب معرّفات ديناميكية، تحقَّق من صحة الإدخال مقارنةً بقائمة السماح المرمّزة في منطق PL/pgSQL قبل التنفيذ.
-- SECURE: Use format() for identifiers and USING for data values
CREATE OR REPLACE PROCEDURE secure_update(
target_table TEXT, new_value TEXT, row_id INT
)
LANGUAGE plpgsql AS $$
BEGIN
-- Validate the dynamic table name against an allowlist
IF target_table NOT IN ('orders', 'users', 'inventory') THEN
RAISE EXCEPTION 'Invalid table name';
END IF;
-- Execute securely
EXECUTE format('UPDATE %I SET status = $1 WHERE id = $2', target_table)
USING new_value, row_id;
END;
$$;