Mám nový projekt, na kterém pracuji, kde chci, aby úloha Oracle odebrala oprávnění, která jsem udělil IT zaměstnancům starším než 30 dnů. Naši pracovníci IT potřebují příležitostný přístup k několika produkčním tabulkám, aby mohli řešit problémy. Poskytujeme SELECT priv na stolech, které daný člověk potřebuje, ale nikdo mi nikdy neřekne, kdy skončí se svým úkolem a tato privilegia tam zůstanou navždy. Chtěl jsem, aby systém automaticky odebral oprávnění starší než 30 dní, abych si to nemusel pamatovat. Než jsem mohl zrušit oprávnění, potřeboval jsem způsob, jak tato oprávnění sledovat. Vytvořil jsem tedy spouštěč, který se spustí vždy, když je vydán GRANT, a zaznamená podrobnosti do tabulky. Později úloha Oracle naskenuje tuto tabulku a odebere oprávnění, která zjistí, že jsou příliš stará. Můj spouštěcí kód je následující:
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; /
Výše uvedený kód není originální. Na internetu jsem našel dobrý příklad a pár věcí upravil. Po testování kódu po dobu 3 týdnů jsem spoušť uvedl do výroby. Trvalo jen několik dní, než jsem obdržel chybu.
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...vytvářím uživatele, který nic neuděluje. Ale můžeme si být jisti, že můj spouštěč má problém se spuštěním. Proč se tedy tento spouštěč spouští, když vše, co dělám, je vytvoření uživatele? Jednoduché sledování SQL mi ukázalo, co se děje s tím rekurzivním SQL. V zákulisí vydává Oracle mým jménem následující:
UDĚLEJTE VEŘEJNOST DĚDITELNÁ PRIVILEGIU UŽIVATELE „BOB“;
Dobře…takže v tuto chvíli vím, že když vytvořím uživatele, je vydán GRANT, ale proč to selhává? Testoval jsem tento spouštěč se systémovými oprávněními a fungovalo to dobře. Je pravda, že jsem netestoval DĚDIT PRIVILEGES, takže je to trochu okrajový případ.
Po značném úsilí při ladění jsem zjistil, že volání funkce ora_privilege_list vrací prázdnou sadu do kolekce s názvem „priv“. Jako takový je npriv nastaven na hodnotu NULL. Protože NPRIV je NULL, řádek, kde je uvedeno „pro i v 1..npriv“, nedává moc smysl, proto došlo k chybě.
Podle mého názoru by měl ora_privilege_list vrátit jednu položku, „ZDĚDIT PRIVILEGES“ a věřím, že nevracení tohoto seznamu je chyba. Pokud však ora_privilege_list bude vracet prázdnou kolekci, pak by výstup z funkce měl být nula a pak by npriv získal správnější hodnotu. Pro účely vzdělávání je ora_privilege_list synonymem pro DBMS_STANDARD.PRIVILEGE_LIST.
Vše, co bylo řečeno, nemohu ovládat funkci Oracle. A nechci čekat, až Oracle změní svůj kód v DBMS_STANDARD na to, co si myslím, že by měl být. Takže jen nakóduji svůj spouštěč, abych problém vyřešil. Přidání dvou jednoduchých řádků vyřešilo můj problém (viz níže tučně).
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; /
Takže oprava je docela jednoduchá. Proveďte pouze dvě smyčky FOR, pokud NPRIV není null.