Mam nowy projekt, nad którym pracuję, w którym chcę, aby praca w Oracle odebrała uprawnienia, które nadałem personelowi IT starszemu niż 30 dni. Nasz personel IT potrzebuje okazjonalnego dostępu do kilku stołów produkcyjnych w celu rozwiązywania problemów. Przyznajemy przywileje SELECT na stołach, których potrzebuje osoba, ale nikt nigdy nie mówi mi, kiedy skończą z zadaniem, a te przywileje pozostają tam na zawsze. Chciałem, aby system automatycznie odbierał uprawnienia starsze niż 30 dni, abym nie musiał o tym pamiętać. Zanim mogłem cofnąć uprawnienia, potrzebowałem sposobu na śledzenie tych uprawnień. Stworzyłem więc wyzwalacz, który jest uruchamiany przy każdym wydaniu GRANT i rejestruje szczegóły w tabeli. Później zadanie Oracle przeskanuje tę tabelę i odbierze uprawnienia, które uzna za zbyt stare. Mój kod wyzwalacza wygląda następująco:
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; /
Powyższy kod nie jest oryginalny. Znalazłem dobry przykład w Internecie i zmodyfikowałem kilka rzeczy. Po testowaniu kodu przez 3 tygodnie uruchomiłem wyzwalacz w środowisku produkcyjnym. Otrzymanie błędu zajęło mi tylko kilka dni.
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… tworzę użytkownika, który niczego nie udziela. Ale możemy być pewni, że mój wyzwalacz ma problem z wykonaniem. Dlaczego więc ten wyzwalacz się uruchamia, jeśli jedyne, co robię, to tworzenie użytkownika? Prosty ślad SQL pokazał mi, co się dzieje z tym rekurencyjnym SQL. Za kulisami Oracle wydaje w moim imieniu następujące dokumenty:
PRZYZNAJ PUBLICZNE UPRAWNIENIA DZIEDZICZĄCE UŻYTKOWNIKOWI „BOB”;
Ok… więc w tym momencie wiem, że podczas tworzenia użytkownika jest wydawany GRANT, ale dlaczego to się nie udaje? Przetestowałem ten wyzwalacz z uprawnieniami systemowymi i działał dobrze. To prawda, że nie testowałem INHERIT PRIVILEGES, więc jest to trochę skrajny przypadek.
Po sporym wysiłku związanym z debugowaniem ustaliłem, że wywołanie funkcji ora_privilege_list zwraca pusty zestaw do kolekcji o nazwie „priv”. W związku z tym npriv jest ustawiany na wartość NULL. Ponieważ NPRIV ma wartość NULL, wiersz, w którym jest napisane „for i in 1..npriv”, nie ma większego sensu, stąd błąd.
Moim zdaniem ora_privilege_list powinna zwrócić jedną pozycję „Odziedzicz przywileje” i uważam, że nie zwraca tej listy jako błędu. Jeśli jednak ora_privilege_list ma zamiar zwrócić pustą kolekcję, to wyjście z funkcji powinno wynosić zero, a wtedy npriv otrzyma bardziej odpowiednią wartość. Dla celów edukacyjnych ora_privilege_list jest synonimem DBMS_STANDARD.PRIVILEGE_LIST.
Biorąc to wszystko pod uwagę, nie mogę kontrolować funkcji Oracle. I nie chcę czekać, aż Oracle zmieni swój kod w DBMS_STANDARD na taki, jaki moim zdaniem powinien być. Więc po prostu zakoduję mój wyzwalacz, aby poradzić sobie z problemem. Dodanie dwóch prostych linii rozwiązało mój problem (poniżej pogrubioną czcionką).
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; /
Więc poprawka jest dość prosta. Wykonaj dwie pętle FOR tylko wtedy, gdy NPRIV nie ma wartości NULL.