Friday, January 2, 2026

Blog List

"Disk file operations I/O" with (FileOperation: 8, fileno: 0, filetype: 8) Test

In this blog, we will make a test to demonstrate Oracle Wait Event "Disk file operations I/O" with
(P1/FileOperation: 8, P2/fileno: 0, P3/filetype: 8).

Note: Tested in Oracle 19.27


1. Test Setup


The database auditing is configured as follows:

NAME                      TYPE        VALUE
------------------------- ----------- --------------------------------
audit_file_dest           string      /home/oracle/suk/slow_dir3/adump
audit_sys_operations      boolean     TRUE
audit_trail               string      OS
audit_file_dest is mounted on a slow file system with 5000 ms read/write delay.
(See Blog: Oracle Database Stuck on DB Writer Wait Event: "oradebug request completion" ).


2. Wait Event Tracing


2.1 10046 Trace


We start system-wide 10046 trace by:

alter system set events '10046 trace name context forever, level 12';  

2.2 strace


Find TNS listener process and strace it:

oracle@db3audit:db3audit > ps -efl |grep tns
  0 S oracle    728055       1  0  80   0 - 40462 do_epo Sep27 ?        00:00:15 /orabin/app/oracle/product/19.27.0.0./bin/tnslsnr LISTENER_oradb3audit             

oracle@db3audit:db3audit >  strace -ttTfy -p 728055 -o strace_output_2.txt

2.3 Sqlplus Session


Now we open a Sqlplus session by

SQL > sqlplus k/k@db3audit

2.4 10046 Trace Output


Trace file /orabin/app/oracle/admin/db3audit/diag/rdbms/db3audit/db3audit/trace/db3audit_ora_1072963.trc
Build label:    RDBMS_19.27.0.0.0DBRU_LINUX.X64_250331
Oracle process number: 57
Unix process pid: 1072963, image: oracle@db3audit
*** MODULE NAME:(sqlplus.exe) 2025-12-23T10:11:56.689481+01:00

*** 2025-12-23T10:12:01.847101+01:00
WAIT #0: nam='Disk file operations I/O' ela= 5100597 FileOperation=8 fileno=0 filetype=8 obj#=-1 tim=18667193313139
It shows "ela= 5100597" microseconds with "FileOperation=8 fileno=0 filetype=8".
So we can say that in this case, "FileOperation=8" is file open, and "filetype=8" is OS Audit file.

After test, stop 10046 trace by (otherwise too many trace files):

  alter system set events '10046 trace name context off';

2.5 strace Output


We can see that Sqlplus Process 1072963 was spawned by TNS Process 728055:

oracle@db3audit:db3audit > strace -ttTfy -p 728055 -o strace_output_3.txt
./strace: Process 728055 attached
./strace: Process 1072962 attached
./strace: Process 1072963 attached
Open strace_output_2.txt, it shows:

1072963 10:11:56.746551 openat(AT_FDCWD, "/home/oracle/suk/slow_dir3/adump/db3audit_ora_1072963_20251223101156746537740094.aud", O_RDWR|O_CREAT|O_EXCL, 0640) = 10
                                 <5.098572>
1072963 10:12:01.845204 write(10, "Audit file ", 11) = 11 <0.000058>
1072963 10:12:01.845315 write(10, "/home/oracle/suk/slow_dir3/adump"..., 84) = 84 <0.000023>
1072963 10:12:01.845372 write(10, "\n", 1) = 1 <0.000022>
1072963 10:12:01.845433 write(10, "Oracle Database 19c Enterprise E"..., 90) = 90 <0.000021>
1072963 10:12:01.845482 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.845533 write(10, "Build label:    RDBMS_19.27.0.0."..., 54) = 54 <0.000020>
1072963 10:12:01.845582 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.845639 write(10, "ORACLE_HOME:    /orabin/app/orac"..., 64) = 64 <0.000020>
1072963 10:12:01.845689 write(10, "System name:\tLinux\n", 19) = 19 <0.000020>
1072963 10:12:01.845738 write(10, "Node name:\tdb3audit\n", 20) = 20 <0.000021>
1072963 10:12:01.845796 write(10, "Release:\t4.18.0-372.9.1.el8.x86_"..., 35) = 35 <0.000020>
1072963 10:12:01.845845 write(10, "Version:\t#1 SMP Fri Apr 15 22:12"..., 45) = 45 <0.000021>
1072963 10:12:01.845895 write(10, "Machine:\tx86_64\n", 16) = 16 <0.000021>
1072963 10:12:01.845953 write(10, "CLID:\tP\n", 8) = 8 <0.000021>
1072963 10:12:01.846010 write(10, "Instance name: db3audit\n", 24) = 24 <0.000062>
1072963 10:12:01.846105 write(10, "Instance number: 1\n", 19) = 19 <0.000020>
1072963 10:12:01.846161 write(10, "Database name: db3audit ", 24) = 24 <0.000023>
1072963 10:12:01.846215 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.846264 write(10, "Database unique name: db3audit ", 31) = 31 <0.000020>
1072963 10:12:01.846313 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.846388 write(10, "Database id: 1059538969\n", 24) = 24 <0.000021>
1072963 10:12:01.846442 write(10, "Database role: PRIMARY\n", 23) = 23 <0.000020>
1072963 10:12:01.846491 write(10, "Redo thread mounted by this inst"..., 40) = 40 <0.000020>
1072963 10:12:01.846541 write(10, "Oracle process number: 57\n", 26) = 26 <0.000021>
1072963 10:12:01.846592 write(10, "Unix process pid: 1072963, image"..., 49) = 49 <0.000019>
1072963 10:12:01.846640 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.846689 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.846762 write(10, "Tue Dec 23 10:12:01 2025 +01:00\n", 32) = 32 <0.000021>
1072963 10:12:01.846812 write(10, "LENGTH: \"408\"", 13) = 13 <0.000021>
1072963 10:12:01.846860 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.846909 write(10, "SESSIONID:[9] \"102147891\" ENTRYI"..., 406) = 406 <0.000030>
1072963 10:12:01.846973 write(10, "\n", 1) = 1 <0.000020>
1072963 10:12:01.847023 write(10, "\n", 1) = 1 <0.000021>
The above output shows that openat "/home/oracle/suk/slow_dir3/adump/db3audit_ora_1072963_20251223101156746537740094.aud" returns FD = 10,
and it takes 5.098572 seconds from 10:11:56 to 10:12:01.

Look OS audit file, its name is composed by DB SID: db3audit, Unix process pid: 1072963, and creating timestamp: 20251223101156, which is 2025-12-23 10:11:56.
In the OS audit file, the first timestamp "10:12:01" is recorded for session LOGON, which has about 5 seconds delay (from 10:11:56 to 10:12:01),
and the second timestamp "10:28:44" is for session LOGOFF. Here the full audit file:

