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.