Записки жертвы высоких технологий

    Здравствуйте! Мой блог посвящен в большей своей части моей профессиональной деятельности на поприще использования технологий ORACLE для разработки баз данных OLTP и OLAP хранилищ данных. В заметках я периодически размещаю разнообразные SQL, PL/SQL и Java скрипты написанные мной и не только мной, ссылки на интересные источники в сети либо другую полезную информацию, которая каким либо образом касается моей работы с замечательными продуктами компании ORACLE.
    Вы можете связаться со мной по контактному емейлу, если у вас есть какие-либо вопросы, связанные с разработкой баз данных на основе продуктов ORACLE, буду рад помочь вам, если это будет в моих силах.

13 сент. 2013 г.

Получение имени текущего метода с помощью директив $$PLSQL_*

Написал очередную уродливую, но работающую, реализацию алгоритма получения имени текущего метода. Это нужно, например, при выводе сообщений об ошибках системы, типа 


"Ошибка в строке #17 метода PKG_PERSONS.CreateNewPerson(..): Не указана фамилия физического лица"

Очередная она потому что какой дурак ее не пишет разработчик эту проблему не решает. Уродливая она потому, что в PL/SQL просто обязана быть системная переменная, типа $$PLSQL_METHOD_NAME, содержащая имя текущего PL/SQL метода/процедуры/функции, а все прочие методы получения - очевидно уродливы. Кстати, вроде бы в 12-ой версии ORACLE эту проблему кое-как решили, добавив пакет UTL_CALL_STACK, о чем счастливо отрапортовали Том Кайт и документация ORACLE. А, что делать нам, несчастным и отсталым?!...

Короче, выглядит все это вот так:


   function get_fname(p_unit in varchar2, p_line in numberreturn varchar2
    is
        l_name varchar2(30);
    begin
        select distinct
            max(tt.proc_name) keep (dense_rank last order by tt.line) over() text
        into l_name
        from
        (
        select
            t.proc_name,
            t.line
        from
        (
            select
                regexp_substr(text, '(^|;\s*|\s+is\s+|)(procedure|function)\s+(return)*(\w+)\s*(\(|is)*',1,1,'i',4) proc_name,
                line
            from
                user_source s
            where
                s.type in ('TYPE BODY''PACKAGE BODY''TRIGGER''PROCEDURE''FUNCTION'and
                s.name = upper(p_unit) and
                line <= p_line
        ) t
        where t.proc_name is not null and
        upper(t.proc_name) in (select procedure_name from user_procedures p where p.object_name = upper(p_unit))
        order by line
        ) tt;
        return '"' || UPPER(sys_context('USERENV''CURRENT_SCHEMA')) || '"' || '.' || '"' || l_name || '"';
    end;


Пакет, в котором размещена функция, создан с указанием AUTHID CURRENT_USER. 

Вызов метода в коде:

...
raise_application_error(-20001, 'Ошибка в строке #'||$$PLSQL_LINE||' метода ' || PKG_DEBUG_TOOLS.get_fname($$PLSQL_UNIT, $$PLSQL_LINE)  || '(..): Не указана фамилия физического лица');'
...


P.S. Функцию поиска имени метода в USER_SOURCE, конечно, можно обмануть, вставив, например, между объявлением метода пакета и вызовом функции литерал, в котором будет описан вызов какого-либо другого метода пакета, только вот зачем...

P.P.S. 02.10.2013 Переписал метод, чтобы было более красиво и менее ресурсоемко, заодно исправил ошибку в разборе имени функции

P.P,P.S. 10.03.2023 Понаписал за прошедшее время разных определялок текущих методов и пакетов. Вот прилагаю, если у кого-то еще остались вопросы. Выбираем на свой вкус. Я сейчас предпочитаю п2 для определения текущего метода и п3 для определения текущего пакета (oracle 19c).

  1. ThisPackageAndMethod varchar2(128) := regexp_substr(regexp_substr(dbms_utility.format_call_stack, 'package\s+body\s+([^[:space:]]+)',1,1,'i',1), '[^.]+[.][^.]+$');
  2. ThisMethod           varchar2(128) := utl_call_stack.concatenate_subprogram(utl_call_stack.subprogram(1));
  3. ThisPackage          varchar2(128) := $$PLSQL_UNIT;
  4. ThisPackage          varchar2(128) := nvl(regexp_substr(dbms_utility.format_call_stack, 'package\sbody\s(((\w)+[.])?(\w+))',1,1,'i',4), 'task_'||to_char(sysdate, 'YYYYMMDDHH24MISS'));