oracle@db3audit:db3audit > cat /home/oracle/suk/slow_dir3/adump/db3audit_ora_1072963_20251223101156746537740094.aud
Audit file /home/oracle/suk/slow_dir3/adump/db3audit_ora_1072963_20251223101156746537740094.aud
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.27.0.0.0
Build label:    RDBMS_19.27.0.0.0DBRU_LINUX.X64_250331
ORACLE_HOME:    /orabin/app/oracle/product/19.27.0.0.
System name:	Linux
Node name:	db3audit
Release:	4.18.0-372.9.1.el8.x86_64
Version:	#1 SMP Fri Apr 15 22:12:19 EDT 2022
Machine:	x86_64
CLID:	P
Instance name: db3audit
Instance number: 1
Database name: db3audit 
Database unique name: db3audit 
Database id: 1059538969
Database role: PRIMARY
Redo thread mounted by this instance: 1
Oracle process number: 57
Unix process pid: 1072963, image: oracle@db3audit

Tue Dec 23 10:12:01 2025 +01:00
LENGTH: "408"
SESSIONID:[9] "102147891" ENTRYID:[1] "1" STATEMENT:[1] "1" USERID:[1] "K" USERHOST:[16] "SYS\MACHINE12345" TERMINAL:[12] "MACHINE12345" ACTION:[3] "100" RETURNCODE:[1] "0" COMMENT$TEXT:[126] "Authenticated by: DATABASE;AUTHENTICATED IDENTITY: K; Client address: (ADDRESS=(PROTOCOL=tcp)(HOST=10.44.105.100)(PORT=50160))" OS$USERID:[7] "KS00383" DBID:[10] "1059538969" PRIV$USED:[1] "5" CURRENT_USER:[1] "K"

Thu Dec 23 10:28:44 2025 +01:00
LENGTH: "231"
SESSIONID:[9] "102147891" ENTRYID:[1] "1" USERID:[1] "K" ACTION:[3] "101" RETURNCODE:[1] "0" LOGOFF$PREAD:[5] "35037" LOGOFF$LREAD:[6] "166495" LOGOFF$LWRITE:[1] "4" LOGOFF$DEAD:[1] "0" DBID:[10] "1059538969" SESSIONCPU:[3] "258"
Here the process lsof output showing FD 10u for audit file:

oracle@db3audit:db3audit > lsof -p 1072963 |grep aud
  COMMAND       PID   USER   FD      TYPE             DEVICE    SIZE/OFF       NODE NAME
  oracle_10 1072963 oracle   10u      REG             253,2         1167         77 /home/oracle/suk/slow_dir3/adump/db3audit_ora_1072963_20251223101156746537740094.aud

2.6 v$active_session_history and Oracle Performance Views


select sample_time, program, event, p1, p2, p3, p1text, p2text, p3text
  from v$active_session_history t 
 where event = 'Disk file operations I/O' and p1=8 and p2=0 and p3=8 
   and sample_time >= timestamp '2025-12-28 10:31:22.549'
 order by t.sample_time;
 
 
  SAMPLE_TIME         PROGRAM      EVENT                     P1  P2  P3  P1TEXT         P2TEXT  P3TEXT
  ------------------- ------------ ------------------------ --- --- --- --------------- ------- --------
  2025-12-22 10:11:57 sqlplus.exe  Disk file operations I/O   8   0   8  FileOperation  fileno  filetype
  2025-12-22 10:11:58 sqlplus.exe  Disk file operations I/O   8   0   8  FileOperation  fileno  filetype
  2025-12-22 10:11:59 sqlplus.exe  Disk file operations I/O   8   0   8  FileOperation  fileno  filetype
  2025-12-22 10:12:00 sqlplus.exe  Disk file operations I/O   8   0   8  FileOperation  fileno  filetype
  2025-12-22 10:12:01 sqlplus.exe  Disk file operations I/O   8   0   8  FileOperation  fileno  filetype


select filetype_id, filetype_name, function_id, function_name, sum(small_read_reqs), sum(small_write_reqs), sum(number_of_waits), sum(wait_time) 
from  v$iostat_function_detail 
where filetype_id = 8
group by filetype_id, filetype_name, function_id, function_name
order by filetype_id, function_id;

  FILETYPE_ID FILETYPE FUNCTION_ID FUNCTION_NAME        SUM(SMALL_READ_REQS) SUM(SMALL_WRITE_REQS) SUM(NUMBER_OF_WAITS) SUM(WAIT_TIME)
  ----------- -------- ----------- -------------------- -------------------- --------------------- -------------------- --------------
            8 Other              2 LGWR                                    0                     0               136398          50459
            8 Other              7 Recovery                                0                     0                    1              0
            8 Other              8 Buffer Cache Reads                      0                     0                  245            283
            8 Other             13 Others                                  0                     0               273272         120046


-- also in v$session_event, dba_hist_system_event
select event, total_waits, time_waited, time_waited_micro, event_id, wait_class_id, wait_class#, wait_class from v$system_event 
 where event = 'Disk file operations I/O';

  EVENT                     TOTAL_WAITS TIME_WAITED TIME_WAITED_MICRO   EVENT_ID WAIT_CLASS_ID WAIT_CLASS# WAIT_CLASS
  ------------------------- ----------- ----------- ----------------- ---------- ------------- ----------- ----------
  Disk file operations I/O        67226       28853         288534923  166678035    1740759767           8 User I/O

-- no rows in v$iostat_file
select * from v$iostat_file where filetype_id = 8;
	no rows selected

2.7 AWR and ASH


TOP Wait Event "Disk file operations I/O" in AWR and ASH looks like:

