Monthly Archives: November 2008

Qualified names

Have you ever looked at a big long package/procedure and wondered what the scope of a variable might be in this web of nested named and unnamed PL/SQL blocks? I’m sure all of you have.
There are a few naming convention proposals out there that suggest using prefixes like “l_” for local, “g_” for global variables, etc which is some kind of Systems Hungarian notation (I’m not convinced of the usefulness of System Hungarian whereas I can see some benefit of App Hungarian if properly applied on the variable scope – read more about it at Joel’s).
The problem rises when there are multiple nested blocks, what’s now the scope of this “l_” prefixed variable again? You simply don’t know until you back track it to the declaration of that variable.
That’s where qualified names come in pretty handy, let’s examine:

create or replace package pck
as
  procedure p1 (
     text in varchar2
  );
end pck;
/

create or replace package body pck
as
  text varchar2(10);
 
  procedure p1 (
     text in varchar2
  )
  is
  begin
    pck.text := p1.text;
  end p1;
end pck;
/

Note that in procedure P1 we use qualifiers for the variable and parameter TEXT. Even though they have the same name, the scope is very clear. We could even qualify the parameter with the package name which would make it a bit long, e.g. “pck.text := pck.p1.text;
Isn’t this beautiful? Absolutely, but as soon as we use long package, procedure and variable names it can make the code somewhat cumbersome to read as you end up with very long expressions. In my opinion I think the benefits of qualified names far outweigh the disadvantages of having a bit longer code lines – you can look at one (maybe big) line of code and you have all the information, no need for scrolling up to the declaration to find out the scope.

Statement level rollback and exception propagation

What’s obvious from reading the Oracle Concepts and PL/SQL Developers Guide is that Oracle sets an implicit savepoint before each single SQL statement and in case the statement fails (for whatever reason) Oracle rolls back to that savepoint. That’s what’s called “statement level rollback”. Apparently, for stored procedures that are called from a client the same rules apply. Before the procedure call a savepoint is set and if that procedure raises/propagates an exception Oracle rolls back to the implicit savepoint set just before the procedure call. It is fundamental to understand this behaviour as a Oracle database developer, especially when it comes to exception handling. Let’s examine the implications. First we create the table with index and constraint:

SQL> create table t1 (id number, text varchar2(50));
Table created.

SQL> create unique index t1_idx_01 on t1 (text);
Index created.

SQL> alter table t1 add constraint t1_uk_01 unique (text) using index t1_idx_01;
Table altered.

Then a stored procedure to insert some data into table T1:

SQL> create or replace procedure insert_t1
is
begin
  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (1, 'first');

  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (2, 'first'); -- this will rais ORA-00001

  dbms_output.put_line('inserting third row');
  insert into t1 (id, text) values (3, 'third'); -- this statement will not be executed due to previous exception
end insert_t1;
/
Procedure created.

This procedure tries to insert three records into table T1 but due to the unique constraint on TEXT the second insert statement will fail and the procedure returns with an exception propagated to the caller and subsequently everything done within the procedure will be rolled back.
Let’s put that to a test:

SQL> truncate table t1;
Table truncated.

SQL> exec insert_t1
BEGIN insert_t1; END;
*
ERROR at line 1:
ORA-00001: unique constraint (TESTUSER.T1_UK_01) violated
ORA-06512: at "TESTUSER.INSERT_T1", line 8
ORA-06512: at line 1

SQL> select * from t1;
no rows selected

So far so good, now let’s see what happens when we trap ORA-00001 (PL/SQL named exception DUP_VAL_ON_INDEX) and swallow it, not propagating the exception to the top-level call.

SQL> create or replace procedure insert_t1
is
begin
  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (1, 'first');

  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (2, 'first'); -- this will rais ORA-00001

  dbms_output.put_line('inserting third row');
  insert into t1 (id, text) values (3, 'third'); -- this statement will not be executed due to previous exception
exception
when dup_val_on_index then
  null;
end insert_t1;
/
Procedure created.

This time the procedures again tries to insert three records into table T1 but fails badly with the second record. The exception is trapped and silently swallowed so the procedure returns without an error, no exception propagation and therefore leaving one record in the table.

SQL> truncate table t1;
Table truncated.

SQL> exec insert_t1
PL/SQL procedure successfully completed.

SQL> select * from t1;
ID                  TEXT
---------- --------------------------------------------------
1                   first

1 row selected.

Ooops, something went wrong and we didn’t notice, if we proceed with the program flow in the application and eventually commit the transaction we leave the database in a inconsistent state as we expect three records to be present and not only one.

Alright, next time just let’s log the error and propagate it:

SQL> create or replace procedure insert_t1
is
begin
  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (1, 'first');

  dbms_output.put_line('inserting first row');
  insert into t1 (id, text) values (2, 'first'); -- this will rais ORA-00001

  dbms_output.put_line('inserting third row');
  insert into t1 (id, text) values (3, 'third'); -- this statement will not be executed due to previous exception
exception
when dup_val_on_index then
  dbms_output.put_line('Error: duplicate records');
  raise_application_error(-20001, 'There can only be one "first"!');
end insert_t1;
/
Procedure created.

There we go, again whatever the application does we ensure the consistency of the data within the database.

SQL> truncate table t1;
Table truncated.

SQL> exec insert_t1
BEGIN insert_t1; END;
*
ERROR at line 1:
ORA-20001: There can only be one "first"!
ORA-06512: at "TESTUSER.INSERT_T1", line 15
ORA-06512: at line 1

SQL> select * from t1;
no rows selected

The propagation of exceptions can also be observed in a SQL trace where for a cursor “begin insert_t1; end;” with depth 0 a ERROR is following the EXEC call. If the error is trapped and ignored there is only a ERROR following the EXEC call of the the cursor “INSERT INTO T1(ID, TEXT) VALUES (2, ‘first’)” with depth 1 and no ERROR for the parent cursor.

Conclusion:
Be careful with stored procedures that trap exceptions and do not propagate them as this kind of breaks the statement level rollback that Oracle would perform when the exceptions are not caught at all or are being re-raised.

Foot note 1: in this text “stored procedure” or “procedure” represents any named PL/SQL construct (except triggers)