No data found in trigger - sql

I have a problem with my trigger. It returns "no data found" and i don't know how to resolve it. Can you help me ?
create or replace
TRIGGER nb_action
AFTER INSERT ON Message
FOR EACH ROW
DECLARE
vAuteur integer;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT id_auteur INTO vAuteur FROM Message where id_message = :new.id_message;
UPDATE Utilisateur SET nb_action=nb_action+1 where id_utilisateur=vAuteur;
END ;

Do not use autonomous transactions in normal code. The only time that autonomous transactions are really appropriate are cases when you want to write data to a log table whether or not the underlying action commits. For example, if you want to log an error, rollback the transaction, and raise the exception, you probably don't want the log message to be rolled back. You should absolutely never use an autonomous transaction to work around a mutating table exception which is, I assume, the reason you used an autonomous transaction here since the query against the Message table would raise a mutating table exception if it was not in an autonomous transaction.
Fortunately, in this case, there is no need to query the table on which the trigger is defined and no need to use an autonomous transaction. Simply
create or replace trigger nb_action
AFTER INSERT ON Message
FOR EACH ROW
BEGIN
UPDATE Utilisateur
SET nb_action=nb_action+1
where id_utilisateur=:new.id_auteur;
END ;

Since you have PRAGMA AUTONOMOUS_TRANSACTION; in that trigger it means that it can't see the row just inserted because it is in a different not yet committed transaction thus your SELECT doesn't return any data...
try
create or replace
TRIGGER nb_action
AFTER INSERT ON Message
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE Utilisateur SET nb_action=nb_action+1 where id_utilisateur=:new.id_auteur;
END ;

Related

How transactions work in case of Postgres Functions

I am trying to understand how transaction works in Postgres and what happens when multiple commands try to work on the same table. My doubt is related to a small experiment that I carried out.
Consider a table called experiment with a trigger (experiment_log) on it that is fired after every update, delete, or insert.
Now consider this function.
CREATE OR REPLACE FUNCTION test_func() RETURNS void AS $body$
DECLARE
_q_txt text;
version_var integer;
BEGIN
EXECUTE 'DROP TRIGGER IF EXISTS experiment_log ON experiment';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION AFTER DROPPING TRIGGER: %', version_var;
EXECUTE 'SELECT pg_sleep(20);';
SELECT version INTO version_var FROM experiment;
RAISE NOTICE 'VERSION BEFORE RECREATING TRIGGER: %', version_var;
EXECUTE 'CREATE TRIGGER experiment_log AFTER INSERT OR UPDATE OR DELETE ON experiment FOR EACH ROW EXECUTE PROCEDURE experiment_log_trigger_func();';
END;
$body$
language 'plpgsql';
So, this function drops the trigger and waits for 20 seconds before re-creating this trigger. Now any update operation performed during the time when function is sleeping, the update operation blocks. It means that I can not update the experiment table until the function test_func has executed completely.
Can anyone explain this behaviour? It seems I am missing something out to reason this behaviour.
That is because DROP TRIGGER places an ACCESS EXCLUSIVE lock on the table, and the lock is held until the transaction ends, that is, for the whole duration of the function call.
If you want to disable a trigger temporarily, use
ALTER TABLE experiment DISABLE TRIGGER experiment_log;
I would like to give you a reference from the documentation, but the lock level of DROP TRIGGER is not documented. However, it is documented that the SQL statement takes the lock:
Also, most PostgreSQL commands automatically acquire locks of appropriate modes to ensure that referenced tables are not dropped or modified in incompatible ways while the command executes.
There you can also find how long a lock is held:
Once acquired, a lock is normally held until the end of the transaction.
To find the lock taken by DROP TRIGGER, try this simple experiment:
CREATE TABLE t();
CREATE TRIGGER whatever BEFORE UPDATE ON t
FOR EACH ROW EXECUTE FUNCTION suppress_redundant_updates_trigger();
BEGIN; -- start a transaction
DROP TRIGGER whatever ON t;
SELECT mode FROM pg_locks
WHERE pid = pg_backend_pid() -- only locks for the current session
AND relation = 't'::regclass; -- only locks on "t"
mode
═════════════════════
AccessShareLock
AccessExclusiveLock
(2 rows)
COMMIT;
You see that an ACCESS SHARE lock and an ACCESS EXCLUSIVE lock are held on the table.