Top 10 Foreground Events by Total Wait Time

    Event Waits Total Wait Time (sec) Avg Wait % DB time Wait Class
    Disk file operations I/O 2,706 11.4 4.20ms 35.4 User I/O

    Top Event P1/P2/P3 Values

      Event % Event P1, P2, P3 Values % Activity Parameter 1 Parameter 2 Parameter 3
      Disk file operations I/O 45.45 "8","0","8" 45.45 FileOperation fileno filetype


      3. bpftrace


      Start following bpftrace script and then open a Sqlplus session.

      The script output shows file open (sys_enter_openat) "Duration (us): 5089844", and its Oracle CallStack.
      
      BPFTRACE_STRLEN=128 bpftrace -e 'tracepoint:syscalls:sys_enter_openat /strncmp("oracle", comm, 6) == 0 && strncmp("/home/oracle/suk/slow_dir3", str(args->filename), 26) == 0 / {
                     time("%H:%M:%S "); @start[tid] = nsecs;
                     printf("comm: %s, pid: %d, tid: %d, __syscall_nr: %d, dfd: %d, flags: %d, mode: %d \n============== filename: %s =======\n", 
                             comm, pid, tid, args->__syscall_nr, args->dfd, args->flags, args->mode, str(args->filename)); }
                   tracepoint:syscalls:sys_exit_openat  /strncmp("oracle", comm, 6) == 0 && @start[tid] / {
                     time("%H:%M:%S "); @fd = (uint64)args->ret; $duration_us = ( nsecs - @start[tid] ) / 1000; 
                     printf("===================== File Descriptor: %d, Duration (us): %d ===================== \n", @fd, $duration_us); 
                     printf("CallStack: %s\n", ustack(30));
                     delete(@start[tid]); }
                   tracepoint:syscalls:sys_enter_write /strncmp("oracle", comm, 6) == 0 && @fd == args->fd / {
                     time("%H:%M:%S "); $length = args->count; $line = str(args->buf);
                     printf("[pid: %d, fd: %d, length: %3d] %s \n", pid, args->fd, $length, $line); }'
      
      13:12:01 comm: oracle_1086521_, pid: 1086521, tid: 1086521, __syscall_nr: 257, dfd: -100, flags: 194, mode: 416
      ============== filename: /home/oracle/suk/slow_dir3/adump/db3audit_ora_1086521_20251228131201036719868850.aud =======
      13:12:06 ===================== File Descriptor: 8, Duration (us): 5089844 =====================
      CallStack:
              __open+79
              szaudinit+506
              szaud+1416
              szaud_mkrec+1774
              audios+2936
              audins+13805
              audlon0+1983
              audlon+526
              auddft_internal+2853
              audStatement+1168
              kpolnb+4729
              kpoauth+1917
              opiodr+1264
              ttcpip+1219
              opitsk+1925
              opiino+939
              opiodr+1264
              opidrv+1094
              sou2o+165
              opimai_real+422
              ssthrdmain+417
              main+256
              __libc_start_main+243
              0x5541f689495641d7
              
      13:12:06 [pid: 1086521, fd: 8, length:  11] Audit file
      13:12:06 [pid: 1086521, fd: 8, length:  84] /home/oracle/suk/slow_dir3/adump/db3audit_ora_1086521_20251228131201036719868850.aud
      13:12:06 [pid: 1086521, fd: 8, length:  90] Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
      Version 19.27.0.0.0
      ......
      13:12:06 [pid: 1086521, fd: 8, length: 406] SESSIONID:[9] "102148556" ENTRYID:[1] "1" STATEMENT:[1] "1" USERID:[1] "K" USERHOST:[16] "SYS\MACHINE12345" TERMINAL:[12] "MACH
      
      In above test, Audit Parameter: audit_syslog_level is not set, and audit files (and audit info) are created under directory audit_file_dest.
      If we set "audit_syslog_level='LOCAL0.INFO'" and re-start DB, audit info is written into /var/log/messages (configured in /etc/rsyslog.conf).
      
      alter system set audit_syslog_level='LOCAL0.INFO' scope=spfile;
      --DB re-start
      
      -- reset by alter system reset audit_syslog_level scope=spfile;
      
      
      $ cat /etc/rsyslog.conf
       
        # Log anything (except mail) of level info or higher.
        # Don't log private authentication messages!
        *.info;mail.none;authpriv.none;cron.none                /var/log/messages
      

      4. Related Work


      Blog: Disk file operations I/O (July 17, 2024 Dinesh Bandelkar) shows one case of "Disk file operations I/O" with (FileOperation: 8, fileno: 0, filetype: 8) when Oracle Redaction is OS audited.

      Blog: Disk file operations I/O (December 21, 2020) shows ASH snapper to monitor such wait event.

      Wednesday, December 10, 2025

      Three Oracle cursor error Tests

      In this Blog, we will test 3 Oracle cursor errors:
      
        ORA-01001: invalid cursor
        ORA-06511: PL/SQL: cursor already open
        ORA-21780: Maximum number of object durations exceeded
      
      Note: Tested in Oracle 19.28


      1. Test Setup


      
      drop table test_tab;
      
      create table test_tab as select level id, 'Name_'||level name from dual connect by level <= 10;
      
      create or replace package test_cursor_errors is
        cursor p_curs(p_id number) is select * from test_tab where id = p_id;
        p_ORA_21780_Maximum number := 0;
        procedure test_ORA_01001(p_id number, p_recur_depth number);
        procedure test_ORA_06511(p_id number);
        procedure test_ORA_21780(p_id number, p_recur_depth number);
      end;
      /
      
      create or replace package body test_cursor_errors is
        procedure prt(p_recur_depth number, text varchar2) is
        begin
          dbms_output.put_line(rpad(chr(32), 4*p_recur_depth, chr(32))||text);
        end;
        
        --ORA-01001: invalid cursor
        procedure test_ORA_01001(p_id number, p_recur_depth number) is
          l_name varchar2(1000);
          l_chg  integer := 1;  
        begin
          prt(p_recur_depth, '1-1 Start --- p_id = '||p_id||', recur_depth = '||p_recur_depth||', curs%isopen = '||bool_to_char(p_curs%isopen));
      
          if p_curs%isopen then
            prt(p_recur_depth, '2-1 Close p_curs  --- p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
            close p_curs;   --Close previous Implicit Cursor
            prt(p_recur_depth, '2-2 p_curs Closed --- p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
          end if;
          
          for c in p_curs(p_id) loop
            l_name :=c.name;
            prt(p_recur_depth, '3-1 Recur Entry --- p_id = '||p_id||', Implicit Cursor Opened'||', curs%isopen = '||bool_to_char(p_curs%isopen));
            test_ORA_01001(p_id - l_chg, p_recur_depth + 1);
            prt(p_recur_depth, '3-2 Recur Exit  --- p_id = '||p_id||', Close  Implicit Cursor'||', curs%isopen = '||bool_to_char(p_curs%isopen));
          end loop;
      
          if p_curs%isopen then
            prt(p_recur_depth, '4-1 Close p_curs --- p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
            close p_curs;
            prt(p_recur_depth, '4-2 p_curs Closed  --- p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
          end if;
         
          prt(p_recur_depth, '1-2 End --- p_id = '||p_id||', recur_depth = '||p_recur_depth||', curs%isopen = '||bool_to_char(p_curs%isopen));
        end;
      
        --ORA-06511: PL/SQL: cursor already open
        procedure test_ORA_06511 (p_id number) is
          l_name varchar2(1000);
        begin
          for c in p_curs(p_id) loop
            dbms_output.put_line('1. Loop p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
            l_name :=c.name;
            for c in p_curs(p_id -1) loop
              dbms_output.put_line('2. Loop p_id = '||p_id||', curs%isopen = '||bool_to_char(p_curs%isopen));
              l_name :=c.name;
            end loop;
          end loop;
        end;
        
        --ORA-21780: Maximum number of object durations exceeded. ORA_21780_Maximum = 65325 (Oracle 19.28), ORA_21780_Maximum = 65318 (Oracle 19.10)
        procedure test_ORA_21780 (p_id number, p_recur_depth number) is
          l_name varchar2(1000);
          l_chg  integer := 0;  
        begin
          if p_curs%isopen then
            close p_curs;   --Close previous Implicit Cursor
          end if;
          
          for c in p_curs(p_id) loop
            begin
      	      l_name := c.name;
      	      p_ORA_21780_Maximum := greatest(p_ORA_21780_Maximum, p_recur_depth + 1);
      	      test_ORA_21780(p_id - l_chg, p_recur_depth + 1);
      	    end;
          end loop;
      
          if p_curs%isopen then
            close p_curs;
          end if;
        end;
      end;
      /
      


      2. Test Run


      
      exec test_cursor_errors.test_ORA_01001(2, 1);
      
      exec test_cursor_errors.test_ORA_06511(2);
      
      begin
        test_cursor_errors.p_ORA_21780_Maximum := 0;
        test_cursor_errors.test_ORA_21780(2, 1);
        exception when others then
          dbms_output.put_line('ORA_21780_Maximum = '||test_cursor_errors.p_ORA_21780_Maximum);
          raise;
      end;
      /
      


      3. Test Outcome


      3.1. ORA-01001: invalid cursor


      Here the indentation print of recursive calls:
      
      SQL > exec test_cursor_errors.test_ORA_01001(2, 1);
      
          1-1 Start --- p_id = 2, recur_depth = 1, curs%isopen = false
          3-1 Recur Entry --- p_id = 2, Implicit Cursor Opened, curs%isopen = true
              1-1 Start --- p_id = 1, recur_depth = 2, curs%isopen = true
              2-1 Close p_curs  --- p_id = 1, curs%isopen = true
              2-2 p_curs Closed --- p_id = 1, curs%isopen = false
              3-1 Recur Entry --- p_id = 1, Implicit Cursor Opened, curs%isopen = true
                  1-1 Start --- p_id = 0, recur_depth = 3, curs%isopen = true
                  2-1 Close p_curs  --- p_id = 0, curs%isopen = true
                  2-2 p_curs Closed --- p_id = 0, curs%isopen = false
                  1-2 End --- p_id = 0, recur_depth = 3, curs%isopen = false
              3-2 Recur Exit  --- p_id = 1, Close  Implicit Cursor, curs%isopen = false
      BEGIN test_cursor_errors.test_ORA_01001(2, 1); END;
      
      *
      ERROR at line 1:
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 20
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 20
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 20
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 20
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 23
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 23
      ORA-06512: at line 1
      Elapsed: 00:00:00.00
      
      In Oracle. cursor for loop starts with one implicit cursor open and finishes with one implicit cursor close.
      In above test, we have closed cursor in Step 2, and in Step 3-2 of cursor for loop Exit,
      Oracle wants to close the implicit cursor again, and throws "ORA-01001: invalid cursor".
      ("invalid cursor" signifies error when closing an already closed cursor).

      Here 10046 trace file with "err=1001" and 'SQL*Net break/reset to client':
      
      alter session set events '10046 trace name context forever, level 12';  
      exec test_cursor_errors.test_ORA_01001(2, 1);
      alter session set events '10046 trace name context off';
      
      
      =====================
      PARSING IN CURSOR #140151808236992 len=52 dep=0 uid=49 oct=47 lid=49 tim=6880035008376 hv=4277818309 ad='9d4835d0' sqlid='7g4pxwzzgnny5'
      BEGIN test_cursor_errors.test_ORA_01001(2, 1); END;
      
      END OF STMT
      PARSE #140151808236992:c=15,e=15,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=0,tim=6880035008375
      =====================
      PARSING IN CURSOR #140151808572984 len=38 dep=1 uid=49 oct=3 lid=49 tim=6880035009339 hv=1133886660 ad='77b15960' sqlid='c8b161d1tbg64'
      SELECT * FROM TEST_TAB WHERE ID = :B1 
      END OF STMT
      PARSE #140151808572984:c=558,e=848,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=1,plh=0,tim=6880035009339
      BINDS #140151808572984:
      
       Bind#0
        oacdty=02 mxl=22(21) mxlc=00 mal=00 scl=00 pre=00
        oacflg=03 fl2=1206001 frm=00 csi=00 siz=24 off=0
        kxsbbbfp=7f77a2c284c0  bln=22  avl=02  flg=05
        value=2
      EXEC #140151808572984:c=932,e=1170,p=0,cr=1,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880035010574
      FETCH #140151808572984:c=65,e=65,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=1,plh=3962208483,tim=6880035010672
      STAT #140151808572984 id=1 cnt=1 pid=0 pos=1 obj=5983275 op='TABLE ACCESS FULL TEST_TAB (cr=3 pr=0 pw=0 str=1 time=58 us cost=3 size=315 card=3)'
      CLOSE #140151808572984:c=0,e=0,dep=1,type=3,tim=6880035010802
      BINDS #140151808572984:
      
       Bind#0
        oacdty=02 mxl=22(21) mxlc=00 mal=00 scl=00 pre=00
        oacflg=03 fl2=1206001 frm=00 csi=00 siz=24 off=0
        kxsbbbfp=7f77a2c284c0  bln=22  avl=02  flg=05
        value=1
      EXEC #140151808572984:c=55,e=55,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880035010880
      FETCH #140151808572984:c=18,e=18,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=1,plh=3962208483,tim=6880035010927
      CLOSE #140151808572984:c=0,e=0,dep=1,type=3,tim=6880035010963
      BINDS #140151808572984:
      
       Bind#0
        oacdty=02 mxl=22(21) mxlc=00 mal=00 scl=00 pre=00
        oacflg=03 fl2=1206001 frm=00 csi=00 siz=24 off=0
        kxsbbbfp=7f77a2d11a98  bln=22  avl=01  flg=05
        value=0
      EXEC #140151808572984:c=62,e=62,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880035011043
      FETCH #140151808572984:c=13,e=13,p=0,cr=3,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880035011068
      CLOSE #140151808572984:c=0,e=0,dep=1,type=3,tim=6880035011100
      EXEC #140151808236992:c=2237,e=2765,p=0,cr=10,cu=0,mis=0,r=0,dep=0,og=1,plh=0,tim=6880035011182
      ERROR #140151808236992:err=1001 tim=6880035011196
      WAIT #140151808236992: nam='SQL*Net break/reset to client' ela= 3 driver id=1413697536 break?=1 p3=0 obj#=528 tim=6880035011230
      WAIT #140151808236992: nam='SQL*Net break/reset to client' ela= 5553 driver id=1413697536 break?=0 p3=0 obj#=528 tim=6880035016797
      WAIT #140151808236992: nam='SQL*Net message to client' ela= 1 driver id=1413697536 #bytes=1 p3=0 obj#=528 tim=6880035016842
      WAIT #140151808236992: nam='SQL*Net message from client' ela= 711 driver id=1413697536 #bytes=1 p3=0 obj#=528 tim=6880035017582
      


      3.2 ORA-06511: PL/SQL: cursor already open


      In this test, the same cursor opened twice, hence "ORA-06511: PL/SQL: cursor already open".
      
      SQL > exec test_cursor_errors.test_ORA_06511(2);
      
      1. Loop p_id = 2, curs%isopen = true
      BEGIN test_cursor_errors.test_ORA_06511(2); END;
      
      *
      ERROR at line 1:
      ORA-06511: PL/SQL: cursor already open
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 2
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 43
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 43
      ORA-06512: at line 1
      
      Here 10046 trace file with "err=6511" and 'SQL*Net break/reset to client':
      
      alter session set events '10046 trace name context forever, level 12';  
      exec test_cursor_errors.test_ORA_06511(2);
      alter session set events '10046 trace name context off';
      
      
      =====================
      PARSING IN CURSOR #140498746628816 len=49 dep=0 uid=49 oct=47 lid=49 tim=6880557312015 hv=243630832 ad='72f8ab68' sqlid='1cu3xcc78b0rh'
      BEGIN test_cursor_errors.test_ORA_06511(2); END;
      
      END OF STMT
      PARSE #140498746628816:c=50,e=51,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=0,tim=6880557312014
      =====================
      PARSING IN CURSOR #140498750073448 len=38 dep=1 uid=49 oct=3 lid=49 tim=6880557312222 hv=1133886660 ad='77b15960' sqlid='c8b161d1tbg64'
      SELECT * FROM TEST_TAB WHERE ID = :B1 
      END OF STMT
      PARSE #140498750073448:c=77,e=77,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880557312222
      BINDS #140498750073448:
      
       Bind#0
        oacdty=02 mxl=22(21) mxlc=00 mal=00 scl=00 pre=00
        oacflg=03 fl2=1206001 frm=00 csi=00 siz=24 off=0
        kxsbbbfp=7fc869ff9388  bln=22  avl=02  flg=05
        value=2
      EXEC #140498750073448:c=52,e=52,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3962208483,tim=6880557312308
      FETCH #140498750073448:c=62,e=62,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=1,plh=3962208483,tim=6880557312393
      STAT #140498750073448 id=1 cnt=1 pid=0 pos=1 obj=5983275 op='TABLE ACCESS FULL TEST_TAB (cr=3 pr=0 pw=0 str=1 time=58 us cost=3 size=315 card=3)'
      CLOSE #140498750073448:c=0,e=1,dep=1,type=3,tim=6880557312532
      EXEC #140498746628816:c=512,e=513,p=0,cr=3,cu=0,mis=0,r=0,dep=0,og=1,plh=0,tim=6880557312562
      ERROR #140498746628816:err=6511 tim=6880557312573
      WAIT #140498746628816: nam='SQL*Net break/reset to client' ela= 2 driver id=1413697536 break?=1 p3=0 obj#=528 tim=6880557312605
      WAIT #140498746628816: nam='SQL*Net break/reset to client' ela= 696 driver id=1413697536 break?=0 p3=0 obj#=528 tim=6880557313324
      WAIT #140498746628816: nam='SQL*Net message to client' ela= 1 driver id=1413697536 #bytes=1 p3=0 obj#=528 tim=6880557313347
      WAIT #140498746628816: nam='SQL*Net message from client' ela= 5220 driver id=1413697536 #bytes=1 p3=0 obj#=528 tim=6880557318586
      


      3.3 ORA-21780: Maximum number of object durations exceeded


      In this test, we make an infinitive recursive calls, in each call, open the same cursor,
      then hit "ORA-21780: Maximum number of object durations exceeded".
      (The only difference of this test to "ORA-01001" code is that "l_chg = 0" instead of "l_chg = 1").

      The test shows that the maximum value is around 65339.
      
      SQL > begin
              test_cursor_errors.p_ORA_21780_Maximum := 0;
              test_cursor_errors.test_ORA_21780(2, 1);
              exception when others then
                dbms_output.put_line('ORA_21780_Maximum = '||test_cursor_errors.p_ORA_21780_Maximum);
                raise;
            end;
            /
                  
      ORA_21780_Maximum = 65339
      begin
      *
      ERROR at line 1:
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-01001: invalid cursor
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 59
      ORA-21780: Maximum number of object durations exceeded.
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 51
      ORA-06512: at "K.TEST_CURSOR_ERRORS", line 63
      
      Elapsed: 00:00:03.00
      


      4. Pass Cursor as Integer Input


      In one application, client program wants to receive cursor input represented by an integer.
      Here is one implementation:
      
      create or replace package test_cursor_number as 
        --p_recordset     sys_refcursor;  -- PLS-00994: Cursor Variables cannot be declared as part of a package
        function get_id_cur (p_id in test_tab.id%type) return integer;
      end;
      /
      
      create or replace package body test_cursor_number as 
        function get_id_cur (p_id in test_tab.id%type) return integer as
          l_cursor_number   integer;
        begin
          l_cursor_number := dbms_sql.open_cursor;
          dbms_sql.parse (l_cursor_number, 'SELECT id, name from test_tab where id <= '||p_id, dbms_sql.NATIVE);
          return l_cursor_number;
        end;
      end;
      /
      
      declare
        l_cursor_number   integer;
        l_return          integer;
        l_cursor          sys_refcursor;
        l_id              test_tab.id%type;
        l_name            test_tab.name%type;
      begin
        l_cursor_number := test_cursor_number.get_id_cur(p_id => 3);
        dbms_output.put_line('cursor_number = '||l_cursor_number);
        
        l_return := dbms_sql.execute (l_cursor_number);
        dbms_output.put_line('execute return = '||l_return);
        
        l_cursor := dbms_sql.to_refcursor (l_cursor_number);
        --dbms_output.put_line('l_cursor = '||l_cursor);      
        loop
          fetch l_cursor
          into l_id, l_name;
      
          exit when l_cursor%notfound;
          dbms_output.PUT_LINE (l_id || ' | ' || l_name);
        end loop;
      
        close l_cursor;
      end;
      /
      
      -- Test Output
      cursor_number = 357706397
      execute return = 0
      1 | Name_1
      2 | Name_2
      3 | Name_3
      
      By the way, Oracle Magazine On Connection Pools, Cursor Differentiation, and Optimal Ordering (By Tom Kyte May/June 2012) discussed the regular cursor vs. ref cursor, and showed the performance difference.

      Monday, September 15, 2025

      ORA-06502: NULL index table key value and plsql_optimize_level

      In this Blog, we will show that the logically equivalent Plsql code can have different behaviour when toggling plsql_optimize_level.

      Note: Tested in Oracle 19.27


      1. plsql_optimize_level: 0 or 1


      If we run all 4 tests below in Plsql code blocks with 0 or 1, they all throw the same error: ORA-06502: NULL index table key value.
      
      alter session set plsql_optimize_level = 1;
      
      --------- Test-1 ---------
      declare
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        v_t(1) := 1;
        v_t(null) := v_t(1);
        dbms_output.put_line(v_t(null));
      end;
      /
      
      --------- Test-2: error: NULL index table key value ---------
      declare
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        v_t(1) := 1;
        v_t(i) := v_t(1);
        dbms_output.put_line(v_t(i));
      end;
      /
      
      
      --------- Test-3 ---------
      declare
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        if true then
          dbms_output.put_line(1);
        end if;
        v_t(1) := 1;
        v_t(null) := v_t(1);
        dbms_output.put_line(v_t(null));
      end;
      /
      
      --------- Test-4: error: NULL index table key value ---------
      declare
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        if v_t.exists(1) or not v_t.exists(1) then
          dbms_output.put_line(1);
        end if;
        v_t(1) := 1;
        v_t(null) := v_t(1);
        dbms_output.put_line(v_t(null));
      end;
      /


      2. plsql_optimize_level: 2 or 3


      When we run above 4 Plsql code blocks with plsql_optimize_level: 2 (Default value) or 3:
      
        alter session set plsql_optimize_level = 2;
      
      we can see that Test-1 and Test-3 are OK, but Test-2 and Test-4 hit:
      
         ORA-06502: PL/SQL: numeric or value error: NULL index table key value
      
      although Test-1 and Test-2 are logically equivalent, as well as Test-3 and Test-4.


      3. PLSQ/SQL MCODE and DIANA Nodes


      We tried to use PL/SQL event 10928, 10938 and dumpdian.sql to dump runtime PLSQ/SQL MCODE and DIANA Nodes:
      
      event 10928 PL/SQL Machine Code Trace 
      event 10938 PL/SQL calls and exceptions
      admin/dumpdian.sql
      
      (see:
         PL/SQL Machine Code Trace - event 10928, 
         PL/SQL, AST, DIANA, Attributes and IDL
      )
      
      but it is still hard to observe any difference.
      
      alter session set events '10928 trace name context forever, level 1';
      --------- Test-1 ---------
      declare
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        v_t(1) := 1;
        v_t(null) := v_t(1);
        dbms_output.put_line(v_t(null));
      end;
      /
      alter session set events '10928 trace name context off';
      
      
      --  Run under sys 
      create or replace procedure test_1_proc as
        type t is table of number index by binary_integer;
        v_t  t;
        i    pls_integer := null;
      begin
        v_t(1) := 1;
        v_t(null) := v_t(1);
        dbms_output.put_line(v_t(null));
      end;
      /
      
      -- dump into session trc file --
      
      exec sys.dumpdiana.dump(aname => 'TEST_1_PROC');
      exec sys.dumpdiana.node_count(aname => 'TEST_1_PROC');
      
      We have also tried to switch plsql_code_type from interpreted (Default ) to native, no effect is observed.

      Saturday, September 6, 2025

      "ORA-00001: unique constraint violated" Debugging

      In this Blog, we demonstrate two approaches to debug ORA_00001, one is using errorstack event; another is with DML before trigger.
      Both are especially useful for closed system, in which application is hard to be modified to fully expose code path and error causes.

      Note: Tested in Oracle 19c


      1. Test Setup


      We create a table with one unique constraint, then insert a few rows:
      
      drop table ora1_test_tab;
      
      create table ora1_test_tab(id number, text varchar2(10));
      
      create unique index ora1_test_tab#u#idx on ora1_test_tab(id);
      
      --truncate table ora1_test_tab;
      
      insert into ora1_test_tab values(1, 'A1');
      insert into ora1_test_tab values(2, 'A2');
      insert into ora1_test_tab values(3, 'A3');
      commit;
      
      select * from ora1_test_tab;
      
         ID  TEXT
        ---  ----
          1  A1
          2  A2
          3  A3
      


      2. ORA-00001 Errorstack Trace


      Run following test, in which ORA-00001 is cut away (Plsql callstack printed out in exception handler):
      
      create or replace procedure ORA_00001_cut(p_id number) as
      begin
        insert into ora1_test_tab values(p_id, 'B3');
      exception when others then 
        dbms_output.put_line('Error');
        dbms_output.put_line(substr(dbms_utility.format_call_stack,1,2000));
      end;
      /
      
      alter system set events='10046 trace name context forever, level 12';
      alter system set events '1 trace name errorstack level 3';
      
      begin
        ORA_00001_cut(3); 
      end;
      /
      
      alter system set events '1 trace name errorstack off'; 
      alter system set events='10046 trace name context off';
      
      Sqlplus output:
      
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 3
      Detector: Check-2: ID = 3 (in Session_ID: 17), Exists by Already Committed TRX
      Error
      ----- PL/SQL Call Stack -----
        object      line  object
        handle    number  name
      0x8a5b1568         6  procedure K.ORA_00001_CUT
      0x81dd4768         2  anonymous block
      
      DB alert.log output:
      
      Errors in file /orabin/app/oracle/admin/test_db3/diag/rdbms/test_db3/test_db3/trace/test_db3_ora_650408.trc:
      ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      Open trc file: test_db3_ora_650408.trc listed in above alert.log, we can see Bind value=3, DML SQL statement (sqlid='74zg41sgbw33n'), PL/SQL Call Stack and DIAG Call Stack Trace as follows:
      
      =====================
      PARSING IN CURSOR #140050023218008 len=44 dep=1 uid=49 oct=2 lid=49 tim=9261352709305 hv=515771508 ad='8db01638' sqlid='74zg41sgbw33n'
      INSERT INTO ORA1_TEST_TAB VALUES(:B1 , 'B3')
      END OF STMT
      PARSE #140050023218008:c=170,e=170,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=1,plh=0,tim=9261352709305
      BINDS #140050023218008:
      
       Bind#0
        oacdty=02 mxl=22(21) mxlc=00 mal=00 scl=00 pre=00
        oacflg=13 fl2=206001 frm=00 csi=00 siz=24 off=0
        kxsbbbfp=8b3cb490  bln=22  avl=02  flg=09
        value=3
      =====================
      
      ----- Error Stack Dump -----
      ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      ----- Current SQL Statement for this session (sql_id=74zg41sgbw33n) -----
      INSERT INTO ORA1_TEST_TAB VALUES(:B1 , 'B3')
      
      ----- PL/SQL Call Stack -----
        object      line  object
        handle    number  name
      0x8a5b1568         3  procedure K.ORA_00001_CUT
      0x81dd4768         2  anonymous block
      
      ----- Call Stack Trace -----
      calling              call     entry                argument values in hex      
      location             type     point                (? means dubious value)     
      -------------------- -------- -------------------- ----------------------------
      ksedst1()+95         call     kgdsdst()            7FFF914C8680 000000002
                                                         7FFF914C28D0 ? 7FFF914C29E8 ?
                                                         000000000 000000000
      ......
      kauerr()+522         call     kxcser()             7FFF914CBB40 000000001
                                                         7FFF914CBAC0 000000013
                                                         000000001 000000000
      qesltcLoadIndexList  call     kauerr()             0005AAA5D 000000001 ?
      ()+2215                                            7FFF914CBAC0 ? 000000013 ?
                                                         000000001 ? 000000000 ?
      qerltcNoKdtBuffered  call     qesltcLoadIndexList  7F682EC89290 09FC19BA8
      InsRowCBK()+370               ()                   000000000 000000000
                                                         000000001 ? 000000000 ?
      qerltcSingleRowLoad  call     qerltcNoKdtBuffered  7F682EC89230 09FC1A220
      ()+293                        InsRowCBK()          09FC1A750 000000000 ?
                                                         7F6835BACC08 000000000
      qerltcFetch()+391    call     qerltcSingleRowLoad  09FC1A750 7F682EC89230
                                    ()                   09FC1A220 000000000 ?
                                                         7F6835BACC08 ? 000000000 ?
      qerstFetch()+541     call     qerltcFetch()        09FC1A750 7F682EC89230
                                                         009748EB0 7FFF914CD140
                                                         000007FFF 09FC1BC28
      insexe()+733         call     qerstFetch()         09FC1A6E8 7F682EC89568
                                                         00D0CBE20 07302917A 000007FFF
                                                         09FC1BC28 ?
      opiexe()+7194        call     insexe()             09FC1A6E8 ? 7F682EC89568 ?
                                                         00D0CBE20 ? 07302917A ?
                                                         000007FFF ? 09FC1BC28 ?
      


      3. ORA-00001 Before Trigger


      Here our test procedures:


      3.1 ORA-00001 Detector

      
      create or replace procedure debug_output(p_text varchar2) as
      begin
        dbms_output.put_line(p_text);
        sys.DBMS_System.ksdwrt(2, p_text);
      end;
      /
      
      create or replace procedure ora_00001_detector(p_id number) as
        l_id        number := p_id;
        l_sid       number; 
        l_trx_date  date;
        l_trx_ts    varchar2(100);
        l_stmt      varchar2(1000);
        l_id_found  number;
        l_ret_str   varchar2(1000);
      begin
        select sid into l_sid from v$mystat where rownum = 1;
        l_ret_str := 'ID = '||l_id||' (in Session_ID: '||l_sid||')';
        select max(id) into l_id_found from ora1_test_tab where id = l_id;
        if l_id_found is null then
          --Insert ID not yet exist
          l_ret_str := 'Check-1: '||l_ret_str||' Not Exist';
        else
          select max(start_date) into l_trx_date from v$session s, v$transaction t where s.taddr=t.addr and sid = l_sid;
          if l_trx_date is null then 
            --Insert ID exists by Already Committed TRX
            l_ret_str := 'Check-2: '|| l_ret_str||', Exists by Already Committed TRX';
          else
            l_trx_ts := to_char(l_trx_date, 'YYYY-MM-DD hh24:mi:ss');
            l_ret_str := l_ret_str||', My TRX start_date: '||l_trx_ts ||', flashback query timestamp: '||l_trx_ts;
            l_stmt := 'select max(id) from ora1_test_tab as of timestamp timestamp''' ||l_trx_ts||''' where id = '||l_id;
            l_ret_str := l_ret_str||chr(10)||'    Stmt:'||l_stmt||chr(10);
            execute immediate l_stmt into l_id_found;
            if l_id_found is null then 
              --Insert ID inserted twice in the same TRX: first insert not yet committed, and inserted again. flashback query not found it.
              l_ret_str := 'Check-3: '||l_ret_str||'    flashback query No Found, My SID Inserted, Not Yet Committed.';
            else
              --Insert ID inserted after TRX started by another DML, flashback query found it.
              l_ret_str := 'Check-4: '||l_ret_str||'    flashback query Found Committed ID : '||l_id_found;
            end if;
          end if;
        end if;
        debug_output('Detector: '||l_ret_str);
        
        -- @TODO Printout Plsql call_stack:
        --debug_output(substr(dbms_utility.format_call_stack,1,2000));
      end;
      /
      


      3.2 Before Trigger

      
      create or replace trigger ora_00001_test_tab_trig
        before insert or update on ora1_test_tab referencing new as new old as old for each row
      declare
        --pragma autonomous_transaction;
        tab    varchar2(30) := 'ora1_test_tab: ';
        action varchar2(30);
      begin
      	case
      	  when inserting then action := 'BEFORE INSERT';
      	  when updating  then action := 'BEFORE UPDATE';
      	end case;
      	debug_output('Trigger: '||action||' on '|| tab ||' :Old Val: '||:old.id||' :New Val: '||:new.id);
      	ora_00001_detector(:new.id);
      	
      	exception when others then 
      	  debug_output('ora_00001_test_tab_trig: '||SQLERRM);    --catch ORA-04091
      	--commit;
      end;
      /
      


      3.3 Test Procedure

      
      create or replace procedure test_ora_00001_detector(p_id number, p_mode varchar2 := 'Detect') as
        l_id_tab t_id_tab := t_id_tab(-3, 3, 13);
      begin
        rollback;
        
        ------------- Case-1 No ORA-00001: Insert Not Exist: ID = -3 -------------
        debug_output(chr(10)||'------------- Case-1 No ORA-00001: Insert Not Exist: ID = -3 -------------');
        if p_mode = 'Detect' then 
          ora_00001_detector(-p_id);
        else
          insert into ora1_test_tab values(-p_id, 'C1');
        end if;
        debug_output('Case-1 No ORA-00001 Insert: ID = -3 Not Exist');
        rollback;
        
        ------------- Case-2 ORA-00001: Insert Already Committed ID = 3 -------------
        debug_output(chr(10)||'------------- Case-2 ORA-00001: Insert Already Committed ID = 3 -------------');
        begin
           if p_mode = 'Detect' then 
             ora_00001_detector(p_id);
           else
             insert into ora1_test_tab values(p_id, 'C2');
           end if;
        exception when others then 
          debug_output('Case-2 ORA-00001 Insert: '||SQLERRM);
        end;
        rollback;
        
        ------------- Case-3 ORA-00001: Insert Twice ID = 13 in the Same TRX -------------
        debug_output(chr(10)||'-------------Case-3 ORA-00001: Insert Twice ID = 13 in the Same TRX -------------');
        begin
          insert into ora1_test_tab values(10+p_id, 'C3_1');
           if p_mode = 'Detect' then 
             ora_00001_detector(10+p_id);
           else
             insert into ora1_test_tab values(10+p_id, 'C3_2');
            end if;
        exception when others then 
          debug_output('Case-3 ORA-00001 Insert: '||SQLERRM);
        end;
        rollback;
        
        ------------- Case-4 ORA-00001: Insert ID = 3 in one already started TRX, flashback query detect Committed ID = 3 -------------
        debug_output(chr(10)||'------------- Case-4 ORA-00001: Insert ID = 3 in one already started TRX, flashback query detect Committed ID = 3 -------------');
        begin
          insert into ora1_test_tab values(10+p_id, 'C4_1');
          if p_mode = 'Detect' then 
            ora_00001_detector(p_id);
          else
            insert into ora1_test_tab values(p_id,  'C4_2');
          end if;
        exception when others then 
          debug_output('Case-4 ORA-00001 Insert: '||SQLERRM);
        end;
        rollback;
        
        ------------- Case-5 ORA-00001 Update to ID = 3 from ID = 13, flashback query detect Committed ID = 3 -------------
        debug_output(chr(10)||'------------- Case-5 ORA-00001 Update to ID = 3 from ID = 13, flashback query detect Committed ID = 3 -------------');
        begin
          if p_mode = 'Detect' then 
            ora_00001_detector(p_id);
          else
            insert into ora1_test_tab values(10+p_id, 'A13');
            update ora1_test_tab set id = p_id where id = 10+p_id;
          end if;
        exception when others then 
          debug_output('Case-5 ORA-00001 Update: '||SQLERRM);
        end;
        rollback;
        
        ------------- Case-6 ORA-00001 merge when matched, update to an existing ID -------------
        debug_output(chr(10)||'------------- Case-6 ORA-00001 merge when matched, update to an existing ID -------------');
        begin
          if p_mode = 'Detect' then 
            ora_00001_detector(p_id);
          else
            merge into ora1_test_tab t
            using (select p_id id, 'A3' text from dual) s
            on (t.text = s.text)
            when matched     then update set t.id = s.id - 1 
            when not matched then insert values (s.id, s.text);
          end if;
        exception when others then 
          debug_output('Case-6 ORA-00001 Update: '||SQLERRM);
        end;
        rollback;
      
        ------------- Case-7 ORA-00001 merge when not matched, insert one existing ID -------------
        debug_output(chr(10)||'------------- Case-7 ORA-00001 merge when not matched, insert one existing ID -------------');
        begin
          if p_mode = 'Detect' then 
            ora_00001_detector(p_id);
          else
            merge into ora1_test_tab t
            using (select p_id + 10 id, 'B3' text from dual) s
            on (t.text = s.text)
            when matched     then update set t.id = s.id - 1 
            when not matched then insert values (s.id - 10, s.text);
          end if;
        exception when others then 
          debug_output('Case-7 ORA-00001 Update: '||SQLERRM);
        end;
        rollback;
          
      end;
      /
      


      4. Test Run


      Here our tests and output recorded in DB alert.log:


      4.1 Smoking Test

      
      begin
        insert into ora1_test_tab values(13, 'A13');
        ora_00001_detector(3);
      end;
      /
      rollback;
      
      DB alert.log output:
      
      2025-09-01T10:13:41.357832+02:00
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Detector: Check-4: ID = 3 (in Session_ID: 297), My TRX start_date: 2025-09-01 10:13:41, flashback query timestamp: 2025-09-01 10:13:41
          Stmt:select max(id) from ora1_test_tab as of timestamp timestamp'2025-09-01 10:13:41' where id = 3
          flashback query Found Committed ID : 3
      


      4.2 Detect Test


      
      exec test_ora_00001_detector(3, 'Detect');
      
      DB alert.log output:
      
      2025-09-01T10:15:48.582676+02:00
      
      ------------- Case-1 No ORA-00001: Insert Not Exist: ID = -3 -------------
      Detector: Check-1: ID = -3 (in Session_ID: 297) Not Exist
      Case-1 No ORA-00001 Insert: ID = -3 Not Exist
      
      ------------- Case-2 ORA-00001: Insert Already Committed ID = 3 -------------
      Detector: Check-2: ID = 3 (in Session_ID: 297), Exists by Already Committed TRX
      
      -------------Case-3 ORA-00001: Insert Twice ID = 13 in the Same TRX -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Detector: Check-3: ID = 13 (in Session_ID: 297), My TRX start_date: 2025-09-01 10:15:48, flashback query timestamp: 2025-09-01 10:15:48
          Stmt:select max(id) from ora1_test_tab as of timestamp timestamp'2025-09-01 10:15:48' where id = 13
          flashback query No Found, My SID Inserted, Not Yet Committed.
      
      ------------- Case-4 ORA-00001: Insert ID = 3 in one already started TRX, flashback query detect Committed ID = 3 -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Detector: Check-4: ID = 3 (in Session_ID: 297), My TRX start_date: 2025-09-01 10:15:48, flashback query timestamp: 2025-09-01 10:15:48
          Stmt:select max(id) from ora1_test_tab as of timestamp timestamp'2025-09-01 10:15:48' where id = 3
          flashback query Found Committed ID : 3
      
      ------------- Case-5 ORA-00001 Update to ID = 3 from ID = 13, flashback query detect Committed ID = 3 -------------
      Detector: Check-2: ID = 3 (in Session_ID: 297), Exists by Already Committed TRX
      
      ------------- Case-6 ORA-00001 merge when matched, update to an existing ID -------------
      Detector: Check-2: ID = 3 (in Session_ID: 297), Exists by Already Committed TRX
      
      ------------- Case-7 ORA-00001 merge when not matched, insert one existing ID -------------
      Detector: Check-2: ID = 3 (in Session_ID: 297), Exists by Already Committed TRX
      


      4.3. Execute Test


      
      exec test_ora_00001_detector(3, 'Execute');
      
      DB alert.log output:
      
      2025-09-01T10:16:27.833564+02:00
      
      ------------- Case-1 No ORA-00001: Insert Not Exist: ID = -3 -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: -3
      Detector: Check-1: ID = -3 (in Session_ID: 297) Not Exist
      Case-1 No ORA-00001 Insert: ID = -3 Not Exist
      
      ------------- Case-2 ORA-00001: Insert Already Committed ID = 3 -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 3
      Detector: Check-2: ID = 3 (in Session_ID: 297), Exists by Already Committed TRX
      Case-2 ORA-00001 Insert: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      -------------Case-3 ORA-00001: Insert Twice ID = 13 in the Same TRX -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-3: ID = 13 (in Session_ID: 297), My TRX start_date: 2025-09-01 10:16:27, flashback query timestamp: 2025-09-01 10:16:27
          Stmt:select max(id) from ora1_test_tab as of timestamp timestamp'2025-09-01 10:16:27' where id = 13
          flashback query No Found, My SID Inserted, Not Yet Committed.
      Case-3 ORA-00001 Insert: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      ------------- Case-4 ORA-00001: Insert ID = 3 in one already started TRX, flashback query detect Committed ID = 3 -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 3
      Detector: Check-4: ID = 3 (in Session_ID: 297), My TRX start_date: 2025-09-01 10:16:27, flashback query timestamp: 2025-09-01 10:16:27
          Stmt:select max(id) from ora1_test_tab as of timestamp timestamp'2025-09-01 10:16:27' where id = 3
          flashback query Found Committed ID : 3
      Case-4 ORA-00001 Insert: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      ------------- Case-5 ORA-00001 Update to ID = 3 from ID = 13, flashback query detect Committed ID = 3 -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 13
      Detector: Check-1: ID = 13 (in Session_ID: 297) Not Exist
      Trigger: BEFORE UPDATE on ora1_test_tab:  :Old Val: 13 :New Val: 3
      ora_00001_test_tab_trig: ORA-04091: table K.ORA1_TEST_TAB is mutating, trigger/function may not see it
      Case-5 ORA-00001 Update: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      ------------- Case-6 ORA-00001 merge when matched, update to an existing ID -------------
      Trigger: BEFORE UPDATE on ora1_test_tab:  :Old Val: 3 :New Val: 2
      ora_00001_test_tab_trig: ORA-04091: table K.ORA1_TEST_TAB is mutating, trigger/function may not see it
      Case-6 ORA-00001 Update: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      ------------- Case-7 ORA-00001 merge when not matched, insert one existing ID -------------
      Trigger: BEFORE INSERT on ora1_test_tab:  :Old Val:  :New Val: 3
      ora_00001_test_tab_trig: ORA-04091: table K.ORA1_TEST_TAB is mutating, trigger/function may not see it
      Case-7 ORA-00001 Update: ORA-00001: unique constraint (K.ORA1_TEST_TAB#U#IDX) violated
      
      We can see that with before trigger, different scenarios of ORA-00001 (insert / update / merge, TRX started by previous DML or own DML) can be detected, and bind values are exposed.