postgres: Using trigger function to remove user from a group - postgresql

I have a trigger function that gets a person's username from their first and last name, and then drops the user with that username from a group with privileges, essentially locking that person out of the database.
returns "trigger" AS'
DECLARE
uname varchar(255);
begin
uname = (SELECT CONCAT(LOWER(first_name), LOWER(last_name)) FROM members WHERE library_card_id = (SELECT members_library_card_id FROM borrowed_books WHERE fine_id IS NOT NULL));
alter group members drop user uname;
end;
' LANGUAGE 'plpgsql';
CREATE TRIGGER block_member_if_fine
AFTER UPDATE on borrowed_books
FOR EACH ROW
EXECUTE PROCEDURE f_block_member_if_fine();
However, when I use the trigger function, I get an error role "uname" does not exist
Is there a way to use uname as a variable? Or maybe there's a different way of dropping a user with a trigger function?

Try this :
CREATE OR REPLACE PROCEDURE f_block_member_if_fine()
RETURNS "trigger" LANGUAGE 'plpgsql' AS
$$
DECLARE
uname varchar(255);
BEGIN
SELECT CONCAT(LOWER(m.first_name), LOWER(m.last_name))
INTO uname
FROM members AS m
WHERE m.library_card_id = NEW.members_library_card_id
AND NEW.fine_id IS NOT NULL;
IF FOUND
THEN
EXECUTE 'ALTER GROUP members DROP USER '|| quote_nullable(uname) ;
END IF ;
RETURN NEW ;
END ;
$$
CREATE TRIGGER block_member_if_fine
AFTER UPDATE on borrowed_books
FOR EACH ROW
EXECUTE PROCEDURE f_block_member_if_fine();

Related

create a procedure based on column data change in postgrersql

Fire trigger on update of columnA or ColumnB or ColumnC
Set up the scene
begin;
create temp table account_details(email text primary key, username text, password text);
insert into account_details(email, username, password) values('a.com','b','c'),('b.com','d','e');
commit;
create function:
CREATE OR REPLACE FUNCTION notify_insert_account_details()
RETURNS trigger
LANGUAGE plpgsql AS
$$
BEGIN
RAISE NOTICE 'hello world';
END
$$;
create trigger:
CREATE TRIGGER trigger_update_account_details
AFTER UPDATE ON account_details
FOR EACH ROW
WHEN (OLD.email IS DISTINCT FROM NEW.email
OR OLD.username IS DISTINCT FROM NEW.username
OR OLD.password IS DISTINCT FROM NEW.password)
EXECUTE PROCEDURE notify_insert_account_details();
Trying to update:
update account_details set username = 'username1' where email = 'a.com';
The result: notify_insert_account_details() fired. but didn't execute the update clause.
NOTICE: hello world
ERROR: control reached end of trigger procedure without RETURN
CONTEXT: PL/pgSQL function notify_insert_account_details()
My question: how sophisticated this procedure/function notify_insert_account_details() Can become, let's say linked within 3 table. Can anyone showcase an example/demo?

postresql trigger to join lastname and firstname with pgadmin

I've tried this trigger using PgAdmin4; (this GUI i think have some bugs)
my trigger is:
CREATE TRIGGER name_update BEFORE UPDATE ON customer
FOR EACH ROW
BEGIN
SET NEW.name = CONCAT_WS(', ', NEW.lastname, NEW.firstname);
END;
CREATE TRIGGER name_insert BEFORE INSERT ON customer
FOR EACH ROW
BEGIN
SET NEW.name = CONCAT_WS(', ', NEW.lastname, NEW.firstname);
END;
but i'm getting this error:
i've tried remove begin and end, but nothings happens, maybe must I to try on the shell, or in txt file? what is the best way to do it outside pgadmin and manage the carriage returns, in mysql i've used to use delimeter on the shell.
You need create procedure and call her in trigger. Example:
create or replace function trigger_function()
returns trigger as
$$
begin
NEW.name = CONCAT_WS(', ', NEW.lastname, NEW.firstname);
return NEW;
end;
$$
language plpgsql;
CREATE TRIGGER name_update
BEFORE UPDATE ON customer
FOR EACH ROW
EXECUTE PROCEDURE trigger_function();

how to create event trigger for create table or select into

