معالجة الأخطاء: RAISE و EXCEPTION
برنامج محترف يرفض المدخلات الخاطئة برسالة واضحة، ويتعامل مع ما يمكن التعامل معه. ⏱ نحو 40 دقيقة.
- تستخدم مستويات
RAISEوتعرف أثر كل منها. - ترفع خطأ عمل برسالة وتلميح.
- تلتقط الأخطاء بأسمائها وتقرأ
SQLSTATEوSQLERRM. - تحدد ما يُلغى عند التقاط الخطأ وتستخدم الكتل الداخلية.
١RAISE: من رسالة إلى إيقاف
| المستوى | الأثر |
|---|---|
RAISE NOTICE | رسالة معلومات، والتنفيذ يستمر |
RAISE WARNING | تحذير، والتنفيذ يستمر |
RAISE EXCEPTION | خطأ: يوقف التنفيذ ويُلغي كل ما فعلته المعاملة |
القاعدة تحمي نفسها بالقيود، لكن قواعد العمل (مثل «لا تبع أكثر من المخزون») تحتاج خطأك أنت، برسالة واضحة وتلميح:
CREATE PROCEDURE sell(pid int, n int)LANGUAGE plpgsqlAS $$DECLARE have int;BEGIN SELECT stock INTO have FROM products WHERE id = pid; IF have IS NULL THEN RAISE EXCEPTION 'لا يوجد منتج رقمه %', pid; ELSIF have < n THEN RAISE EXCEPTION 'المخزون غير كافٍ: المتوفر % والمطلوب %', have, n USING HINT = 'قلل الكمية أو انتظر التوريد', ERRCODE = 'P0001'; END IF; UPDATE products SET stock = stock - n WHERE id = pid;END;$$; CALL sell(12, 5);
sell(integer,integer)، السطر 9 داخلها (RAISE)الرسالة والتلميح يظهران لمستخدم التطبيق كما كتبتهما. و P0001 هو رمز SQLSTATE العام لأخطاء RAISE.
٢التقاط الخطأ: EXCEPTION WHEN
تستطيع الكتلة أن «تلتقط» الخطأ بدل أن تتوقف، بقسم EXCEPTION في آخرها. لكل نوع خطأ اسم:
| الاسم | متى يقع |
|---|---|
division_by_zero | قسمة على صفر |
unique_violation | قيمة مكررة |
foreign_key_violation | إشارة إلى صف غير موجود |
check_violation | مخالفة CHECK |
no_data_found | SELECT INTO STRICT لم يجد صفًّا |
raise_exception | خطأ من RAISE EXCEPTION |
OTHERS | أي خطأ آخر |
DO $$DECLARE msg text; cons text;BEGIN INSERT INTO customers (name, city, email, joined) VALUES ('سارة', 'جدة', 'sara@mail.sa', current_date);EXCEPTION WHEN unique_violation THEN GET STACKED DIAGNOSTICS cons = CONSTRAINT_NAME; RAISE NOTICE 'البريد مسجل من قبل (القيد %)', cons; WHEN OTHERS THEN RAISE NOTICE 'خطأ غير متوقع: % (%)', SQLERRM, SQLSTATE;END $$;
SQLSTATE رمز الخطأ، وSQLERRM رسالته، وGET STACKED DIAGNOSTICS يعطي تفاصيل أكثر مثل اسم القيد والجدول.
٣ماذا يُلغى عند الالتقاط؟
عند التقاط الخطأ يُلغى ما فعلته هذه الكتلة فقط، ويبقى ما قبلها. لذلك نضع الخطوة الخطرة في كتلة داخلية BEGIN … EXCEPTION … END:
CREATE TABLE sales_log (msg text); DO $$BEGIN INSERT INTO sales_log VALUES ('بدأت المعالجة'); FOR pid IN 10..12 LOOP BEGIN -- كتلة داخلية لكل منتج UPDATE products SET stock = stock - 5 WHERE id = pid; INSERT INTO sales_log VALUES ('بيع 5 من المنتج ' || pid); EXCEPTION WHEN check_violation THEN INSERT INTO sales_log VALUES ('تخطّي المنتج ' || pid || ': المخزون لا يكفي'); END; END LOOP;END $$; SELECT * FROM sales_log;
| msg |
|---|
| بدأت المعالجة |
| بيع 5 من المنتج 10 |
| بيع 5 من المنتج 11 |
| تخطّي المنتج 12: المخزون لا يكفي |
فشل المنتج 12 لم يُلغِ بيع 10 و 11، ولا سطر «بدأت المعالجة».
توقّع وتحقّق
أي مستوى يوقف التنفيذ؟
اسم خطأ القيمة المكررة:
عند التقاط الخطأ يُلغى:
مقابل RAISE EXCEPTION في Oracle:
الخلاصة
NOTICEوWARNINGرسائل، وEXCEPTIONيوقف ويُلغي.RAISE EXCEPTION '…%', v USING HINT = '…'.EXCEPTION WHENاسم_الخطأTHEN… في آخر الكتلة.- الالتقاط يُلغي عمل الكتلة نفسها فقط؛ ضع الخطوة الخطرة في كتلة داخلية.
- لا تبتلع الأخطاء بصمت.
تحدٍّ برمجي: بيع آمن
متقدماكتب إجراء safe_sell(pid int, n int) يبيع n قطعة من المنتج بشروط عمل واضحة.
- إن كانت n أقل من 1:
RAISE EXCEPTIONبرسالة فيها «كمية غير صالحة». - إن كان المنتج غير موجود: خطأ برسالة فيها «غير موجود».
- إن كان المخزون أقل من n: خطأ برسالة فيها «غير كافٍ».
- وإلا: أنقص المخزون بمقدار n.
👀 اعرض الناتج المطلوب
- الكود يعمل بلا أخطاء
- بيع 2 من المنتج 4 ← المخزون 58
- كمية 0 تُرفض
- منتج غير موجود يُرفض
- مخزون غير كافٍ يُرفض
- رسالة «كمية غير صالحة»
- رسالة «غير موجود»
- رسالة «غير كافٍ»
- RAISE EXCEPTION
💡 تلميح 1
SELECT … INTO يخبرك المتغير FOUND هل وُجد صف.💡 تلميح 2
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
CREATE PROCEDURE safe_sell(pid int, n int)LANGUAGE plpgsqlAS $$DECLARE have int;BEGIN IF n < 1 THEN RAISE EXCEPTION 'كمية غير صالحة: %', n; END IF; SELECT stock INTO have FROM products WHERE id = pid; IF NOT FOUND THEN RAISE EXCEPTION 'المنتج % غير موجود', pid; END IF; IF have < n THEN RAISE EXCEPTION 'المخزون غير كافٍ: المتوفر %', have; END IF; UPDATE products SET stock = stock - n WHERE id = pid;END;$$;