not start trigger on view from pg_stat_activity - postgresql

In postgres not real create trigger on pg_stat_activity, becouse i create my view based on pg_stat_activity and create trigger.
DROP FUNCTION IF EXISTS get_sa() CASCADE;
DROP FUNCTION IF EXISTS f_call_count_conn();
DROP FUNCTION IF EXISTS f_update_count_conn();
CREATE OR REPLACE FUNCTION get_sa() RETURNS SETOF pg_stat_activity AS
$$ SELECT * FROM pg_catalog.pg_stat_activity; $$
LANGUAGE sql
VOLATILE
SECURITY DEFINER;
CREATE OR REPLACE VIEW pg_stat_activity_allusers AS SELECT * FROM get_sa();
GRANT SELECT ON pg_stat_activity_allusers TO public;
CREATE OR REPLACE FUNCTION f_call_count_conn()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF TG_OP = 'INSERT' THEN
COPY (SELECT time_change, count FROM count_conn) TO '/tmp/query.csv' (format csv, delimiter ';');
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
COPY (SELECT time_change, count FROM count_conn) TO '/tmp/query.csv' (format csv, delimiter ';');
RETURN OLD;
END IF;
-- PERFORM f_update_count_conn();
-- RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER t_check_activity_conn
INSTEAD OF INSERT OR DELETE ON pg_stat_activity_allusers
FOR EACH ROW
EXECUTE PROCEDURE f_call_count_conn();
CREATE FUNCTION f_update_count_conn()
RETURNS VOID
AS
$BODY$
BEGIN
insert into count_conn (time_change, count)
values (NOW(), (select count(*)
from pg_stat_activity_allusers));
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This code is not working, but when i replace my view (pg_stat_activity_allusers) on real table and change this table, my trigger work. Why? Thank you!

Your code worked for me on Postgres 9.5, except I manually called f_update_count_conn() to populate count_conn, because you're not (or no longer) calling it anywhere.
# select f_update_count_conn();
f_update_count_conn
---------------------
(1 row)
mw=# select * from count_conn;
time_change | count
----------------------------+-------
2017-02-03 17:22:34.846179 | 1
(1 row)
mw=# insert into pg_stat_activity_allusers(datid) values(123456::oid);
INSERT 0 1
mw=#
[1]+ Stopped '/Applications/Postgres.app/Contents/Versions/9.5/bin'/psql -p5432
$ cat /tmp/query.csv
2017-02-03 17:22:34.846179;1

Related

Save execute results into a table

