Предупреждения при компиляции в PL/SQL

Стас Белков

Стас Белков

Автор статьи. Известный специалист в мире IT. Консультант по продуктам и решениям Oracle. Практикующий программист и администратор баз данных. Подробнее.

PL/SQL - предупреждения компилятораПредупреждения во время компиляции способны существенно упростить сопровождение вашего кода и снизить вероятность ошибок. Не путайте предупреждения компилятора с ошибками; с предупреждениями ваша программа все равно будет компилироваться и работать. Тем не менее при выполнения кода, для которого выдавались предупреждения, возможно неожиданное поведение или снижение производительности.

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


Оглавление статьи[Показать]


 

Пример

Очень полезное предупреждением компилятора 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;

 

Вас заинтересует / Intresting for you:

Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 4633 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 14736 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Опережающие объявления в PL/SQ...
Опережающие объявления в PL/SQ... 3738 просмотров Максим Николенко Mon, 24 Sep 2018, 18:26:22
Тип данных RAW в PL/SQL
Тип данных RAW в PL/SQL 12237 просмотров Doctor Thu, 12 Jul 2018, 08:41:33
Войдите чтобы комментировать

Gwen аватар
Gwen ответил в теме #9288 5 года 4 мес. назад
Толково! Спасибо за статью :-)