Is it possible to set user defined session/connection flag for use in trigger? - sql

Consider a trigger that sets an id-column to an Oracle-sequence (next-)value on-insert. Now I want to extend it with some extra debugging actions. But they should be only active for certain connection/sessions (i.e. debugging ones).
Pseudo-Code on client side:
Open connection to oracle database
set mydebugflag=yes for that connection only
insert some stuff
close connection
Pseudo-Code on server-side (inside the trigger):
set id = someseq.next_val
if mydebugflag=yes then { do_some_extra_sanity_checks(); diagnostics();}
else: finished
How to implement such logic for an Oracle database?
Which Oracle features should I use for this?

There are a couple of ways to do this.
One is to create a package, create a package global variable (which will have session scope) in the package, and then have your client set the package variable and your trigger read it. Something like
CREATE OR REPLACE PACKAGE pkg_debug_mode
AS
PROCEDURE set_debug_mode( p_debug_mode IN NUMBER );
FUNCTION get_debug_mode
RETURN NUMBER;
DEBUG_MODE_ON constant number := 1;
DEBUG_MODE_OFF constant number := 2;
END;
CREATE OR REPLACE PACKAGE BODY pkg_debug_mode
AS
g_debug_mode NUMBER := DEBUG_MODE_ON;
PROCEDURE set_debug_mode( p_debug_mode IN NUMBER )
AS
BEGIN
g_debug_mode := p_debug_mode;
END;
FUNCTION get_debug_mode
RETURN NUMBER
IS
BEGIN
RETURN g_debug_mode;
END;
END;
The client calls pkg_debug_mode.set_debug_mode to set the debug mode and the trigger calls pkg_debug_mode.get_debug_mode to determine the current debug mode for the session.
create or replace context my_ctx using pkg_debug_mode;
CREATE OR REPLACE PACKAGE BODY pkg_debug_mode
AS
PROCEDURE set_debug_mode( p_debug_mode IN NUMBER )
AS
BEGIN
dbms_session.set_context( 'MY_CTX', 'DEBUG_MODE', p_debug_mode );
END;
FUNCTION get_debug_mode
RETURN NUMBER
IS
BEGIN
RETURN SYS_CONTEXT( 'MY_CTX', 'DEBUG_MODE' );
END;
END;
Your trigger can either call the get_debug_mode function or it can directly reference the context by putting the SYS_CONTEXT call in the trigger.

Related

Delphi 10.2: Using Local SQL with Firedac Memory Tables

How can I use Firedac LocalSQL with FDMemtable? Is there any working example available?
According to the Embarcadero DocWiki I set up a local connection (using SQLite driver), a LocalSQL component and connected some Firedac memory tables to it. Then I connected a FDQuery and try to query the memory tables. But the query always returns "table xyz not known" even if I set an explicit dataset name for the memory table in the localSQL dataset collection.
I suspect that I miss something fundamental that is not contained in the Embarcadero docs. If anyone has ever got this up and running I would be grateful for some tips.
Here is some code I wrote for an answer here a while ago, which is a self-contained example of using LocalSQL, tested in D10.2 (Seattle). It should suffice to get you going. Istr that the key to getting it working was a comment somewhere in the EMBA docs that FD's LocalSQL is based on Sqlite, as you've noted.
procedure TForm3.CopyData2;
begin
DataSource2.DataSet := FDQuery1;
FDConnection1.DriverName := 'SQLite';
FDConnection1.Connected := True;
FDLocalSQL1.Connection := FDConnection1;
FDLocalSQL1.DataSets.Add(FDMemTable1);
FDLocalSQL1.Active := True;
FDQuery1.SQL.Text := 'select * from FDMemTable1 order by ID limit 5';
FDQuery1.Active := True;
FDMemTable1.Close;
FDMemTable1.Data := FDQuery1.Data;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
i : integer;
MS : TMemoryStream;
begin
FDMemTable1.CreateDataSet;
for i := 1 to 10 do
FDMemTable1.InsertRecord([i, 'Row:' + IntToStr(i), 10000 - i]);
FDMemTable1.First;
// Following is to try to reproduce problem loading from stream
// noted by the OP, but works fine
MS := TMemoryStream.Create;
try
FDMemTable1.SaveToStream(MS, sfBinary);
MS.Position := 0;
FDMemTable1.LoadFromStream(MS, sfBinary);
finally
MS.Free;
end;
end;
As you can see, you can refer in the SQL to an existing FireDAC dataset simply by using its component name.

