Август 2004


Профессионалу разработчику


Стивен Фернстайн
Член OTN с 2001

Создание “Поставщика сервисов”:
пакетов с концентрированной функциональностью.

(Crafting Service Providers: Packages with Focused Functionality,
by Steven Feuerstein, OTN Member since 2001)

Статья 6 из серии С.Фернстайна “Построение утилиты анализа кода”

Источник: http://otn.oracle.com/oramag/webcolumns/2003/techarticles/feuerstein_codech6.html

[От редакции OM/RE: Переводы первых пяти статей серии опубликованы в следующих выпусках OM/RE:


В каждой статье автор дает содержание всей серии:

Шаги по созданию утилиты

Ниже приведено краткое содержание статей, которые будут появляться на Oracle Technology Network (OTN) в течении следующих нескольких месяцев:

Статья 1. Формулировка проблемы: неоднозначные перегрузки в пакетах.
Я начну с подробного изучения важной задачи, которую должна будет решить утилита Codecheck: проблемы неоднозначных перегрузок в пакетах. Знаете ли вы, что весьма реально, и даже очень просто, перегружать программы в пакетах таким образом, что пакет будет компилироваться, но вы не сможете вызвать эти программы? Иногда такая неоднозначность очевидна, а иногда она едва уловима. Моей целью является написание утилиты, которая будет находить эти неоднозначности. На этом первоначальном шаге я покажу, что вовлекается в создание утилиты, как выполнять необходимый анализ, и как переводить результаты этого анализа в форму, полезную для разработчиков.

Статья 2. Начинаем, начинаем с тестирования
Я меня есть основная идея относительно того, что Codecheck должна делать. Я нашел некоторые встроенные пакеты и представления словаря данных, которые мне помогут. Что дальше? Моим естественным желание является открыть мой любимый IDE (интегрированную среду разработки - integrated development environment) и начать писать код – писать быстро и неистово, увлекаясь волнением творческого процесса, продумывая детали по мере продвижения, решая возникающие задачи, заставляя что-то работать, и затем настраивая это. Однако, этот путь не ведет к высококачественной реализации. Поэтому, я отложу написание кода и, вместо этого определю, как я буду тестировать мой код, чтобы убедиться, что он удовлетворяет всем требованиям.

Статья 3. Создание дизайна высокого уровня
Хотя я не собираюсь использовать никаких инструментов проектирования для построения Codecheck, все же необходимо посвятить некоторое время продумыванию общей архитектуры приложения, которое я собираюсь конструировать. Должно ли оно состоять из одной большой толстой процедуры? Едва ли. Нуждается ли оно во вспомогательных структурах данных? В этой статье я буду создавать фундаментальную, но осуществимую архитектуру Codecheck, стараясь избежать перепроектирования.

Статья 4. Реализация Codecheck: фаза конструирования
Наконец-то! Я начинаю писать код. Однако, это странный сорт кода, потому что я собираюсь строго следовать нисходящему дизайну, или стратегии пошаговой детализации. На каждом этапе этого пути, я буду прятать сложности и подробности реализации. Другими словами, я буду чрезвычайно мешкать, но ради стоящей цели: улучшение читаемости кода и уменьшение количества ошибок. Я буду также следовать одной из моих 10 заповедей для высококачественного PL/SQL-кодирования: ограничить размер исполняемой секции не более, чем 50 или 60 строками. Звучит невероятно? Последуйте моему совету, и это существенно улучшит ваш код.

Статья 5. Улучшение информации об аргументах
Забираясь дальше в дебри Codecheck, я примусь за наиболее сложную логику, связанную с анализом потенциально неоднозначных перегрузок. Я подробно рассмотрю многоуровневые коллекции и коллекции, индексированные строками (string-indexed), и покажу, насколько они важны для того, чтобы спрятать сложность структур такого рода в процедуру или функцию.

Статья 6. Создание “Поставщика сервисов”: пакетов с концентрированной функциональностью.
На нижнем уровне иерархии программного обеспечения Codecheck находятся несколько пакетов, являющихся относительно маленькими концентрированными модулями. Они предоставляют сервисы пакетам более высоких уровней. Одним из важных элементов дизайна программного обеспечения является определение таких отдельных сервисов и создание индивидуальных пакетов (или, может быть, объектных типов) для объединения всего, относящегося к этому сервису.

Статья 7. Создание “Поставщика сервисов”, Часть 2.
Я объясню, как использовать преимущества очень полезной процедуры DBMS_UTILITY.NAME_RESOLVE и рассмотрю отдельный механизм отчетности для Codecheck. Я покажу также общий пакет обработки ошибок, который включает механизм утверждений и “изящную” процедуру RAISE.

Статья 8. Повторная настройка.
Теперь, имея работающую версию Codecheck, настало время использовать механизм тестирования utPLSQL и подготовленные мной тестовые данные, чтобы выполнить тестирование. Когда я обнаружу проблемы в коде, я буду не только исправлять их, но и открывать возможности улучшения внутренней работы моих программ, этот процесс называется рефакторинг.


Использование преимуществ низкоуровневых пакетов

Первые пять статей этой серии были посвящены различным вопросам, от основного описания задачи, которую моя утилита анализа кода должна решать, до погружения в некоторую достаточно сложную логику, необходимую программе. Теперь пришло время взглянуть на самый нижний уровень иерархии программы Codecheck, на котором расположено несколько пакетов, являющихся относительно небольшими, концентрированными модулями. Они предоставляют услуги пакетам более высоких уровней. Одним из важных элементов проектирования программного обеспечения является определение таких отдельных сервисов и создание индивидуальных пакетов (или может быть объектных типов), которые объединяют информацию, относящуюся к этим сервисам. В следующих двух статьях я буду исследовать различные “поставщики сервисов” утилиты Codecheck.

Пакет cc_arguments гораздо меньше и проще, чем cc_smartargs. У него понятное назначение в жизни: извлекать информацию из ALL_ARGUMENTS и DBMS_DESCRIBE так, чтобы максимум наиболее точных данных об аргументах были доступны через понятный простой программный интерфейс приложения (API). Он также предоставляет функции для возвращения информации об аргументах.

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

  • Завершение описания структуры данных, которую использует cc_smartargs
  • Использование именованных констант, чтобы скрыть “волшебные значения”
  • Слияние данных из этих двух источников.
Структура данных cc_arguments Пакет cc_smartargs ссылается на две структуры:
  • cc_arguments.one_argument_rt: предполагается, что здесь содержится информация об одной строке из ALL_ARGUMENTS и DBMS_DESCRIBE.
  • cc_arguments.arguments_tt: предполагается, что здесь содержится список записей, каждая из которых типа one_argument_rt.

Я говорю “предполагается” потому, что я определил назначение этих структур до того, как фактически создал их. Время их создания, наконец, пришло. Если бы я получал информацию только из ALL_ARGUMENTS, мне не нужна была бы one_argument_rt, можно было бы просто использовать ALL_ARGUMENTS%ROWTYPE, чтобы определить структуру записи. Однако, поскольку мне необходимо извлекать информацию также и из DBMS_DESCRIBE, придется “переключить передачу” и создать свой собственный тип запись, как показано в Листинге 1.

Где только возможно я использую атрибут %TYPE по отношению к ALL_ARGUMENTS для того, чтобы описать тип данных, это позволяет избегать жесткого кодирования (в частности длины VARCHAR2). Однако некоторые из этих значений будут приходить непосредственно из DBMS_DESCRIBE, поскольку (как я описал в первой статье этой серии в разделе “Как насчёт поставляемых пакетов?”) он будет ведущим.

Создав свой тип записи, можно легко определить коллекцию для хранения таких записей:

TYPE arguments_tt IS TABLE OF one_argument_rt
     INDEX BY BINARY_INTEGER;

И коллекция этого типа – это именно то, что возвращает главная программа cc_arguments:

FUNCTION fullset (program_in  IN   VARCHAR2)
     RETURN arguments_tt;

Это может показаться вам невероятно простым после погружения в тайны и чудеса типов коллекций cc_smartargs!

Использование именованных констант, чтобы скрыть волшебные значения

Волшебное значение – это один из тех специальных символов, которые всплывают снова и снова в приложении, обычно с комментарием “оно никогда не меняется” или “оно всегда имеет это значение”. Конечно, опытные разработчики всегда чуют недоброе, когда они видят или слышат о чем-либо подобном. Это именно те фрагменты данных, которые, как кажется, имеют некоторое постоянство и которые весьма вероятно изменятся в самый неподходящий момент.

В общем случае, никогда не стоит доверять волшебным значениям. Будем считать, что они изменятся, и избежим жестко закодированных ссылок на них. Каким образом? Просто создав именованные константы в спецификации пакета. Задав значению имя, вы сделаете свой код более удобным для чтения и спрячете текущее значение.

В контексте информации об аргументах приходят в голову следующие волшебные значения:

  • Значение уровня, которое указывает на параметр верхнего уровня (0)
  • Позиция аргумента строки предложения RETURN (0)
  • Вид параметра (IN, OUT, IN OUT)
  • Индикатор наличия значения по умолчанию (1 или 0)
Вот константы, созданные для этих волшебных значений:
   c_top_level    CONSTANT PLS_INTEGER := 0;
   c_return_pos   CONSTANT PLS_INTEGER := 0;
   --
   -- Значения вида параметра, используемые в DBMS_DESCRIBE
   c_in           CONSTANT PLS_INTEGER := 0;
   c_out          CONSTANT PLS_INTEGER := 1;
   c_inout        CONSTANT PLS_INTEGER := 2;
   --
   -- Индикаторы наличия значения по умолчанию.
   c_has_default  CONSTANT PLS_INTEGER := 1;
  c_has_no_default CONSTANT PLS_INTEGER := 0;

Давайте посмотрим на пару программ cc_arguments, которые ссылаются на эти константы, чтобы вы увидели, как использование таких элементов делает код более понятным. Функция is_return_clause принимает одну строку информации и возвращает TRUE, если позиция аргумента соответствует возвращаемой позиции:

FUNCTION cc_arguments.is_return_clause (
      arg_in IN one_argument_rt)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN arg_in.POSITION = c_return_pos;
   END;

И если мне необходимо определить является ли аргумент параметром верхнего уровня, у меня нет необходимости помнить, что уровень должен быть 0. Я просто вызову соответствующую функцию:

   FUNCTION cc_arguments.is_toplevel_parameter (
      arg_in IN one_argument_rt)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN arg_in.data_level = c_top_level;
   END;

Возможно, посмотрев на эти маленькие программы, вам захочется сказать: “Стоит ли создавать функцию? Это же просто однострочное условное выражение. Это пустая трата времени!” Узнаю голос Гоблина Программирования, вредного создания, смысл жизни которого – убеждать нас создавать сокращения, якобы для того, чтобы сделать нашу работу более быстрой и эффективной. Ничто не может быть дальше от истины. Сокращения в программировании неизбежно ведут к большим ошибкам и хрупкому программному обеспечению, которое легко ломается, и которое трудно снова заставить работать.

Давайте посмотрим на одно завершенное использование этих именованных констант: виды параметров. DBMS_DESCRIBE возвращает вид параметра как одно из трех значений : 0 для IN, 1 для OUT, и 2 для IN OUT. Я, конечно, не хочу помнить этого, поэтому я создал именованные константы. Прекрасно, но этого не достаточно в данном случае. Мне также необходим некоторый способ перевода этих числовых значений в название вида (предположим, например, что я хочу генерировать корректные заголовки для программ из информации об аргументах).

В cc_smartargs я использовал коллекцию для хранения названий, и затем, создал функцию, которая выполняет перевод. Вот объявление типа коллекции и самой коллекции:

CREATE OR REPLACE PACKAGE BODY cc_arguments
IS
   TYPE mode_names_t IS TABLE OF VARCHAR2(6) 
      INDEX BY BINARY_INTEGER;
   g_mode_names mode_names_t;

После создания этой коллекции, я заполнил ее в разделе инициализации пакета:

BEGIN
   g_mode_names (c_in) := 'IN';
   g_mode_names (c_out) := 'OUT';
   g_mode_names (c_inout) := 'IN OUT';
END cc_arguments;

Наконец, вот очень простая функция перевода:

FUNCTION mode_name (code_in IN PLS_INTEGER)
  RETURN VARCHAR2
IS
BEGIN
  RETURN g_mode_names (code_in);
EXCEPTION
  WHEN NO_DATA_FOUND
  THEN
     RETURN NULL;
END;

Слияние данных ALL_ARGUMENTS и DBMS_DESCRIBE

Наиболее важной программой в cc_arguments является fullset, которая возвращает коллекцию собранной информации об аргументах. Давайте рассмотрим наиболее яркие фрагменты реализации программы. В листинге 2 показаны шаги основной исполняемой секции.

В строке 2 я обращаюсь к cc_names, чтобы получить имена элементов (владелец, название пакета и название программы) для заданного объекта кода. В строках с 4 по 7 я открываю курсор, который обнаруживает все уникальные (distinct ) названия программ для этого объекта. Мне приходится делать это, потому что пакет DBMS_DESCRIBE работает только с одной конкретной программой в один момент времени; ему нельзя передать целый пакет и получить описания различных программ этого пакета.

Для каждой программы, выбранной запросом, я вызываю DBMS_DESCRIBE (подробности спрятаны в get_dbms_describe_info), которая размещает результаты в наборе коллекций (подробности см. в cc_arguments.pkb в файле Codecheck.zip). Затем, для каждой строки этого массива, используя массив l_argument_name в качестве ведущего, я переношу данные в коллекцию arguments_tt, которая и будет возвращаться функцией fullset,.

Программа transfer_data состоит из двух шагов:

BEGIN
   copy_to_arguments_array (rec, indx, l_argindx);
   add_all_arguments_info (retval (l_argindx));
END transfer_data;

Первая программа просто перемещает данные. Вторая программа понимает, что DBMS_DESCRIBE не дает всей необходимой информации. Она просматривает соответствующую строку в ALL_ARGUMENTS и заполняет все пропущенные поля в строке коллекции (см. Листинг 3), где onerow – это другая функция в cc_arguments, которая возвращает одну строку (подробности приведены в исходном коде)

Я разобрался с реализацией (или, по крайней мере, с наиболее яркими ее моментами) пакетов cc_smartargs и cc_arguments утилиты Codecheck. Итак, что осталось?

  • cc_report
  • cc_names
  • cc_types

(О да, и есть еще реальное тестирование всего этого кода, чтобы проверить работает ли он! Но оставим этот вопрос для обсуждения в заключительной статье этой серии – Статье 8 – которая появится в свое время).

Предоставление информации о типах данных

Пакет cc_types – это один из моих небольших низкоуровневых модулей. Его назначение – предоставить знания (некоторые из них совершенно скрыты) о различных типах данных, поддерживаемых Oracle вообще, и PL/SQL в частности.

В таблице 1 описаны программы, которые потребовались мне в cc_smartargs и cc_arguments.

В начале спецификации пакета вы увидите длинный список именованных констант, которые служат той же цели, что и в cc_smartargs. В этом случае, я дал имена значениям типов данных, так например:

   c_date CONSTANT PLS_INTEGER := 12;

Codecheck использует эти константы для заполнения коллекции, которую функция cc_types.name использует для перевода кода в название. Фактически, почти все функции пакета cc_types используют преимущества коллекций для хранения и легкой выборки различных видов информации, связанной с типами.

Давайте посмотрим на простой пример такого использования коллекции – функцию cc_types.is_composite_type, и затем разберем наиболее сложный аспект пакета cc_types - определение, принадлежат ли два типа данных одному семейству.

Использование коллекций для хранения знаний

Реализация cc_types.is_composite_type проста:

FUNCTION is_composite_type (type_in IN PLS_INTEGER)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN c_composite_types.EXISTS (type_in);
   END;

Иначе говоря, в функцию передается код типа данных и, если строка для этого числового кода существует в коллекции c_composite_types, то это сложный тип данных. Я объявляю тип коллекции и саму коллекцию в начале тела пакета:

  TYPE booleans_tt IS TABLE OF BOOLEAN INDEX BY BINARY_INTEGER;
   c_composite_types    booleans_tt;

Я использую таблицу булевских значений, потому что мне не нужно хранить что-либо еще, кроме индикатора в этих строках коллекции: в качестве номера строки используется код типа данных, а все остальное вытекает из этого. Я объявляю эту таблицу с префиксом c_, потому что хочу, чтобы она трактовалась как константа, даже если я не объявил ее как таковую (чтобы сделать это, мне пришлось бы инициализировать ее функцией, объявленной в другом пакете; не стоит усложнять программу).

Я создал программу для загрузки коллекции:

   PROCEDURE load_composite_types
   IS
   BEGIN
      c_composite_types (c_record) := TRUE ;
      c_composite_types (c_indexby_table) := TRUE ;
      c_composite_types (c_object_type) := TRUE ;
      c_composite_types (c_nested_table) := TRUE ;
      c_composite_types (c_varray) := TRUE ;
   END;

И я вызвал эту процедуру в разделе инициализации пакета вместе с несколькими другими программами, заполняющими коллекции (мы посмотрим на load_in_same_family в следующем разделе):

BEGIN
   load_in_same_family;
   load_trouble_types;
   load_type_translators;
   load_composite_types;
END cc_types;

Мне нет необходимости помнить, какие типы данных являются сложными. И если Oracle введет новый сложный тип данных, я просто добавлю одно присваивание в процедуру load_composite_types, и is_composite_type распознает его.

Анализ принадлежности к одному семейству в cc_types

Чтобы определить неоднозначные перегрузки мне необходимо сравнивать типы данных соответствующих параметров. Если параметры одного типа, то это потенциальная неоднозначность. Но даже, если их тип не совпадает в точности, могут возникнуть проблемы – достаточного того, чтобы типы данных принадлежали одному “семейству”, как в случае с NUMBER и INTEGER.

Вопрос, принадлежат ли два типа данных одному семейству, естественно относится к области действия cc_types; и cc_types.in_same_family является функцией, которая отвечает на него. Но теперь я сталкиваюсь с вопросом, КАК я буду реализовывать эту функцию?

Может быть, можно сделать что-нибудь подобное функции cc_types.is_composite_type, а именно:

   FUNCTION in_same_family (
      type1_in IN PLS_INTEGER, type2_in IN PLS_INTEGER)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN same_family.EXISTS (type1_in, type2_in);
   END;

Да, внешне это выглядит привлекательно, но это не работает – по крайней мере, в таком виде. Я должен сравнить два различных типа, но Oracle пока еще не поддерживает многомерные массивы, которые подразумеваются синтаксисом:

same_family.EXISTS (type1_in, type2_in) or same_family (type1_in, type2_in). 

Что делать разработчику? Эмулировать. Даже если нельзя создать реальный двумерный массив, можно достигнуть такого же эффекта, используя вложенные коллекции: одна внутренняя коллекция в каждой строке внешней коллекции. Вот необходимое объявление:

TYPE booleans_tt IS TABLE OF BOOLEAN
      INDEX BY BINARY_INTEGER;

   TYPE type_families_tt IS TABLE OF booleans_tt
      INDEX BY BINARY_INTEGER; 
	  	  
   type_families type_families_tt;

Используя эту коллекцию, можно переписать первоначальный вариант in_same_family следующим образом:

   FUNCTION in_same_family (
      type1_in IN PLS_INTEGER, type2_in IN PLS_INTEGER)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN type_families (type1_in).EXISTS (type2_in);
   END;

На первый взгляд все в порядке, но что если строка для type1_in не существует? Функция вызовет исключение NO_DATA_FOUND (кое-чего можно избежать, используя метод EXISTS). Поэтому, на самом деле, нужно делать так:

   FUNCTION in_same_family (
      type1_in IN PLS_INTEGER, type2_in IN PLS_INTEGER)
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN type_families (type1_in).EXISTS (type2_in);
   EXCEPTION
      WHEN NO_DATA_FOUND THEN RETURN FALSE;
   END;

Однако код требует, чтобы разработчик-создатель (и, что более важно, человек, поддерживающий код) понимал важность раздела исключений. Вероятно, нужно добавить комментарий, чтобы объяснить. Но всякий раз, когда я говорю: “Я должен объяснить это в комментарии”, я останавливаю себя и спрашиваю: “Можно ли переписать эту логику так, чтобы необходимость в комментарии отпала – так, чтобы код был самодокументированным”?

В этом случае, я полагаю, можно. Посмотрите на следующую реализацию:

   FUNCTION in_same_family (
      type1_in IN PLS_INTEGER, type2_in IN PLS_INTEGER)
      RETURN BOOLEAN
   IS
   BEGIN
      IF NOT type_families.EXISTS (type1_in)
	     THEN 
	        RETURN FALSE;
	     ELSE
	        RETURN type_families (type1_in).EXISTS (type2_in);
      END IF;
   END;

Мне не нужно больше беспокоиться о NO_DATA_FOUND, потому что я использовал EXISTS, чтобы проверить наличие обоих строк. И теперь в самом коде говорится явно: “если строка первого типа не существует, возвращаем FALSE. Иначе, возвращаем TRUE, если строка второго типа также существует”. Иначе говоря, я отмечаю эти комбинации, как очень похожие. Я полагаю, вы согласитесь, что это превосходная реализация.

Однако нужно еще понять, как заполнить этот двумерный (как бы) массив. Давайте посмотрим на конкретный пример: семейство строковых типов. Все эти типы – строковые, они будут неразличимы для компилятора:
VARCHAR2, VARCHAR, CHAR, LONG, NVARCHAR2, и NCHAR.

Массив необходимо заполнить так, чтобы:

  • Каждый тип данных был зарегистрирован как принадлежащий к тому же самому семейству, что и он сам.
  • Каждый тип данных был зарегистрирован как принадлежащий к тому же самому семейству, что и другие.

Давайте рассмотрим все поэтапно. Для каждых двух кодов типов данных, можно покрыть все перестановки для двух вышеприведенных условий в программе, приведенной в Листинге 4. Строки 7 и 8 обеспечивают, что тип данных будет отмечен как принадлежащий тому же самому семейству, что и он сам. Строки 9 и 10 строят список семейств для различных типов данных. Достаточно просто, но как теперь использовать эту процедуру? Каким образом сказать ей, какие комбинации нужно загрузить, так чтобы это было кратко и удобно для чтения?

Допустим, я создам список всех типов данных, принадлежащих одному семейству (назовем его one_family_tt). Затем можно запустить генерацию всех перестановок, следующим образом:

PROCEDURE load_one_family (family_in IN one_family_tt)
IS
BEGIN
   FOR o_index IN family_in.FIRST .. family_in.LAST
   LOOP
      FOR i_index IN family_in.FIRST .. family_in.LAST
      LOOP
         load_permutations (o_index, i_index);
      END LOOP;
   END LOOP;
END load_one_family;

Теперь можно завершить реализацию программы, которая загружает двухуровневую коллекцию, используя in_same_family (см. Листинг 5). В таблице 2 приведено объяснение кода, которое поможет вам разобраться в логике. Я должен сказать, что этот код объясняет сам себя. Легко ли им управлять? Допустим, что мне необходимо добавить другой тип данных в строковое семейство, тогда я просто добавлю элемент в список конструктора. Если потребуется добавить целое новое семейство, то я вставлю другой вызов load_one_family с соответствующим списком типов данных.

Я завершил объяснение реализации логики, необходимой для распознавания, принадлежат ли два типа данных одному семейству, и показал наиболее яркие моменты пакета cc_types. В следующей статье я объясню, как использовать преимущества очень полезной процедуры DBMS_UTILITY.NAME_RESOLVE, и рассмотрю отдельные механизмы отчетности для Codecheck. Я также покажу вам общий пакет обработки ошибок, который включает механизм утверждений и изящную процедуру RAISE.

Таблица 1: Программы, используемые cc_smartargs и cc_arguments

Программа Назначение
FUNCTION NAME (code_in IN PLS_INTEGER) RETURN VARCHAR2 Перевод кода типа данных в название
FUNCTION is_composite_type ( type_in IN PLS_INTEGER) RETURN BOOLEAN Возвращает TRUE, если заданный код типа данных означает сложный тип данных.
FUNCTION is_record_type ( type_in IN PLS_INTEGER) RETURN BOOLEAN Возвращает TRUE, если заданный код типа данных означает тип запись.
FUNCTION is_rowtype ( type_in IN PLS_INTEGER, type_subname_in in varchar2) RETURN BOOLEAN Возвращает TRUE, если заданный код типа данных означает тип запись, объявленную с %ROWTYPE.
FUNCTION in_same_family ( type1_in IN PLS_INTEGER, type2_in IN PLS_INTEGER) RETURN BOOLEAN Возвращает TRUE, если два типа данных являются членами одного семейства.

Таблица 2: Объяснение кода Листинга 5

Строка(и) Назначение
3 Объявляем вложенную таблицу (первую, использованную в Codecheck) для хранения списка кодов типов данных для одного семейства.
5-6 Реализация двух локальных процедур, о которых я говорил раньше.
8-14 Я вызываю load_one_family, чтобы загрузить все необходимые перестановки для семейства строковых типов данных. Обратите внимание, что поскольку я решил работать с вложенными таблицами, я могу использовать конструктор для этого типа напрямую в вызове load_one_family. У меня нет необходимости объявлять локальную переменную для хранения коллекции. Я просто передаю коды различных типов конструктору, он возвращает вложенную таблицу; и затем эта таблица передается программе, чтобы загрузить все перестановки в коллекцию type_families.
15 Эллипсы (...) означают повторяющиеся вызовы для каждого из различных семейств типов данных, которые могут создавать неоднозначные перегрузки (такие как date, timestamp, и number). Подробности приведены в исходном коде.
E-mail this page