I believe I may have uncovered a production bug, which causes intermittent problems. Basically, I am trying to understand what the AS400 does when dealing with embedded SQL and cursors. I am thinking that the cursor does not get closed in some cases, causing the next case to fail since the cursor is still open.
Here is a snapshot of the code:
begsr checkfile;
exec sql
declare T1 cursor for
select * from FILE1
where field1 = :var1;
exec sql
open T1;
exec sql
fetch next from T1 into :vrDS;
dow SQLCOD = *zeros;
if a=b;
eval found = 'Y';
leavesr;
endif;
enddo;
exec sql
close T1;
endsr;
My concern is in the leavesr line. If the condition is met, it leaves the subroutine, which skips the close of the cursor T1. In the job log, there are informational messages like "Cursor T1 already open or allocated.". I assume this means that it didn't do anything, or maybe even fetched from the previous cursor? I'm also wondering if the declare statement gets executed every time, or if it just skips that part of the code after the first execution of it. I think I need to put a close T1 statement before the open statement, but I wanted to get a second opinion since it's a production issue that is nearly impossible to recreate due to security key differences between test & production.
Thanks!
a DECLARE CURSOR is actually a compile time statement.
It's never executed at run-time.
:var1 is passed to the DB when the OPEN is done at run time. Here's an answer with a detailed example Using cursor for multiple search conditions
Yes, as the code is shown, the cursor would be left open. And possibly read from rather than opening a fresh cursor during the next run. Depends on what the CLOSQLCUR option is during compile (or as set using EXEC SQL SET OPTION)
You should be checking SQLSTATE/SQLCODE after the OPEN and the FETCH
Another common practice is to do a CLOSE before the OPEN, again be sure to check SQLSTATE/SQLCODE and ignore the CURSOR NOT OPEN on the close.
What Charles said, and also I believe you may in some cases even create an infinite loop with this code! Maybe you aren't giving the whole code, but if the fetch is successful (SQLCOD = 0) and a <> b, then you are stuck in a loop.
I like to put the fetch in a sub procedure that returns *On if a record is successfully read. Then you can do something like this:
dcl-proc MyProc;
dcl-pi *n;
... parameters ...
end-pi;
C1_OpenCursor(parameters);
dow C1_FetchCursor(record);
... do something with the record ...
enddo;
C1_CloseCursor();
end-proc;
// ------------------------
// SQL Open the cursor
dcl-proc C1_OpenCursor;
dcl-pi *n;
... parameters ...
end-pi;
exec sql
declare C1 cursor for ...
exec sql
open C1;
if SQLCOD < 0;
.. error processing ...
endif;
end-proc;
// ------------------------
// SQL Read the cursor
dcl-proc C1_FetchCursor;
dcl-pi *n Ind;
... parameters ...
end-pi;
exec sql
fetch C1 into ...
if SQLCOD = 100;
return *Off;
elseif SQLCOD < 0;
... error handling ...
return *Off;
endif;
return *On;
end-proc;
// ------------------------
// SQL Close the cursor
dcl-proc C1_CloseCursor;
exec sql close C1;
end-proc;
This lets you keep all of your database code in one place, and just call it from your program. Database procedures just access the database and report errors in some way. Your program logic can remain uncluttered by sometimes wordy database and error handling code.
One other thing, I don't check for errors on cursor close because the only error (other than syntax errors) that can be returned here is that the cursor is not open. I don't care about that because that's what I want anyway.
Related
In SQL Developer, when running some PL/SQL, when the procedure is completed, the message 'PL/SQL procedure successfully completed.' is returned.
The PL/SQL that is run may return error messages to the user if the operation could not be completed for any reason through DBMS_OUTPUT.PUT_LINE, however, the user will also see 'PL/SQL procedure successfully completed.', which could be misleading (especially if the Script Output window is small enough that the DBMS_OUTPUT is not visible).
Is there any way to have the DBMS_OUTPUT return what it should whilst also having the script not return 'PL/SQL procedure successfully completed.'?
If not, are there any alternatives in SQL Developer that I may be unaware of to provide instant personalised feedback to the user?
declare
testex exception;
begin
if 1=1 then
raise testex;
end if;
exception when testex then
dbms_output.put_line('Error msg');
end;
Below code works in my end. Did you try to run your code like below?
Copied text from a website to explain the SET FEEDBACK OFF command.
Source link: https://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12040.htm
SET FEED[BACK] {6 | n | ON | OFF} -
Displays the number of records returned by a script when a script selects at least n records.
ON or OFF turns this display on or off. Turning feedback ON sets n to 1. Setting feedback to zero is equivalent to turning it OFF.
SET FEEDBACK OFF also turns off the statement confirmation messages such as 'Table created' and 'PL/SQL procedure successfully completed' that are displayed after successful SQL or PL/SQL statements.
Add a RAISE statement in the error handler to re-raise the exception so that any outer handler can deal with it:
declare
testex exception;
begin
if 1=1 then
raise testex;
end if;
exception when testex then
dbms_output.put_line('Error msg');
RAISE; -- re-raise the exception to an outer handler
end;
Best of luck.
You have coded it explicitly to complete successfully in the event of a testex exception. The code says: if this happens, then print a message and end. If you want it to actually fail then it needs to raise an exception.
I would just use something like this:
begin
if 1=1 then
raise_application_error(-20000, 'Bananas are not available on a Tuesday');
end if;
end;
/
begin
*
ERROR at line 1:
ORA-20000: Bananas are not available on a Tuesday
ORA-06512: at line 3
This will cause an actual error, rather than just printing out a message that happens to talk about an error, allowing you to control deployments and build scripts etc.
It will also roll back any uncommitted transactions within the block, which your current approach does not, and it will show the actual line number.
I have two service programs: mySrvPgm and myErr
mySrvPgm has a procedure which contains:
/free
...
exec sql INSERT INTO TABLE VALUES(:RECORD_FMT);
if sqlError() = *ON;
//handle error
endif;
...
/end-free
myErr has a procedure sqlError:
/free
exec sql GET DIAGNOSTICS CONDITION 1
:state = RETURNED_SQLSTATE;
...
/end-free
Background info: I am using XMLSERVICE to call the given procedure in mySrvPgm from PHP. I am not using a persistent connection. myErr is bound-by-reference via a binding directory used by mySrvPgm. Its activation is set to *IMMED, its activation group is set to *CALLER.
The problem: Assume there is an error on the INSERT statement in mySvrPgm. The first time sqlError() is called it will return SQLSTATE 00000 despite the error. All subsequent calls to sqlError() return the expected SQLSTATE.
A workaround: I added a procedure to myErr called initSQL:
/free
exec sql SET SCHEMA MYLIB;
/end-free
If I call initSQL() before the INSERT statement in mySrvPgm, sqlError() functions correctly. It doesn't have to be SET SCHEMA, it can be another GET DIAGNOSTICS statement. However, if it does not contain an executable SQL statement it does not help.
The question: I believe the myErr service program is activating properly and has the correct scope, but I am wondering if there is something more I need to do to activate the SQL part of it. Is there some way to set it up so SQL auto-initializes when the service program is activated, or do I have to execute an unneeded SQL statement in order to get it started?
There is some more background information available here.
Thank you for reading.
What version an release of the OS? Are you upto date on PTFs?
Honestly, seems to me that it's possibly a bug. Or the manual(s) need clarification.. I'd open a PMR.
Hello i've this function to update my Access DB using TUniQuery :
var
Res:Boolean;
begin
Res:=false;
try
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
ExecSQL;
Res:=true;
end;
except
Res:=False;
end ;
Result:=Res;
end;
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
or is there any other better approach ?
thank you
You may want to consider your update succeeded if no exception is raised. It means the database is responsive and parsed and executed your statement without syntax errors.
In a statement like shown, you may also want to be sure that exactly one row was updated, because I assume that's your intention.
To check that, you can resort to the result of the ExecSQL method, which returns the number of rows affected by the execution of the statement. So, you may change your code to this:
begin
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
Result := ExecSQL = 1; //exactly 1 row updated
end;
end;
I also changed the unconditional exception handler, since it may not be the proper site to eat any exception, and also removed the local variable to store the Result, since that really is not necessary.
After reading your added text and re-thinking your question:
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
You really have to change your mind about exception handling and returning a boolean from this routine. Exceptions were introduced as a whole new concept on how to address exceptional and error situations on your programs, but you're killing this whole new (and IMHO better) approach and resorting to the old way to return a value indicating success or failure.
Particularly a try/exception block that eats any exception is a bad practice, since you will be killing exceptions that may be raised for too many reasons: out of memory, network problems like connection lost to database, etc.
You have to re-think your approach and handle these exceptional or error conditions at the appropriate level on your application.
My advise is:
change this from a function to a procedure, the new contract is: it returns only if succeded, otherwise an exception is raised.
if an exception occurs let it fly out of the routine and handle that situation elsewhere
raise your own exception in case no exactly 1 row is updated
change the query to use parameters (avoiding sql injection)
The routine may look like this:
procedure TMySecurityManager.ChangeUserNameAndPassword();
begin
MyQuery.SQL.Text := 'Update MYTABLE'
+ ' set username = :NewUserName'
+ ' , password = :NewPassword'
+ ' where username = :username';
MyQuery.Params.ParamByName('NewUserName').AsString := NewUserName;
MyQuery.Params.ParamByName('NewPassword').AsString := NewPassword;
MyQuery.Params.ParamByName('username').AsString := ACurrentUserName;
if MyQuery.ExecSQL <> 1 then
raise EUpdateFailed.Create('A internal error occurred while updating the username and password');
//EUpdateFailed is a hypotetical exception class you defined.
end;
I have a bug I've been trying to track down for a few weeks now, and have pretty much isolated exactly what's going wrong. However, why this happens is beyond me.
I have a very large Oracle package (around 4,100 lines of code) and I'm calling several procedures. However, data seems to be getting lost between procedure calls.
The data that is being lost is:
dpmethodstate varchar_state_local_type;
First, I call this procedure:
PROCEDURE delivery_plan_set_state (
messages OUT ReferenceCursor,
state IN varchar_state_local_type
) AS
BEGIN
logMessage('state COUNT is: ' || state.COUNT);
dpmethodstate := state;
FOR I IN 1..dpmethodstate.COUNT LOOP
logMessage(dpmethodstate(I));
END LOOP;
logMessage('delivery_plan_set_state end - dpmethodstate count is now ' || dpmethodstate.COUNT);
OPEN messages FOR SELECT * FROM TABLE(messageQueue);
messageQueue := NULL;
END delivery_plan_set_state;
I pass in state, which is a valid array of a single string. I can verify in the logs that dpmethodstate has a COUNT of 1 when the procedure ends.
Next, I call the execute_filter procedure which looks like this:
PROCEDURE execute_filter (
--Whole bunch of OUT parameters
) AS
--About 50 different local variables being set here
BEGIN
SELECT TO_CHAR(SYSTIMESTAMP, 'HH24:MI:SS.ff') INTO TIMING FROM DUAL;
logMessage('[' || TIMING || '] execute_filter begin');
logMessage('a) dpmethodstate Count is: ' || dpmethodstate.COUNT);
--Rest of procedure
However, this time dpmethodstate.COUNT is 0. The value I set from delivery_plan_set_state has vanished!
When I look at my logs, it looks something like this:
proposed
delivery_plan_set_state end - dpmethodstate count is now 1
[21:39:48.719017] execute_filter begin
a) dpmethodstate Count is: 0
As you can see, dpmethodstate got lost between procedure calls. There's a few things to note:
Nothing else in this package is capable of setting the value for dpmethodstate besides delivery_plan_set_state. And I can see nothing else has called it.
My client side code is written in C#, and not much happens between the two procedure calls.
This happens perhaps 1 out of every 100 times, so it's very difficult to track down or debug.
First off, what's the best way to debug a problem like this? Is there any more logging I can do? Also, how does Oracle maintain state between procedure calls and is there anything that can intermittently reset this state? Any pointers would be much appreciated!
Is dpmethodstate a package global variable? I'm assuming it is, but I don't see that explicitly mentioned.
Since package global variables have session scope, are you certain that the two procedure calls are always using the same physical database connection and that nothing is using this connection in the interim? If you're using some sort of connection pool where you get a connection from the pool before each call and return the connection to the pool after the first call, it wouldn't be terribly unusual in a development environment (or low usage environment) to get the same connection 99% of the time for the second call but get a different session 1% of the time.
Can you log the SID and SERIAL# of the session where you are setting the value and where you are retrieving the value?
SELECT sid, serial#
FROM v$session
WHERE sid = sys_context( 'USERENV', 'SID' );
If those are different, you wouldn't expect the value to persist.
Beyond that, there are other ways to clear session state but they require someone to take explicit action. Calling DBMS_SESSION.RESET_PACKAGE or DBMS_SESSION.MODIFY_PACKAGE_STATE(DBMS_SESSION.REINITIALIZE) will clear out any session state set in your session. Compiling the package will do the same thing but that should throw an error warning you that your session state was discarded when you try to read it.
I'm having difficulty debugging triggers in Oracle. Currently I'm using Oracle's Sql Developer tool.
To test a trigger I write inserts or deletes in a test window but I can't see what is happening inside the trigger. I would like to step through the trigger and see what is happening as the trigger fires. Is there any way to use select statements to display variable values inside the triggers?
Firstly, don't "start from here", or more specifically, don't use triggers. Triggers are going to force switching to row-level processing if the triggers are going to fire for each row. It's better to put the logic in a stored procedure which you call. You've then got a start (where you validate inputs) and an end and a logic path all the way through. Stored procedures are a lot easier to debug as you follow one path.
Second, never test for an error you don't know how to handle. If you don't catch it, it bubbles up to the client who gets an error report saying what went wrong (error message) and where (i.e. the error/call stack). If you try to catch it, you have to know what to do with it (and if you don't know the tendency is to ignore it - which is BAD).
Finally, you can't readily see each 'layer' of a select. The explain plan will generally tell you how its going about things. v$session_longops MAY indicate what it is currently doing. The current wait event MAY give clues as to what table/block/row it is currently working on.
A rough-and-ready simple method if you must debug triggers is to use DBMS_OUTPUT.
e.g.
SQL> CREATE OR REPLACE TRIGGER mytrigger
BEFORE UPDATE ON mytable
FOR EACH ROW
...
BEGIN
DBMS_OUTPUT.put_line('mytrigger STARTING');
... do some logic ...
DBMS_OUTPUT.put_line('old=' || :OLD.mycolumn);
DBMS_OUTPUT.put_line('new=' || :NEW.mycolumn);
DBMS_OUTPUT.put_line('mytrigger FINISHED');
END;
/
SQL> SET SERVEROUT ON
SQL> UPDATE mytable SET mycolumn = mycolumn + 1;
2 rows updated.
mytrigger STARTING
old=10
new=11
mytrigger FINISHED
mytrigger STARTING
old=20
new=21
mytrigger FINISHED
Application
I use a program from Quest called TOAD available at www.quest.com/toad/toad-for-oracle.aspx.
As mentioned above, DBMS_OUTPUT is very handy. In your editor, make sure you enable the Output window.
PL/SQL works on "blocks" of code and you can catch it with an EXCEPTION keyword.
(Please forgive my formatting, not sure how to format for web)
DECLARE
C_DATE_FORMAT VARCHAR2(20) := 'DD-Mon-YYYY';
C_TIME_FORMAT VARCHAR2(20) := 'HH24:MI:SS';
C_NOT_IMPLEMENTED_CODE CONSTANT NUMBER(5) := -20200;
C_NOT_IMPLEMENTED_MESSAGE CONSTANT VARCHAR2(255) := 'Not implemented';
not_implemented EXCEPTION; -- user defined exception
BEGIN
--RAISE not_implemented; -- raise user defined exception
RAISE_APPLICATION_ERROR(C_NOT_IMPLEMENTED_CODE, C_NOT_IMPLEMENTED_MESSAGE); -- user defined exception
EXCEPTION -- exception block
WHEN not_implemented THEN -- catch not_implemented exception
DBMS_OUTPUT.PUT_LINE('Error: Not implemented');
WHEN OTHERS THEN -- catch all other exceptions
DBMS_OUTPUT.PUT_LINE('Error occured.');
DBMS_OUTPUT.PUT_LINE('Date: ' || TO_CHAR(SYSDATE, C_DATE_FORMAT));
DBMS_OUTPUT.PUT_LINE('Time: ' || TO_CHAR(SYSDATE, C_TIME_FORMAT));
DBMS_OUTPUT.PUT_LINE('Error code: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('Error message: ' || SQLERRM); --deal with error
RAISE; -- raise to calling object
END;
Follow below steps to DEBUG trigger in Oracle PL/SQL
Open your trigger using Edit option. Set breakpoint by clicking on line number (See below).
Open Test Window as shown below.
Write Insert/Update query for your table. (Note: Your trigger could be set on update/insert or both)
Click Start debugger button.
As soon as value will be updated in your Table. Debugger will take you to trigger. Where you can continue your debugging.