Kodax

استعلامات متقدمة — Supabase

الاستعلامات المتقدمة و RPC

فلاتر مركبة

import { supabase } from "./supabaseClient.js";

export async function searchProducts({ term, min, max }) {
  let query = supabase.from("products").select("id, name, price, category");
  if (term) query = query.ilike("name", `%${term}%`);
  if (min !== undefined) query = query.gte("price", min);
  if (max !== undefined) query = query.lte("price", max);
  const { data, error } = await query.order("price");
  if (error) throw error;
  return data;
}

بناء الاستعلام تدريجياً حسب الفلاتر الموجودة نمط شائع. .or("price.lt.50,featured.eq.true") لشروط "أو"، و .in("category", [...]) لقائمة. ⚠️ لا تلصق مدخلات المستخدم داخل نص .or(...) بلا تحقق — صيغته نصية ويمكن التلاعب بها.

العلاقات المتداخلة

بالمفاتيح الأجنبية، يجلب select الجداول المرتبطة في طلب واحد:

import { supabase } from "./supabaseClient.js";

export async function getPost(postId) {
  const { data, error } = await supabase
    .from("posts")
    .select("id, title, author:profiles(id, name), comments(id, content, created_at)")
    .eq("id", postId)
    .single();
  if (error) throw error;
  return data;
}

author:profiles(...) اسم مستعار للعلاقة. RLS تُطبق على كل جدول مرتبط أيضاً.

الترقيم والعد

import { supabase } from "./supabaseClient.js";

export async function page(n, size = 20) {
  const from = (n - 1) * size;
  const { data, count, error } = await supabase
    .from("products")
    .select("id, name", { count: "exact" })
    .range(from, from + size - 1);          // شامل للطرفين
  if (error) throw error;
  return { data, total: count };
}

RPC: دوال PostgreSQL

للمنطق الذي يحتاج عدة عمليات في معاملة واحدة، أو حسابات معقدة، اكتب دالة SQL واستدعها بـ supabase.rpc("name", args):

CREATE OR REPLACE FUNCTION get_user_stats(p_user_id uuid)
RETURNS json
LANGUAGE sql STABLE
SECURITY INVOKER          -- تعمل بصلاحيات المستدعي: RLS تُطبق
AS $$
  SELECT json_build_object(
    'posts',    (SELECT count(*) FROM posts    WHERE author_id = p_user_id),
    'comments', (SELECT count(*) FROM comments WHERE user_id   = p_user_id)
  );
$$;

⚠️ لاحظ اسم المعامل p_user_id: لو سُمّي user_id لكان الشرط WHERE user_id = user_id يقارن العمود بنفسه (صحيح دائماً) ويعدّ كل التعليقات — خطأ موجود في النسخة السابقة من هذا الدرس. استخدم بادئة للمعاملات.

SECURITY DEFINER بحذر

دالة SECURITY DEFINER تعمل بصلاحيات منشئها وتتجاوز RLS. استخدمها فقط عند الحاجة، مع: فحص auth.uid() داخلها، و SET search_path = ''، وأسماء جداول كاملة (public.posts)، وسحب صلاحية التنفيذ من anon إن لم تكن للعموم.

تمرين

اكتب دالة RPC place_order(p_items jsonb) تتحقق من المخزون وتنقصه وتنشئ الطلب في معاملة واحدة، وتستخدم auth.uid() كمالك للطلب بدل قيمة من العميل.

import { supabase } from "./supabaseClient.js";

// فلاتر اختيارية تُبنى تدريجياً
export async function searchProducts({ term, minPrice, maxPrice, categories } = {}) {
  let query = supabase.from("products").select("id, name, price, category", { count: "exact" });
  if (term) query = query.ilike("name", `%${term}%`);
  if (minPrice !== undefined) query = query.gte("price", minPrice);
  if (maxPrice !== undefined) query = query.lte("price", maxPrice);
  if (categories?.length) query = query.in("category", categories);

  const { data, count, error } = await query.order("price").range(0, 19);
  if (error) throw error;
  return { data, total: count };
}

// منشور مع كاتبه وتعليقاته في طلب واحد
export async function getPostWithComments(postId) {
  const { data, error } = await supabase
    .from("posts")
    .select("id, title, content, author:profiles(id, name, avatar_url), comments(id, content, created_at, user:profiles(name))")
    .eq("id", postId)
    .single();
  if (error) throw error;
  return data;
}

// استدعاء دالة PostgreSQL
export async function getMyStats(userId) {
  const { data, error } = await supabase.rpc("get_user_stats", { p_user_id: userId });
  if (error) throw error;
  return data;   // { posts: 3, comments: 12 }
}

/* ===== الدالة في SQL Editor — بادئة p_ تمنع حجب اسم العمود =====
CREATE OR REPLACE FUNCTION get_user_stats(p_user_id uuid)
RETURNS json LANGUAGE sql STABLE SECURITY INVOKER AS $$
  SELECT json_build_object(
    'posts',    (SELECT count(*) FROM posts    WHERE author_id = p_user_id),
    'comments', (SELECT count(*) FROM comments WHERE user_id   = p_user_id)
  );
$$;
*/
اختبر فهمك