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