i want create event trigger for create table or select into,
eg:
when create table xxxx must table name bigen with 'temp'
my code
CREATE OR REPLACE FUNCTION create_table_func()
RETURNS event_trigger
AS
$$
DECLARE
V_TABLE name := TG_TABLE_NAME;
BEGIN
if V_TABLE !~ '^temp'
then
RAISE EXCEPTION 'must bigen with temp';
end if;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE EVENT TRIGGER create_table_1 ON ddl_command_start
WHEN TAG IN ('SELECT INTO')
EXECUTE PROCEDURE create_table_func();
but when execute
select * into test11 from test_bak
[Err] ERROR: column "tg_table_name" does not exist
this is my code ,it's meet my needs
code:
CREATE OR REPLACE FUNCTION trg_create_table_func()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag in ('SELECT INTO','CREATE TABLE','CREATE TABLE AS')
LOOP
if obj.object_identity !~ 'public.temp_'
THEN
raise EXCEPTION 'The table name must begin with temp_';
end if;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_create_table ON ddl_command_end
WHEN TAG IN ('SELECT INTO','CREATE TABLE','CREATE TABLE AS')
EXECUTE PROCEDURE trg_create_table_func();
out recods
[Err] ERROR: The table name must begin with temp_
CONTEXT: PL/pgSQL function trg_create_table_func() line 10 at RAISE
it's cool ~
The special variable TG_TABLE_NAME is only supported in normal triggers, not in event triggers (there is not always an associated table!).
The documentation has a list of functions that can return context information in an event trigger.
You could use pg_event_trigger_ddl_commands() to get the information you need, but that only works in ddl_command_end event triggers. That should work for you; I don't see a reason why the trigger should not run at the end of the statement.

How to select from variable that is a table name n Postgre >=9.2

i have a variable that is a name of a table. How can i select or update from this using variable in query , for example:
create or replace function pg_temp.testtst ()
returns varchar(255) as
$$
declare
r record; t_name name;
begin
for r in SELECT tablename FROM pg_tables WHERE schemaname = 'public' limit 100 loop
t_name = r.tablename;
update t_name set id = 10 where id = 15;
end loop;
return seq_name;
end;
$$
language plpgsql;
it shows
ERROR: relation "t_name" does not exist
Correct reply is a comment from Anton Kovalenko
You cannot use variable as table or column name in embedded SQL ever.
UPDATE dynamic_table_name SET ....
PostgreSQL uses a prepared and saved plans for embedded SQL, and references to a target objects (tables) are deep and hard encoded in plans - a some characteristics has significant impact on plans - for one table can be used index, for other not. Query planning is relatively slow, so PostgreSQL doesn't try it transparently (without few exceptions).
You should to use a dynamic SQL - a one purpose is using for similar situations. You generate a new SQL string always and plans are not saved
DO $$
DECLARE r record;
BEGIN
FOR r IN SELECT table_name
FROM information_schema.tables
WHERE table_catalog = 'public'
LOOP
EXECUTE format('UPDATE %I SET id = 10 WHERE id = 15', r.table_name);
END LOOP;
END $$;
Attention: Dynamic SQL is unsafe (there is a SQL injection risks) without parameter sanitization. I used a function "format" for it. Other way is using "quote_ident" function.
EXECUTE 'UPDATE ' || quote_ident(r.table_name) || 'SET ...

postgresql copy with schema support

I'm trying to load some data from CSV using the postgresql COPY command. The trick is that I'd like to implement multi-tenancy on a userid (which is contained in the CSV). Is there an easy way to tell the postgres copy command to filter based on this userid when loading the csv?
i.e. all rows with userid=x go to schema=x, rows with userid=y go to schema=y.
There is not a way of doing this with just the COPY command, but you could copy all your data into a master table, and then put together a simple PL/PGSQL function that does this for you. Something like this -
CREATE OR REPLACE FUNCTION public.spike()
RETURNS void AS
$BODY$
DECLARE
user_id integer;
destination_schema text;
BEGIN
FOR user_id IN SELECT userid FROM master_table GROUP BY userid LOOP
CASE user_id
WHEN 1 THEN
destination_schema := 'foo';
WHEN 2 THEN
destination_schema := 'bar';
ELSE
destination_schema := 'baz';
END CASE;
EXECUTE 'INSERT INTO '|| destination_schema ||'.my_table SELECT * FROM master_table WHERE userid=$1' USING user_id;
-- EXECUTE 'DELETE FROM master_table WHERE userid=$1' USING user_id;
END LOOP;
TRUNCATE TABLE master_table;
RETURN;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE
COST 100;
This gets all unique user_ids from the master_table, uses a CASE statement to determine the destination schema, and then executes an INSERT SELECT to move rows, and finally deletes the moved rows.