PL/SQL block does not execute to end when using EXECUTE IMMEDIATE - oracle

What am I trying to achieve?
I would like to copy Oracle procedures from one user to another using PL/SQL.
I would like to copy all procedures - including those with compilation errors.
Where am I stuck?
After EXECUTE IMMEDIATE creates a procedure with compilation errors the PL/SQL block does not execute any further.
There is no exception!
The following two blocks of code demonstrate the problem. The first block executes as expected. The second block does not execute in whole. Procedure p3 having a compilation error is not the problem. My problem is that procedure p4 is not created.
-- creates procedure p1 and p2
BEGIN
EXECUTE IMMEDIATE 'create or replace procedure p1 is begin null; end;';
EXECUTE IMMEDIATE 'create or replace procedure p2 is begin null; end;';
dbms_output.put_line('Done!');
END;
-- creates only procedure p3 and exits with no error
BEGIN
EXECUTE IMMEDIATE 'create or replace procedure p3 is begin null end;'; -- compilation error (missing semicolon)
EXECUTE IMMEDIATE 'create or replace procedure p4 is begin null; end;';
dbms_output.put_line('Done!');
END;

To go into more detail on Bob's answer: if you want to ignore compile exceptions, you'll need to wrap each execute immediate in an anonymous block.
BEGIN
BEGIN
EXECUTE IMMEDIATE 'create or replace procedure p3 is begin null end;'; -- compilation error (missing semicolon)
EXCEPTION WHEN OTHERS THEN null; -- ignore exceptions
END;
BEGIN
EXECUTE IMMEDIATE 'create or replace procedure p4 is begin null; end;';
EXCEPTION WHEN OTHERS THEN null; -- ignore exceptions
END;
dbms_output.put_line('Done!');
END;

Yes, there is an exception being raised. You see, "Success with compilation error" IS an exception. You can catch it by defining an exception variable and initializing it using EXCEPTION_INIT:
eCompilation_error EXCEPTION;
PRAGMA EXCEPTION_INIT(eCompilation_error, -24344);
db<>fiddle here

Related

Oracle Pl/SQL Exception Flow

Given a simple SP like;
CREATE OR REPLACE PROCEDURE TEST1
AS
BEGIN
EXECUTE IMMEDIATE 'truncate table missingtable';
dbms_output.put_line('here');
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
END IF;
END;
I never get to the output statement, I thought control returned to the same block, which is the only block.. and yes, missingtable reports a -942 if I try to truncate it.
it's a logic problem, in fact the exception happens but you coded to raise an exception only if the return code is different of 942 which is the the error happening.
if you want to continue to the dbms_output in you first block you need an inner exception
CREATE OR REPLACE PROCEDURE TEST1
AS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'truncate table missingtable';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
RAISE;
ELSE
NULL;
END IF;
END;
dbms_output.put_line('here');
END;
/

Execute procedure dynamically from another procedure