Delete latest DML action

I have a trigger that prevents any users that is not me from doing DML injections, whoever tries get the error message "Sorry, you are not allowed to do this"
However my trigger shows this message, but still goes ahead with the code that the user sent. Is there any way to prevent this from happening, ignoring the statement, or deleting the most recent transaction?
Sidenote: I know, using a trigger for this is probably not the way to go, but please ignore this, I'm trying to learn, and right now I'm learning triggers.
Thank you
create or replace trigger my_ex
before insert or update or delete on kund
declare
my_ex exception;
begin
if user not in ('me') then raise my_ex;
end if;
exception
When only_boss then raise_application_error(-20009,'Illegal activity!');
rollback;
end;
/
Your exception must be declared like this:
my_ex EXCEPTION;
PRAGMA EXCEPTION_INIT(my_ex, -20001);
Then user names in Oracle are capital by default, i.e. you should write
if user not in ('ME') then
raise my_ex;
end if;

Exception to handle unpermitted DML-operation

I'm trying to hinder a user who is not me from performing DML-operations on a table. I'm using a trigger but have some issues with the syntax. It is the "if user is not 'me' part that troubles me. Also, is stating "rollback;" enough to undo the operation? Thanks!
create or replace trigger only_me_ex
before insert or update or delete on table
for each row
declare
only_me_ex exception;
begin
if user is not 'me' then
raise only_boss exception;
end if;
exception
when only_me_ex then
raise_appication_error(-200001,'Not permitted!');
rollback;
end;
/
In your code you use 'USER' keyword, which is actually schema from which is operation performed. E.g. SCOTT is owner of emp table. If you log in as SCOTT an perform update on emp table, the USER as used in your trigger is SCOTT. If you log in as SYS and perform DML on table USER will be SYS. To sum it up:
You don't need trigger like this, you need to grant insert, update, delete privileges on this table only to those users who ought to be allowed to.
In fact rather than schema(user) you may need to know operation system user:
SYS_CONTEXT('USERENV','OS_USER')
EDIT: based on your comment that this is for academic purpose I made changes to your trigger, so it now compiles and works (I stick to your method of declaring exception, raising it and handle it as rerising an application error, which doesn't make much sense to me, but nevermind)
create table my_table (id number)
/
create or replace trigger only_me_ex
before insert or update or delete on my_table
declare
only_me_ex exception;
begin
if user!='TESTUSER' then
raise only_me_ex ;
end if;
exception
when only_me_ex then
raise_application_error(-20001,'Not permitted!');
end;
/
Note that I changed your trigger from level row trigger to statement trigger because it needs to be executed only once and I omitted rollback keyword which is not needed because there will not be anything to rollback (unless you want to rollback some previous operation in transaction);
if user not in ('testuser') then raise not_a_chance;
solved it

Preventing certain rows from being deleted in Oracle

I want to prevent any row with VERSIONID=1 from being deleted in a certain table. I also want to log this in an audit table so we can see when this happens for logging purposes. I'm trying to do this with a trigger:
CREATE TRIGGER TPMDBO.PreventVersionDelete
BEFORE DELETE ON TPM_PROJECTVERSION
FOR EACH ROW
DECLARE
BEGIN
IF( :old.VERSIONID = 1 )
THEN
INSERT INTO TPM_AUDIT VALUES ('Query has attempted to delete root project version!', sysdate);
RAISE_APPLICATION_ERROR( -20001, 'Query has attempted to delete root project version!' );
END IF;
END;
I get the following results:
SQL> delete from TPM_PROJECTVERSION where PROJECTID=70 and VERSIONID=1;
delete from TPM_PROJECTVERSION where PROJECTID=70 and VERSIONID=1
*
ERROR at line 1:
ORA-20001: Query has attempted to delete root project version!
ORA-06512: at "TPMDBO.PREVENTVERSIONDELETE", line 6
ORA-04088: error during execution of trigger 'TPMDBO.PREVENTVERSIONDELETE'
However, the table TPM_AUDIT is empty. Am I doing something wrong?
If your trigger raises an error, the DELETE statement fails and the transaction is rolled back to the implicit savepoint that is created before the statement is run. That means that any changes made by the trigger are rolled back as well.
You can work around this by using autonomous transactions. Something like
CREATE PROCEDURE write_audit
AS
PRAGMA AUTOMOMOUS_TRANSACTION;
BEGIN
INSERT INTO tpm_audit
VALUES( 'Query has attempted to delete root project version!',
sysdate );
commit;
END;
CREATE TRIGGER TPMDBO.PreventVersionDelete
BEFORE DELETE ON TPM_PROJECTVERSION
FOR EACH ROW
DECLARE
BEGIN
IF( :old.VERSIONID = 1 )
THEN
write_audit;
RAISE_APPLICATION_ERROR( -20001, 'Query has attempted to delete root project version!' );
END IF;
END;
This will put the INSERT into TPM_AUDIT into a separate transaction that can be committed outside the context of the DELETE statement. Be very careful about using autonomous transactions, however
If you ever find yourself using autonomous transactions for anything other than writing to a log table, you're almost certainly doing something wrong.
Code in a PL/SQL block declared using autonomous transactions is truly autonomous so it cannot see uncommitted changes made by the current session.
Because of write consistency, it is entirely possible that Oracle will partially execute a DELETE statement, firing the row-level trigger a number of times, roll back that work, and then re-execute the DELETE. That silent rollback, however, will not roll back the changes made by the autonomous transaction. So it is entirely possible that a single DELETE of a single row would actually cause the trigger to be fired more than once and, therefore, create multiple rows in TPM_AUDIT.
If you can create a UNIQUE constraint on the TPM_PROJECTVERSION pk columns + the version column, then you can create a second table that would reference those rows.
Trying to delete a row in TPM_PROJECTVERSION would then fail because child rows are present. This would at least throw an error in your application and prevent the deletion.
The other table could be automatically populated through an insert trigger on TPM_PROJECTVERSION.
If you revoke the DELETE privilege on that helper table, it would never be possible to remove those rows.
I believe you need to COMMIT the INSERT operation before calling RAISE_APPLICATION_ERROR, which rolls back the transaction.

Solving the mutating table problem in Oracle SQL produces a deadlock

Hey, I'm trying to create a trigger in my Oracle database that changes all other records except the one that has just been changed and launched the trigger to 0. Because I am updating records in the same table as the one that launched the trigger I got the mutating table error. To solve this, I put the code as an anonymous transaction, however this causes a deadlock.
Trigger code:
CREATE OR REPLACE TRIGGER check_thumbnail AFTER INSERT OR UPDATE OF thumbnail ON photograph
FOR EACH ROW
BEGIN
IF :new.thumbnail = 1 THEN
check_thumbnail_set_others(:new.url);
END IF;
END;
Procedure code:
CREATE OR REPLACE PROCEDURE check_thumbnail_set_others(p_url IN VARCHAR2)
IS PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE photograph SET thumbnail = 0 WHERE url <> p_url;
COMMIT;
END;
I assume I'm causing a deadlock because the trigger is launching itself within itself. Any ideas?
Using an autonomous transaction for this sort of thing is almost certainly a mistake. What happens if the transaction that inserted the new thumbnail needs to rollback? You've already committed the change to the other rows in the table.
If you want the data to be transactionally consistent, you would need multiple triggers and some way of storing state. The simplest option would be to create a package with a collection of thumbnail.url%type then create three triggers on the table. A before statement trigger would clear out the collection. A row-level trigger would insert the :new.url value into the collection. An after statement trigger would then read the values from the collection and call the check_thumbnail_set_others procedure (which would not be an autonomous transaction).