PostgreSQL command for dropping all views and functions owned by role? - postgresql

Is there a command in PostgreSQL for dropping all views and functions owned by a particular role? I don't understand how all the internal tables are connected together so that I can create such a command.

Drop views:
create function drop_views_by_owner(owner regrole, do_drop boolean default true)
returns setof text language plpgsql as $$
declare r text;
begin
for r in
select format('%s.%s', nspname, relname)
from pg_class c
join pg_namespace n on n.oid = relnamespace
where c.relowner = $1
and relkind = 'v'
loop
if do_drop then
execute format('drop view %s', r);
end if;
return next r;
end loop;
end $$;
Drop functions:
create function drop_functions_by_owner(owner regrole, do_drop boolean default true)
returns setof text language plpgsql as $$
declare r text;
begin
for r in
select format('%s.%s(%s)',
nspname, proname, pg_get_function_identity_arguments(p.oid))
from pg_proc p
join pg_namespace n on n.oid = pronamespace
where p.proowner = $1
loop
if do_drop then
execute format('drop function %s', r);
end if;
return next r;
end loop;
end $$;
If the second argument is false the function does not drop the objects:
select drop_views_by_owner('newuser'); -- list views and drop them
select drop_views_by_owner('newuser', false); -- only list views
select drop_functions_by_owner('newuser'); -- list functions and drop them
select drop_functions_by_owner('newuser', false); -- only list functions

If your user only owns views and functions, you can simply :
DROP OWNED BY your_role;
You must do it for each and every database in your cluster where your user owns an object.
You might find this documentation usefull: https://www.postgresql.org/docs/current/static/role-removal.html

Related

Triggers in Postgres: Access NEW fields by name at runtime

In Postgres, someone knows how to substitute the value of the variable in a NEW.variable in a trigger?
For instance, I have a variable with value order_code. I want to execute NEW.variable so that it's getting in fact NEW.order_code.
In detailed:
I have a function to obtain the primary key column of a table:
CREATE FUNCTION getPrimaryKey(_table_name VARCHAR(50))
RETURNS SETOF VARCHAR(50) AS $$
DECLARE
primary_key VARCHAR(50);
BEGIN
FOR primary_key IN SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = _table_name::regclass
AND i.indisprimary LOOP
RETURN NEXT primary_key;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Then I have a trigger to collect some info when an INSERT is done in a table. The procedure in the trigger is called from several triggers from different tables. That's why it's so generic and I have this need.
What I want is to obtain the primary key of the object inserted.
CREATE FUNCTION logAudit()
RETURNS trigger AS $$
DECLARE primary_key VARCHAR(50);
BEGIN
primary_key := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
INSERT INTO test VALUES (TG_TABLE_NAME);
INSERT INTO test VALUES (NEW.primary_key);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_in_client
AFTER INSERT ON tb_client
FOR EACH STATEMENT EXECUTE PROCEDURE logAudit();
The NEW.primary_key is what is causing me issues. I expect primary_key to be the column name of the source table where the insert happened. What I want in NEW.primary_key is to actually use the value in the variable.
Here is the example of anonymous pl/pgsql block which doing something what you want:
do $$
declare
v pg_database = (pg_database) from pg_database where datname = 'template1';
fname text = 'datname';
n text;
begin
n := to_jsonb(v)->>fname;
raise info '%', n;
end $$;
Output:
INFO: template1
It is working example. In your trigger function it could be something like
declare
pk_name text;
pk_value text;
begin
pk_name := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
pk_value := to_jsonb(NEW) ->> pk_name;
-- Do what you want with pk_value here
return null;
end $$;

Could you explain part of this function that begins with " if not exist...."?

CREATE OR REPLACE FUNCTION event_partition()
RETURNS trigger AS
$BODY$
DECLARE
_tbl text := to_char(NEW.the_date, 'YYYY-MM-DD');
BEGIN
***IF NOT EXISTS (
SELECT 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' -- your schema
AND c.relname = _tbl
AND c.relkind = 'r') THEN***
EXECUTE format('CREATE TABLE %I (CHECK (the_date >= %L AND
the_date < %L)) INHERITS (events)'
, _tbl
, to_char(NEW.the_date, 'YYYY-MM-DD')
, to_char(NEW.the_date + 1, 'YYYY-MM-DD')
);
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(_tbl) || ' VALUES ($1.*)'
USING NEW;
RETURN NULL;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION event_partition() SET search_path=public;
It's querying the database's internal table list (pg_class) to see if the table _tbl already exists in the public schema, and creating it if it doesn't.
You should be able to remove the check, and just use a CREATE TABLE IF NOT EXISTS... statement instead.

Wait for the sequence to get last_value

