Библиотека полезных функций для PostgreSQL
Автор компиляции: Иван Золотухин
Данная статья содержит выборку PL/PgSQL и SQL функций, которые могут быть полезны разработчикам и администраторам PostgreSQL, а также всем желающим познакомиться с основными процедурными языками в СУБД PostgreSQL на работающих примерах. Большая часть этих функций обсуждалась в англоязычных рассылках и иных каналах общения членов сообщества PostgreSQL, некоторые функции написаны мною. Где это возможно, у функций приведены авторы и небольшие комментарии. Сам лист функций время от времени пополняется, так что следите за изменениями, если примеры представляют для вас интерес.
Генерация случайного пароля
Вариант 1
-- Author: Magnus Hagander
CREATE FUNCTION generate_random_password() RETURNS text
AS $$
DECLARE
j int4;
result text;
allowed text;
allowed_len int4;
BEGIN
allowed := '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ%@';
allowed_len := length(allowed);
result := '';
WHILE length(result) < 16 LOOP
j := int4(random() * allowed_len);
result := result || substr(allowed, j+1, 1);
END LOOP;
RETURN result;
END;
$$
LANGUAGE plpgsql;
Вариант 2
-- Author: Jeff Ross
CREATE OR REPLACE FUNCTION gen_password() RETURNS text AS $$
DECLARE
password text;
chars text;
BEGIN
password := '';
chars :=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
FOR i IN 1..8 LOOP
password := password || SUBSTRING(chars,
ceil(random()*LENGTH(chars))::integer, 1);
END LOOP;
return password;
END;
$$
LANGUAGE plpgsql;
Работа с интервалами: сдвиг на N месяцев
Простая функция на языке SQL для добавления или вычитания из даты заданного количества месяцев.
-- Author: Martijn van Oosterhout CREATE OR REPLACE FUNCTION GOMONTH(DATE, INTEGER, OUT DATE) IMMUTABLE AS $_$ SELECT ($1 + ($2 * '1 MONTH'::INTERVAL))::DATE; $_$ LANGUAGE SQL
Найти всех пользователей данной роли
Рекурсивная функция поиска всех ролей, принадлежащих данной роли. То есть, если роль ID1 имеет членов ID2 и ID3, а в роль ID2 входят роли ID4 и ID5, то функция вернет список всех этих ролей.
-- Author: David Fetter
CREATE OR REPLACE FUNCTION get_roles_under(OID)
RETURNS SETOF OID
LANGUAGE sql
AS $$
SELECT
$1
UNION
SELECT
member
FROM
pg_catalog.pg_auth_members
WHERE
roleid = $1
UNION
SELECT
get_roles_under(roleid) AS "roleid"
FROM
pg_catalog.pg_auth_members
WHERE
roleid IN (
SELECT
member
FROM
pg_catalog.pg_auth_members
WHERE
roleid = $1
)
$$;
Kill бакенда и получение имени текущего пользователя
Довольно глупый, но тем не менее reference-пример прекращения работы бакенда и получения имени пользователя, вызывающего функцию:
CREATE FUNCTION kill_process(integer) RETURNS boolean
AS 'SELECT pg_cancel_backend(procpid)
FROM (SELECT procpid FROM pg_stat_activity WHERE procpid=$1 and usename=session_user)
AS kill;'
LANGUAGE SQL SECURITY DEFINER;
Начало и конец недели по ее номеру в году
Работа с функциями, связанными со временем:
-- Author: Andreas Kretschmer
create or replace function get_week(IN jahr int, IN kw int) returns text as $$
declare
datum date;
ret text;
begin
datum = (jahr || '-01-01')::date;
loop
exit when extract(dow from datum) = 4;
datum = datum + '1day'::interval;
end loop;
ret = to_char(datum+(7*(kw-1)-3||'days')::interval,'dd-mm-yyyy') || ' - ' || to_char(datum+(3+7*(kw-1)||'days')::interval,'dd-mm-yyyy');
return ret;
end;
$$ language plpgsql immutable strict;
-- Example:
test=# select get_week(2007,2);
get_week
-------------------------
08-01-2007 - 14-01-2007
Манипуляции с хозяином/пермиссиями таблиц и функций в схеме
Таблицы
Функция для изменения прав доступа к таблицам в заданной схеме по заданной LIKE-маске. Администраторам должно быть удобно на основании этого прототипа также сделать функции revoke_on_tables() и change_owner_of_tables() -- они будут использовать абсолютно такой же цикл, только динамический SQL внутри него будет несколько иной.
-- Author: Ivan Zolotukhin
-- License: BSD
CREATE OR REPLACE FUNCTION grant_on_tables(role_name text, permission text, mask text, schema_name text)
RETURNS integer LANGUAGE plpgsql
AS $$
-- Function that grants given permissions to given role on tables with given LIKE mask within given schema
-- Example:
-- SELECT grant_on_tables('role_developer','SELECT, INSERT, UPDATE, DELETE, RULE, REFERENCE, TRIGGER','%','public');
-- will grant all the maximum permissions on all tables within public schema to role_developer role
DECLARE
obj record;
num integer;
BEGIN
num := 0;
FOR obj IN
SELECT
relname
FROM
pg_class c
JOIN pg_namespace ns ON (c.relnamespace = ns.oid)
WHERE
relkind in ('r','v','S')
AND nspname = schema_name
AND relname LIKE mask
ORDER BY
relname
LOOP
EXECUTE 'GRANT ' || permission || ' ON ' || obj.relname || ' TO ' || role_name;
RAISE NOTICE '%', 'Done: GRANT ' || permission || ' ON ' || obj.relname || ' TO ' || role_name;
num := num + 1;
END LOOP;
RETURN num;
END;
$$;
Функции
Эта функция является вспомогательной и как правило ее не нужно вызывать руками. Она нужна для получения необходимой информации об аргументах функций, так как в PostgreSQL для манипуляций с хозяином или пермиссиями функции требуется указывать fully-qualified имя со списком аргументов.
-- Author: Ivan Zolotukhin -- License: BSD CREATE OR REPLACE FUNCTION pg_get_function_args( IN funcname character varying, IN funcargs oidvector, IN schema character varying, OUT pos integer, OUT direction character, OUT argname character varying, OUT datatype character varying) RETURNS SETOF RECORD AS $$ -- For a function name and schema, this procedure selects for each argument the following data: -- - position in the argument list (0 for the return value) -- - direction 'i', 'o', or 'b' -- - name (NULL if not defined) -- - data type DECLARE rettype character varying; argtypes oidvector; allargtypes oid[]; argmodes "char"[]; argnames text[]; mini integer; maxi integer; BEGIN /* get object ID of function */ SELECT INTO rettype, argtypes, allargtypes, argmodes, argnames CASE WHEN pg_proc.proretset THEN 'setof ' || pg_catalog.format_type(pg_proc.prorettype, NULL) ELSE pg_catalog.format_type(pg_proc.prorettype, NULL) END, pg_proc.proargtypes, pg_proc.proallargtypes, pg_proc.proargmodes, pg_proc.proargnames FROM pg_catalog.pg_proc JOIN pg_catalog.pg_namespace ON (pg_proc.pronamespace = pg_namespace.oid) WHERE pg_proc.proname = funcname AND pg_proc.proargtypes = funcargs AND pg_namespace.nspname = schema AND pg_catalog.pg_function_is_visible(pg_proc.oid); /* bail out if not found */ IF NOT FOUND THEN RETURN; END IF; /* return a row for the return value */ pos = 0; direction = 'o'::char; argname = 'RETURN VALUE'; datatype = rettype; RETURN NEXT; /* unfortunately allargtypes is NULL if there are no OUT parameters */ IF allargtypes IS NULL THEN mini = array_lower(argtypes, 1); maxi = array_upper(argtypes, 1); ELSE mini = array_lower(allargtypes, 1); maxi = array_upper(allargtypes, 1); END IF; IF maxi < mini THEN RETURN; END IF; /* loop all the arguments */ FOR i IN mini .. maxi LOOP pos = i - mini + 1; IF argnames IS NULL THEN argname = NULL; ELSE argname = argnames[i]; END IF; IF allargtypes IS NULL THEN direction = 'i'::char; datatype = pg_catalog.format_type(argtypes[i], NULL); ELSE direction = argmodes[i]; datatype = pg_catalog.format_type(allargtypes[i], NULL); END IF; RETURN NEXT; END LOOP; RETURN; END;$$ LANGUAGE plpgsql STABLE STRICT SECURITY INVOKER;
А этот пример представляет непосредственный интерес. Функция выдает необходимые пермиссии всем функциям с названиями, удовлетворяющим LIKE-маске в заданной схеме:
-- Author: Ivan Zolotukhin
-- License: BSD
CREATE OR REPLACE FUNCTION grant_on_functions(role_name text, permission text, mask text, schema_name text)
RETURNS integer LANGUAGE plpgsql
AS $$
-- Function that grants given permissions to given role on functions with given LIKE mask within given schema
-- Example:
-- SELECT grant_on_functions('role_developer','EXECUTE','%','public');
-- will grant the execute permission on all functions within public schema to role_developer role
DECLARE
func record;
func_argument record;
argument_list varchar;
num integer;
BEGIN
num := 0;
/* select all function's names in given schema */
FOR func IN
SELECT
proname, proargtypes
FROM
pg_proc AS pro
JOIN pg_namespace AS ns ON pro.pronamespace = ns.oid
WHERE
ns.nspname = schema_name
AND proname LIKE mask
AND pg_catalog.pg_function_is_visible(pro.oid)
ORDER BY
proname
LOOP
argument_list := '(';
-- get input arguments of the function we're granting permissions on
FOR func_argument IN
SELECT datatype FROM pg_get_function_args(func.proname::varchar, func.proargtypes::oidvector, schema_name) WHERE direction = 'i'
LOOP
argument_list := argument_list || func_argument.datatype || ', ';
END LOOP;
SELECT INTO argument_list rtrim(argument_list, ', ');
argument_list := argument_list || ')';
-- grant permissions when we know complete function designation like
-- my_best_function(varchar, text, integer, float)
EXECUTE 'GRANT ' || permission || ' ON FUNCTION ' || schema_name || '.' || func.proname || argument_list || ' TO ' || role_name;
RAISE NOTICE '%', 'Done: GRANT ' || permission || ' ON FUNCTION ' || schema_name || '.' || func.proname || argument_list || ' TO ' || role_name;
num := num + 1;
END LOOP;
RETURN num;
END;
$$;
Как и в случае с таблицами, администратору может быть удобно использовать функцию change_owner_of_functions(), которая практически не будет отличаться от приведенного выше примера.
Any feedback is welcome at iz at sai dot msu dot ru
Jun 2007