Here’s a detail I discovered a long time ago – and rediscovered very recently: it’s possible to delete data from a table which is subject to audit without the delete being audited. I think the circumstances where it would matter are a little peculiar, and I’ve rarely seen systems that use the basic Oracle audit feature anyway, but this may solve a puzzle for someone, somewhere, someday.
The anomaly appears if you create a referential integrity constraint as “on delete cascade”. A delete from the parent table will automatically (pre-)delete matching rows from the child table but the delete on the child table will not be audited. Here’s a demonstration script – note that you will need to have set the parameter audit_trail to ‘DB’ to prove the point.
rem rem Script: del_cascade_2.sql rem Author: Jonathan Lewis rem Dated: Mar 2004 rem rem Last tested rem 12.1.0.2 rem 11.2.0.4 rem drop table t2 purge; drop table t1 purge; create table t1 ( id number(6), v1 varchar2(10), padding varchar2(100), constraint t1_pk primary key (id) ); create table t2 ( id_par number(6), id_ch number(6), v1 varchar2(10), padding varchar2(100), constraint t2_pk primary key (id_par,id_ch), constraint t2_fk_t1 foreign key (id_par) references t1 on delete cascade ); insert into t1 select rownum, rownum, rpad('x',100) from all_objects where rownum <= 100 -- > comment to avoid wordpress format issue ; insert into t2 select 1+trunc((rownum-1)/5), rownum, rownum, rpad('x',100) from all_objects where rownum <= 500 -- > comment to avoid wordpress format issue ; commit; prompt ================================= prompt Parent/Child rowcounts for id = 1 prompt ================================= select count(*) from t1 where id = 1; select count(*) from t2 where id_par = 1; column now new_value m_now select to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') now from dual; audit delete on t2 by access; audit delete on t1 by access; prompt ======================================================= prompt If you allow the cascade (keep the t2 delete commented) prompt then the cascade deletion is not audited. prompt ======================================================= -- delete from t2 where id_par = 1; delete from t1 where id = 1; noaudit delete on t1; noaudit delete on t2; column obj_name format a32 select action_name, obj_name from user_audit_trail where timestamp >= to_date('&m_now','dd-mon-yyyy hh24:mi:ss') ;
The script has an optional delete from the child table (t2) just before the delete from the parent table (t1). When you run the script you should see that before the delete t1 reports one row while t2 reports 5 rows. After the delete(s) both tables will report zero rows.
If you leave the t2 delete commented out then the delete from t2 will have been the recursive delete due to the cascade and the number of rows returned from user_audit_trail will be one (the delete from t1). If you allow the explicit delete from t2 to take place then user_audit_trail will report two rows, one each for t1 and t2.
Sample output (with a little cosmetic editing) – when the delete from t2 is commented out:
================================= Parent/Child rowcounts for id = 1 ================================= COUNT(*) ---------- 1 COUNT(*) ---------- 5 Audit succeeded. Audit succeeded. ======================================================= If you allow the cascade (keep the t2 delete commented) then the cascade deletion is not audited. ======================================================= 1 row deleted. Noaudit succeeded. Noaudit succeeded. COUNT(*) ---------- 0 COUNT(*) ---------- 0 ACTION_NAME OBJ_NAME ---------------------------- -------------------------------- DELETE T1 1 row selected.
As you can see, I’ve deleted just one row from one table (the t1 delete), but the final query against t2 shows that the child rows have also been deleted, but the only audit record reported is the one for the parent – despite the fact that if you enable sql_trace before the delete from t1 you will find the actual recursive statement ‘ delete from “TEST_USER”.”T2″ where “ID_PAR” = :1’ in the trace file.
The “recursive” nature of the delete in the trace file might be a bit of a clue – it is reported as being operated by SYS (lid = 0), not by the real end-user, and the parsing_user_id and parsing_schema_id in v$sql are both SYS (i.e. 0). Checking dba_audit_trail and the audit_file_dest for audited SYS operations, though, there was still no sign of any audit record for the child delete.