Cannot run block in pl/sql getting ora-06550 and pls-00103

I am trying to make anonymous blocks to update esri's oracle versioned views. The code runs fine when I execute it from pl/sql developer without the begin/end. But as soon as I add the begin/end I get errors.
begin
call sde.version_util.set_current_version ('ARCFM8.vtemp');
call sde.version_user_ddl.edit_version ('ARCFM8.vtemp', 1);
update arcfm8.t_conductormarker_vw set CEAREFERENCEDRAWING = 'my fisrt multiversion view update'
where OBJECTID = 3;
call sde.version_user_ddl.edit_version ('ARCFM8.vtemp', 2);
end;
Call is an SQL keyword not a PLSQL keyword. It will work outside the anonymous block, but not inside it. PLSQL will take care of this for you so all you should need is...
begin
sde.version_util.set_current_version ('ARCFM8.vtemp');
sde.version_user_ddl.edit_version ('ARCFM8.vtemp', 1);
update arcfm8.t_conductormarker_vw set CEAREFERENCEDRAWING = 'my fisrt multiversion view update' where OBJECTID = 3;
sde.version_user_ddl.edit_version ('ARCFM8.vtemp', 2);
end;

Stored procedures in delphi

I'm trying to call a stored procedure from some delphi code
I have a function like
procedure TDatabaseConnection.GetHourlyFiltergramLabSamples(StartTime, EndTime : TDateTime; Samples : TList<THourlyFilterCount>);
var
StoredProc : TADOStoredProc;
begin
StoredProc := TADOStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.ProcedureName := 'GetHourlyFiltergramLabSamples';
StoredProc.Parameters.Refresh;
StoredProc.Parameters.ParamByName('#StartTime').Value := startTime;
StoredProc.Parameters.ParamByName('#EndTime').Value := EndTime;
StoredProc.Open;
while not StoredProc.Eof do
begin
//Do stuff with the results here ...
StoredProc.Next;
end;
finally
FreeAndNil(StoredProc);
end;
end;
When I hit the line StoredProc.Open;
I get an error
'CommandText does not return a result set.'
I have checked using SQL server management studio that the stored procedure does in fact return results.
I've found that this issues seems to be temperamental. Surely this isn't a bug in the database connector??
I'm out of ideas
I've had this one open for a number of months now.
The best solution I have is to switch to using FireDAC. I have not had the same issues when using FireDAC to execute stored procedures.
instead I use the TFDStoredProc type to run stored procedures

PL/SQL Rollback Transaction with multiple package calls

