Saya memiliki proyek baru yang sedang saya kerjakan di mana saya ingin pekerjaan Oracle mencabut hak istimewa yang saya berikan kepada staf TI yang berusia lebih dari 30 hari. Staf TI kami memerlukan akses sesekali ke beberapa tabel produksi untuk memecahkan masalah. Kami memberikan SELECT privs pada tabel yang dibutuhkan orang, tetapi tidak ada yang pernah memberi tahu saya ketika mereka selesai dengan tugas mereka dan hak istimewa itu ada di sana selamanya. Saya ingin sistem secara otomatis mencabut hak istimewa yang lebih lama dari 30 hari sehingga saya tidak perlu ingat untuk melakukannya. Sebelum saya dapat mencabut hak istimewa, saya membutuhkan cara untuk melacak hak istimewa tersebut. Jadi saya membuat pemicu yang dipicu setiap kali HIBAH dikeluarkan dan mencatat detailnya ke tabel. Kemudian, pekerjaan Oracle akan memindai tabel itu dan mencabut hak istimewa yang dianggap terlalu lama. Kode pemicu saya adalah sebagai berikut:
create or replace trigger sys.grant_logging_trig after grant on database declare priv dbms_standard.ora_name_list_t; who dbms_standard.ora_name_list_t; npriv pls_integer; nwho pls_integer; begin npriv := ora_privilege_list(priv); if (ora_sysevent = 'GRANT') then nwho := ora_grantee(who); else nwho := ora_revokee(who); end if; for i in 1..npriv loop for j in 1..nwho loop insert into system.grant_logging values ( systimestamp, ora_login_user, ora_sysevent, who(j), priv(i), ora_dict_obj_owner, ora_dict_obj_name ); end loop; end loop; end; /
Kode di atas bukan asli. Saya menemukan contoh yang bagus di Internet dan memodifikasi beberapa hal. Setelah menguji kode selama 3 minggu, saya meluncurkan pelatuk ke dalam produksi. Hanya butuh beberapa hari bagi saya untuk menerima kesalahan.
SQL> CREATE USER bob IDENTIFIED BY password; ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-04088: error during execution of trigger 'SYS.GRANT_LOGGING_TRIG' ORA-00604: error occurred at recursive SQL level 2 ORA-06502: PL/SQL: numeric or value error ORA-06512: at line 28
Hmmm…Saya membuat pengguna yang tidak memberikan apa pun. Tapi kami yakin bisa melihat pemicu saya mengalami masalah saat dieksekusi. Jadi mengapa pemicu ini aktif jika yang saya lakukan hanyalah membuat pengguna? Jejak SQL sederhana menunjukkan kepada saya apa yang terjadi dengan SQL rekursif itu. Di balik layar, Oracle mengeluarkan yang berikut atas nama saya:
MEMBERIKAN HAK ISTIMEWA PADA PENGGUNA “BOB” kepada PUBLIK;
Ok…jadi pada titik ini, saya tahu bahwa ada HIBAH yang dikeluarkan ketika saya membuat pengguna tetapi mengapa ini gagal? Saya menguji pemicu ini dengan hak istimewa sistem dan itu berfungsi dengan baik. Memang, saya tidak menguji HAK ISTIMEWA, jadi ini semacam kasus tepi.
Setelah banyak upaya debugging, saya menentukan bahwa panggilan fungsi ora_privilege_list mengembalikan set kosong ke koleksi bernama "priv". Dengan demikian, npriv disetel ke nilai NULL. Karena NPRIV adalah NULL, baris di mana dikatakan "for i in 1..npriv" tidak masuk akal, maka kesalahannya.
Menurut pendapat saya, ora_privilege_list harus mengembalikan satu item, "INHERIT PRIVILEGES" dan saya percaya bahwa itu tidak mengembalikan daftar itu sebagai bug. Namun, jika ora_privilege_list akan mengembalikan koleksi kosong, maka output dari fungsi harus nol dan kemudian npriv akan mendapatkan nilai yang lebih tepat. Untuk tujuan pendidikan, ora_privilege_list adalah sinonim untuk DBMS_STANDARD.PRIVILEGE_LIST.
Semua itu dikatakan, saya tidak dapat mengontrol fungsi Oracle. Dan saya tidak ingin menunggu Oracle mengubah kode mereka di DBMS_STANDARD menjadi apa yang saya pikir seharusnya. Jadi saya hanya akan membuat kode pemicu saya untuk menangani masalah ini. Menambahkan dua baris sederhana memecahkan masalah saya (lihat di bawah dalam huruf tebal).
create or replace trigger sys.grant_logging_trig after grant on database declare priv dbms_standard.ora_name_list_t; who dbms_standard.ora_name_list_t; npriv pls_integer; nwho pls_integer; begin npriv := ora_privilege_list(priv); if (ora_sysevent = 'GRANT') then nwho := ora_grantee(who); else nwho := ora_revokee(who); end if; if to_char(npriv) is not null then for i in 1..npriv loop for j in 1..nwho loop insert into system.grant_logging values ( systimestamp, ora_login_user, ora_sysevent, who(j), priv(i), ora_dict_obj_owner, ora_dict_obj_name ); end loop; end loop; end if; end; /
Jadi perbaikannya cukup sederhana. Lakukan hanya dua perulangan FOR jika NPRIV bukan null.