I'm new to SQL programming, and I couldn't find an answer to this question online.
I'm working with pl/pgsql and I wish to achieve the following result:
I have a table A with certain attributes.
I am supposed to keep this table updated at any time - thus whenever a change was made that can affect A's values (in other tables B or C which are related to A) - a trigger is fired which updates the values (in the process - new values can be inserted into A, as well as old values can be deleted).
At the same time, I want to prevent from someone insert values into A.
What I want to do is to create a trigger which will prevent insertion into A (by returning NULL) - but I don't want this trigger to be called when I'm doing the insertion from another Trigger - so eventually - insertion to A will only be allowed from within a specific trigger.
As I said before, I'm new to SQL, and I don't know if this is even possible.
Yes, totally possible.
1. Generally disallow UPDATE to A
I would operate with privileges:
REVOKE ALL ON TABLE A FROM public; -- and from anybody else who might have it
That leaves superusers such as postgres who ignore these lowly restrictions. Catch those inside your trigger-function on A with pg_has_role():
IF pg_has_role('postgres', 'member') THEN
RETURN NULL;
END IF;
Where postgres is an actual superuser. Note: this catches other superusers as well, since they are member of every role, even other superusers.
You could catch non-superusers in a similar fashion (alternative to the REVOKE approach).
2. Allow UPDATE for daemon role
Create a non-login role, which is allowed to update A:
CREATE ROLE a_update NOLOGIN;
-- GRANT USAGE ON SCHEMA xyz TO a_update; -- may be needed, too
GRANT UPDATE ON TABLE A TO a_update;
Create trigger functions on tables B and C, owned by this daemon role and with SECURITY DEFINER. Details:
Is there a way to disable updates/deletes but still allow triggers to perform them?
Add to the trigger function on A:
IF pg_has_role('postgres', 'member') THEN
RETURN NULL;
ELSIF pg_has_role('a_update', 'member') THEN
RETURN NEW;
END IF;
For simple 1:1 dependencies, you can also work with foreign key constraints (additionally) using ON UPDATE CASCADE.
Related
I recently learned about row-level security policies in SQL, and love the idea of being able to run important security logic inside the database. However, I'm not sure how to make the workflow for updating RLS policies as good as updating API code is.
Since RLS policies are essentially just stateless logic, it feels like I should be able to define them all in an sql schema file that is checked into git and run on each deploy to idempotently set the policies.
We can get some of the way there by using drop if exists, e.g.:
drop policy if exists "everyone can read" on patterns;
create policy "everyone can read" on patterns for select using (auth.role() = 'anon');
This seems pretty good, but it's not quite truly declarative, because it won't drop any policies that still exist in the database from a previous version of the schema. Can I drop all existing policies for a table and then recreate them? Or is there another way to go about this?
I figured out how to drop all policies. There are two catalog tables that list policies; pg_policy has low-level info, whereas pg_policies is more useful for this because it includes the table names directly.
-- Drop all existing policies
do
$$
declare
rec record;
begin
for rec in (SELECT tablename, policyname FROM pg_policies)
loop
execute 'drop policy "'||rec.policyname||'" on '||rec.tablename;
end loop;
end;
$$;
You have two options:
query pg_policy for all policies on a table and drop them
drop all policies that ever existed in any version of your schema
I have a general question about Triggers. Is there anyway to program the SQL database in a way that whenever a trigger is fired, SQL tells us what rows of data were removed or edited to meet one's criteria. In my case, I am using postgreSQL and I have one specific trigger which deletes certain rows being inserted in a table if a certain criteria is met. Is there anything I can add or change settings in postgreSQL that will indicate to me what rows and how many rows were deleted in this case:
CREATE TRIGGER unknowns
AFTER INSERT
ON employees
FOR EACH ROW
EXECUTE PROCEDURE delete_rows();
CREATE OR REPLACE FUNCTION delete_rows()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM employees WHERE Customer = 'unknown';
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
You would not typically do this at the database level.
Normally, you would modify the trigger to store changed data in an archive/history table. Then you will have a record of the data that has changed, when it was changed, and who changed it.
If you specifically want to know about changes initiated in a trigger, I'm not sure if there is any mechanism to distinguish those changes from user-initiated changes.
I'm currently working on creating a table for customers in an order management system for a course at university. The system relies on being able to retrieve order histories for the customers. Since we've chosen not to store these in a separate table, removing the option to delete rows from our customer table is essential.
How and where do I set this up in the CREATE statement? I suspect I'll have to create a rule about what should happen instead, but I'm not entirely sure about the specifics.
On SQL Server, you have the following options:
Deny object permissions. For example: DENY DELETE ON OBJECT::dbo.Customer TO db_datawriter;
Raise an error in a trigger: CREATE TRIGGER nodelete1 ON dbo.Customer INSTEAD OF DELETE AS RAISERROR('You can't delete from this table', 16, 10)
Rely on referential integrity without cascading updates/deletes. Note that this will only prevent deletion of a customer, if the customer has at least 1 order.
In my honest opinion, however, I think that this should be solved at the application level and not the database level. Even if using the techniques above, what would prevent someone from simply removing the trigger or grant the necessary permissions before DELETE'ing the records? Or simply dropping the entire table?
If you don't want your users to delete records from a table, simply make sure that your application does not allow them to do that. Anyone working directly with the database should know that issuing a DELETE statement could be dangerous - especially if you don't have a backup.
If the table is accessed only through an application, you can use a soft delete, to do that add a column to the table, for example IsDeleted, and check it in the software to see whether the row is live or deleted.
If the final users can access the DB, you can to change his/her login or group permission to remove the delete grant on that table.
i do it with a simple trigger on each table that i want disable delete
Create Trigger [dbo].[RollBackDelete]
ON [dbo].[Your Table Name]
INSTEAD OF DELETE
AS
BEGIN
ROLLBACK;
END
ofcourse if you have any key with cascade operation on delete or update it can not be work and you should set it to "No Action"
i hope this be be useful
I have two tables, t1(foo) and t2(bar), and a trigger on update t2.bar that updates t1.foo to something else.
At first I thought of just prohibiting any updates on t1 by using a trigger before update on t1.foo that always throws an exception. However, wouldn't that also block changes from the first trigger?
How do I do this?
Use a "daemon" role (a non-login role for this dedicated purpose) that owns the trigger function in combination with SECURITY DEFINER And grant the necessary privileges on t1 to it.
Details in these related questions:
Is there a way to disable updates/deletes but still allow triggers to perform them?
Allow insertion only from within a trigger
I have a dozen tables of whom I want to keep the history of the changes. For every one I created a second table with the ending _HISTO and added fields modtime, action, user.
At the moment before I insert, modify or delete a record in this tables I call ( from my delphi app ) a oracle procedure that copies the actual values to the histo table and then do the operation.
My procedure generates a dynamic sql via DBA_TAB_COLUMNS and then executes the generated ( insert into tablename_histo ( fields s ) select fields, sysdate, 'acition', userid from table_name
I was told that I can not call this procedure from a trigger because it has to select the table the trigger is triggered on. Is this true ? Is it possible to implement what I need ?
Assuming you want to maintain history using triggers (rather than any of the other methods of tracking history data in Oracle-- Workspace Manager, Total Recall, Streams, Fine_Grained Auditing etc.), you can use dynamic SQL in the trigger. But the dynamic SQL is subject to the same rules that static SQL is subject to. And even static SQL in a row-level trigger cannot in general query the table that the trigger is defined on without generating a mutating table exception.
Rather than calling dynamic SQL from your trigger, however, you can potentially write some dynamic SQL that generates the trigger in the first place using the same data dictionary tables. The triggers themselves would statically refer to :new.column_name and :old.column_name. Of course, you would have to either edit the trigger or re-run the procedure that dynamically creates the trigger when a new column gets added. Since you, presumably, need to add the column to both the main table and the history table, however, this generally isn't too big of a deal.
Oracle does not allow a trigger to execute a SELECT against the table on which the trigger is defined. If you try it you'll get the dreaded "mutating table" error (ORA-04091), and while there are ways to get around that error they add a lot of complexity for little value. If you really want to build a dynamic query every time your table is updated (IMO this is a bad idea from the standpoint of performance - I find that metadata queries are often slow, but YMMV) it should end up looking something like
strAction := CASE
WHEN INSERTING THEN 'INSERT'
WHEN UPDATING THEN 'UPDATE'
WHEN DELETING THEN 'DELETE'
END;
INSERT INTO TABLENAME_HISTO
(ACTIVITY_DATE, ACTION, MTC_USER,
old_field1, new_field1, old_field2, new_field2)
VALUES
(SYSDATE, strAction, USERID,
:OLD.field1, :NEW.field1, :OLD.field2, :NEW.field2)
Share and enjoy.