I am new to Oracle PL/SQL and am trying to adjust from SQL Server with C# to a Oracle custom web application using PL/SQL for multiple layers of the application.
I currectly am having issues with rolling back a transaction. Since I am using to writing reusable code, I have written my PL/SQL code using packages and methods within those classes. My update procedure first completes validation and if validated successfully, calls a succession of various package methods to complete the save. Unfortunately, my rollback in the EXCEPTION portion of the update procedure does not rollback everything when the Rollback function is called. I'm at a loss as to why it is doing this. My basic code (although not exact due to legal issues) is as follows:
PROCEDURE SaveApplicationData(
variableName IN VARCHAR2 DEFAULT NULL,
--...
seq_id OUT INT )
AS
BEGIN
SET TRANSACTION NAME 'Transaction Name';
--Save initial program record
SaveNewRecord(variableName, seq_id);
IF (seq_id != 0) THEN
--If saved successfully, seq_id represents record ID
package_class.secondarySaveMethod(variableName, seq_id);
second_package_class.anotherSaveMethod(variableName, seq_id);
END IF;
COMMIT;
htp.p('Sequence ID: ' || seq_id);
htp.p('Saved the record"' || programName || '" successfully!');
EXCEPTION
WHEN OTHERS THEN
utilityPackage.rollbacktransaction;
END SaveApplicationData;
The utilityPackage.rollbacktransaction includes a ROLLBACK as well as the custom error exception handling package used by our organization.
Essentially, it will roll back the section that causes the error, but as soon as it rolls the section back, it continues with the rest of the transaction (and does not roll back previously executed blocks of code).
Please let me know if this does not make sense - and thank you in advance for your help!
Coming from a SQL Server environment before Oracle I can understand the confusion. Oracle does not use a BEGIN TRANSACTION. Instead, a transaction is implicitly started for you.
Thus, I believe in your case SET TRANSACTION NAME is not what you are looking to do, see
SET TRANSACTION.
I recommend removing the rollback code from your package and placing it in C#. It should be the responsibility of the caller to commit. Use a transaction in C# to guarantee the the transaction is committed upon successful execution of the package(s).
Ideally, your package structure should look more like this.
declare
ex_custom EXCEPTION;
PRAGMA EXCEPTION_INIT( ex_custom, -20001 );
begin
--Save initial program record
SaveNewRecord(variableName, seq_id);
IF (seq_id != 0) THEN
--If saved successfully, seq_id represents record ID
package_class.secondarySaveMethod(variableName, seq_id);
second_package_class.anotherSaveMethod(variableName, seq_id);
ELSE
-- seq_id invalid throw an exception
RAISE_APPLICATION_ERROR(-20001,'Custom error')
END IF;
htp.p('Sequence ID: ' || seq_id);
htp.p('Saved the record"' || programName || '" successfully!');
EXCEPTION
WHEN ex_custom THEN
-- if needed we log it
utility.log_exception;
-- Raise it for the client to handle
raise;
END SaveApplicationData;

Sleep function in ORACLE

