Recoverable Scripts

The DBMS_RECOVERABLE_SCRIPT package is called by the MAINTAIN_TABLES procedure in the DBMS_STREAMS_ADM package. It is probably called from a few other places as well. As I hit an error attempting to set up Streams replication of a table from one database to another, I thought I would document the troubleshooting process.

My script failed with the following error:

ERROR at line 1:
ORA-23616: Failure in executing block 8 for script 954444D7D90C47DBE040A8C07702188D with
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.DBMS_RECO_SCRIPT_INVOK", line 139
ORA-06512: at "SYS.DBMS_STREAMS_RPC", line 465
ORA-06512: at "SYS.DBMS_RECOVERABLE_SCRIPT", line 659
ORA-06512: at "SYS.DBMS_RECOVERABLE_SCRIPT", line 682
ORA-06512: at "SYS.DBMS_STREAMS_MT", line 7972
ORA-06512: at "SYS.DBMS_STREAMS_ADM", line 2526
ORA-06512: at "SYS.DBMS_STREAMS_ADM", line 2593
ORA-06512: at line 2

In 11.2 there is a package called DBMS_RECOVERABLE_SCRIPT that can be used to manage recoverable scripts. Following my original post I now know this package should not be called directly. See below for further details.

There is also a set of data dictionary views including:

DBA_RECOVERABLE_SCRIPT contains one row for each recoverable script. The primary key is the SCRIPT_ID - in my case "954444D7D90C47DBE040A8C07702188D" which is obviously system-generated.

The following query reports the current status of the script:

SQL> SELECT status, total_blocks, done_block_num
  2  FROM dba_recoverable_script
  3  WHERE script_id = '954444D7D90C47DBE040A8C07702188D';

STATUS       TOTAL_BLOCKS DONE_BLOCK_NUM
------------ ------------ --------------
ERROR                  11              7

So this query confirms that my script has failed with an error having completed seven of its eleven blocks.

The recoverable script is divided into blocks. DBA_RECOVERABLE_SCRIPT_BLOCKS contains one row for each block in the recoverable script. The primary key is formed by the SCRIPT_ID and BLOCK_NUM columns. Each block consists of a FORWARD_BLOCK CLOB which contains the actions required to complete the block and an UNDO_BLOCK CLOB which contains the actions required to reverse out the block. There is also a STATUS column and a BLOCK_COMMENT column.

The following script provides a more detailed summary of the execution of the script:

SQL> SELECT block_num,status
  2  FROM dba_recoverable_script_blocks
  3* WHERE script_id = '954444D7D90C47DBE040A8C07702188D' ORDER BY 1
BLOCK_NUM STATUS
---------- ------------
         1 EXECUTED
         2 EXECUTED
         3 EXECUTED
         4 EXECUTED
         5 EXECUTED
         6 EXECUTED
         7 EXECUTED
         8 ERROR
         9 NOT EXECUTED
        10 NOT EXECUTED
        11 NOT EXECUTED

The error was in block 8.

Let's find out what the block 8 was trying to do.

SELECT forward_block
FROM dba_recoverable_script_blocks
WHERE script_id = '954444D7D90C47DBE040A8C07702188D'
AND block_num = 8;
--
-- Get tag value to be used for Apply
--
DECLARE
  found            BINARY_INTEGER := 0;
  tag_num          NUMBER;
  apply_nm         VARCHAR2(30);
  apply_nm_dqt     VARCHAR2(32);
BEGIN
  SELECT apply_name INTO apply_nm
  FROM dba_apply_progress
  WHERE source_database = 'EAST.JULIANDYKE.COM';
  
  apply_nm_dqt := '"' || apply_nm || '"';
  -- Use the apply object id as the tag
  SELECT o.object_id INTO tag_num
  FROM dba_objects o
  WHERE o.object_name= apply_nm 
  AND o.object_type='APPLY';
  LOOP
    BEGIN
      found := 0;
      SELECT 1 INTO found FROM dba_apply
      WHERE apply_name != apply_nm 
      AND apply_tag = hextoraw(tag_num);
    EXCEPTION WHEN no_data_found THEN
      EXIT;
    END;
    EXIT WHEN (found = 0);
    tag_num := tag_num + 1;
  END LOOP;
  -- alter apply
  dbms_apply_adm.alter_apply(
    apply_name => apply_nm_dqt,
    apply_tag => hextoraw(tag_num));
END;

Now let's check the error message in DBA_RECOVERABLE_SCRIPT_ERRORS for this block.

SQL> SELECT error_message
  2  FROM dba_recoverable_script_errors  
  3  WHERE script_id = '954444D7D90C47DBE040A8C07702188D'
  4  AND block_num = 8;

ERROR_MESSAGE
----------------------------------------------------------------------------
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.DBMS_RECO_SCRIPT_INVOK", line 139
ORA-06512: at "SYS.DBMS_STREAMS_RPC", line 465

