Someone who attended my sessions at the Bucharest Oracle Summit earlier on this year sent me an example of a quirky little bug, possibly related to the newer “fine-grained” invalidation mechanisms, possibly related to ANSI syntax SQL, that’s very easy to reproduce. (That’s always nice for Oracle support – a perfect test case.)
All it takes is two tables and a packaged procedure that queries those tables. The package is coded to do something that should not be allowed in production code; but “should not” and “is not” are very different things. For anyone who wants to play with the example, here’s the script to create the necessary objects:
drop package pkg_test; drop table t2 purge; drop table t1 purge; create table t1 (id1 number, val1 varchar2(10)); create table t2 (id2 number, val2 varchar2(10)); insert into t1 values(1,rpad('x',10,'x')); insert into t2 values(1,rpad('x',10,'x')); execute dbms_stats.gather_table_stats(user,'t1') execute dbms_stats.gather_table_stats(user,'t2') create or replace package pkg_test is procedure pr_call; end pkg_test; / create or replace package body pkg_test as procedure pr_call is cursor cur_ids is select * -- Naughty ! from t1 join t2 on t2.id2 = t1.id1 ; rec_id cur_ids%rowtype := null; begin open cur_ids; fetch cur_ids into rec_id; close cur_ids; dbms_output.put_line(rec_id.val1 || '-' || rec_id.val2); exception when others then if cur_ids%isopen then close cur_ids; end if; raise; end pr_call; end pkg_test; /
Having created the procedure I’m now going to call it – and then add a column to table t1. What’s that going to do to a packaged procedure with a “select *”?
Pause for thought …
Here’s some SQL to run the test.
set serveroutput on prompt *** Make a first call to the procedure: no error *** execute pkg_test.pr_call prompt *** add a column to one of the tables alter table t1 add col_test varchar2(20); prompt *** Make two more calls to the procedure: ouch! *** execute pkg_test.pr_call execute pkg_test.pr_call prompt *** Recompile before a third call *** execute dbms_ddl.alter_compile('package body', user, 'pkg_test') execute pkg_test.pr_call
Unless I’ve managed to cut-n-paste the wrong bits of code, you would have got the following error for the 2nd and 3rd calls to the package:
BEGIN * ERROR at line 1: ORA-00932: inconsistent datatypes: expected - got - ORA-06512: at "TEST_USER.PKG_TEST", line 25 ORA-06512: at line 2
The package body should (I believe) have invalidated and recompiled itself for the second execution, and even if it failed on the first attempt surely it should have invalidated itself on the ORA-932 and recompiled itself and succeeded on the third execution. (If you remove the exception clause you’ll find that the error is intially raised at the fetch, by the way).
If we change the “select *” to explicitly name the columns we want, viz:“select t1.id1, t1.val1, t2.id2, t2.val2” we don’t get the ORA-00932 errors (just as we would probably expect). What we might not expect is that the errors also disappear if we leave the “select *” in place but change the query from ANSI syntax to traditional Oracle syntax.
Obviously you shouldn’t use the lazy “*” notation in any production code – it can cause several different problems (including the dangers of “whoops, I didn’t mean to make that one invisible”) – but if you do you may find that you end up with packaged procedures that crash for no apparent reason until you recompile them. Perhaps ORA-00932 is the only possible error message, but maybe it’s possible to cause other errors to appear. Even worse, though I haven’t tried to force it yet, you may find that you can construct cases where the package reports no error but modifies the wrong data.
I’ve tested this code on versions 184.108.40.206 and 220.127.116.11 and see the same results on both.