Предупреждения во время компиляции способны существенно упростить сопровождение вашего кода и снизить вероятность ошибок. Не путайте предупреждения компилятора с ошибками; с предупреждениями ваша программа все равно будет компилироваться и работать. Тем не менее при выполнения кода, для которого выдавались предупреждения, возможно неожиданное поведение или снижение производительности.
В этой заметке моего блога вы узнаете, как работают предупреждения компилятора и какие проблемы выявляются в текущих версиях. Начнем с краткого примера применения предупреждений времени компиляции в сеансе.
Пример
Очень полезное предупреждением компилятора PLW-06002
сообщает о наличии недостижимого кода. Рассмотрим следующую программу. Так как переменная salary
инициализируется значением 10 000, условная команда всегда будет отправлять меня на строку 9. Строка 7 выполняться не будет:
1 PROCEDURE cant_go_there
2 AS
3 l_salary NUMBER := 10000;
4 BEGIN
5 IF l_salary > 20000
6 THEN
7 DBMS_OUTPUT.put_line ('Executive');
8 ELSE
9 DBMS_OUTPUT.put_line ('Rest of Us');
10 END IF;
11 END cant_go_there;
Если откомпилировать этот код в любой версии до Oracle Database 10g
, компилятор просто сообщит о том, что процедура создана. Но если включить предупреждения компиляции в сеансе этой или более поздней версии, то при попытке откомпилировать процедуру будет получен следующий ответ от компилятора:
SP2-0804: Procedure created with compilation warnings
SQL> SHOW err
Errors for PROCEDURE CANT GO THERE:
LINE/COL ERROR
-------- -----------------------
7/7 PLW-06002: Unreachable code
С этим предупреждением я могу вернуться к указанной строке, определить, почему она недостижима, и внести необходимые исправления.
Включение предупреждений компилятора PL/SQL
Oracle позволяет включать и отключать предупреждения компилятора, а также указывать, какие виды предупреждений представляют интерес. Предупреждения делятся на три категории:
- Критичные — ситуации, которые могут привести к неожиданному поведению или получению неверных результатов (как, например, проблемы с псевдонимами параметров).
- Производительные — ситуации, способные вызвать проблемы с производительностью (например, указание значения
VARCHAR2
для столбцаNUMBER
в командеUPDATE
). - Информационные — ситуации, не влияющие на производительность или правильность выполнения кода, но которые стоит изменить ради того, чтобы упростить сопровождение.
Oracle позволяет включать и отключать предупреждения конкретной категории, всех категорий и даже конкретные предупреждения. Для этого используется команда ALTER DDL
или встроенный пакет DBMS_WARNING
.
Следующая команда включает предупреждения компиляции для системы в целом:
ALTER SYSTEM SET PLSQL_WARNINGS='string'
А следующая команда, например, включает предупреждения в вашей системе для всех категорий:
ALTER SYSTEM SET PLSQL_WARNINGS='ENABLE:ALL';
Это значение особенно полезно во время разработки, потому что оно позволит обнаружить наибольшее количество потенциальных проблем в вашем коде.
Чтобы включить предупреждения в сеансе только для критичных проблем, введите следующую команду:
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:SEVERE';
А для изменения настройки предупреждений компилятора для конкретной, уже откомпилированной программы вводится команда следующего вида:
ALTER PROCEDURE hello COMPILE PLSQL_WARNINGS='ENABLE:ALL' REUSE SETTINGS;
Обязательно включите секцию REUSE SETTINGS
, чтобы команда ALTER
не влияла на все остальные настройки (например, уровень оптимизации).
Объединяя разные параметры, можно уточнять настройки с очень высоким уровнем детализации. Допустим, я хочу знать обо всех проблемах, относящихся к производительности, на данный момент не желаю отвлекаться на серверные проблемы, а предупреждение PLW-05005
(выход из функции без RETURN
) должно рассматриваться как ошибка компиляции. Для этого вводится следующая команда:
ALTER SESSION SET PLSQL_WARNINGS=
'DISABLE:SEVERE'
,'ENABLE:PERFORMANCE'
,'ERROR:05005';
Особенно полезна возможность интерпретации предупреждений как ошибок. Возьмем предупреждение PLW-05005; если оставить его без внимания при компиляции функции no_return (см. ниже), программа откомпилируется, и я смогу использовать ее в приложении:
SQL> CREATE OR REPLACE FUNCTION no_return
2 RETURN VARCHAR2
3 AS
4 BEGIN
5 DBMS_OUTPUT.PUT_LINE (
6 'Here I am, here I stay');
7 END no_return;
8 /
SP2-0806: Function created with compilation warnings
SQL> SHOW ERR
Errors for FUNCTION NO_RETURN:
LINE/COL ERROR
-------------------------------------------------------
1/1 PLW-05005: function NO_RETURN returns without value at line 7
Если теперь изменить интерпретацию ошибки приведенной выше командой ALTER SESSION
и перекомпилировать no_return
, компилятор немедленно остановит попытку:
Warning: Procedure altered with compilation errors
Кстати говоря, настройки также можно изменить только для конкретной программы и пометить предупреждение как ошибку командой следующего вида:
ALTER PROCEDURE no_return COMPILE PLSQL_WARNINGS = 'error:6002' REUSE SETTINGS
/
Во всех этих разновидностях команды ALTER
ключевое слово ALL
может использоваться как простое и удобное обозначение всех категорий предупреждений:
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL';
Oracle также предоставляет пакет DBMS_WARNING
со сходными возможностями по установке и изменению параметров компиляции через PL/SQL API
. В отличие от команды ALTER, DBMS_WARNING
позволяет вносить изменения в конфигурацию тех предупреждений, которые вас интересуют, оставляя другие без изменений. Также после завершения работы можно легко восстановить исходные настройки.
Пакет DBMS_WARNING
проектировался для использования в установочных сценариях, в которых может возникнуть необходимость отключения некоторых предупреждений или интерпретации предупреждения как ошибки для отдельных компилируемых программ. Может оказаться, что некоторые сценарии (внешние по отношению к тем, за которые вы отвечаете) вам неподконтрольны. Автор каждого сценария должен иметь возможность задать нужную конфигурацию предупреждений, наследуя более широкий спектр настроек из глобальной области действия.
Некоторые полезные предупреждения PL/SQL
В следующих разделах я представлю небольшую подборку предупреждений, реализованных Oracle
, — с примерами кода, для которого они выдаются, и описаниями особенно интересного поведения.
Чтобы просмотреть полный список предупреждений для любой конкретной версии Oracle
, найдите раздел PLW
в книге "Error Messages" документации Oracle.
PLW-05000: несовпадение в NOCOPY между спецификацией и телом
Рекомендация NOCOPY
сообщает базе данных Oracle, что вы, если это возможно, предпочли бы не создавать копии аргументов IN OUT
. Отказ от копирования может повысить производительность программ, передающих большие структуры данных — например, коллекции или CLOB
.
Рекомендация NOCOPY
должна быть включена как в спецификацию, так и в тело программы (актуально для пакетов и объектных типов). Если рекомендация не присутствует в обоих местах, база данных применяет настройку, указанную в спецификации.
Пример кода, генерирующего это предупреждение:
PACKAGE plw5000
IS
TYPE collection_t IS
TABLE OF VARCHAR2 (100);
PROCEDURE proc (
collection_in IN OUT NOCOPY
collection_t);
END plw5000;
PACKAGE BODY plw5000
IS
PROCEDURE proc (
collection_in IN OUT
collection_t)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE ('Hello!');
END proc;
END plw5000;
Предупреждения компилятора отображаются в следующем виде:
SQL> SHOW ERRORS PACKAGE BODY plw5000
Errors for PACKAGE BODY PLW5000:
LINE/COL ERROR
-------- ---------------------------------------------------------------
3/20 PLW-05000: mismatch in NOCOPY qualification between specification
and body
3/20 PLW-07203: parameter 'COLLECTION_IN' may benefit from use of the
NOCOPY compiler hint
PLW-05001: предыдущее использование строки противоречит этому использованию
Предупреждение проявляется при объявлении нескольких переменных или констант с одинаковыми именами. Оно также может проявиться в том случае, если список параметров программы, определенный в спецификации пакета, отличается от списка в определении из тела пакета.
Возможно, вы скажете: «Да, я видел эту ошибку, но это именно ошибка компиляции, а не предупреждение». Собственно, вы правы — следующая программа не откомпилируется:
PROCEDURE plw5001
IS
a BOOLEAN;
a PLS_INTEGER;
BEGIN
a := 1;
DBMS_OUTPUT.put_line ('Will not compile');
END plw5001;
Компилятор выдает ошибку PLS-00371
(в разделе объявлений разрешено не более одного объявления ‘A’).
Почему же для этой ситуации создано предупреждение? Попробуем удалить присваивание переменной с именем a:
SQL> CREATE OR REPLACE PROCEDURE plw5001
2 IS
3 a BOOLEAN;
4 a PLS_INTEGER;
5 BEGIN
6 DBMS_OUTPUT.put_line ('Will not compile?');
7 END plw5001;
8 /
Procedure created.
Программа откомпилируется! База данных не выдает ошибку PLS-00371
, потому что я не использую ни одну из переменных в своем коде. Предупреждение PLW
-05001 устраняет этот недостаток, сообщая о том, что я объявляю одноименные переменные без использования:
SQL> ALTER PROCEDURE plw5001 COMPILE plsql_warnings = 'enable:all';
SP2-0805: Procedure altered with compilation warnings
SQL> SHOW ERRORS
Errors for PROCEDURE PLW5001:
LINE/COL ERROR
--------------------------------------------------------------------------
4/4 PLW-05001: previous use of 'A' (at line 3) conflicts with this use
PLW-05003: фактический параметр с IN и NOCOPY может иметь побочные эффекты
Используя NOCOPY
с параметром IN OUT
, вы приказываете PL/SQL
передавать аргумент по ссылке, а не по значению. Это означает, что любые изменения в аргументе вносятся непосредственно в переменную во внешней области действия. С другой стороны, при передаче «по значению» (ключевое слово NOCOPY
отсутствует, или компилятор игнорирует рекомендацию NOCOPY
) изменения вносятся в локальную копию параметра IN OUT
. Когда программа завершается, изменения копируются в фактический параметр. (Если произойдет ошибка, измененные значения не копируются в фактический параметр.) Рекомендация NOCOPY
повышает вероятность совмещения имен аргументов, то есть ссылки на один блок памяти по двум разным именам.
Совмещение имен усложняет понимание и отладку кода; предупреждение компилятора, выявляющее эту ситуацию, будет чрезвычайно полезным.
Возьмем следующую программу:
PROCEDURE very_confusing (
arg1 IN VARCHAR2
, arg2 IN OUT VARCHAR2
, arg3 IN OUT NOCOPY VARCHAR2
) продолжение #
IS
BEGIN
arg2 := 'Second value';
DBMS_OUTPUT.put_line ('arg2 assigned, arg1 = ' || arg1);
arg3 := 'Third value';
DBMS_OUTPUT.put_line ('arg3 assigned, argl = ' || argl);
END;
Программа достаточно проста: передаются три строки, две из которых объявлены как IN OUT
; аргументам IN OUT
присваиваются значения; после каждого присваивания выводится значение первого аргумента IN
. Теперь я запускаю процедуру и передаю одну локальную переменную во всех трех параметрах:
SQL> DECLARE
2 str VARCHAR2 (100) := 'First value';
3 BEGIN
4 DBMS_OUTPUT.put_line ('str before = ' || str);
5 ery_confusing (str, str, str);
6 DBMS_OUTPUT.put_line ('str after = ' || str);
7 END;
8 /
str before = First value
arg2 assigned, arg1 = First value
arg3 assigned, arg1 = Third value
str after = Second value
Хотя процедура very_confusing
продолжает выполняться, присваивание arg2 не отражается на значении аргумента arg1
. Однако когда значение присваивается arg3
, значение arg1
(аргумент IN) заменяется на «Third value
»! Более того, при завершении very_confusing присваивание arg2
было применено к переменной str
. Таким образом, при возврате управления во внешний блок переменной str
присваивается значение «Second value
», фактически заменяющее результат присваивания «Third value
».
Как говорилось ранее, совмещение имен параметров может порождать очень запутанные ситуации. Если включить предупреждения компилятора, в таких программах, как plw5003, могут быть выявлены потенциальные проблемы совмещения имен:
SQL> CREATE OR REPLACE PROCEDURE plw5003
2 IS
3 str VARCHAR2 (100) := 'First value';
4 BEGIN
5 DBMS_OUTPUT.put_line ('str before = ' || str);
6 very_confusing (str, str, str);
7 DBMS_OUTPUT.put_line ('str after = ' || str);
8 END plw5003;
9 /
SP2-0804: Procedure created with compilation warnings
SQL> SHOW ERR
Errors for PROCEDURE PLW5003:
LINE/COL ERROR
-------- -----------------------------------------------------------------
6/4 PLW-05003: same actual parameter(STR and STR) at IN and NOCOPY
may have side effects
6/4 PLW-05003: same actual parameter(STR and STR) at IN and NOCOPY
may have side effects
PLW-05004: идентификатор также объявлен в пакете STANDARD или является встроенным в SQL
Многие разработчики PL/SQL не знают о пакете STANDARD
и его влиянии на код PL/ SQL
. Например, многие программисты считают, что такие имена, как INTEGER
и TO_CHAR
, являются зарезервированными словами языка PL/SQL
. Однако на самом деле это тип данных и функция, объявленные в пакете STANDARD
.
standard
— один из двух пакетов по умолчанию в PL/SQL
(другой — DBMS_STANDARD
). Поскольку STANDARD
является пакетом по умолчанию, вам не нужно уточнять ссылки на такие типы данных, как INTEGER, NUMBER, PLS_INTEGER
и т. д., именем STANDARD
— хотя при желании это можно сделать.
Предупреждение PLW-5004
сообщает об объявлении идентификатора с таким же именем, как у элемента STANDARD
(или встроенным именем SQL; многие встроенные имена, хотя и не все, объявляются в STANDARD
).
Рассмотрим эту процедуру:
1 PROCEDURE plw5004
2 IS
3 INTEGER NUMBER;
4
4 PROCEDURE TO_CHAR
5 IS
6 BEGIN
7 INTEGER := 10;
8 END TO_CHAR;
9 BEGIN
10 TO_CHAR;
11 END plw5004;
Для этой процедуры компилятор выводит следующие предупреждения:
LINE/COL ERROR
-------- ------------------------------------------------------------
3/4 PLW-05004: identifier INTEGER is also declared in STANDARD
or is a SQL builtin
5/14 PLW-05004: identifier TO_CHAR is also declared in STANDARD
or is a SQL builtin
Старайтесь избегать использования имен элементов, определенных в пакете STANDARD
, если только у вас нет для этого очень веских причин.
PLW-05005: функция возвращает управление без значения
Очень полезное предупреждение — функция, не возвращающая значение, явно очень плохо спроектирована. Это одно из предупреждений, которые я бы рекомендовал интерпретировать как ошибку (синтаксис «ERROR:5005
») в настройках PLSQL_WARNINGS
. Мы уже рассматривали один пример такой функции: no_return. Тот код был тривиальным; во всем исполняемом разделе не было ни одной команды RETURN
. Конечно, код может быть и более сложным. Тот факт, что команда RETURN
не выполняется, может быть скрыт за завесой сложной условной логики.
Впрочем, по крайней мере иногда в подобных ситуациях база данных способна обнаружить проблему, как в следующей программе:
1 FUNCTION no_return (
2 check_in IN BOOLEAN)
3 RETURN VARCHAR2
4 AS
5 BEGIN
6 IF check_in
7 THEN
8 RETURN 'abc';
9 ELSE
10 DBMS_OUTPUT.put_line (
11 'Here I am, here I stay');
12 END IF;
13 END no_return;
База данных обнаружила логическую ветвь, не приводящую к выполнению RETURN
, поэтому для программы выдается предупреждение. Файл plw5005.sql
на сайте github содержит более сложную условную логику, которая демонстрирует, что предупреждение выдается и в более сложных программных структурах.
PLW-06002: недостижимый код
База данных Oracle теперь умеет проводить статический анализ программы для выявления строк кода, которые ни при каких условиях не получат управление во время выполнения. Это исключительно ценная информация, но иногда компилятор предупреждает о наличии проблемы в строках, которые на первый взгляд недостижимыми вовсе не являются. Более того, в описании действий, предпринимаемых для этой ошибки, говорится, что «предупреждение следует отключить, если большой объем кода был сделан недостижимым намеренно, а предупреждение приносит больше раздражения, чем пользы».
Пример такого предупреждения приводился ранее в разделе «Пример». Теперь рассмотрим следующий код:
1 PROCEDURE plw6002
2 AS
3 l_checking BOOLEAN := FALSE;
4 BEGIN
5 IF l_checking
6 THEN
7 DBMS_OUTPUT.put_line ('Never here...');
8 ELSE
9 DBMS_OUTPUT.put_line ('Always here...');
10 GOTO end_of_function;
11 END IF;
12 <<end_of_function>>
13 NULL;
14 END plw6002;
В Oracle Database 10g и выше для этой программы выдаются следующие предупреждения:
LINE/COL ERROR
--------- ---------------------------------
5/7 PLW-06002: Unreachable code
7/7 PLW-06002: Unreachable code
13/4 PLW-06002: Unreachable code
Понятно, почему строка 7 помечена как недостижимая: l_checking
присваивается значение FALSE
, поэтому строка 7 выполняться не будет. Но почему строка 5 помечена как недостижимая? На первый взгляд этот код будет выполняться всегда! Более того, строка 13 тоже должна выполняться всегда, потому что GOTO
передает управление этой строке по метке. И все же эта строка тоже помечена как недостижимая.
Такое поведение объясняется тем, что до выхода Oracle Database 11g предупреждение о недостижимости кода генерируется после его оптимизации. В Oracle Database 11g и выше анализ недостижимого кода стал намного более понятным и полезным.
Компилятор не вводит вас в заблуждение; говоря, что строка N недостижима, он сообщает, что она никогда не будет выполняться в соответствии со структурой оптимизированного кода.
Некоторые ситуации с недостижимым кодом не обнаруживаются компилятором. Пример:
FUNCTION plw6002 RETURN VARCHAR2
AS
BEGIN
RETURN NULL;
DBMS_OUTPUT.put_line ('Never here...');
END plw6002;
Разумеется, вызов DBMS_OUTPUT.PUT_LINE
недостижим, но в настоящее время компилятор не обнаруживает это обстоятельство — до версии 12.1.
PLW-07203: рекомендация NOCOPY может принести пользу в объявлении параметра
Как упоминалось ранее в отношении PLW-05005
, использование NOCOPY
для сложных, больших параметров IN OUT
может улучшить производительность программ в некоторых условиях. Это предупреждение выдается для программ, у которых включение NOCOPY
для параметров IN OUT
может повысить эффективность выполнения. Пример такой программы:
PACKAGE plw7203
IS
TYPE collection_t IS TABLE OF VARCHAR2 (100);
PROCEDURE proc (collection_in IN OUT collection_t);
END plw7203;
Это еще одно предупреждение, которое будет генерироваться во многих программах и вскоре начнет раздражать. Безусловно, предупреждение вполне справедливо, но в большинстве случаев последствия такой оптимизации останутся незамеченными. Более того, вряд ли вам удастся переключиться на NOCOPY
без внесения изменений для обработки ситуаций с аварийным завершением программы, при котором данные остаются в неопределенном состоянии.
PLW-07204: преобразование типа столбца может привести к построению неоптимального плана запроса
Предупреждение выдается при вызове команд SQL из PL/SQL, при котором происходят неявные преобразования. Пример:
FUNCTION plw7204
RETURN PLS_INTEGER
AS
l_count PLS_INTEGER;
BEGIN
SELECT COUNT(*) INTO l_count
FROM employees
WHERE salary = '10000';
RETURN l_count;
END plw7204;
С этим предупреждением тесно связано предупреждение PLW-7202
(тип передаваемого параметра приводит к преобразованию типа столбца).
PLW-06009: обработчик OTHERS не завершается вызовом RAISE или RAISE_APPLICATION_ERROR
Это предупреждение (добавленное в Oracle Database 11g) выводится тогда, когда в обработчике исключений OTHERS
не выполняется та или иная форма RAISE
(повторное инициирование того же исключения или инициирование другого исключения), и не вызывается RAISE_APPLICATI0N_ERR0R
. Другими словами, существует большая вероятность того, что программа «поглощает» исключение и игнорирует его. Ситуации, в которых ошибки действительно должны игнорироваться, встречаются довольно редко. Чаще исключение должно передаваться во внешний блок:
FUNCTION plw6009
RETURN PLS_INTEGER
AS
l_count PLS_INTEGER;
BEGIN
SELECT COUNT ( * ) INTO l_count
FROM dual WHERE 1 = 2;
RETURN l_count;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error!');
RETURN 0;
END plw6009;