I have a query that gives all sequences together with the nextval:
SELECT c.oid::regclass, setval(c.oid, nextval(c.oid), false)
FROM pg_class c
WHERE c.relkind = 'S'
But it throws an error on the production database:
ERROR: cannot access temporary tables of other sessions
I've also created a function with last_value (to avoid setting the sequence value) like in this post Get max id of all sequences in PostgreSQL
That doesn't help.
Is there a way to wait for all sequences to get finished without locking all tables?
Thats my function
CREATE TYPE tp_sequencedetails AS (sequence_name text, last_value bigint);
CREATE OR REPLACE FUNCTION getsequenceswithdetails()
RETURNS SETOF tp_sequencedetails AS
$BODY$
DECLARE
returnrec tp_sequencedetails;
sequence_name text;
BEGIN
FOR sequence_name IN (SELECT c.oid::regclass FROM pg_class c WHERE c.relkind = 'S')
LOOP
FOR returnrec IN EXECUTE 'SELECT ''' || sequence_name || ''', last_value FROM ' || sequence_name
LOOP
RETURN NEXT returnrec;
END LOOP;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ERROR: cannot access temporary tables of other sessions
In another session you're creating a temporary sequence. You are now trying to get or to set a value for this sequence, but it's not visible in your current session. A temporary table and sequence is only visible for the session that creates this object.
Solution: Keep the temporary sequences out of your query.
SELECT c.oid::regclass, setval(c.oid, nextval(c.oid), false)
FROM pg_class c
JOIN pg_namespace ON pg_namespace.oid = relnamespace
WHERE c.relkind = 'S'
AND nspname NOT ILIKE 'pg_temp%';

Alter table add column default and execute the default for each row

I am making a function that adds a id column to a given table, creates a sequence and fills the new columns value. The thing is that the column is created but now I need to fill it with nextval() of the created sequence (1,2,3,4,5...). I don't know how to specify that in the add column sentence.
CREATE OR REPLACE FUNCTION create_id(tabla character varying)
RETURNS void AS
$BODY$
DECLARE
BEGIN
IF NOT EXISTS (SELECT information_schema.columns.column_name FROM information_schema.columns WHERE information_schema.columns.table_name=tabla AND information_schema.columns.column_name='id')
THEN
EXECUTE 'ALTER TABLE '|| tabla ||' ADD COLUMN id numeric(8,0)';
IF NOT EXISTS (SELECT relname FROM pg_class WHERE relname='seq_id_'||tabla)
THEN
EXECUTE 'CREATE SEQUENCE seq_id_'||tabla||' INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1';
EXECUTE 'GRANT ALL ON TABLE seq_id_'||tabla||' TO postgres';
EXECUTE 'ALTER TABLE ONLY '||tabla||' ALTER COLUMN id SET DEFAULT nextval(''seq_id_'||tabla||'''::regclass)';
END IF;
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
Your function suffers from a number of series problems. Use this instead:
CREATE OR REPLACE FUNCTION f_create_id(_tbl text)
RETURNS void AS
$func$
DECLARE
_seq text := _tbl || '_id_seq';
BEGIN
IF EXISTS (
SELECT 1 FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE n.nspname = current_schema() -- default to current schema
AND c.relname = _tbl
AND a.attname = 'id'
AND NOT a.attisdropped)
THEN
RAISE EXCEPTION 'Column already exists!'; RETURN;
END IF;
IF EXISTS (
SELECT 1 FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
WHERE n.nspname = current_schema() -- default to current schema
AND c.relname = _seq)
THEN
RAISE EXCEPTION 'Sequence already exists!'; RETURN;
END IF;
EXECUTE format('CREATE SEQUENCE %I.%I', current_schema(), _seq;
EXECUTE format($$ALTER TABLE %I.%I ADD COLUMN id numeric(8,0)
DEFAULT nextval('%I'::regclass)$$ -- one statement!
, current_schema(), _tbl, _seq);
END
$func$ LANGUAGE plpgsql;
Major points
If you set the column default in the same ALTER TABLE statement, values are inserted automatically. Be aware that this makes a big difference in performance for big tables, since every row has to be updated, while adding a NULL column only needs a tiny change to the system catalog.
You must define the schema to create objects in. If you want to default to the current schema, you still have to consider this in your queries to catalog (or information schema) tables. Table names are only unique in combination with the schema name.
I use the session information functions current_schema() to find out the current schema.
You must safeguard against SQL injection when using dynamic SQL with user input. Details:
Table name as a PostgreSQL function parameter
If the sequence already exists, do not use it! You might interfere wit existing objects.
Normally, you do not need EXECUTE GRANT ALL ON TABLE ... TO postgres. If postgres is a superuser (default) the role has all rights anyway. You might want to make postgres the owner. That would make a difference.
I am using the system catalog in both queries, while you use the information schema in one of them. I am generally not a fan of the information schema.Its bloated views are slow. The presented information adheres to a cross-database standard, but what's that good for when writing plpgsql functions, which are 100% not portable anyway?
Superior alternative
I would suggest not to use the column name id, which is an SQL anti-pattern. Use a proper descriptive name instead, like tablename || '_id'.
What's the point of using numeric(8,0)? If you don't want fractional digits, why not use integer? Simpler, smaller, faster.
Given that, you are much better off with a serial type, making everything much simpler:
CREATE OR REPLACE FUNCTION f_create_id(_tbl text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE n.nspname = current_schema() -- default to current schema
AND c.relname = _tbl
AND a.attname = _tbl || '_id' -- proper column name
AND NOT a.attisdropped)
THEN
RAISE EXCEPTION 'Column already exists!';
ELSE
EXECUTE format('ALTER TABLE %I.%I ADD COLUMN %I serial'
, current_schema(), _tbl, _tbl || '_id');
END IF;
END
$func$ LANGUAGE plpgsql;

How do I temporarily disable triggers in PostgreSQL?

I'm bulk loading data and can re-calculate all trigger modifications much more cheaply after the fact than on a row-by-row basis.
How can I temporarily disable all triggers in PostgreSQL?
Alternatively, if you are wanting to disable all triggers, not just those on the USER table, you can use:
SET session_replication_role = replica;
This disables triggers for the current session.
To re-enable for the same session:
SET session_replication_role = DEFAULT;
Source: http://koo.fi/blog/2013/01/08/disable-postgresql-triggers-temporarily/
PostgreSQL knows the ALTER TABLE tblname DISABLE TRIGGER USER command, which seems to do what I need. See ALTER TABLE.
For disable trigger
ALTER TABLE table_name DISABLE TRIGGER trigger_name
For enable trigger
ALTER TABLE table_name ENABLE TRIGGER trigger_name
SET session_replication_role = replica;
It doesn't work with PostgreSQL 9.4 on my Linux machine if i change a table through table editor in pgAdmin and works if i change table through ordinary query. Manual changes in pg_trigger table also don't work without server restart but dynamic query like on postgresql.nabble.com ENABLE / DISABLE ALL TRIGGERS IN DATABASE works. It could be useful when you need some tuning.
For example if you have tables in a particular namespace it could be:
create or replace function disable_triggers(a boolean, nsp character varying) returns void as
$$
declare
act character varying;
r record;
begin
if(a is true) then
act = 'disable';
else
act = 'enable';
end if;
for r in select c.relname from pg_namespace n
join pg_class c on c.relnamespace = n.oid and c.relhastriggers = true
where n.nspname = nsp
loop
execute format('alter table %I %s trigger all', r.relname, act);
end loop;
end;
$$
language plpgsql;
If you want to disable all triggers with certain trigger function it could be:
create or replace function disable_trigger_func(a boolean, f character varying) returns void as
$$
declare
act character varying;
r record;
begin
if(a is true) then
act = 'disable';
else
act = 'enable';
end if;
for r in select c.relname from pg_proc p
join pg_trigger t on t.tgfoid = p.oid
join pg_class c on c.oid = t.tgrelid
where p.proname = f
loop
execute format('alter table %I %s trigger all', r.relname, act);
end loop;
end;
$$
language plpgsql;
PostgreSQL documentation for system catalogs
There are another control options of trigger firing process:
ALTER TABLE ... ENABLE REPLICA TRIGGER ... - trigger will fire in replica mode only.
ALTER TABLE ... ENABLE ALWAYS TRIGGER ... - trigger will fire always (obviously)
You can also disable triggers in pgAdmin (III):
Find your table
Expand the +
Find your trigger in Triggers
Right-click, uncheck "Trigger Enabled?"
SET session_replication_role = replica;
also dosent work for me in Postgres 9.1.
i use the two function described by bartolo-otrit with some modification.
I modified the first function to make it work for me because the namespace or the schema must be present to identify the table correctly.
The new code is :
CREATE OR REPLACE FUNCTION disable_triggers(a boolean, nsp character varying)
RETURNS void AS
$BODY$
declare
act character varying;
r record;
begin
if(a is true) then
act = 'disable';
else
act = 'enable';
end if;
for r in select c.relname from pg_namespace n
join pg_class c on c.relnamespace = n.oid and c.relhastriggers = true
where n.nspname = nsp
loop
execute format('alter table %I.%I %s trigger all', nsp,r.relname, act);
end loop;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION disable_triggers(boolean, character varying)
OWNER TO postgres;
then i simply do a select query for every schema :
SELECT disable_triggers(true,'public');
SELECT disable_triggers(true,'Adempiere');
A really elegant way to handle this is to create a role that handles database population and set replication for that role:
ALTER ROLE role_name SET session_replication_role = 'replica';
That way you can use that role for populating data and not have to worry about disabling and renabling triggers etc.