Привет! Меня зовут Алексей Маряхин, я разработчик на Oracle. В этой статье продолжим знакомиться с темой отладки PL/SQL-кода.
В предыдущей статье мы изучили возможности отладки в PL/SQL Developer. В этой предлагаю рассмотреть ещё один инструмент — SQL Developer (версия 21.2.0.187 Build 187.1842). Также обозначим плюсы и минусы этих инструментов в сравнении.
Как оказалось, информации на русском языке на эту тему не так много, а документация по SQL Developer не отвечает на многие вопросы. В статье постараюсь осветить основные моменты касательно использования SQL Developer для отладки. Если тема для вас актуальна, велком!
Возможности и ограничения отладки
Отладка с инструментом SQL Developer позволяет выполнить отладку разных типов: пошаговую; с точками прерывания на строках кода и для исключений. Можно просмотреть значения переменных (включая сложные типы), установить значения переменных (кроме сложных типов). Это, в общем, стандартный набор функций отладки.
Дополнительно SQL Developer позволяет отлаживать сессии на удалённом хосте. Это удобно в случаях, когда дефект стабильно воспроизводится на каком-то стенде — например, в тестовом контуре либо на продуктовой базе, если туда есть доступ, — но у разработчика не получается или нет возможности воспроизвести кейс на своём стенде. Тогда можно подключиться к удалённому отладчику и детально посмотреть, что происходит в «чужой» сессии.
Также есть и несколько ограничений:
Отладка кода с определёнными конструкциями. Отладка некоторых объектов не работает: у вас не получится зайти в объект, даже если есть все права и отладочная информация включена. Такое ограничение связано это с наличием в коде определённых конструкций — использование пользовательских типов, см. SQL Developer doesn't step into code. В таком случае можно сделать копию пакета, удалив мешающий код и оставив только те процедуры или функции, которые нужно отладить.
Выполнение запросов в сессии отладки. В SQL Developer нельзя выполнять запросы в сессии отладки. Функция очень полезная, в PL/SQL Developer, например, она присутствует. Надеюсь, в будущем она появится и здесь.
Использование bind-переменных. У инструмента нет возможности использовать bind-переменные в скриптах отладки. Поэтому, чтобы передать, например, в скрипт LOB-объект, придётся вручную дописывать код для заполнения входных данных.
Права для отладки
В предыдущей статье уже говорили про настройку прав для отладки, приведём тут краткую выжимку.
-- Права на отладку объектов своей схемы:
GRANT DEBUG CONNECT SESSION TO <user>;
-- Права на отладку объекта чужой схемы (под пользователем схемы-владельца объекта):
GRANT DEBUG ON <object_name> TO <user>;
-- Права на отладку любого объекта:
GRANT DEBUG ANY PROCEDURE TO <user>;
Если для отладки хотим использовать протокол Java Debug Wire Protocol (в SQL Developer он используется по умолчанию), нужно выдать права ACL (подробнее см. Testing and Debugging Procedures with SQL Developer):
-- Под SYS:
BEGIN
DBMS_NETWORK_ACL_ADMIN.append_host_ace(
-- * = для любых адресов. Можно задать разрешение только для
-- определённого ip-адреса или имени хоста, или задать в виде маски
host => '*',
ace => xs$ace_type( privilege_list => xs$name_list('jdwp')
, principal_name => 'TEST_DEBUG_USR'
, principal_type => xs_acl.PTYPE_DB));
END;
Дополнительно:
Сброс ACL
-- Под SYS:
BEGIN
DBMS_NETWORK_ACL_ADMIN.remove_host_ace(
host => '*',
ace => xs$ace_type( privilege_list => xs$name_list('jdwp')
, principal_name => 'TEST_DEBUG_USR'
, principal_type => xs_acl.PTYPE_DB));
END;
Просмотр списка ACL
SELECT dnap.principal, dna.host, dna.lower_port
, dna.upper_port, dna.acl, dnap.privilege
FROM dba_network_acls dna
JOIN dba_network_acl_privileges dnap ON dnap.acl = dna.acl
AND dnap.aclid = dna.aclid;
Настройки отладки в SQL Developer
Чтобы отладка работала корректно, необходимо выполнить ряд настроек.
Выбираем протокол отладки. Есть два варианта:
DBMS_DEBUG_JDWP — использовать для отладки Java Debug Wire Protocol. Работает через сетевое подключение, что позволяет выполнять в том числе удалённую отладку. И отладку хранимого Java‑source, но в данной статье этот функционал не рассматриваем.
DBMS_DEBUG (
deprecated
) — прямое подключение к БД. Для отладки в таком случае будет создана отдельная сессия, что используется также в PL/SQL Developer. Для SQL Developer вариант можно использовать в том случае, если у DBMS_DEBUG_JDWP есть какие‑то проблемы с доступом при сетевом подключении (firewall и т. п.).
О различиях вариантов протокола кратко рассказано в разделе «Под капотом».
Поведение при запуске отладки
Если в отлаживаемых объектах заранее не установлены точки прерывания, то при запуске отладки скрипт сразу же выполнился до конца с фиксацией транзакции. Чтобы этого избежать, в глобальных настройках отладки рекомендуется выбрать опцию Debugger > Start Debugging Option > Step Into (по умолчанию стоит Run Until a Breakpoint Occurs):
Учитывая, что транзакция при отладке автоматически фиксируется, несмотря на параметр Autocommit = False, это может привести к неожиданным последствиям. Особенно при отладке на проде.
Подготовка скрипта
По аналогии с PL/SQL Developer есть два способа создания отладочного скрипта:
Написать обычный анонимный блок вручную.
Открыть хранимый PL/SQL-код, нажать Debug..., выбрать нужный метод, скорректировать предложенный в шаблоне скрипт. На примере пакета:
Настройка переменных скрипта
Переменные скрипта настраиваются в зависимости от способа создания скрипта для отладки.
1 вариант: скрипт создавался вручную. В этом случае входные параметры указываются прямо в скрипте, как обычно. В качестве рекомендации могу отметить, что входные переменные лучше описывать в блоке DECLARE, чтобы после запуска скрипта под отладкой можно было поменять им значения.
2 вариант: скрипт создавался через меню Debug.... Здесь входные параметры указываются в диалоге создания скрипта: в таблице Parameters или прямо в тексте шаблона скрипта. Пример диалога можно посмотреть в предыдущем разделе.
Для этого варианта есть ограничения: в таблице можно задать только переменные простых типов. Типы LOB, BOOLEAN и пр. придётся прописывать вручную прямо в скрипте. Для типов LOB это несколько неудобно: CLOB не всегда помещается в один строковый блок — нужно делить на несколько и соединять; BLOB в явном виде вообще задать не получится — придётся кодировать его в Base64 или изобретать что‑то подобное.
Запуск
Для запуска отладки анонимного блока необходимо выполнить два шага:
Вызвать контекстное меню на любой строке блока.
Выбрать пункт Debug...:
После этого код анонимного блока будет открыт в новой вкладке, запустится отладка: станут доступны элементы управления отладкой.
Чтобы запустить отладку хранимого PL/SQL‑кода, в диалоговом окне подготовки скрипта нужно просто нажать OK, см. Подготовка скрипта. После этого отладка запустится непосредственно в коде хранимого объекта (пакета): станут доступны элементы управления отладкой.
Пошаговая отладка
Команды пошаговой отладки
Команда |
Описание |
Terminate |
Прервать выполнение |
Find Execution Point |
Перейти к текущей точке выполнения. На случай, если эту точку потеряли |
Step Over |
Пройти шаг отладки без захода в процедуру или функцию. Внутрь зайдём, только если во вложенном методе есть точка остановки |
Step Into |
Зайти внутрь процедуры или функции, находящейся на текущем шаге отладки. Для DML‑операторов кнопка приведёт ко входу в соответствующий триггер, если он есть для таблицы |
Step Out |
Выйти из текущей процедуры или функции на уровень выше: в код, из которого зашли в эту процедуру или функцию в режиме пошаговой отладки |
Step to End of Method |
Выполнить до последней инструкции текущего метода |
Resume |
Продолжить выполнение до конца или до следующей точки остановки |
Pause |
Приостановить выполнение без завершения (можно будет продолжить) |
Suspend All Breakpoints |
Отключить или включить все точки остановки |
Точки прерывания
Для прерывания выполнения кода на указанной строке можно выставить точки прерывания. Их влияние распространяется только на строки, что содержат исполняемые инструкции. То есть точки прерывания не будут работать в комментариях или в середине инструкции.
Точки прерывания имеют эффект только для объектов, скомпилированных с добавлением отладочной информации. В отличие от PL/SQL Developer их можно ставить и в анонимных блоках, но уже после запуска сессии отладки, когда анонимный блок откроется в отдельной вкладке.
Точка применяется к номеру строки объекта согласно номеру из dba_source. Если установить точку в неактуальном объекте, например в ещё не скомпилированной версии объекта, то точка будет учитываться при отладке, если в dba_source на этой строке находится исполняемая инструкция. Однако поведение будет отличаться от ожидаемого, так как фактически точка установлена на другой строке.
Для рассматриваемого экземпляра SQL Developer точка прерывания ставятся локально. То есть другие пользователи не будут видеть установленные вами точки остановки.
Установка
Точка прерывания устанавливается стандартно: кликом на номер строки в объекте. См. скрин:
Установить точку можно как непосредственно в нужном объекте, открыв его для просмотра или редактирования, так и из скрипта отладки, когда отладка уже в процессе.
Также установить точку можно через контекстное меню, вызванное для номера строки объекта:
Ещё один вариант — через вкладку Breakpoints, в которой есть кнопка добавления точки (пункт Source Breakpoint). Учитывайте, что в этом варианте все параметры точки — тип, имя объекта и номер строки — придётся вводить вручную. Например, для пакета нужно ввести тип объекта в виде строки $Oracle.PackageBody.<ИМЯ_СХЕМЫ>, и имя пакета в виде <ИМЯ_ПАКЕТА>.pls, что не слишком удобно:
Настройка параметров
Для настройки параметров точки прерывания нужно открыть диалог настройки. Для этого существует три способа.
Контекстное меню. Через контекстное меню на значке точки в соответствующей строке выберите:
Указатель. Задержите указатель на значке точки остановки на несколько секунд. Отобразится окно быстрой настройки, через которое можно открыть расширенный диалог настройки:
Кнопка Edit. Через меню View откройте вкладку Breakpoints, нажмите кнопку Edit:
Эти настройки можно выполнять как до начала, так и во время отладки.
Окно настройки содержит три вкладки: Definition, Conditions и Actions. Посмотрим, на что стоит обратить внимание.
1. Definition — основные параметры точки:
Набор настроек зависит от типа точки. Общая для всех типов настройка — Breakpoint Group Name. Она позволяет группировать точки по категориям и управлять (отключить, включить, удалить) сразу целой группой точек, что весьма удобно. Для включения точки в группу можно указать её прямо в настройках конкретной точки либо перетащить точку в группу во вкладке Breakpoints. Вид вкладки для сгруппированных точек:
Также можно настраивать поведение точек для всей группы, например задать условия или действия:
2. Conditions — условия, когда должна срабатывать точка:
Для этой вкладки также есть свои параметры:
-
Condition, условия для остановки. Синтаксис как в обычном SQL. Например,
l_res IS NOT NULL
, где l_res — переменная, доступная в контексте данной точки остановки.Тут есть нюанс. Например, для числовых значений условия вида
a_id_client_order = 2839397788
при проходе через точку появляется предупреждение, что выражение вычислить не удалось. При этом вариант с меньшим числомa_id_client_order = 2
работает корректно. Оказалось, что такие большие значения нужно задавать в виде вещественного числа. То есть такой вариантa_id_client_order = 2839397788.0
сработает, как нужно. В документации про такие особенности ни слова. Thread Options, условия для потоков. Как это применять для отладки PL/SQL, непонятно, если кто‑то владеет информацией, прошу поделиться в комментариях.
Pass Count, условие на количество проходов. Остановимся только на указанном здесь количестве проходов через точку.
3. Actions — действия, выполняемые при проходе через точку:
Параметры этой вкладки:
Halt Execution, прерывает выполнение на точке.
Beep, выдаёт звуковой сигнал? В моих экспериментах услышать звук не получилось, но функция выглядит интересно: при отладке тяжёлого кода бывает, что нужно подождать, пока выполнение дойдёт до нужной точки прерывания. Во время этого ожидания можно отвлечься, а звуковой сигнал не даст пропустить момент, когда можно вернуться к отладке.
-
Log Breakpoint Occurrence, управление выводом сообщения в лог при достижении точки:
Tag: пользовательская метка.Expression: значение выражения. Например, можно вывести значение переменной.
Stack: вывести в лог полный стек вызовов.
Пример конфигурации и полученный результат:
Enable — Disable a Group of Breakpoints: включение‑отключение группы точек прерывания при достижении данной точки. Тоже интересная функция. Можно использовать, например, при отладке циклов, когда нужно «поймать» и подробно отладить только определённый шаг цикла. Если сразу поставить точки прерывания в цикле, то будем останавливаться на каждом шаге, что совсем неудобно. Решение: ставим нужные точки прерывания в цикле, включаем их в группу, и делаем их неактивными; ставим ещё одну условную точку внутри цикла без прерывания, при её достижении включаем группу точек — отлаживаем, при выходе также условная точка — отключаем группу точек, если нужно.
Исключения
В SQL Developer также можно установить точки прерывания для исключений. Отличие этих точек в том, что они не привязаны к конкретной строке кода, а срабатывают при возникновении исключений.
По умолчанию при запуске отладки автоматически создаётся универсальная точка для всех исключений, которая перехватывает исключения любых типов:
В случае исключения остановка произойдёт на соответствующей инструкции ещё до поднятия исключения. То есть можно посмотреть значения переменных, которые привели к ошибке:
Такую точку прерывания нельзя удалить, но можно отключить (Disable). Тогда не будет происходить остановки на строке исключения.
Для остановки на исключении с конкретным кодом можно создать новую точку. Для этого алгоритм следующий:
1. На панели Breakpoints нажать «+», выбрать тип точки Exception Breakpoint:
2. В параметрах точки в Exception Class:
Указать код исключения в формате $Oracle.EXCEPTION_ORA_<number>, где <number> — номер ошибки. Например, для ORA-01 476 нужно указать $Oracle.EXCEPTION_ORA_1476.
Установить нужные флаги:
Break for Caught Exceptions — останавливаться на обрабатываемых исключениях. То есть для тех, у которых предусмотрена обработка в блоке EXCEPTION.
Break for Uncaught Exceptions — останавливаться на необрабатываемых исключениях, для которых не предусмотрен перехват в блоке EXCEPTION.
Остальные параметры, на других вкладках, аналогичны прочим типам точек прерывания.
3. Важный момент: если нужно останавливаться только на исключениях с определёнными кодами, то логично отключить общую точку Oracle exception, Persistent — иначе будем останавливаться на всех исключениях. Но если отключить эту точку, то не будет работать прерывание и на всех других Exception Breakpoint. Чтобы добиться нужного результата — остановка только на исключениях определённых кодов, — общую точку Oracle exception, Persistent нужно не отключить, а только снять для неё флаг Halt Execution (прервать выполнение):
Подтвердить, что класс исключений указан верно. После этого в случае исключений остановка выполнения будет происходить только на указанных пользователем исключениях:
Значения переменных
Просмотр
При отладке в SQL Developer есть возможность просмотра и установки значений переменных. Для просмотра значений переменных при отладке есть несколько способов:
Навести курсора на переменную прямо в коде.
Вкладка Smart Data — показать переменные, которые используются в контексте текущей исполняемой строки кода.
Вкладка Data — показать все переменные, доступные в контексте всего исполняемого кода, включая внутренние и внешние переменные пакетов.
Вкладка Watches — показать выбранные пользователем переменные.
Окно Inspect (из контекстного меню на переменной) — просмотреть конкретную выбранную переменную.
Для переменных сложных типов просмотр работает полностью во всех деталях:
Но в Watches или Inspect значение конкретного поля структуры посмотреть нельзя: нужно добавлять всю переменную целиком, и в ней проваливаться до нужного поля в дереве или в таблице.
Изменение
Изменить значение переменной можно из вкладок Smart Data, Data или Watches: по двойному клику на переменную в списке, либо через контекстное меню (пункт Modify Value…):
Важно учесть, что изменение значения работает только для простых типов. Для объектов можно только сбросить значение в NULL.
Удалённая отладка
Права
Чтобы запустить удалённую отладку сессий одного пользователя, понадобятся обычные права DEBUG CONNECT SESSION
. Для удалённой отладки чужих сессий необходимо дать права DEBUG CONNECT ON USER
:
GRANT DEBUG CONNECT ON USER <target_user> TO <debug_user>;
-- <target_user> - кого отлаживаем
-- <debug_user> - кто отлаживает
Права могут быть выданы под SYS, или от пользователя‑владельца, то есть <target_user>.
Пример: выполнение тестовой процедуры
Возьмём пример, где целевая сессия подключена под TEST_DEBUG_USR и сессия отладки подключена под ним же:
В данном примере PL/SQL Developer действует, как клиентское приложение. Клиент запускает его, подключается к базе данных и выполняет какое‑то действие в ней. Это клиентское действие мы и хотим отладить, используя SQL Developer.
Последовательность шагов будет такая:
1. В SQL Developer запускаем сессию удалённой отладки:
Вводим параметры:
В результате запустится процесс, ожидающий подключения удалённой сессии:
2. В отлаживаемой сессии (из PL/SQL Developer) запускаем скрипт подключения к отладчику:
BEGIN
DBMS_DEBUG_JDWP.connect_tcp(host => '10.200.9.149', port => '4000');
END;
Здесь host — это адрес клиентской машины, сессию которой хотим отлаживать. В результате в SQL Developer отобразится сообщение о подключении удалённой сессии и появится соответствующий процесс:
3. В отлаживаемой сессии запускаем соответствующий код (в SQL Developer в этом же коде предварительно ставим точки прерывания):
BEGIN
PK_DEBUG_DEMO.test_debug_vars();
END;
В результате отлаживаемая сессия «зависнет» в ожидании ответа, а мы в сессии отладки попадаем внутрь соответствующего объекта на первую точку остановки и видим, что стали доступны кнопки управления отладкой:
4. После прохода отлаживаемого кода до конца в отлаживаемой удалённой сессии процедура завершится, а в SQL Developer сессия отладки по‑прежнему будет активна: можно будет снова запустить код и повторить отладку. Для завершения сессии отладки нажимаем кнопку Terminate, в логе увидим сообщение Debugger disconnected from database.
Особенности отладки в SQL Developer
Output выводится только после завершения скрипта. Если прервали выполнение, то ничего не увидим.
Управление транзакциями — на ручном приводе! Если забыть добавить обработку в скрипт (ROLLBACK), при завершении отладки транзакция автоматически будет зафиксирована. На такое поведение не влияет настройка Autocommit = False. Поэтому при отладке на продуктовой БД нужно быть аккуратнее и не забывать добавлять ROLLBACK.
Выводы
Про функционал отладки в SQL Developer нельзя сказать, что он понятен интуитивно. Документация сложно организована и часто не отвечает на вопросы. Полагаю, всё из‑за того, что SQL Developer — комплексный продукт, в котором много различных фич и инструментов для работы с БД Oracle.
Однако инструмент использует актуальный интерфейс отладки JDWP, поэтому более надёжен. Также есть несколько удобных функций, типа просмотра содержимого переменных сложных типов, управления группами точек остановки, удалённая отладка, которые могут сильно упростить жизнь разработчика на Oracle.
«Под капотом»
В качестве механизма отладки в Oracle предоставляется интерфейс в виде DBMS-пакетов: DBMS_DEBUG и DBMS_DEBUG_JDWP. Стоит учесть, что DBMS_DEBUG начиная с 19-й версии Oracle помечен как deprecated
. Актуальный механизм отладки — Java Debug Wire Protocol (пакет DBMS_DEBUG_JDWP).
Посмотрим, что находится внутри каждого пакета.
DBMS_DEBUG
Позволяет выполнять отладку следующих PL/SQL-объектов:
procedure,
function,
package,
package body,
trigger,
anonymous block,
object type,
object type body.
Особенность варианта отладки через этот пакет: весь ход отладки управляется через функции или процедуры этого пакета. PL/SQL Developer, по крайней мере до версии 15.0.3.2059, как раз использует для отладки этот пакет, в то время как в SQL Developer используется актуальный пакет DBMS_DEBUG_JDWP.
Отладка в таком варианте подразумевает наличие двух сессий:
Target Session (целевая сессия) — сам отлаживаемый код;
Debug Session (отладочная сессия) — сессия, из которой происходит управление отладкой.
В целевой сессии выполняется код следующего вида:
-- Включение отладки
BEGIN
--sys.dbms_session.reset_package;
sys.dbms_debug.probe_version(major => :major, minor => :minor);
:debug_session_id := sys.dbms_debug.initialize;
sys.dbms_debu.set_timeout_behaviour(sys.dbms_debug.retry_on_timeout);
sys.dbms_debug.debug_on;
END;
...Отладка (управляется из отладочной сессии)...
-- Выключение отладки
BEGIN
sys.dbms_debug.default_timeout := 3600;
sys.dbms_debug.debug_off;
END;
Все остальные команды выполняются из отладочной сессии:
sys.dbms_debug.attach_session -- Подключиться к целевой сессии
sys.dbms_debug.synchronize -- Получить событие из целевой сессии
sys.dbms_debug.set_breakpoint -- Установить точку остановки
sys.dbms_debug.get_value -- Получить значение переменной
sys.dbms_debug.print_backtrace -- Получить стек вызовов
sys.dbms_debug.continue -- Продолжить выполнение
sys.dbms_debug.detach_session -- Отключиться от целевой сессии (закончить отладку)
-- ... и т. п.
Схема процесса отладки:
Нюансы:
Сессия отладки может периодически зависать.
Значения переменных сложных типов не показываются; речь о многоуровневых структурах.
DBMS_DEBUG_JDWP
Пакет реализует механизм отладки через Java Debug Wire Protocol. Позволяет отлаживать как хранимый PL/SQL‑код, так и хранимые Java‑процедуры.
В отличие от предыдущего варианта, тут нет отладочной сессии. Для включения отладки в целевой сессии выполняется подключение к дебаггеру DBMS_DEBUG_JDWP.CONNECT_TCP(). Далее управление отладкой происходит через механизмы JDWP.
Кроме прав на отладку сессии, пользователю схемы БД необходимо выдать права ACL (Access Control List), т. к. отладка производится через внешний Java‑интерфейс (JDWP).
Сравнение инструментов и выводы
В статье мы подробно рассмотрели возможности и особенности SQL Developer в статье. Если вкратце, инструмент полезен для работы с переменными сложных типов, поддерживает удалённую отладку, а ещё — бесплатный.
Настало время сравнить инструмент с PL/SQL Developer, на который я делал обзор в прошлый раз. Для удобства собрал таблицу с характеристиками:
Действие |
PL/SQL Developer |
SQL Developer |
Использование bind‑переменных |
+ |
− |
Выполнение запросов в сессии отладки |
+ |
− |
Условные точки остановки |
+ |
+ |
Точки остановки для исключений |
+ |
+ |
Группировка точек остановки |
− |
+ |
Просмотр переменных простых типов |
+ |
+ |
Просмотр переменных сложных типов |
− |
+ |
Просмотр переменных в больших объектах |
− |
− |
Установка значений простых переменных |
+ |
+ |
Установка значений скалярных массивов |
+ |
− |
Удалённая отладка |
− |
+ |
Отладка Java-source |
− |
− |
Цена вопроса |
− |
+ |
Подведём итоги сравнения.
PL/SQL Developer — достаточно простое и интуитивно понятное приложение. Использование bind‑переменных и возможность выполнения запросов в сессии отладки в ряде случаев сильно упрощают процесс отладки. С другой стороны, инструмент платный и не очень надёжен из‑за использования устаревшего пакета DBMS_DEBUG, который помечен как deprecated
, из‑за этого не умеет отображать значения переменных сложных типов и может зависать.
SQL Developer вызывает много вопросов к эргономике. Не всегда его поведение очевидно и интуитивно понятно, а из документации далеко не всё можно узнать. Но, во‑первых, отладка тут корректно работает со сложными типами переменных, что в разы упрощает процесс отладки. Во‑вторых, есть возможность удалённой отладки. В‑третьих, инструмент разрабатывается и поддерживается самой компанией Oracle, что означает поддержку самых последних и актуальных технологий Oracle. К тому же приложение бесплатное.
Как видим, у каждого из инструментов отладки есть своим плюсы и минусы. Надеюсь, благодаря моим обзорам на них и сравнению вы сможете выбрать оптимальный для себя вариант. Лично я использую оба в зависимости от ситуации и решаемой задачи.
Ниже оставлю полезные материалы по теме: исходники объектов, используемых в примерах, а также ссылки на документацию и полезные материалы.
Спасибо за внимание!
Полезные материалы
Объекты из примеров
Создание тестового пользователя
-- SYS
-- Create the user
CREATE USER TEST_DEBUG_USR
IDENTIFIED BY test_debug_usr
TEMPORARY TABLESPACE TEMP;
-- Grant/Revoke role privileges
GRANT CONNECT TO TEST_DEBUG_USR;
GRANT RESOURCE TO TEST_DEBUG_USR;
GRANT UNLIMITED TABLESPACE TO TEST_DEBUG_USR;
GRANT DEBUG CONNECT SESSION TO TEST_DEBUG_USR;
BEGIN
DBMS_NETWORK_ACL_ADMIN.append_host_ace( host => '*' -- Для любых адресов. Можно задать разрешение только для определённого ip-адреса или имени хоста, или задать в виде маски
, ace => xs$ace_type( privilege_list => xs$name_list('jdwp')
, principal_name => 'TEST_DEBUG_USR'
, principal_type => xs_acl.PTYPE_DB));
END;
/
Тестовая таблица
-- Create table
CREATE TABLE T_CLIENT_ORDER
(
id_client_order INTEGER NOT NULL,
NUM VARCHAR2(256) NOT NULL,
COST NUMBER(14,2),
notes VARCHAR2(2000)
);
-- Add comments to the table
COMMENT ON TABLE T_CLIENT_ORDER IS 'Заказ клиента';
-- Create/Recreate primary, unique and foreign key constraints
ALTER TABLE T_CLIENT_ORDER
ADD CONSTRAINT PK_CLIENT_ORDER PRIMARY KEY (ID_CLIENT_ORDER)
USING INDEX
PCTFREE 10
INITRANS 2
MAXTRANS 255;
-- Вставка данных
INSERT INTO T_CLIENT_ORDER (ID_CLIENT_ORDER, NUM, COST)
WITH w_json AS
( SELECT '{ "orders": [
{ "id": 19010, "num": "N-8812", "cost": 633.50 },
{ "id": 19012, "num": "N-8821", "cost": 133 },
{ "id": 19014, "num": "N-8855", "cost": 560 },
{ "id": 19016, "num": "N-8889", "cost": 1090 }
]}' AS text
FROM dual
)
SELECT o.*
FROM w_json j
, JSON_TABLE( j.text, '$."orders"[*]' COLUMNS ( ID INTEGER PATH '$."id"'
, NUM VARCHAR2(256) PATH '$."num"'
, COST NUMBER PATH '$."cost"'
)) o;
Пакет PK_DEBUG_DEMO
CREATE OR REPLACE PACKAGE PK_DEBUG_DEMO AS
CREATE OR REPLACE PACKAGE PK_DEBUG_DEMO AS
PROCEDURE test_debug_vars;
PROCEDURE put_dbms_output
( a_clob CLOB
);
FUNCTION test_clob
( a_clob CLOB
) RETURN CLOB;
FUNCTION f1
( p1 VARCHAR2
, p2 VARCHAR2
) RETURN VARCHAR2;
FUNCTION f0
RETURN BOOLEAN;
FUNCTION div
( a NUMBER
, b NUMBER
) RETURN NUMBER;
FUNCTION get_order_rec
( a_id_client_order INTEGER
) RETURN T_CLIENT_ORDER%ROWTYPE;
FUNCTION get_order
( a_id_client_order INTEGER
) RETURN VARCHAR2;
PROCEDURE update_order
( a_id_client_order INTEGER
, a_cost NUMBER := NULL
, a_text VARCHAR2 := NULL
);
END PK_DEBUG_DEMO;
/
CREATE OR REPLACE PACKAGE BODY PK_DEBUG_DEMO AS
TYPE TStringArray IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER;
TYPE TStringHash IS TABLE OF VARCHAR2(1024) INDEX BY VARCHAR2(256);
TYPE TStringArrayHash IS TABLE OF TStringArray INDEX BY VARCHAR2(256);
TYPE TStringHashHash IS TABLE OF TStringHash INDEX BY VARCHAR2(256);
--
TYPE TSimpleRec IS RECORD
( text_attr VARCHAR2(256)
, num_attr NUMBER
);
TYPE TSimpleRecArray IS TABLE OF TSimpleRec INDEX BY BINARY_INTEGER;
TYPE TSimpleRecHash IS TABLE OF TSimpleRec INDEX BY VARCHAR2(256);
--
TYPE TSubSubRec IS RECORD
( sub_sub_text_attr VARCHAR2(256)
, sub_sub_num_attr NUMBER
);
TYPE TSubSubRecArray IS TABLE OF TSubSubRec INDEX BY BINARY_INTEGER;
TYPE TSubSubRecHash IS TABLE OF TSubSubRec INDEX BY VARCHAR2(256);
TYPE TSubRec IS RECORD
( sub_text_attr VARCHAR2(256)
, sub_num_attr NUMBER
, sub_sub_array TSubSubRecArray
, sub_sub_hash TSubSubRecHash
);
TYPE TSubRecArray IS TABLE OF TSubRec INDEX BY BINARY_INTEGER;
TYPE TSubRecHash IS TABLE OF TSubRec INDEX BY VARCHAR2(256);
TYPE TComplexRec IS RECORD
( text_attr VARCHAR2(256)
, num_attr NUMBER
, sub_array TSubRecArray
, sub_hash TSubRecHash
);
PROCEDURE test_debug_vars
AS
la_str_arr TStringArray;
lh_str_hash TStringHash;
lh_str_arr_hash TStringArrayHash;
lh_str_hash_hash TStringHashHash;
lr_simple_rec TSimpleRec;
lt_simple_rec_arr TSimpleRecArray;
lh_simple_rec_hash TSimpleRecHash;
lr_complex_rec TComplexRec;
l_key VARCHAR2(256);
l_str VARCHAR2(256);
l_str_2 VARCHAR2(256);
BEGIN
-- 1. TStringArray
la_str_arr(la_str_arr.COUNT+1) := 'la_str_arr-1';
la_str_arr(la_str_arr.COUNT+1) := 'la_str_arr-2';
la_str_arr(la_str_arr.COUNT+1) := 'la_str_arr-3';
-- output
DBMS_OUTPUT.put_line('TStringArray:');
FOR i IN la_str_arr.FIRST..la_str_arr.LAST LOOP
DBMS_OUTPUT.put_line(' la_str_arr('||i||') = '''||la_str_arr(i)||'''');
END LOOP;
DBMS_OUTPUT.new_line();
-- 2. TStringHash
FOR i IN 1..5 LOOP
l_key := 'map_'||TRIM(TO_CHAR(i, '000'));
lh_str_hash(l_key) := 'val_'||i;
END LOOP;
-- output
DBMS_OUTPUT.put_line('TStringHash:');
l_str := lh_str_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' lh_str_hash('''||l_str||''') = '''||lh_str_hash(l_str)||'''');
l_str := lh_str_hash.NEXT(l_str);
END LOOP;
DBMS_OUTPUT.new_line();
-- 3. TStringArrayHash
lh_str_arr_hash('arr_1') := la_str_arr;
la_str_arr(la_str_arr.COUNT+1) := 'la_str_arr-4';
lh_str_arr_hash('arr_2') := la_str_arr;
-- output
DBMS_OUTPUT.put_line('TStringArrayHash:');
l_str := lh_str_arr_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' lh_str_arr_hash('''||l_str||'''):');
FOR i IN lh_str_arr_hash(l_str).FIRST..lh_str_arr_hash(l_str).LAST LOOP
DBMS_OUTPUT.put_line(' lh_str_arr_hash('''||l_str||''')('||i||') = '''||lh_str_arr_hash(l_str)(i)||'''');
END LOOP;
l_str := lh_str_arr_hash.NEXT(l_str);
END LOOP;
DBMS_OUTPUT.new_line();
-- 4. TStringHashHash
lh_str_hash_hash('map_1')('sub_1') := 'val_1_1';
lh_str_hash_hash('map_1')('sub_2') := 'val_1_2';
lh_str_hash_hash('map_2')('sub_1') := 'val_2_1';
lh_str_hash_hash('map_2')('sub_2') := 'val_2_2';
lh_str_hash_hash('map_2')('sub_3') := 'val_2_3';
-- output
DBMS_OUTPUT.put_line('TStringHashHash:');
l_str := lh_str_hash_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' lh_str_hash_hash('''||l_str||'''):');
l_str_2 := lh_str_hash_hash(l_str).FIRST;
WHILE l_str_2 IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' lh_str_hash_hash('''||l_str||''')('''||l_str_2||''') = '''||lh_str_hash_hash(l_str)(l_str_2)||'''');
l_str_2 := lh_str_hash_hash(l_str).NEXT(l_str_2);
END LOOP;
l_str := lh_str_hash_hash.NEXT(l_str);
END LOOP;
DBMS_OUTPUT.new_line();
-- 5. TSimpleRec
lr_simple_rec.text_attr := 'text_1';
lr_simple_rec.num_attr := 1.1;
-- output
DBMS_OUTPUT.put_line('TSimpleRec:');
DBMS_OUTPUT.put_line(' lr_simple_rec.text_attr = '''||lr_simple_rec.text_attr||'''');
DBMS_OUTPUT.put_line(' lr_simple_rec.num_attr = '||lr_simple_rec.num_attr);
DBMS_OUTPUT.new_line();
-- 6. TSimpleRecArray
FOR i IN 1..10 LOOP
lt_simple_rec_arr(i).text_attr := 'text_'||i;
lt_simple_rec_arr(i).num_attr := 1+i/100;
END LOOP;
-- output
DBMS_OUTPUT.put_line('TSimpleRecArray:');
FOR i IN lt_simple_rec_arr.FIRST..lt_simple_rec_arr.LAST LOOP
DBMS_OUTPUT.put_line(' lt_simple_rec_arr('||i||'):');
DBMS_OUTPUT.put_line(' .text_attr = '''||lt_simple_rec_arr(i).text_attr||'''');
DBMS_OUTPUT.put_line(' .num_attr = '||lt_simple_rec_arr(i).num_attr);
END LOOP;
DBMS_OUTPUT.new_line();
-- 7. TSimpleRecHash
FOR i IN 1..10 LOOP
l_key := 'map_'||TRIM(TO_CHAR(i, '000'));
lh_simple_rec_hash(l_key).text_attr := 'text_'||i;
lh_simple_rec_hash(l_key).num_attr := 1+i/100;
END LOOP;
-- output
DBMS_OUTPUT.put_line('TSimpleRecHash:');
l_str := lh_simple_rec_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' lh_simple_rec_hash('''||l_str||'''):');
DBMS_OUTPUT.put_line(' .text_attr = '''||lh_simple_rec_hash(l_str).text_attr||'''');
DBMS_OUTPUT.put_line(' .num_attr = '''||lh_simple_rec_hash(l_str).num_attr||'''');
l_str := lh_simple_rec_hash.NEXT(l_str);
END LOOP;
DBMS_OUTPUT.new_line();
-- 8. TComplexRec
lr_complex_rec.text_attr := 'lvl_1_text';
lr_complex_rec.num_attr := 0.1;
lr_complex_rec.sub_array(1).sub_text_attr := 'lvl_2_text_1';
lr_complex_rec.sub_array(1).sub_num_attr := 1.1;
lr_complex_rec.sub_array(1).sub_sub_array(1).sub_sub_text_attr := 'lvl_3_text_1_1';
lr_complex_rec.sub_array(1).sub_sub_array(1).sub_sub_num_attr := 1.001;
lr_complex_rec.sub_array(1).sub_sub_array(2).sub_sub_text_attr := 'lvl_3_text_1_2';
lr_complex_rec.sub_array(1).sub_sub_array(2).sub_sub_num_attr := 1.002;
lr_complex_rec.sub_array(1).sub_sub_array(3).sub_sub_text_attr := 'lvl_3_text_1_3';
lr_complex_rec.sub_array(1).sub_sub_array(3).sub_sub_num_attr := 1.003;
lr_complex_rec.sub_array(1).sub_sub_hash('map_1_1').sub_sub_text_attr := 'lvl_3_text_1_m1';
lr_complex_rec.sub_array(1).sub_sub_hash('map_1_1').sub_sub_num_attr := 1.001;
lr_complex_rec.sub_array(1).sub_sub_hash('map_1_2').sub_sub_text_attr := 'lvl_3_text_1_m2';
lr_complex_rec.sub_array(1).sub_sub_hash('map_1_2').sub_sub_num_attr := 1.002;
lr_complex_rec.sub_array(2).sub_text_attr := 'lvl_2_text_2';
lr_complex_rec.sub_array(2).sub_num_attr := 2.1;
lr_complex_rec.sub_array(2).sub_sub_array(1).sub_sub_text_attr := 'lvl_3_text_2_1';
lr_complex_rec.sub_array(2).sub_sub_array(1).sub_sub_num_attr := 2.001;
lr_complex_rec.sub_array(2).sub_sub_array(2).sub_sub_text_attr := 'lvl_3_text_2_2';
lr_complex_rec.sub_array(2).sub_sub_array(2).sub_sub_num_attr := 2.002;
lr_complex_rec.sub_array(2).sub_sub_array(3).sub_sub_text_attr := 'lvl_3_text_2_3';
lr_complex_rec.sub_array(2).sub_sub_array(3).sub_sub_num_attr := 2.003;
lr_complex_rec.sub_array(2).sub_sub_hash('map_2_1').sub_sub_text_attr := 'lvl_3_text_2_m1';
lr_complex_rec.sub_array(2).sub_sub_hash('map_2_1').sub_sub_num_attr := 2.001;
lr_complex_rec.sub_array(2).sub_sub_hash('map_2_2').sub_sub_text_attr := 'lvl_3_text_2_m2';
lr_complex_rec.sub_array(2).sub_sub_hash('map_2_2').sub_sub_num_attr := 2.002;
lr_complex_rec.sub_hash('map_1').sub_text_attr := 'lvl_2_text_1';
lr_complex_rec.sub_hash('map_1').sub_num_attr := 1.1;
lr_complex_rec.sub_hash('map_1').sub_sub_array(1).sub_sub_text_attr := 'lvl_3_text_1_1';
lr_complex_rec.sub_hash('map_1').sub_sub_array(1).sub_sub_num_attr := 1.001;
lr_complex_rec.sub_hash('map_1').sub_sub_array(2).sub_sub_text_attr := 'lvl_3_text_1_2';
lr_complex_rec.sub_hash('map_1').sub_sub_array(2).sub_sub_num_attr := 1.002;
lr_complex_rec.sub_hash('map_1').sub_sub_hash('map_1_1').sub_sub_text_attr := 'lvl_3_text_1_m1';
lr_complex_rec.sub_hash('map_1').sub_sub_hash('map_1_1').sub_sub_num_attr := 1.001;
lr_complex_rec.sub_hash('map_1').sub_sub_hash('map_1_2').sub_sub_text_attr := 'lvl_3_text_1_m2';
lr_complex_rec.sub_hash('map_1').sub_sub_hash('map_1_2').sub_sub_num_attr := 1.002;
lr_complex_rec.sub_hash('map_2').sub_text_attr := 'lvl_2_text_1';
lr_complex_rec.sub_hash('map_2').sub_num_attr := 1.1;
lr_complex_rec.sub_hash('map_2').sub_sub_array(1).sub_sub_text_attr := 'lvl_3_text_2_1';
lr_complex_rec.sub_hash('map_2').sub_sub_array(1).sub_sub_num_attr := 2.001;
lr_complex_rec.sub_hash('map_2').sub_sub_array(2).sub_sub_text_attr := 'lvl_3_text_2_2';
lr_complex_rec.sub_hash('map_2').sub_sub_array(2).sub_sub_num_attr := 2.002;
lr_complex_rec.sub_hash('map_2').sub_sub_hash('map_2_1').sub_sub_text_attr := 'lvl_3_text_2_m1';
lr_complex_rec.sub_hash('map_2').sub_sub_hash('map_2_1').sub_sub_num_attr := 2.001;
lr_complex_rec.sub_hash('map_2').sub_sub_hash('map_2_2').sub_sub_text_attr := 'lvl_3_text_2_m2';
lr_complex_rec.sub_hash('map_2').sub_sub_hash('map_2_2').sub_sub_num_attr := 2.002;
-- output
DBMS_OUTPUT.put_line('TComplexRec:');
DBMS_OUTPUT.put_line(' lr_complex_rec:');
DBMS_OUTPUT.put_line(' .text_attr = '''||lr_complex_rec.text_attr||'''');
DBMS_OUTPUT.put_line(' .num_attr = '||lr_complex_rec.num_attr);
FOR i IN lr_complex_rec.sub_array.FIRST..lr_complex_rec.sub_array.LAST LOOP
DBMS_OUTPUT.put_line(' .sub_array('||i||'):');
DBMS_OUTPUT.put_line(' .sub_text_attr = '''||lr_complex_rec.sub_array(i).sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_num_attr = '||lr_complex_rec.sub_array(i).sub_num_attr);
FOR j IN lr_complex_rec.sub_array(i).sub_sub_array.FIRST..lr_complex_rec.sub_array(i).sub_sub_array.LAST LOOP
DBMS_OUTPUT.put_line(' .sub_sub_array('||j||'):');
DBMS_OUTPUT.put_line(' .sub_sub_text_attr = '''||lr_complex_rec.sub_array(i).sub_sub_array(j).sub_sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_sub_num_attr = '||lr_complex_rec.sub_array(i).sub_sub_array(j).sub_sub_num_attr);
END LOOP;
l_str := lr_complex_rec.sub_array(i).sub_sub_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' .sub_sub_hash('''||l_str||'''):');
DBMS_OUTPUT.put_line(' .sub_sub_text_attr = '''||lr_complex_rec.sub_array(i).sub_sub_hash(l_str).sub_sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_sub_num_attr = '||lr_complex_rec.sub_array(i).sub_sub_hash(l_str).sub_sub_num_attr);
l_str := lr_complex_rec.sub_array(i).sub_sub_hash.NEXT(l_str);
END LOOP;
END LOOP;
l_str := lr_complex_rec.sub_hash.FIRST;
WHILE l_str IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' .sub_hash('''||l_str||'''):');
DBMS_OUTPUT.put_line(' .sub_text_attr = '''||lr_complex_rec.sub_hash(l_str).sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_num_attr = '||lr_complex_rec.sub_hash(l_str).sub_num_attr);
FOR j IN lr_complex_rec.sub_hash(l_str).sub_sub_array.FIRST..lr_complex_rec.sub_hash(l_str).sub_sub_array.LAST LOOP
DBMS_OUTPUT.put_line(' .sub_sub_array('||j||'):');
DBMS_OUTPUT.put_line(' .sub_sub_text_attr = '''||lr_complex_rec.sub_hash(l_str).sub_sub_array(j).sub_sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_sub_num_attr = '||lr_complex_rec.sub_hash(l_str).sub_sub_array(j).sub_sub_num_attr);
END LOOP;
l_str_2 := lr_complex_rec.sub_hash(l_str).sub_sub_hash.FIRST;
WHILE l_str_2 IS NOT NULL LOOP
DBMS_OUTPUT.put_line(' .sub_sub_hash('''||l_str_2||'''):');
DBMS_OUTPUT.put_line(' .sub_sub_text_attr = '''||lr_complex_rec.sub_hash(l_str).sub_sub_hash(l_str_2).sub_sub_text_attr||'''');
DBMS_OUTPUT.put_line(' .sub_sub_num_attr = '||lr_complex_rec.sub_hash(l_str).sub_sub_hash(l_str_2).sub_sub_num_attr);
l_str_2 := lr_complex_rec.sub_hash(l_str).sub_sub_hash.NEXT(l_str_2);
END LOOP;
l_str := lr_complex_rec.sub_hash.NEXT(l_str);
END LOOP;
DBMS_OUTPUT.new_line();
END test_debug_vars;
PROCEDURE put_dbms_output(a_clob CLOB)
AS
l_length INTEGER := 30000; --Кол-во символов в строке
l_offset NUMBER := 1;
BEGIN
IF a_clob IS NOT NULL THEN
LOOP EXIT WHEN l_offset > dbms_lob.getlength(a_clob);
dbms_output.put_line(dbms_lob.substr( a_clob, l_length, l_offset));
l_offset := l_offset + l_length;
END LOOP;
END IF;
END put_dbms_output;
FUNCTION test_clob
( a_clob CLOB
) RETURN CLOB
AS
BEGIN
put_dbms_output(a_clob);
RETURN a_clob||CHR(10)||'New Line';
END test_clob;
FUNCTION f2
RETURN VARCHAR2
AS
BEGIN
RETURN 'F_2';
END f2;
FUNCTION f3
RETURN VARCHAR2
AS
BEGIN
RETURN 'F_3';
END f3;
FUNCTION f1
( p1 VARCHAR2
, p2 VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
DBMS_OUTPUT.put_line('p1 = '||p1);
DBMS_OUTPUT.put_line('p2 = '||p2);
RETURN 'F_1';
END f1;
FUNCTION f0
RETURN BOOLEAN
AS
BEGIN
IF f1(p1 => f2(), p2 => f3()) = 'F_1' THEN
RETURN TRUE;
END IF;
RETURN FALSE;
END f0;
FUNCTION div
( a NUMBER
, b NUMBER
) RETURN NUMBER
AS
l_res NUMBER;
BEGIN
l_res := a / b;
RETURN l_res;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('div error: a='||a||' b='||b||'. Error: '||SQLERRM);
RAISE;
END div;
FUNCTION get_order_rec
( a_id_client_order INTEGER
) RETURN T_CLIENT_ORDER%ROWTYPE
AS
lr_order T_CLIENT_ORDER%ROWTYPE;
BEGIN
SELECT *
INTO lr_order
FROM t_client_order o
WHERE o.id_client_order = a_id_client_order;
RETURN lr_order;
END get_order_rec;
FUNCTION get_order
( a_id_client_order INTEGER
) RETURN VARCHAR2
AS
lr_order T_CLIENT_ORDER%ROWTYPE;
l_res VARCHAR2(4000);
BEGIN
lr_order := get_order_rec(a_id_client_order => a_id_client_order);
l_res := 'ID='||lr_order.id_client_order||'; NUM="'||lr_order.num||'"; COST='||TRIM(TO_CHAR(lr_order.cost, '9G999G990D00'))||'';
RETURN l_res;
END get_order;
PROCEDURE update_order
( a_id_client_order INTEGER
, a_cost NUMBER := NULL
, a_text VARCHAR2 := NULL
)
AS
BEGIN
UPDATE t_client_order o
SET o.cost = NVL(a_cost, o.cost)
, o.notes = a_text
WHERE o.id_client_order = a_id_client_order;
DBMS_OUTPUT.put_line(get_order(a_id_client_order => a_id_client_order));
END update_order;
END PK_DEBUG_DEMO;
/
Ссылки
Testing and Debugging Procedures with SQL Developer (www.oracle.com)
Пакет DBMS_DEBUG
Пакет DBMS_DEBUG_JDWP
Протокол Java Debug Wire Protocol
Параметр PLSQL_DEBUG
Отладка в TOAD Debugging PL/SQL Code with Toad for Oracle
Комментарии (2)
awswaltz Автор
11.04.2024 10:12Спасибо за комментарий. DBMS_OUTPUT.PUT_LINE у нас тоже используется повсеместно, вывод в аутпут где-то встроен в код, по умолчанию выключен, а спец. флагом в приложении можно включить и получить доп. информацию, но это если разработчики не забыли добавить это логирование в код. И чаще всего этим и ограничивались. Но, как показала практика, дебаггером обычно получалось решать проблемы значительно быстрее
Antohin
Начинал работу с Oracle 7-й версии в начале двухтысячных. Теоретически там был режим отладки, но он был настолько нестабильным, что пошагово выполнить сколько-то сложную программу было невозможно - на каком-то шаге сессия просто висла. На вопрос, а как отлаживаться и в целом как жить-то с этим, старшие товарищи посоветовали использовать dbms_output.put_line и голову в качестве отладчика.
Давно уже в оракле все нормально с отладкой, но у нас в команде никто ее не использует - приучились писать так, что необходимости в отладке не возникает.