PL/SQL Teaser: A Valid Package With No Callable Subprograms

,

Last week I saw a PL/SQL teaser tweet by Steven Feurestein (@sfonplsql):

The question sounded simple: can you build a valid package with header and body with more than one procedure – that can compile but can never run. That was a really interesting question and I found myself thinking about it while working.

Since it is really had some good things we could learn from it, let’s go over my thought process and get to know this unique PL/SQL behavior.


When I first saw the question, I thought the question itself shouldn’t be very hard, but it also wasn’t very clear (as tweets often are). What does it mean “couldn’t run”? Does “fail at all attempts” count as “couldn’t run”?
I tried to understand the idea behind it and I had a couple of directions I could go on.

Privileges

My first direction is looking at the privileges perspective. Can we create a valid procedure for one user but it will be invalid to another?
The answer is yes. By using AUTHID CURRENT_USER we can create a procedure in our package that can be only run from one of the users due to lack in privileges. Why is that? Because when we use the default AUTHID DEFINER we say that the privileges are derived from the owner of the package. When we user CURRENT_USER we say that the privs derived from the runner of the package and he might not have privleges to use one of the underlying tables.
Example:

conn Zohar

create table zohar_test as select * From dual;

CREATE OR REPLACE PACKAGE MYTEST AUTHID CURRENT_USER
AS
        PROCEDURE MYTEST_1;
END MYTEST;
/

CREATE OR REPLACE PACKAGE body MYTEST  as 
        PROCEDURE MYTEST_1 is
			x number;
        BEGIN
            select 1 into x from zohar_test;
        END MYTEST_1;
END MYTEST;
/

grant execute on ZOHAR.MYTEST to public;

begin
        ZOHAR.MYTEST.MYTEST_1;
end;
/

And when we run it from a different user:

SQL> begin
  2    zohar.mytest.mytest_1;
  3  end;
  4  /
  zohar.mytest.mytest_1;
  *
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "ZOHAR.MYTEST", line 5
ORA-06512: at line 2 

But there is a problem here – this only fails because Zohar2 don’t have privileges to Zohar.zohar_test. If he had the privs, this would have worked so this doesn’t fit the question.

Dynamic SQL

The next think I thought of was using an invalid dynamic SQL inside the procedure. That will insure no one could run the procedure because it will always fail with the invalid command:

CREATE OR REPLACE PACKAGE MYTEST
AS
        PROCEDURE MYTEST_1;
END MYTEST;
/

CREATE OR REPLACE PACKAGE body MYTEST  as 
        PROCEDURE MYTEST_1 is
			x number;
        BEGIN
            execute immediate 'aaa';
        END MYTEST_1;
END MYTEST;
/

And when we run it:

SQL> begin
  2          ZOHAR.MYTEST.MYTEST_1;
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-00900: invalid SQL statement
ORA-06512: at "ZOHAR.MYTEST", line 5
ORA-06512: at line 2

This also not very good because the procedure had started running – it just failed during runtime.

At the point, it was clear: what we’re looking for is a compilation error cause by runtime. This could happen if we send the wrong parameter type (but we can avoid it by sending the right one) or using wrong number of parameters (also can be avoided by sending the right number).
At that point I looked really good and hard at the question. There was a huge hint there to direct me in the right way. The question stated that there should be more than one procedure in package. All my solutions so far had only one. Someone at twitter had suggested doing an endless loop but this is obviously wasn’t the answer.
And then it hit me!

Overloading

PL/SQL has an overloading properties. That means that when we write a pl/sql code (packages, in particular) we can have multiple definitions to the same procedure (or function) – if all the procedures has different parameters. This is mostly useful when changing interfaces between versions (adding or removing parameters) and we want to keep the old interface active. This is also useful when we can get multiple types to the same value and have different behavior based on the type we got (i.e. date, number, string etc.).
This is all excellent, but Oracle have multiple types that looks exactly the same – strings are the most common amongst them. When we get a string from a user we can never tell if he meant “CHAR”, “VARCHAR2” or “NVARCHAR”. It can also be a CLOB or LONG.

CREATE OR REPLACE PACKAGE MYTEST
AS
        PROCEDURE MYTEST_1 (p1 varchar2);
        PROCEDURE MYTEST_1 (p1 char);
END MYTEST;
/

CREATE OR REPLACE PACKAGE body MYTEST
AS
        PROCEDURE MYTEST_1 (p1 varchar2) IS
        BEGIN
                null;
        END MYTEST_1;

        PROCEDURE MYTEST_1 (p1 char) IS
        BEGIN
                null;
        END MYTEST_1;
END MYTEST;
/

begin
        MYTEST.MYTEST_1(p1 => 'a');
end;
/

And the result is:

SQL> begin
  2          MYTEST.MYTEST_1(p1 => 'a');
  3  end;
  4  /
        MYTEST.MYTEST_1(p1 => 'a');
        *
ERROR at line 2:
ORA-06550: line 2, column 9:
PLS-00307: too many declarations of 'MYTEST_1' match this call
ORA-06550: line 2, column 9:
PL/SQL: Statement ignored

This is exactly the thing Steven was asking about – we have multiple declarations that meets that call. Even if we created a variable that will actively state the type of the parameter, the database still won’t be sure which declaration it should use:

SQL> declare
  2     a char(1) := 'a';
  3  begin
  4          MYTEST.MYTEST_1(p1 => a);
  5  end;
  6  /
        MYTEST.MYTEST_1(p1 => a);
        *
ERROR at line 4:
ORA-06550: line 4, column 9:
PLS-00307: too many declarations of 'MYTEST_1' match this call
ORA-06550: line 4, column 9:
PL/SQL: Statement ignored

We would have expected that the database will know this is a wrong condition and stop us from messing around, but even if we use all the plsql warning, it won’t warn us about that:

SQL> alter session set plsql_warnings = 'ENABLE:ALL';
Session altered.
SQL> CREATE OR REPLACE PACKAGE MYTEST  AUTHID  definer
  2  AS
  3          PROCEDURE MYTEST_1 (p1 varchar2);
  4          PROCEDURE MYTEST_1 (p1 char);
  5  END MYTEST;
  6  /

Conclusion

This was a fun question and a great topic to write a post about even though I have so much in my backlog…. 🙂
Steven had also posted a video explaining this behiviour – I recommend watching it:

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.