هذا المقال يتطلب مستوى متوسط من القراء. يفترض إنك تعرف SQL أساسي، وفاهم schema الجدول والـ transactions في PostgreSQL.
لو عميل اتصل وقال إن سعر فاتورته اتغيّر من غير علمه ومحدّش في الفريق فاكر مين عدّله، انت محتاج Audit Log من امبارح. الـ trigger الواحد اللي هتبنيه دلوقتي بيمسك كل تعديل على أي جدول، مع المستخدم والوقت والقيمة قبل وبعد، بدون لمس سطر واحد في كود التطبيق.
Audit Log في PostgreSQL بدون مكتبة: trigger واحد يمسك كل تعديل من أي مصدر
المشكلة باختصار
في أي تطبيق بيكبر، فيه لحظة بتسأل فيها سؤال "مين عدّل الصف ده وامتى وكان قيمته كام؟". لو الإجابة مش متخزّنة في مكان موثوق، اللي بيحصل غالباً انك تفتح الـ git blame والـ application logs لساعتين وفي الآخر تقول للعميل "مش واضح". محاولة تسجيل التعديلات من جوّا كود التطبيق وحدها فيها مشكلتين حقيقيتين:
- أي مسار تعديل مش بيمر على نفس الـ helper بيكسر السجل: سكربت يدوي على السيرفر، migration وقت release، dashboard إداري قديم، حتى psql مفتوح من لابتوب DBA.
- أول endpoint جديد ينسى فيه developer يستدعي logger، السجل بيبقى ناقص بصمت بدون أي error.
الحل اللي بيقفل البابين دول هو نقل التسجيل لمستوى الـ database نفسها عبر Triggers. أي تعديل، من أي client، بيمر إجبارياً على نفس الـ checkpoint.
تمثيل بسيط للمبتدئ: دفتر البوّاب في عمارة سكنية
تخيّل عمارة فيها بوّاب لازم يكتب في دفتر كل واحد بيدخل: مين، إمتى، رايح فين، خرج إمتى. لو سيبت كل ساكن يكتب بنفسه في الدفتر وقت ما يدخل، أي زيارة من حد مش مهتم بالقاعدة هتفوت. لكن لو الباب نفسه فيه كاميرا متربوطة بنظام تسجيل، مفيش زيارة بتفوت — حتى لو الزائر اتسلّل من السلم الخلفي، التسجيل بيحصل لأن المكان نفسه هو اللي بيسجّل، مش الناس.
الـ trigger في PostgreSQL هو الكاميرا دي بالظبط. هو شغّال على مستوى الـ DB، فأي محاولة تعديل من أي مصدر بتمر عليه. مفيش طريقة "تتسلّل" من تحت الـ trigger إلا لو معاك صلاحية superuser تعطّل الـ trigger نفسه (وده اللي هنتكلم عنه في قسم الـ trade-offs).
التعريف الدقيق للـ Trigger
الـ trigger في PostgreSQL هو دالة (في الغالب مكتوبة بـ PL/pgSQL) بتتنفّذ تلقائياً قبل أو بعد عملية INSERT أو UPDATE أو DELETE على جدول معيّن. الدالة بتشوف الصف القديم في متغير اسمه OLD والصف الجديد في NEW، وبتعرف اسم الجدول من TG_TABLE_NAME ونوع العملية من TG_OP. التعريف الرسمي موجود في توثيق PostgreSQL 16 تحت CREATE TRIGGER و PL/pgSQL Trigger Procedures.
الحلو في الموضوع إن نفس الـ function تقدر تشتغل على عشرات الجداول. مش لازم تكتب trigger function منفصلة لكل جدول.