I need execute an SQL query in ORACLE it takes a certain amount of time.
So I wrote this function:
CREATE OR REPLACE FUNCTION MYSCHEMA.TEST_SLEEP
(
TIME_ IN NUMBER
)
RETURN INTEGER IS
BEGIN
DBMS_LOCK.sleep(seconds => TIME_);
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RAISE;
RETURN 1;
END TEST_SLEEP;
and I call in this way
SELECT TEST_SLEEP(10.5) FROM DUAL
but to work I need set grant of DBMS_LOCK to the owner of the procedure.
How I can rewrite this function without using the DBMS_LOCK.sleep function?
Short of granting access to DBMS_LOCK.sleep, this will work but it's a horrible hack:
IN_TIME INT; --num seconds
v_now DATE;
-- 1) Get the date & time
SELECT SYSDATE
INTO v_now
FROM DUAL;
-- 2) Loop until the original timestamp plus the amount of seconds <= current date
LOOP
EXIT WHEN v_now + (IN_TIME * (1/86400)) <= SYSDATE;
END LOOP;
Create a procedure which just does your lock and install it into a different user, who is "trusted" with dbms_lock ( USERA ), grant USERA access to dbms_lock.
Then just grant USERB access to this function. They then wont need to be able to access DBMS_LOCK
( make sure you don't have usera and userb in your system before running this )
Connect as a user with grant privs for dbms_lock, and can create users
drop user usera cascade;
drop user userb cascade;
create user usera default tablespace users identified by abc123;
grant create session to usera;
grant resource to usera;
grant execute on dbms_lock to usera;
create user userb default tablespace users identified by abc123;
grant create session to userb;
grant resource to useb
connect usera/abc123;
create or replace function usera.f_sleep( in_time number ) return number is
begin
dbms_lock.sleep(in_time);
return 1;
end;
/
grant execute on usera.f_sleep to userb;
connect userb/abc123;
/* About to sleep as userb */
select usera.f_sleep(5) from dual;
/* Finished sleeping as userb */
/* Attempt to access dbms_lock as userb.. Should fail */
begin
dbms_lock.sleep(5);
end;
/
/* Finished */
From Oracle 18c you could use DBMS_SESSION.SLEEP procedure:
This procedure suspends the session for a specified period of time.
DBMS_SESSION.SLEEP (seconds IN NUMBER)
DBMS_SESSION.sleep is available to all sessions with no additional grants needed.
Please note that DBMS_LOCK.sleep is deprecated.
If you need simple query sleep you could use WITH FUNCTION:
WITH FUNCTION my_sleep(i NUMBER)
RETURN NUMBER IS
BEGIN
DBMS_SESSION.sleep(i);
RETURN i;
END;
SELECT my_sleep(3) FROM dual;
db<>fiddle demo
There is a good article on this topic: PL/SQL: Sleep without using DBMS_LOCK that helped me out. I used Option 2 wrapped in a custom package. Proposed solutions are:
Option 1: APEX_UTIL.sleep
If APEX is installed you can use the procedure “PAUSE” from the publicly available package APEX_UTIL.
Example – “Wait 5 seconds”:
SET SERVEROUTPUT ON ;
BEGIN
DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
APEX_UTIL.PAUSE(5);
DBMS_OUTPUT.PUT_LINE('End ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
Option 2: java.lang.Thread.sleep
An other option is the use of the method “sleep” from the Java class “Thread”, which you can easily use through providing a simple PL/SQL wrapper procedure:
Note: Please remember, that “Thread.sleep” uses milliseconds!
--- create ---
CREATE OR REPLACE PROCEDURE SLEEP (P_MILLI_SECONDS IN NUMBER)
AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
--- use ---
SET SERVEROUTPUT ON ;
BEGIN
DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
SLEEP(5 * 1000);
DBMS_OUTPUT.PUT_LINE('End ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
If executed within "sqlplus", you can execute a host operating system command "sleep" :
!sleep 1
or
host sleep 1
What's about Java code wrapped by a procedure? Simple and works fine.
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED SNOOZE AS
public final class Snooze {
private Snooze() {
}
public static void snooze(Long milliseconds) throws InterruptedException {
Thread.sleep(milliseconds);
}
}
CREATE OR REPLACE PROCEDURE SNOOZE(p_Milliseconds IN NUMBER) AS
LANGUAGE JAVA NAME 'Snooze.snooze(java.lang.Long)';
It would be better to implement a synchronization mechanism. The easiest is to write a file after the first file is complete. So you have a sentinel file.
So the external programs looks for the sentinel file to exist. When it does it knows that it can safely use the data in the real file.
Another way to do this, which is similar to how some browsers do it when downloading files, is to have the file named base-name_part until the file is completely downloaded and then at the end rename the file to base-name.
This way the external program can't "see" the file until it is complete. This way wouldn't require rewrite of the external program. Which might make it best for this situation.
If Java is installed on your 11G then you can do it in a java class and call it from your PL/SQL, but I am not sure that it does not require also a specific grant to call java.
Seems the java procedure/function could work. But why don't you compile your function under a user like the application schema or a admin account that has this grant and just grant your developer account execute on it. That way the definer rights are used.
You can use DBMS_PIPE.SEND_MESSAGE with a message that is too large for the pipe, for example for a 5 second delay write XXX to a pipe that can only accept one byte using a 5 second timeout as below
dbms_pipe.pack_message('XXX');<br>
dummy:=dbms_pipe.send_message('TEST_PIPE', 5, 1);
But then that requires a grant for DBMS_PIPE so perhaps no better.
You can use the DBMS_ALERT package as follows:
CREATE OR REPLACE FUNCTION sleep(seconds IN NUMBER) RETURN NUMBER
AS
PRAGMA AUTONOMOUS_TRANSACTION;
message VARCHAR2(200);
status INTEGER;
BEGIN
DBMS_ALERT.WAITONE('noname', message, status, seconds);
ROLLBACK;
RETURN seconds;
END;
SELECT sleep(3) FROM dual;