So this confirms what we already knew - an exact fetch has probably returned more than one row.

Not particularly useful if you captured the original failure in SQL*Plus; very useful if the error was embedded in application code or the exception was not captured correctly.

DBA_RECOVERABLE_SCRIPT_PARAMS contains a list of parameters for the script together with their values; in this case there were 22. In this example the list of parameters is very similar to the list of parameters for the MAINTAIN_TABLES procedure in the DBMS_STREAMS_ADM package.

The other view is DBA_RECOVERABLE_SCRIPT_HIST which contains a history of script executions. When I was testing this feature in a newish database, this view was empty suggesting that rows are only added to the history view when the entire script has executed successfully or when it is deleted.

Back to the error - well I'm not impressed. Prior to testing Oracle Streams I had set up Change Data Capture of which more another time. On the apply database the statement:

SELECT apply_name,source_database FROM dba_apply_progress;

returned the following output:

APPLY_NAME                     SOURCE_DATABASE
------------------------------ ------------------------------
CDC$A_CHANGE_SET3              EAST.JULIANDYKE.COM
APPLY$_EAST_5                  EAST.JULIANDYKE.COM
I guess there are a couple of ways to fix this - delete the CDC configuration or modify the FORWARD_BLOCK for the script. Neither solution is particularly user-friendly. However, at least I know what the problem is...

I decided to drop the CDC apply process using:

BEGIN
  dbms_apply_adm.stop_apply ('CDC$A_CHANGE_SET3',force=>TRUE);
  dbms_apply_adm.drop_apply ('CDC$A_CHANGE_SET3');
END;
/

Now the query of DBA_APPLY_PROGRESS only returns one row (the correct one).

Alternatively I suspect I could have corrected the recoverable script using the MODIFY_FORWARD_BLOCK in the DBMS_RECOVERABLE_SCRIPT package.

We now need to restart the recoverable script. The obvious thing to do here is to look up the package in the PL/SQL Packages and Types Reference, but unfortunately this package is not documented. So back to trial and error...

The obvious candidate is the RUN procedure in the DBMS_RECOVERABLE_SCRIPT package. This turns out to have been the wrong choice as noted in the comment from Pat McElroy; I should have used the RECOVER_OPERATION procedure in the DBMS_STREAMS_ADM package which can be used to roll forward, roll back or purge a recoverable script. This makes a lot more sense - I was uncomfortable using the RUN procedure as I was not certain whether it would re-execute the successful steps for a second time.

I originally executed the MAINTAIN_TABLES procedure as the GP user (part of my Formula 1 database). When I attempted to execute the RUN procedure as this user I got an error because it could not directly access the DBMS_RECOVERABLE_SCRIPT package. To speed things up I switched to the SYS user and run:

GRANT EXECUTE ON DBMS_RECOVERABLE_SCRIPT TO GP;

CREATE PUBLIC SYNONYM DBMS_RECOVERABLE_SCRIPT 
FOR SYS.DBMS_RECOVERABLE_SCRIPT;

Then as the GP user I was able to execute the following command:

BEGIN
  dbms_recoverable_script.run 
  (script_id=> '954444D7D90C47DBE040A8C07702188D');
END;
/

After a few seconds this script returned the following:

PL/SQL procedure successfully completed.

Do not try this at home - as noted above I should have used the RECOVER_OPERATION procedure in the DBMS_STREAMS_ADM package.

So Oracle thinks it worked. Do I agree? A review of the dictionary table reveals the following:

DBA_RECOVERABLE_SCRIPT is now empty; the script I was executing has disappeared.

DBA_RECOVERABLE_SCRIPT_ERRORS is also empty. Good - no more errors.

DBA_RECOVERABLE_SCRIPT_BLOCKS still contains the 11 blocks for the script we have been executing.

DBA_RECOVERABLE_SCRIPT_PARAMS still contains the 22 parameters for the script we have been executing.

DBA_RECOVERABLE_SCRIPT_HIST now contains one row for the script we have successfully executed. The primary key is SCRIPT_ID. We can check the status of this script using:

SQL> SELECT status, total_blocks, done_block_num
  2  FROM dba_recoverable_script_hist
  3  WHERE script_id = '954444D7D90C47DBE040A8C07702188D';

STATUS   TOTAL_BLOCKS DONE_BLOCK_NUM
-------- ------------ --------------
EXECUTED           11             11
All seems to make sense.

However I do think it would help if the DBMS_RECOVERABLE_SCRIPT package was added to the Package Types and Reference manual stating that scripts should not be run directly using this package and that the RECOVER_OPERATION procedure in the DBMS_STREAMS_ADM package should be run instead. The important thing for Oracle to remember here is that users are unlikely to encounter these scripts until something breaks at which point (like me) they will be under pressure to find a quick fix so they can proceed with the task in hand.