Inside packA I have a procedure:
procedure testA
is
v1 varchar2(3200) = 'schema1.packB.test2('''||P1||''','''||P2||''',''Name1'',''Name2'',20,30)';
begin
EXECUTE IMMEDIATE v1;
end;
when I test it:
begin
packA.testA();
end;
I get error: "invalid SQL statement"
But, when I execute v1 from the command line, like:
excecute schema1.packB.test2('ABC','DEF','Name1','Name2',20,30);
it's working.
I printed v1 to see what is executed, and looks exactly the same as "schema1.packB.test2('ABC','DEF','Name1','Name2',20,30)"
You need to include BEGIN..END when running a stored procedure using EXECUTE IMMEDIATE.
v1 varchar2(3200) = 'BEGIN schema1.packB.test2('''||P1||''','''||P2||''',''Name1'',''Name2'',20,30) END;';
EXECUTE IMMEDIATE runs an SQL(DML/select) statements or PL/SQL anonymous block
Also, consider using BIND variables instead of PL/SQL variables directly.
'schema1.packB.test2( :p1, :p2, :p3, :p4 ,20,30)' USING P1,P2, 'Name1', 'Name2'
You need a block to use execute immediate to call a procedure.
For example:
SQL> create or replace package pck is
2 procedure proc;
3 end;
4 /
Package created.
SQL> create or replace package body pck is
2 procedure proc is
3 begin
4 null;
5 end;
6 end;
7 /
Package body created.
SQL> begin
2 execute immediate 'pck.proc;';
3 end;
4 /
begin
*
ERROR at line 1:
ORA-00900: invalid SQL statement
ORA-06512: at line 2
SQL> begin
2 execute immediate 'begin pck.proc; end;';
3 end;
4 /
PL/SQL procedure successfully completed.
Also, while using an execute immediate, consider passing parameters with bind variables. For example, you can (better) re-write the following
begin
execute immediate 'begin pck.proc(''x''); end;';
end;
as
begin
execute immediate 'begin pck.proc(:1); end;' using 'x';
end;

Running PLSQL ( Oracle ) Stored Procedure in Liquibase error : Package or function X is in an invalid state

I am trying to run the following stored procedure:
CREATE OR REPLACE PROCEDURE RETRY_TRANS_EXCEPTION
AS
BEGIN
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT('Try #' || i);
ALTER TABLE CIS_CASE ADD TEST01 varchar2(1) NOT NULL;
END;
END;
/
and calling it in changelog.xml as:
<sql>CALL RETRY_TRANS_EXCEPTION();</sql>
i get error:
Liquibase update Failed: Migration failed for change set eldata-changelog.xml::2016-08-25-cn-01::Ch Will:Reason: liquibase.exception.DatabaseException: Error executing SQL CALL RETRY_TRANS_EXCEPTION(): ORA-06575: Package or function RETRY_TRANS_EXCEPTION is in an invalid state
What i am trying to achieve is to be able to run a stored procedure through Liquibase with a loop in it.
Thanks for your help Prashant. What worked in my case was your solution plus one change:
CREATE OR REPLACE PROCEDURE RETRY_TRANS_EXCEPTION
AS
v_query varchar2(100);
BEGIN
FOR i IN 1..500 LOOP
DBMS_OUTPUT.PUT('Try #' || i);
v_query := 'ALTER TABLE CIS_CASE ADD TEST01 varchar2(1) NULL';
execute immediate v_query;
END loop;
END;
/
and then calling the Stored Procedure from the changelog, as:
<changeSet id="2016-08-25-cw-01" author="Ch Will">
<comment>
Testing retry logic on liquibase
</comment>
<sql>CALL RETRY_TRANS_EXCEPTION();</sql>
</changeSet>
You can't call it because the procedure does not compile correctly. Go back and fix the compilation errors, then try again.
Here are a couple of errors that stand out to me:
the for loop should end with end loop;, not end;
You can't have DDL statements directly in the code. You need dynamic SQL to execute a DDL statement from a procedure: execute immediate 'ALTER TABLE CIS_CASE ADD TEST01 varchar2(1) NOT NULL';
Additional note: I don't understand why you are trying to execute the same DDL statement multiple times inside a loop. Obviously, you won't be able to add the same column with the same name over and over. You will get a runtime error.
SQL> CREATE OR REPLACE PROCEDURE RETRY_TRANS_EXCEPTION
2 AS
3 BEGIN
4 FOR i IN 1..5 LOOP
5 DBMS_OUTPUT.PUT('Try #' || i);
6 ALTER TABLE CIS_CASE ADD TEST01 varchar2(1) NOT NULL;
7 END;
8 END;
9 /
Warning: Procedure created with compilation errors
SQL> show err
Errors for PROCEDURE PRASHANT-MISHRA.RETRY_TRANS_EXCEPTION:
LINE/COL ERROR
-------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
6/4 PLS-00103: Encountered the symbol "ALTER" when expecting one of the following: ( begin case declare end exit for goto if loop mod null pragma raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge
Did required fixes :
CREATE OR REPLACE PROCEDURE RETRY_TRANS_EXCEPTION
AS
v_query varchar2(100);
BEGIN
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT('Try #' || i);
v_query := 'ALTER TABLE CIS_CASE ADD TEST01 varchar2(1) NOT NULL' ;
execute immediate v_query;
END loop;
END;
PLSQL stored procedure can't use DDL statements, like
alter table ...
so the
execute immediate ("...")
statement is required because in fact it creates an autonomous implicit transition that can't be rollbacked

Pl/SQL Nested Procedure Exception Handling

This is a best practice question on error handling through multiple levels of PL/SQL procedures. I've looked at a few other questions to help me out, in particular this one.
Currently, I have a program with Procedure 1, which calls Procedure 2, which calls Procedure 3. I'm trying to perform adequate error handling - but I'd like to output eventually the exact problem back to the application layer. I'm hoping to get some ideas on how I can do this efficiently and clearly.
My current solution method is below, but it seems rather messy to me, with lots of variable declarations. I am very new to PL/SQL (and SQL in general) so I'd appreciate any advice on:
Good error handling techniques when dealing with multiple layers of procedures.
Feeding error messages back up to application layer (in my procedure below, represented by "out_overall_output" variable.
Program Flow: UI -> Proc 1 -> Proc 2 -> Proc 3
Procedure 1:
--One input variable, one output.
in_id VARCHAR2;
out_overall_output VARCHAR2;
...
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100);
BEGIN
Proc2(id, l_success, l_error_output);
IF l_success = FALSE THEN
out_overall_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
out_overall_output:= 'Error calling Proc 2'
RETURN;
END;
--Normal flow continues if l_success is true...
Procedure 2:
-- One input variable, two output.
in_id
out_success
out_error_output
//other logic
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100)
BEGIN
Proc3(id, l_success, l_error_output)
IF l_success = FALSE THEN
out_error_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS
out_error_output = 'Error calling Proc 3'
RETURN;
END;
Procedure 3:
--One input variable, two output.
in_id VARCHAR2;
out_success BOOLEAN;
out_error_message VARCHAR2;
...
BEGIN
DELETE
FROM table
WHERE id = in_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
out_success = FALSE;
out_error_message = 'Error - No data to delete'
WHEN OTHERS THEN
out_success = FALSE;
out_error_message = 'Error deleting data.'
END;
Note: The levels of procedure calling goes deeper than this. The snippets I have shown are greatly simplified. The error messages and variable names in my real procedures are more descriptive.
To show exact explanations of "what happens with a server" for application level you can try following. In procedures:
create or replace procedure p1 is
...
exception
when <some_error> then
<do something>
-- re-raise error:
raise_application_error(-20001, 'Client with ID '|| ID || ' has no right to perform action "' || ACTION_NAME || '"', true);
end;
create or replace procedure p2 is
begin
p1;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20002, 'Action "' || ACTION_NAME || '" is not completed', true);
end;
create or replace procedure p3 is
begin
p2;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20003, 'Purchasing of "' || CAR_NAME || '" cancelled', true);
end;
And in top level procedure:
create or replace procedure top_level_procedure is
begin
p1;
exception
when <one_more_error> then
<do something>
raise_application_error(-20004, dbms_utility.format_error_backtrace);
end;
After exception in p1 you will see something like this:
ORA-20003: Purchasing of "Cool red Ferrari" cancelled
ORA-20002: Action "car purchase" is not completed
ORA-20001: Client with ID 123 has no right to perform action "Spent all money of Bill Gates"
Third parameter of procedure raise_application_error with false value cuts all previous error messages. If you will use false value in procedure p3, you will see only one error message with code ORA-20003 in this example.
P. S. Also you can define your own exceptions and use them in WHEN .. THEN clause. Here you find more information and examples: https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/errors.htm#LNPLS00704
P. P. S. How to log. Log procedure:
create or replace procedure log(p_log_message varchar2) is
pragma autonomous_transaction;
begin
insert into log_table(..., log_message) values (..., p_log_message);
commit;
end;
Call log procedure:
when <one_more_error> then
<do something>
log(..., dbms_utility.format_error_backtrace);
raise_application_error(-20004, dbms_utility.format_error_backtrace);

Oracle EXECUTE IMMEDIATE error

There is UserA and UserB in my oracle.
This is the package from UserA:
CREATE OR REPLACE PACKAGE BODY pkgA AS
PROCEDURE procA
AS
l_sql = 'BEGIN ' || UserB.procB || (:l_v1,:l_v2) END;';
EXECUTE IMMEDIATE l_sql USING IN l_v1,IN l_v2;
END;
Thie procB is come from UserB;
When I run this, I get the error:
PLS-00201:IDENTIFIER 'UserB.procB' must be declared;
User A need the EXECUTE right on the userb.procB.
grant the right as User B:
grant execute on UserB.procB to userA;
Unless it's supplied as a parameter, the procedure name needs to be inside the string as a fixed value; you have it outside so it's trying to be interpreted as a variable name, which doesn't exist:
l_sql = 'BEGIN UserB.procB(:l_v1,:l_v2) END;';
But then you wouldn't need to execut it dynamically anyway, you could just do:
PROCEDURE ProcA AS
BEGIN
UserB.procB(l_v1, l_v2);
END;
If you were passing the procedure as a variable, which would be a little odd, you'd have something like:
PROCEDURE procA (proc_name in varchar2) AS
BEGIN
l_sql = 'BEGIN ' || proc_name || '(:l_v1,:l_v2) END;';
EXECUTE IMMEDIATE l_sql USING IN l_v1,IN l_v2;
END;
... and you'd call that as procA('UserB.procB'). I don't think that's what you're trying to do, but it isn't entirely clear.
In both cases you don't seem to have l_v1 or l_v2 defined, so I guess you've just missed that part of the code out.

Resources