Preventing the deletion of a specific row in a database - mysql

I have a website that is linked to a database. When a user is logged in, they have the ability to delete something called a category. The website creates a prepared statement and removes this category from the database.
I want to be able to prevent the deletion of categories with a specific name or id. This is simple enough to do a check using jquery, but I want to add another layer of security by adding a check within the database. Couple questions...
Trigger or procedure? I have never used procedures before, and from what little trigger experience I have with triggers, I don't know how to go about the issue. Assuming that triggers can be used, how would I get the category being deleted? And then how would I go about stopping that row in the database from being deleted?
As a start, I have the following code for a trigger.
delimiter $$
CREATE TRIGGER category_delete BEFORE DELETE ON categories
FOR EACH ROW
BEGIN
END$$
delimiter ;

Throw an exception from within the trigger to abort the deletion:
delimiter $$
CREATE TRIGGER category_delete BEFORE DELETE ON categories
FOR EACH ROW
BEGIN
IF old.id = 5 THEN -- use whatever condition you need
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'May not delete id 5';
END IF;
END$$
delimiter ;

Just add a trigger and bound it.
Use the power of the database to maintain the business logic and keep it consistent and what is required - despite whatever PHP, JQuery etc. is throwing at it. It is the primary business assert

Related

MYSQL trigger before delete (instead of deleting rather update

I have two tables, one containing candidate information and the other containing the personal information of everyone considered as a person in the database. However, a candidate should not be deleted from the database, their non-personal data should be kept for record. The deletion of a candidate must result in the deletion of his personal data only. So i am trying to write a before delete trigger on the candidate table that takes the id of the candidate we are trying to delete and sets all their personal info to null. When i run the trigger, it only returns a msg saying candidate was deleted however when i check the personal info table, the row for that candidate still has all the information. what might i be doing wrong?
here is the code for the trigger:
USE `agence_interim`;
DELIMITER $$
DROP TRIGGER IF EXISTS agence_interim.candidat_BEFORE_DELETE$$
USE `agence_interim`$$
CREATE DEFINER = CURRENT_USER TRIGGER `agence_interim`.`candidat_BEFORE_DELETE` BEFORE DELETE ON `candidat` FOR EACH ROW
BEGIN
DECLARE errorMessage VARCHAR(255);
Update personne SET personne.id_personne = null,
personne.nom = null,
personne.prenom = null,
personne.email = null,
personne.telephone =null,
personne.date_naissance =null,
personne.description = null
WHERE personne.id_personne = OLD.id_personne;
SET errorMessage = CONCAT('The personal information for candidate number ',
OLD.id_personne,
' has been deleted');
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = errorMessage;
END$$
DELIMITER ;
I'm afraid this is not possible using a trigger in MySQL.
Using a SIGNAL statement in the trigger body aborts the DELETE action that spawned the trigger, but when the trigger is aborted, this also cancels all subordinate changes executed within the trigger body (and also any actions performed by triggers spawned by those changes, etc.).
There is no support for "instead of" triggers in MySQL. Either all changes succeed, or none of them succeed.
To do what you want, you can't use DELETE from the client.
You must use UPDATE.

Error Code: 1442. Can't update table 'customer' in stored function/trigger because it is already used by statement which invoked this stored function

I'm writing a query that creates a trigger to soft delete a row in the table customer using the a flag called "IsDelete" when the flag is '0' it is not deleted and when the flag becomes 1 the row has been marked as deleted.
When the query is run the error code 1442c is generated. can anyone explain why??
DELIMITER $$
CREATE TRIGGER SOFT_DELETE_TRIGGER
BEFORE DELETE ON customer
FOR EACH ROW
BEGIN
IF OLD.IsDelete = 0 THEN
CALL cannot_delete_error;
UPDATE customer
SET IsDelete = 1;
END IF;
END
$$
Deleting a row in the table to test the trigger.
DELETE FROM customer
WHERE C_username = 'testuser'
Yes, triggers don't allow you to INSERT/UPDATE/DELETE against the same table that spawned the trigger, because that could run invoke the trigger again, and then that trigger might do another INSERT/UPDATE/DELETE, and so on. You have too high a chance of causing an infinite loop.
I'm afraid MySQL doesn't support an "instead of" trigger like some other brands of SQL database do. You can't make a trigger on DELETE that does an update instead.
You can use SIGNAL to make it throw an error, and that blocks the DELETE, but it doesn't do an UPDATE instead.
To implement soft deletes as you are doing, you'll just have to make the client use UPDATE instead of DELETE.

Alter a where clause on a update through a trigger or similar

I'm currently building a replication database (for reporting from) for an already existing database and we are wanting to obfuscate/hash certain columns. Both are on AWS RDS platform with the replication set up as a 'read replica' of the source.
One of the issues with using RDS Replication I've found is you cannot specify which columns to ignore (given that RDS read replicas are supposed to be 1:1 this isn't surprising and I fully understand I'm doing something very niche)
The solution to this problem was to set up triggers on updates/inserts to alter these values, like so:
DELIMITER $$
CREATE TRIGGER clients_insert_obfuscate
BEFORE INSERT ON clients
FOR EACH ROW
BEGIN
SET NEW.access_token = NULL, NEW.user_token_ttl = 0;
END$$
DELIMITER ;
However one of the values we want to alter is a primary key (the PKs on this table are used as coupon code redemption numbers).
Which leads me to my question - is there anyway of altering the where clause in a update before its executed from within mysql? So on the replica databse, instead of matching against the unhashed code its matching against the hashed code?
So far I have this:
DELIMITER $$
CREATE TRIGGER insert_obfuscate
BEFORE INSERT ON codes
FOR EACH ROW
BEGIN
SET NEW.code = SHA2(NEW.code, 256);
END$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER update_obfuscate
BEFORE UPDATE ON codes
FOR EACH ROW
BEGIN
SET NEW.code = SHA2(NEW.code, 256);
END$$
DELIMITER ;
So the insert is fine - but the update trigger is only looking at the params to update - not the where clause.
I understand a trigger might not be the right route to take on this but I'm struggling to find anything else.
Is there anyway of doing this or do I need to look at changing the schema? I would prefer not to change the schema on a production DB though.
Thanks

How to add many constrains to a mysql database

I am trying to set up a MySQL database. The final database will consist of approx. 200 columns distributed over 7 tables. Now I’ve got the problem that I’d like to add check constraints to most of the columns. Some columns will only have one constraint, others will have many and/or are affected by columns of different tables.
E.g. rows can only be added to a table if the age is older than 20 years or if the zip code consists of 5 characters and starts with 1 or if the admission date (in table admission) is before the discharge date (in table discharge).
Just three examples. I can think of 1000 more, which is my problem.
I’d like to add these constraints in a more structured way. A trigger with more than 10 constraints will be complex and no one, except for the author will be able to reconstruct all constraints. But if the database will be used for years, many administrators will have to work with the database and maybe add new or remove unnecessary constraints.
DELIMITER $$
CREATE TRIGGER `test``_before_insert` BEFORE INSERT ON `zip`
FOR EACH ROW
BEGIN
IF NEW.zip_id < 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint 1';
END IF;
IF NEW.zip_id = 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint 2';
END IF;
END $$
DELIMITER ;
That's the way I don't want to handle the problem. 
My question is, if there is a way to declare constraints in a normal-human-readable-way (e.g. one constrain with a nice name per textfile) and add these to the database.
Unfortunately, the check constraint in MySQL does not work. You can learn more at this SO question. Even the accepted answer for this question suggests the use of triggers.
You are on the right track when you decided to use triggers. However you have a few misconceptions.
A trigger is basically defined on an event on a table. As you have 7 tables, you will have 7 separate triggers per table, at least. Read more about triggers in MySQL at MySQL Reference Manual.
It is you and you only that has to find ways to keep the code well organized so that your teammates and even you can understand it well. Everything that you need to do is follow the best practices followed across the software industry. For a few hints you can refer here and here on dba.stackexchange.com.
I have thought about my problem over, and have another argument and solution about the checking of constrains in a trigger.
First, the argument: I probably need at least a BEFORE INSERT and a BEFORE UPDATE trigger to check my data. Both triggers are more or less identical. If a constrain changes over time, I have to submit the change to both triggers or my data can be come inconsistent.
I thought of stored procedures to implement one constrain, which can be called in the INSERT and UPDATE trigger. Now If I change a procedure, it affects both triggers.
DROP TRIGGER IF EXISTS test_before_insert;
DROP PROCEDURE IF EXISTS `procedureLess5`;
DROP PROCEDURE IF EXISTS `procedureEquals5`;
DELIMITER $$
CREATE PROCEDURE `procedureLess5` (IN myInput INT)
BEGIN
IF myInput < 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint 1';
END IF;
END $$
CREATE PROCEDURE `procedureEquals5` (IN myInput INT)
BEGIN
IF myInput = 5 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint 2';
END IF;
END $$
CREATE TRIGGER `test_before_insert` BEFORE INSERT ON `zip`
FOR EACH ROW
BEGIN
CALL procedureLess5(NEW.zip_id);
CALL procedureEquals5(NEW.zip_id);
END $$
DELIMITER ;
Is this an opportunity?
How about the performance of my database if I have more than 20 called procedures?
Or do you think of another way? I’m not fixed to MySQL, PostgreSQL is also an opportunity. Is it better/faster/more standard to use CHECK CONSTRAINS in PostgreSQL?

MySQL Triggers to Disable A User Account

I am trying to create a MySQL Trigger to disable someone's account if they have logged in to the site 3 times. I have tried to create this trigger using the following code, but it is not setting is_active to 0 no matter what times_logged_in is. Any help would be appreciated.
CREATE TRIGGER updateTrigger AFTER UPDATE ON users
FOR EACH ROW
BEGIN
UPDATE users SET is_active=0 WHERE NEW.is_code=1
AND NEW.times_logged_in>=3
AND NEW.user_id=user_id;
END;
You've hit a limitation of MySQL. Your table users invokes the trigger (AFTER UPDATE ON users), therefore the triggered code cannot modify it. See the MySQL-Manual:
Within a stored function or trigger,
it is not permitted to modify a table
that is already being used (for
reading or writing) by the statement
that invoked the function or trigger.
I'm not sure where user_id comes from in your trigger, looks like an extraneous check, try this:
CREATE TRIGGER updateTrigger AFTER UPDATE ON users
FOR EACH ROW
BEGIN
UPDATE users SET is_active=0 WHERE NEW.is_code=1
AND NEW.times_logged_in>=3
END;
Also, be sure that is_code = 1 is actually matching on the users you're updating, if it's not, it won't update any rows.
I would use a BEFORE UPDATE trigger instead:
CREATE TRIGGER updateTrigger BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF NEW.is_code=1 AND NEW.times_logged_in>=3 THEN
SET NEW.is_active=0;
END IF;
END;