Библиотека полезных функций для 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