Below is a simplified postgres stored procedure I am trying to run:
create or replace procedure my_schema.tst(suffix varchar)
as $$
begin
execute(' select *
into my_schema.MyTable_'||suffix||'
From my_schema.MyTable
');
end;
$$
language plpgsql;
When I attempt to run using something like:
call my_schema.tst('test');
I get this error Invalid operation: EXECUTE of SELECT ... INTO is not supported;
Is it possible to execute a dynamic query that creates a new table? I have seen examples that look like:
Execute('... some query ...') into Table;
but for my use case I need the resulting tablename to be passed as a variable.
In PostgreSQL you can use INSERT INTO tname SELECT...
create or replace procedure my_schema.tst(suffix varchar)
as $$
begin
execute ' INSERT INTO my_schema.MyTable_'||suffix||' SELECT *
FROM my_schema.MyTable
';
end;
$$
language plpgsql;
or Use CREATE TABLE tname AS SELECT..., :
create or replace procedure my_schema.tst(suffix varchar)
as $$
begin
execute ' CREATE TABLE my_schema.MyTable_'||suffix||' as SELECT *
FROM my_schema.MyTable
';
end;
$$
language plpgsql;

Update row in trigger without recursion

I have two tables:
CREATE TABLE first (
id text primary key,
updated_at timestamp,
data text
);
CREATE TABLE second (
id text REFERENCES first (id),
book_error text,
);
and I need to update updated_at field in first table always, when any of these tables has updated. I wrote this:
CREATE FUNCTION update_timestamp() RETURNS trigger AS $$
BEGIN
UPDATE first
SET updated_at = current_timestamp
WHERE id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE format('CREATE TRIGGER update_timestamp
BEFORE INSERT OR UPDATE ON %I
FOR EACH ROW EXECUTE PROCEDURE update_timestamp()',
t);
END LOOP;
END;
$$ LANGUAGE plpgsql;
But it's not working because update statement inside my trigger causes call of this trigger again before executing.
How can I do update inside trigger without firing it trigger again?
Per the documentation:
TG_TABLE_NAME
Data type name; the name of the table that caused the trigger invocation.
Use the variable in the trigger function:
CREATE OR REPLACE FUNCTION update_timestamp() RETURNS trigger AS $$
BEGIN
IF TG_TABLE_NAME = 'first' THEN
NEW.updated_at = current_timestamp;
ELSE
UPDATE first
SET updated_at = current_timestamp
WHERE id = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
From Postgresql documentation,
pg_trigger_depth() - current nesting level of PostgreSQL triggers (0
if not called, directly or indirectly, from inside a trigger)
You can use this inside your trigger function to check if it is called from inside trigger
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE format('CREATE TRIGGER update_timestamp
BEFORE INSERT OR UPDATE ON %I
FOR EACH ROW
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE update_timestamp()',
t);
END LOOP;
END;
$$ LANGUAGE plpgsql;
test=# Insert into first select 1,now(), 'test';
INSERT 0 1
test=# select * from first;
id | updated_at | data
----+----------------------------+------
1 | 2018-10-29 20:18:25.227281 | test
(1 row)
test=# Insert into second select 1, 'test_error';
INSERT 0 1
test=# select * from first;
id | updated_at | data
----+----------------------------+------
1 | 2018-10-29 20:19:07.456737 | test

How To Restrict Delete using PL/pgSQL trigger?

If the client user is trying to delete more than 5 records from a Table i want to restrict that using a trigger. I have a basic idea to do that but i don't know how to implement the Idea. I appreciate any HELP.
Basic Idea : In Trigger IF TG_OP = Delete and the count of records to be deleted are more than 5 then Restrict.
CREATE TRIGGER adjust_count_trigger BEFORE DELETE ON schemaname.tablename
FOR EACH ROW EXECUTE PROCEDURE public.adjust_count();
CREATE OR REPLACE FUNCTION adjust_count()
RETURNS TRIGGER AS
$$
DECLARE
num_rows int;
num_rows1 int;
BEGIN
IF TG_OP = 'DELETE' THEN
EXECUTE 'select count(*) from '||TG_TABLE_SCHEMA ||'.'||TG_RELNAME ||' where oid = old.oid ' into num_rows ;
IF num_rows > 5 Then
RAISE NOTICE 'Cannot Delete More than 5 Records , % ', num_rows ;
END IF ;
END IF ;
RETURN OLD;
END;
$$
LANGUAGE 'plpgsql';
In earlier versions of Postgres you can simulate a transition table introduced in Postgres 10. You need two triggers.
create trigger before_delete
before delete on my_table
for each row execute procedure before_delete();
create trigger after_delete
after delete on my_table
for each statement execute procedure after_delete();
In the first trigger create a temp table and insert a row into it:
create or replace function before_delete()
returns trigger language plpgsql as $$
begin
create temp table if not exists deleted_rows_of_my_table (dummy int);
insert into deleted_rows_of_my_table values (1);
return old;
end $$;
In the other trigger count rows of the temp table and drop it:
create or replace function after_delete()
returns trigger language plpgsql as $$
declare
num_rows bigint;
begin
select count(*) from deleted_rows_of_my_table into num_rows;
drop table deleted_rows_of_my_table;
if num_rows > 5 then
raise exception 'Cannot Delete More than 5 Records , % ', num_rows;
end if;
return null;
end $$;
The above solution may seem a bit hacky but it is safe if only the temp table does not exist before delete (do not use the same name of the temp table for multiple tables).
Test it in rextester.
You can easily do that with the new transition relation feature from PostgreSQL v10:
CREATE OR REPLACE FUNCTION forbid_more_than() RETURNS trigger
LANGUAGE plpgsql AS
$$DECLARE
n bigint := TG_ARGV[0];
BEGIN
IF (SELECT count(*) FROM deleted_rows) <= n IS NOT TRUE
THEN
RAISE EXCEPTION 'More than % rows deleted', n;
END IF;
RETURN OLD;
END;$$;
CREATE TRIGGER forbid_more_than_5
AFTER DELETE ON mytable
REFERENCING OLD TABLE AS deleted_rows
FOR EACH STATEMENT
EXECUTE PROCEDURE forbid_more_than(5);

How to know the date of modification of an SP?

Is it possible to know the date of modification and / or creation of an SP in PostgreSQL 9.4?
I need to identify them to upload them next Deploy.-
PostgreSQL has not this functionality. You can create own table and update it from event triggers.
create table updates(proc regprocedure primary key, t timestamp);
create or replace function event_trigger_for_ddl_command_end()
returns event_trigger as $$
declare obj record;
begin
for obj in select * from pg_event_trigger_ddl_commands()
loop
if obj.classid = 'pg_proc'::regclass then
insert into updates values(obj.objid, current_timestamp)
on conflict (proc) do update set t = current_timestamp
where updates.proc = excluded.proc;
end if;
end loop;
end;
$$ language plpgsql;
create event trigger trigger_for_ddl_command_end
on ddl_command_end
execute procedure event_trigger_for_ddl_command_end();
create or replace function fx(a int) returns int as $$ select 1 $$ language sql;
postgres=# select * from updates ;
+-------------+----------------------------+
| proc | t |
+-------------+----------------------------+
| fx(integer) | 2017-11-22 14:21:11.367036 |
+-------------+----------------------------+
(1 row)
-- alternative code without INSERT ON CONFLICT
create or replace function event_trigger_for_ddl_command_end()
returns event_trigger as $$
declare obj record;
begin
for obj in select * from pg_event_trigger_ddl_commands()
loop
if obj.classid = 'pg_proc'::regclass then
begin
update updates set t = current_timestamp
where proc = obj.objid;
if not found then
begin
insert into updates values(obj.objid, current_timestamp);
exception when unique_violation then
update updates set t = current_timestamp
where proc = obj.objid;
end;
end if;
end if;
end loop;
end;
$$ language plpgsql;

How to log delete queries on Postgresql?

I created a function which writes information about table deletions.
And another function which simply adds a trigger call after delete.
But I would like to store the whole row as string into my table.
According to Postgresql Documentation it should work by adding "OLD.*" into a text based column. But it fails telling me that I try to put too many columns into this table.
OLD is from type RECORD. And i want to have it in my text field like "value1,value2,value3" or it could be "colname:value,colname2:value". I dont care, I just want to see the row which has been deleted.
Another approach can be to log all delete queries from pg_stat_activity. But I don't know how to do that. Simply accessing pg_stat_activity every second would cause too much traffic I guess.
My table is simple:
create table delete_history (date timestamp, tablename varchar(100), data text);
This is my function:
CREATE or REPLACE FUNCTION ondelete() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO delete_history VALUES (CURRENT_TIMESTAMP, TG_TABLE_NAME, OLD.*);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
This is my trigger:
CREATE OR REPLACE FUNCTION history_create_triggers() RETURNS void
AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type='BASE TABLE' LOOP
EXECUTE 'CREATE TRIGGER log_history AFTER DELETE ON public.' || r.table_name || ' FOR EACH ROW EXECUTE PROCEDURE ondelete();';
END LOOP;
END;
$$ LANGUAGE plpgsql;
You can convert type record into text:
CREATE or REPLACE FUNCTION ondelete() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO delete_history VALUES (CURRENT_TIMESTAMP, TG_TABLE_NAME, OLD::text);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
sql fiddle demo
another approach could be converting your row into JSON with row_to_json function (if you have version 9.2):
CREATE or REPLACE FUNCTION ondelete() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO delete_history VALUES (CURRENT_TIMESTAMP, TG_TABLE_NAME, row_to_json(OLD));
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
sql fiddle demo
Another approach can be convert your data to hstore
CREATE or REPLACE FUNCTION ondelete() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO delete_history VALUES (CURRENT_TIMESTAMP, TG_TABLE_NAME, hstore(OLD));
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
I can't test it now - sqlfiddle is not allowing to use hstore.