Введение в генерацию программного кода

Приложение А. Пример генератора пакетов PL/SQL

Разбить на страницы
Показывать лекцию целиком

Описание

Рассмотрим пример генерации кода пакета для одной таблицы. Пакет будет содержать функциональность для выполнения базовых и стандартных операций над записями в таблице. Это поиск и фильтрация записей, обновление, вставка и удаление строки. То есть все те операции, которые необходимо выполнять над практически любой таблицей в любой базе данных. Возьмем в качестве примера немного измененную таблицу drawing из лекции 3. Поля таблицы drawing следующие:

  • id (идентификатор),
  • name (название чертежа),
  • title (описание),
  • revision (номер последней версии чертежа),
  • type_id (идентификатор типа документа, внешний ключ).
  • Для начала необходимо будет сгенерировать код создания объектов. Первый объект, который нужно создать, это сама таблица:

    create table drawing ( 
    id            number primary key,
    name            varchar2(100 char),
    title            varchar2(1000 char),
    revision            varchar2(10 char),
    type_id            number,
    update_date            date,
    update_user_id            number
    )
        

    Как видим, кроме указанных полей имеются также поля update_date - для сохранения даты последнего обновления строки и update_user_id - для сохранения идентификатора пользователя (внешнего ключа), выполнившего последнее обновление строки таблицы. Здесь предполагается, что в базе имеется таблица пользователей приложения. Более подробно в примерах она рассматриваться не будет.

    Последовательность

    Нужен также запрос на создание последовательности (sequence) Oracle по следующему шаблону:

    create sequence seq_drawing
    start with 1
    maxvalue 999999999999999999999999999
    minvalue 1
    nocycle
    nocache
    noorder
        

    Спецификация пакета

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

    create or replace package pkg_drawing as 
    
        --------function to create search query------------------
        function fun_search_query(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number
        ) return varchar2;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_update_user_id in number);
    
        --------search procedure------------------
        procedure prc_search(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor);
    
        --------count procedure------------------
        procedure prc_count(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_pagesize in number,
            p_recordset out types.ref_cursor);
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor );
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number);
    
    end pkg_drawing;
    /
        

    Описание содержимого пакета

    В таблице представлены названия и описания стандартных функций и процедур, находящихся в пакете:

    fun_search_query функция, формирующая текст запроса с условиями пакета. В нем для формирования части запроса используются функции пакета pkg_lib. То есть фактически генерируемый пакет сам генерирует часть текста запроса;
    prc_search процедура, возвращающая результат поиска согласно указанным критериям в виде ссылки на курсор. Также сортирует результат поиска по параметрам p_order_by, p_order_type;
    prc_count возвращает в виде курсора количество записей при поиске по указанным критериям, а также количество страниц с записями при размере страницы p_pagesize;
    prc_save сохраняет запись в таблице базы данных. Если запись уже существует, то она обновляется (применяется запрос UPDATE), иначе создается новая (применяется запрос INSERT);
    prc_delete удаляет запись из таблицы базы данных;
    prc_show_by_id возвращает в результате поиска одну запись по указанному первичному ключу.

    В теле пакета также имеется константа, содержащая часть текста запроса:

    const_sqltxt constant varchar2(1000 char):= 'select '||
                ' id,number, title, revision, type_id, update_date, update_user_id'|| 
                ' from drawing tbl where 1=1 ';
        

    Впоследствии к этому запросу будут добавляться необходимые условия для фильтрации и сортировки. Хотя текст запроса генерируется только для одной таблицы, генератор можно усовершенствовать для создания запроса с участием нескольких таблиц, с учетом их взаимосвязей. Для простоты часть текста запроса сформирована просто как "where 1=1".

    Разработанный вручную код

    В коде приложения всегда содержится код, который надо разработать вручную. Прежде чем рассмотреть код тела сгенерированного пакета для лучшего его понимания изучим применяемые разработанные вручную пакеты. Это два пакета types и pkg_lib, которые вызываются из генерируемых пакетов.

    Пакет types содержит только объявление ссылки на курсор REF CURSOR. Она используется в тех процедурах пакета, где требуется извлечь записи - результат запроса SELECT. Это сделано таким образом, чтобы другое приложение, например веб-страница, могла вызвать нужную ей процедуру и получить соответствующий набор записей.

    create or replace package types is
      type ref_cursor is REF CURSOR;
    end types;
        

    В пакете pkg_lib содержатся функции работы со строками. Используются они для формирования отдельных частей запросов SELECT.

    fun_add_equal формирует часть строки запроса с условием выбора в виде "and column1 = value1". Имеет параметр p_def, хранящий значение (value), при равенстве которому p_ value функция возвращает пустой результат. Это позволяет по необходимости включать/выключать условия фильтрации для разных полей.
    fun_add_equal_m1 делает то же самое, что и предыдущая функция, но параметр p_def принимает значение "-1".
    fun_add_like выводит условие фильтрации поля в виде "and column1 like '%value1%'". Если параметр p_value пустой, то функция возвращает пустой результат.
    fun_sorting_query выводит условие сортировки вида "order by column1 <asc/desc>".

    Спецификация пакета pkg_lib:

    create or replace package pkg_lib as
    
    function fun_add_equal(p_name varchar2, p_value varchar2, p_def varchar2) return varchar2;
    
    function fun_add_equal_m1(p_name varchar2, p_value varchar2) return varchar2;
    
    function fun_add_like(p_name varchar2, p_value varchar2) return varchar2;
    
    function fun_sorting_query(p_order_by varchar2, p_order_type varchar2, p_def varchar2) return varchar2;
    
    end pkg_lib;
        

    Тело пакета pkg_lib:

    create or replace package body pkg_lib as
    
    function fun_add_equal(p_name varchar2, p_value varchar2, p_def varchar2) return varchar2 is 
             p_result varchar2(2000 char):='';
             begin
                  if p_value <> p_def then
                     p_result := p_result || 'and ' || p_name || ' = ' || '''' || p_value || '''';
                  end if; 
                  return p_result;
             end;
             
    function fun_add_equal_m1(p_name varchar2, p_value varchar2) return varchar2 is
             begin
                  return fun_add_equal(p_name, p_value, '-1');
             end;
             
    function fun_add_like(p_name varchar2, p_value varchar2) return varchar2 is 
             p_result varchar2(2000 char):='';
             begin
                  if length(p_value) > 0 then
                     p_result := p_result || ' and lower (' || p_name || ') like lower (''%' || p_value || '%'')';
                  end if; 
                  return p_result;
             end;
             
    function fun_sorting_query(p_order_by varchar2, p_order_type varchar2, p_def varchar2) return varchar2 is
             p_result varchar2(2000 char) :=' order by ';
             p_order_temp varchar2(2000 char);
             p_type_temp varchar2(2000 char);
             begin
                if length(p_order_by) > 0 then
                   p_order_temp := p_order_by;
                else
                    p_order_temp := p_def;
                end if;
                if length(p_order_type) > 0 then
                   p_type_temp := p_order_type;
                else
                    p_type_temp := 'asc';
                end if;
                return p_result || ' ' || p_order_temp || ' ' || p_type_temp;
             end;  
             
    end pkg_lib;
        

    Тело генерируемого пакета

    Ниже приводится код тела пакета для таблицы drawing в том виде, каким он должен быть сгенерирован.

    create or replace package body pkg_drawing as 
        const_sqltxt constant varchar2(1000 char):= 'select '||
                    ' id,name, title, revision, type_id, update_date, update_user_id'|| 
                    ' from drawing tbl where 1=1 '; 
    
        --------function to create search query------------------
        function fun_search_query(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number
            ) return varchar2 is
            p_wheretxt varchar2(4000);
        begin
            --------------equals------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
    
            --------------likes------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.name',p_name);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.title',p_title);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.revision',p_revision);
            
            return const_sqltxt||p_wheretxt;
        end;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_update_user_id in number) is 
        begin
            if p_id > 0 then 
                update drawing set
                    name = p_name,
                    title = p_title,
                    revision = p_revision,
                    type_id = p_type_id,
                    update_user_id = p_update_user_id,
                    update_date = sysdate
                where id = p_id;
                commit;
            else
                insert into drawing(id,name, title, revision, type_id, update_date, update_user_id)
                values (seq_drawing.nextval,p_name, p_title, p_revision, p_type_id, sysdate, p_update_user_id);
                commit;
            end if;
        end;
    
        --------search procedure------------------
        procedure prc_search(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor) is 
                sqltxt varchar2(4000);
                p_sqltxt varchar2(4000);
                p_sqltxt_page varchar2(4000);
                p_startpage number;
                p_maxpage number;
                p_ordersqltxt varchar2(1000);
        begin
            p_startpage := p_pagesize*(p_page-1);
            p_maxpage := p_pagesize*p_page;
            p_sqltxt := fun_search_query(p_name, p_title, p_revision, p_type_id);
            p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'tbl.id');
            p_sqltxt_page := 'select * from (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||
                    'where rnum<=:max_row_to_fetch and rnum > :min_row_to_fetch ' || p_ordersqltxt;
            open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;
        end;
    
        --------count procedure------------------
        procedure prc_count(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
                p_pagesize in number,
                p_recordset out types.ref_cursor) is 
            p_sqltxt varchar2(4000);
        begin
            p_sqltxt := fun_search_query(p_name, p_title, p_revision, p_type_id);
            p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) pagecount from ('||p_sqltxt||') ';
            open p_recordset for p_sqltxt using p_pagesize;
        end;
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor ) is 
        begin
            if p_id > 0 then
                open p_recordset for
                const_sqltxt || ' and tbl.id = :p_id' using p_id;
            end if;
        end;
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number) is 
            t_var number;
        begin
            select count(1) into t_var from drawing where id = p_id;
            if t_var > 0 then
                delete from drawing where id = p_id;
            commit;
            end if;
        end;
    
    end pkg_drawing;
    /
        

    Прежде чем рассматривать код генератора приведем код класса таблицы, который используется в генераторе:

    class DBTable
    {
        //Название таблицы
        public string TableName;
    
        //Количество полей в таблице
        public int FieldCount;
    
        //Массив с названиями полей таблицы
        public string[] FieldName;
    
        //Массив с названиями типов полей таблицы
        public string[] FieldTypeName;
    
        //Массив с указанием длины строковых полей таблицы
        public string[] FieldLength;
    
        //Часть запроса для сортировки по умолчанию
        public string defaultOrderString;
    
        //Часть пути для сохранения сгенерированного кода
        public string filesPath;
    
    }
        

    А теперь приведем код класса CodeGenerator. Пояснения по коду даны в комментариях.

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Text;
    using System.IO;
    
    namespace PLSQLCodeGenerator
    {
        class CodeGenerator
        {
            //Константы tab1, tab2, tab3, tab4 предназначены для установки отступов
            //в начале строк генерируемого приложения.
            public const string tab1 = "    ";
            public const string tab2 = "        ";
            public const string tab3 = "            ";
            public const string tab4 = "                ";
    
            //В этих константах хранятся часто применяемые участки кода.
            public const string p_recordset = " p_recordset out types.ref_cursor ";
            public const string add_equal_m1 = "p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1";
            public const string add_like = "p_wheretxt := p_wheretxt || pkg_lib.fun_add_like";
    
            //Здесь хранятся окончания имен для генерируемых файлов
            //В трех файлах сохраняются запросы на создание объектов,
            //спецификация и тело пакета.
            public const string obj_filename = @"Objects.txt";
            public const string package_spec_filename = @"PackageSpec.txt";
            public const string package_body_filename = @"PackageBody.txt";
    
            //Сокращенное имя таблицы по умолчанию.
            public const string tbl = "tbl";
    
            //Процедура для вывода сгенерированного результата в файл.
            public void PutResult(List<string> pList, String filePath)
            {
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
                using (StreamWriter sw = File.CreateText(filePath))
                {
                    foreach (string str in pList)
                    {
                        sw.WriteLine(str);
                    }
                }
            }
    
            //Возвращается текст списка переменных
            //в виде: "p_number, p_title, p_revision, p_type_id",
            //используется при генерации тела пакета.
            public string GetParamListMain(DBTable table)
            {
                string result = "";
                for (int i = 0; i < table.FieldCount; i++)
                {
                    result = result + "p_" + table.FieldName[i];
                    if (i < table.FieldCount - 1) result = result + ", ";
    
                }
                return result;
            }
    
            //К тексту списка переменных добавляются:
            //в начало - следующее значение последовательности
            //в конец - текущая дата и идентификатор пользователя.
            //Используется при генерации запроса вставки строки.
            //Результат получается следующего вида:
            //"seq_drawing.nextval,p_number, p_title, p_revision, p_type_id, sysdate, p_update_user_id".
            public string GetParamListAll(DBTable table)
            {
                return "seq_" + table.TableName + ".nextval," + GetParamListMain(table) +
                   ", sysdate, p_update_user_id";
            }
    
            //Возвращается текст списка полей таблицы
            //в виде: "number, title, revision, type_id".
            //Используется при генерации запросов SELECT и INSERT.
            public string GetFieldListMain(DBTable table)
            {
                string result = "";
                for (int i = 0; i < table.FieldCount; i++)
                {
                    result = result + table.FieldName[i];
                    if (i < table.FieldCount - 1) result += ", ";
                }
                return result;
            }
    
            //К тексту списка полей таблицы добавляются
            //поля идентификатора таблицы, даты последнего обновления,
            //а также идентификатора пользователя.
            //Результат получается следующего вида:
            //"id,number, title, revision, type_id, update_date, update_user_id".
            public string GetFieldListAll(DBTable table)
            {
                return "id," + GetFieldListMain(table) + ", update_date, update_user_id";
            }
    
            //Создается список параметров, применяемый
            //в сигнатурах функций и процедур как в теле,
            //так и в спецификации пакета.
            //Результат получается следующего вида:
            //        p_number in varchar2,
            //        p_title in varchar2,
            //        p_revision in varchar2,
            //        p_type_id in number
            public List<string> GetParametersDeclaration(DBTable table, bool is_last)
            {
                //Переменная result имеет тип List<string> и является набором строк.
                //В ней сохраняется сгенерированный код, который впоследствии
                //может быть выведен в файл.
                List<string> result = new List<string>();
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = tab2;
                    temp += "p_" + table.FieldName[i] + " in " + table.FieldTypeName[i];
                    if (i < table.FieldCount - 1 || !is_last) temp += ",";
                    result.Add(temp);
                }
                return result;
            }
    
            //Формируется запрос на создание таблицы.
            //Результат возвращается в виде набора строк.
            public List<string> GetCreateTable(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add("create table " + table.TableName + " ( ");
                result.Add("id" + tab3 + "number primary key,");
                //В цикле для каждого поля таблицы
                //формируется код объявления.
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = "";
                    temp = table.FieldName[i] + tab3 + table.FieldTypeName[i];
                    if (!(table.FieldLength[i] == ""))
                    {
                        temp += "(" + table.FieldLength[i] + ")";
                    }
                    temp += ",";
                    result.Add(temp);
                }
                //Всегда добавляются
                //поля update_date и update_user_id.
                result.Add("update_date" + tab3 + "date,");
                result.Add("update_user_id" + tab3 + "number");
                result.Add(")");
                return result;
            }
    
            //Формирование запроса на создание последовательности Oracle.
            public List<string> GetCreateSequence(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add("create sequence seq_" + table.TableName);
                result.Add("start with 1");
                result.Add("maxvalue 999999999999999999999999999");
                result.Add("minvalue 1");
                result.Add("nocycle");
                result.Add("nocache");
                result.Add("noorder");
                return result;
            }
    
            //В одном файле сохраняются запросы
            //на создание таблицы и последовательности.
            public void SaveCreateObjects(DBTable table)
            {
                List<string> result = new List<string>();
                //В набор строк добавляется запрос на создание таблицы.
                result.AddRange(GetCreateTable(table));
                //Запросы будут разделены пустой строкой.
                result.Add("");
                //В набор строк добавляется запрос на создание последовательности.
                result.AddRange(GetCreateSequence(table));
                PutResult(result, table.filesPath + obj_filename);
            }
    
            //Функция для создания кода спецификации пакета.
            public List<string> GetPackageSpecification(DBTable table)
            {
                List<string> result = new List<string>();
                //Объявление спецификации пакета.
                result.Add("create or replace package pkg_" + table.TableName + " as ");
    
                //Объявление функции, формирующей текст запроса.
                result.Add("");
                result.Add(tab1 + "--------function to create search query------------------");
                result.Add(tab1 + "function fun_search_query(");
                result.AddRange(GetParametersDeclaration(table, true));
                result.Add(tab1 + ") return varchar2;");
    
                //Объявление процедуры сохранения записи.
                result.Add("");
                result.Add(tab1 + "--------procedure to save------------------");
                result.Add(tab1 + "procedure prc_save(p_id number,");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_update_user_id in number);");
    
                //Объявление процедуры поиска записей по заданным критериям.
                result.Add("");
                result.Add(tab1 + "--------search procedure------------------");
                result.Add(tab1 + "procedure prc_search(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_page in number,");
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_order_by varchar2,");
                result.Add(tab2 + "p_order_type varchar2,");
                result.Add(tab2 + "p_recordset out types.ref_cursor);");
    
                //Объявление процедуры подсчета количества записей по заданным критериям.
                result.Add("");
                result.Add(tab1 + "--------count procedure------------------");
                result.Add(tab1 + "procedure prc_count(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_recordset out types.ref_cursor);");
    
                //Объявление процедуры выборки одной строки по заданному идентификатору.
                result.Add("");
                result.Add(tab1 + "--------show one item procedure ------------------");
                result.Add(tab1 + "procedure prc_show_by_id(p_id number, " + p_recordset + ");");
    
                //Объявление процедуры удаления строки.
                result.Add("");
                result.Add(tab1 + "--------delete procedure ------------------");
                result.Add(tab1 + "procedure prc_delete(p_id number);");
    
                //Конец спецификации пакета
                result.Add("");
                result.Add("end pkg_" + table.TableName + ";");
                result.Add("/");
                
                //Возвращается набор строк, результат работы процедуры.
                return result;
            }
    
            //Сохранение спецификации пакета в отдельном файле.
            public void SavePackageSpecification(DBTable table)
            {
                PutResult(GetPackageSpecification(table), table.filesPath + package_spec_filename);
            }
    
            //Здесь формируется код функции создания запроса.
            public List<string> GetSearchFunction(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------function to create search query------------------");
                result.Add(tab1 + "function fun_search_query(");
                result.AddRange(GetParametersDeclaration(table, true));
                result.Add(tab2 + ") return varchar2 is");
    
                result.Add(tab2 + "p_wheretxt varchar2(4000);");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "--------------equals------------------------");
    
                //В цикле формируется код вида:
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
                for (int i = 0; i < table.FieldCount; i++)
                {
                    if (table.FieldTypeName[i] == "number")
                    {
                        result.Add(tab2 + add_equal_m1 + "('" + tbl + "." + table.FieldName[i] + 
                           "',p_" + table.FieldName[i] + ");");
                    }
                }
                result.Add("");
                result.Add(tab2 + "--------------likes------------------------");
    
                //В цикле формируется код вида:
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.number',p_number);
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.title',p_title);
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.revision',p_revision);
                for (int i = 0; i < table.FieldCount; i++)
                {
                    if (table.FieldTypeName[i] == "varchar2")
                    {
                        result.Add(tab2 + add_like + "('" + tbl + "." + table.FieldName[i] + 
                           "',p_" + table.FieldName[i] + ");");
                    }
                }
    
                result.Add(tab2);
                result.Add(tab2 + "return const_sqltxt||p_wheretxt;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры поиска записей.
            public List<string> GetSearchProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------search procedure------------------");
                result.Add(tab1 + "procedure prc_search(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_page in number,");
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_order_by varchar2,");
                result.Add(tab2 + "p_order_type varchar2,");
                result.Add(tab2 + "p_recordset out types.ref_cursor) is ");
    
                result.Add(tab3 + "sqltxt varchar2(4000);");
                result.Add(tab3 + "p_sqltxt varchar2(4000);");
                result.Add(tab3 + "p_sqltxt_page varchar2(4000);");
                result.Add(tab3 + "p_startpage number;");
                result.Add(tab3 + "p_maxpage number;");
                result.Add(tab3 + "p_ordersqltxt varchar2(1000);");
    
                result.Add(tab1 + "begin");
                result.Add(tab2 + "p_startpage := p_pagesize*(p_page-1);");
                result.Add(tab2 + "p_maxpage := p_pagesize*p_page;");
                result.Add(tab2 + "p_sqltxt := fun_search_query(" + GetParamListMain(table) + ");");
                result.Add(tab2 + "p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'" 
                   + tbl + "." + table.defaultOrderString + "');");
                result.Add(tab2 + "p_sqltxt_page := 'select * from 
                   (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||");
                result.Add(tab4 + "'where rnum<=:max_row_to_fetch and rnum >
                :min_row_to_fetch ' || p_ordersqltxt;");
                result.Add(tab2 + "open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры сохранения записи.
            public List<string> GetSaveProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------procedure to save------------------");
                result.Add(tab1 + "procedure prc_save(p_id number,");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_update_user_id in number) is ");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "if p_id > 0 then ");
                result.Add(tab3 + "update " + table.TableName + " set");
    
                //В этом цикле генерируется часть запроса UPDATE вида:
                //  number = p_number,
                //  title = p_title,
                //  revision = p_revision,
                //  type_id = p_type_id,
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = tab4;
                    temp += table.FieldName[i] + " = p_" + table.FieldName[i] + ",";
                    result.Add(temp);
                }
    
                result.Add(tab4 + "update_user_id = p_update_user_id,");
                result.Add(tab4 + "update_date = sysdate");
                result.Add(tab3 + "where id = p_id;");
                result.Add(tab3 + "commit;");
                result.Add(tab2 + "else");
                result.Add(tab3 + "insert into " + table.TableName + 
                "(" + GetFieldListAll(table) + ")");
                result.Add(tab3 + "values (" + GetParamListAll(table) + ");");
                result.Add(tab3 + "commit;");
                result.Add(tab2 + "end if;");
    
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры подсчета количества записей.
            public List<string> GetCountProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------count procedure------------------");
                result.Add(tab1 + "procedure prc_count(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab3 + "p_pagesize in number,");
                result.Add(tab3 + "p_recordset out types.ref_cursor) is ");
                result.Add(tab2 + "p_sqltxt varchar2(4000);");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "p_sqltxt := fun_search_query
                (" + GetParamListMain(table) + ");");
                result.Add(tab2 + "p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) 
                pagecount from ('||p_sqltxt||') ';");
                result.Add(tab2 + "open p_recordset for p_sqltxt using p_pagesize;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры выборки одной записи.
            public List<string> GetShowOneItemProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------show one item procedure ------------------");
                result.Add(tab1 + "procedure prc_show_by_id(p_id number, 
                " + p_recordset + ") is ");
    
                result.Add(tab1 + "begin");
    
                result.Add(tab2 + "if p_id > 0 then");
                result.Add(tab3 + "open p_recordset for");
                result.Add(tab3 + "const_sqltxt || ' and " + tbl + ".id =
                 :p_id' using p_id;");
                result.Add(tab2 + "end if;");
    
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры удаления записи.
            public List<string> GetDeleteProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------delete procedure ------------------");
                result.Add(tab1 + "procedure prc_delete(p_id number) is ");
                result.Add(tab2 + "t_var number;");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "select count(1) into t_var from " + 
                table.TableName + " where id = p_id;");
                result.Add(tab2 + "if t_var > 0 then");
                result.Add(tab3 + "delete from " + table.TableName + 
                " where id = p_id;");
                result.Add(tab2 + "commit;");
                result.Add(tab2 + "end if;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Функция для создания кода тела пакета.
            public List<string> GetPackageBody(DBTable table)
            {
                List<string> result = new List<string>();
                //Начало пакета.
                result.Add("create or replace package body pkg_" + 
                table.TableName + " as ");
    
                //Генерация запроса и константы, в которой она будет сохранена.
                result.Add(tab1 + "const_sqltxt constant varchar2(1000 char):= 'select '||");
                result.Add(tab4 + "' " + GetFieldListAll(table) + "'|| ");
                result.Add(tab4 + "' from " + table.TableName + " " +
                 tbl + " where 1=1 '; ");
    
                //Добавление к телу пакета кода функции, возвращающей запрос.
                result.Add("");
                result.AddRange(GetSearchFunction(table));
    
                //Добавление к телу пакета кода процедуры, сохраняющей запись.
                result.Add("");
                result.AddRange(GetSaveProcedure(table));
    
                //Добавление к телу пакета кода процедуры, выполняющей поиск записей.
                result.Add("");
                result.AddRange(GetSearchProcedure(table));
    
                //Добавление к телу пакета кода процедуры, возвращающей количество записей.
                result.Add("");
                result.AddRange(GetCountProcedure(table));
    
                //Добавление к телу пакета кода процедуры, возвращающей одну запись.
                result.Add("");
                result.AddRange(GetShowOneItemProcedure(table));
    
                //Добавление к телу пакета кода процедуры, удаляющей запись.
                result.Add("");
                result.AddRange(GetDeleteProcedure(table));
    
                //Конец тела пакета.
                result.Add("");
                result.Add("end pkg_" + table.TableName + ";");
                result.Add("/");
    
                return result;
            }
    
            //Сохранение тела пакета в одном файле.
            public void SavePackageBody(DBTable table)
            {
                PutResult(GetPackageBody(table), table.filesPath + package_body_filename);
            }
    
            //Конструктор, вызывающий процедуры для генерации кода
            //создания объектов, спецификации и тела пакета
            //и сохранения их в трех файлах.
            public CodeGenerator(DBTable table)
            {
                SaveCreateObjects(table);
                SavePackageSpecification(table);
                SavePackageBody(table);
            }
        }
    }
        

    А теперь посмотрим на код, который будет сгенерирован для таблицы equipment. Поля таблицы equipment следующие:

  • id (идентификатор),
  • tag_number (номер ярлыка оборудования),
  • model (наименование модели),
  • description (краткое описание назначения оборудования),
  • type_id (идентификатор типа оборудования, является внешним ключом).
  • Следующий код можно добавить в событие кнопки для настольного приложения или событие запуска консольного приложения:

    //Объявление таблицы, его названия и количества полей
    DBTable table = new DBTable();
    table.TableName = "equipment";
    table.FieldCount = 4;
    
    //Метаданные для простоты примера заполняются напрямую в коде.
    //Однако их можно считывать и из базы данных или файла XML.
    table.FieldName = new string[table.FieldCount];
    table.FieldTypeName = new string[table.FieldCount];
    table.FieldLength = new string[table.FieldCount];
    
    table.FieldName[0] = "tag_number";
    table.FieldTypeName[0] = "varchar2";
    table.FieldLength[0] = "100 char";
    
    table.FieldName[1] = "model";
    table.FieldTypeName[1] = "varchar2";
    table.FieldLength[1] = "100 char";
    
    table.FieldName[2] = "description";
    table.FieldTypeName[2] = "varchar2";
    table.FieldLength[2] = "200 char";
    
    table.FieldName[3] = "type_id";
    table.FieldTypeName[3] = "number";
    table.FieldLength[3] = "";
    
    table.defaultOrderString = "id";
    
    //Указывается путь и часть названия файла вида:
    //"A:\Result\equipment_" для вывода результатов.
    //К нему впоследствии добавляется часть названия 
    //файла в зависимости от типа сгенерированного кода:
    //A:\Result\equipment_Objects.txt
    //A:\Result\equipment_PackageSpec.txt
    //A:\Result\equipment_PackageBody.txt
    table.filesPath = @"A:\Result\" + table.TableName + "_";
    
    //Вызывается конструктор для осуществления генерации кода
    CodeGenerator cg = new CodeGenerator(table);
        

    После запуска будет сгенерировано три файла.

    Файл equipment_Objects.txt:

    create table equipment ( 
    id            number primary key,
    tag_number            varchar2(100 char),
    model            varchar2(100 char),
    description            varchar2(200 char),
    type_id            number,
    update_date            date,
    update_user_id            number
    )
    
    create sequence seq_equipment
    start with 1
    maxvalue 999999999999999999999999999
    minvalue 1
    nocycle
    nocache
    noorder
        

    Файл equipment_PackageSpec.txt:

    create or replace package pkg_equipment as 
    
        --------function to create search query------------------
        function fun_search_query(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number
        ) return varchar2;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_update_user_id in number);
    
        --------search procedure------------------
        procedure prc_search(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor);
    
        --------count procedure------------------
        procedure prc_count(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_pagesize in number,
            p_recordset out types.ref_cursor);
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor );
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number);
    
    end pkg_equipment;
    /
        

    Файл equipment_PackageBody.txt:

    create or replace package body pkg_equipment as 
        const_sqltxt constant varchar2(1000 char):= 'select '||
                    ' id,tag_number, model, description, type_id, update_date, update_user_id'|| 
                    ' from equipment tbl where 1=1 '; 
    
        --------function to create search query------------------
        function fun_search_query(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number
            ) return varchar2 is
            p_wheretxt varchar2(4000);
        begin
            --------------equals------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
    
            --------------likes------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.tag_number',p_tag_number);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.model',p_model);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.description',p_description);
            
            return const_sqltxt||p_wheretxt;
        end;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_update_user_id in number) is 
        begin
            if p_id > 0 then 
                update equipment set
                    tag_number = p_tag_number,
                    model = p_model,
                    description = p_description,
                    type_id = p_type_id,
                    update_user_id = p_update_user_id,
                    update_date = sysdate
                where id = p_id;
                commit;
            else
                insert into equipment(id,tag_number, model, description, type_id, update_date, 
                 update_user_id)
                values (seq_equipment.nextval,p_tag_number, p_model, p_description,
                 p_type_id, sysdate, p_update_user_id);
                commit;
            end if;
        end;
    
        --------search procedure------------------
        procedure prc_search(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor) is 
                sqltxt varchar2(4000);
                p_sqltxt varchar2(4000);
                p_sqltxt_page varchar2(4000);
                p_startpage number;
                p_maxpage number;
                p_ordersqltxt varchar2(1000);
        begin
            p_startpage := p_pagesize*(p_page-1);
            p_maxpage := p_pagesize*p_page;
            p_sqltxt := fun_search_query(p_tag_number, p_model, p_description, p_type_id);
            p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'tbl.id');
            p_sqltxt_page := 'select * from (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||
                    'where rnum<=:max_row_to_fetch and rnum > :min_row_to_fetch ' || p_ordersqltxt;
            open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;
        end;
    
        --------count procedure------------------
        procedure prc_count(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
                p_pagesize in number,
                p_recordset out types.ref_cursor) is 
            p_sqltxt varchar2(4000);
        begin
            p_sqltxt := fun_search_query(p_tag_number, p_model, p_description, p_type_id);
            p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) pagecount from ('||p_sqltxt||') ';
            open p_recordset for p_sqltxt using p_pagesize;
        end;
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor ) is 
        begin
            if p_id > 0 then
                open p_recordset for
                const_sqltxt || ' and tbl.id = :p_id' using p_id;
            end if;
        end;
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number) is 
            t_var number;
        begin
            select count(1) into t_var from equipment where id = p_id;
            if t_var > 0 then
                delete from equipment where id = p_id;
            commit;
            end if;
        end;
    
    end pkg_equipment;
    /
        

    Для того чтобы сгенерировать 170 строк программного кода, потребовалось создать генератор объемом более 500 строк, то есть в три раза больше. Может показаться, что генерировать код сложнее, чем писать вручную. Однако, если вам надо разработать еще 250-300 схожих пакетов, то это будет около 50000 строк кода. И здесь становится совершенно ясно, что гораздо удобнее и быстрее разработать генератор, ввести описания всех таблиц и сгенерировать за короткое время все 50000 строк программного кода. И это будет стандартный шаблонный код, который практически не содержит ошибок.

    Страницы:

    Описание

    Рассмотрим пример генерации кода пакета для одной таблицы. Пакет будет содержать функциональность для выполнения базовых и стандартных операций над записями в таблице. Это поиск и фильтрация записей, обновление, вставка и удаление строки. То есть все те операции, которые необходимо выполнять над практически любой таблицей в любой базе данных. Возьмем в качестве примера немного измененную таблицу drawing из лекции 3. Поля таблицы drawing следующие:

  • id (идентификатор),
  • name (название чертежа),
  • title (описание),
  • revision (номер последней версии чертежа),
  • type_id (идентификатор типа документа, внешний ключ).
  • Для начала необходимо будет сгенерировать код создания объектов. Первый объект, который нужно создать, это сама таблица:

    create table drawing ( 
    id            number primary key,
    name            varchar2(100 char),
    title            varchar2(1000 char),
    revision            varchar2(10 char),
    type_id            number,
    update_date            date,
    update_user_id            number
    )
        

    Как видим, кроме указанных полей имеются также поля update_date - для сохранения даты последнего обновления строки и update_user_id - для сохранения идентификатора пользователя (внешнего ключа), выполнившего последнее обновление строки таблицы. Здесь предполагается, что в базе имеется таблица пользователей приложения. Более подробно в примерах она рассматриваться не будет.

    Последовательность

    Нужен также запрос на создание последовательности (sequence) Oracle по следующему шаблону:

    create sequence seq_drawing
    start with 1
    maxvalue 999999999999999999999999999
    minvalue 1
    nocycle
    nocache
    noorder
        

    Спецификация пакета

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

    create or replace package pkg_drawing as 
    
        --------function to create search query------------------
        function fun_search_query(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number
        ) return varchar2;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_update_user_id in number);
    
        --------search procedure------------------
        procedure prc_search(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor);
    
        --------count procedure------------------
        procedure prc_count(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_pagesize in number,
            p_recordset out types.ref_cursor);
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor );
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number);
    
    end pkg_drawing;
    /
        

    Описание содержимого пакета

    В таблице представлены названия и описания стандартных функций и процедур, находящихся в пакете:

    fun_search_query функция, формирующая текст запроса с условиями пакета. В нем для формирования части запроса используются функции пакета pkg_lib. То есть фактически генерируемый пакет сам генерирует часть текста запроса;
    prc_search процедура, возвращающая результат поиска согласно указанным критериям в виде ссылки на курсор. Также сортирует результат поиска по параметрам p_order_by, p_order_type;
    prc_count возвращает в виде курсора количество записей при поиске по указанным критериям, а также количество страниц с записями при размере страницы p_pagesize;
    prc_save сохраняет запись в таблице базы данных. Если запись уже существует, то она обновляется (применяется запрос UPDATE), иначе создается новая (применяется запрос INSERT);
    prc_delete удаляет запись из таблицы базы данных;
    prc_show_by_id возвращает в результате поиска одну запись по указанному первичному ключу.

    В теле пакета также имеется константа, содержащая часть текста запроса:

    const_sqltxt constant varchar2(1000 char):= 'select '||
                ' id,number, title, revision, type_id, update_date, update_user_id'|| 
                ' from drawing tbl where 1=1 ';
        

    Впоследствии к этому запросу будут добавляться необходимые условия для фильтрации и сортировки. Хотя текст запроса генерируется только для одной таблицы, генератор можно усовершенствовать для создания запроса с участием нескольких таблиц, с учетом их взаимосвязей. Для простоты часть текста запроса сформирована просто как "where 1=1".

    Разработанный вручную код

    В коде приложения всегда содержится код, который надо разработать вручную. Прежде чем рассмотреть код тела сгенерированного пакета для лучшего его понимания изучим применяемые разработанные вручную пакеты. Это два пакета types и pkg_lib, которые вызываются из генерируемых пакетов.

    Пакет types содержит только объявление ссылки на курсор REF CURSOR. Она используется в тех процедурах пакета, где требуется извлечь записи - результат запроса SELECT. Это сделано таким образом, чтобы другое приложение, например веб-страница, могла вызвать нужную ей процедуру и получить соответствующий набор записей.

    create or replace package types is
      type ref_cursor is REF CURSOR;
    end types;
        

    В пакете pkg_lib содержатся функции работы со строками. Используются они для формирования отдельных частей запросов SELECT.

    fun_add_equal формирует часть строки запроса с условием выбора в виде "and column1 = value1". Имеет параметр p_def, хранящий значение (value), при равенстве которому p_ value функция возвращает пустой результат. Это позволяет по необходимости включать/выключать условия фильтрации для разных полей.
    fun_add_equal_m1 делает то же самое, что и предыдущая функция, но параметр p_def принимает значение "-1".
    fun_add_like выводит условие фильтрации поля в виде "and column1 like '%value1%'". Если параметр p_value пустой, то функция возвращает пустой результат.
    fun_sorting_query выводит условие сортировки вида "order by column1 <asc/desc>".

    Спецификация пакета pkg_lib:

    create or replace package pkg_lib as
    
    function fun_add_equal(p_name varchar2, p_value varchar2, p_def varchar2) return varchar2;
    
    function fun_add_equal_m1(p_name varchar2, p_value varchar2) return varchar2;
    
    function fun_add_like(p_name varchar2, p_value varchar2) return varchar2;
    
    function fun_sorting_query(p_order_by varchar2, p_order_type varchar2, p_def varchar2) return varchar2;
    
    end pkg_lib;
        

    Тело пакета pkg_lib:

    create or replace package body pkg_lib as
    
    function fun_add_equal(p_name varchar2, p_value varchar2, p_def varchar2) return varchar2 is 
             p_result varchar2(2000 char):='';
             begin
                  if p_value <> p_def then
                     p_result := p_result || 'and ' || p_name || ' = ' || '''' || p_value || '''';
                  end if; 
                  return p_result;
             end;
             
    function fun_add_equal_m1(p_name varchar2, p_value varchar2) return varchar2 is
             begin
                  return fun_add_equal(p_name, p_value, '-1');
             end;
             
    function fun_add_like(p_name varchar2, p_value varchar2) return varchar2 is 
             p_result varchar2(2000 char):='';
             begin
                  if length(p_value) > 0 then
                     p_result := p_result || ' and lower (' || p_name || ') like lower (''%' || p_value || '%'')';
                  end if; 
                  return p_result;
             end;
             
    function fun_sorting_query(p_order_by varchar2, p_order_type varchar2, p_def varchar2) return varchar2 is
             p_result varchar2(2000 char) :=' order by ';
             p_order_temp varchar2(2000 char);
             p_type_temp varchar2(2000 char);
             begin
                if length(p_order_by) > 0 then
                   p_order_temp := p_order_by;
                else
                    p_order_temp := p_def;
                end if;
                if length(p_order_type) > 0 then
                   p_type_temp := p_order_type;
                else
                    p_type_temp := 'asc';
                end if;
                return p_result || ' ' || p_order_temp || ' ' || p_type_temp;
             end;  
             
    end pkg_lib;
        

    Тело генерируемого пакета

    Ниже приводится код тела пакета для таблицы drawing в том виде, каким он должен быть сгенерирован.

    create or replace package body pkg_drawing as 
        const_sqltxt constant varchar2(1000 char):= 'select '||
                    ' id,name, title, revision, type_id, update_date, update_user_id'|| 
                    ' from drawing tbl where 1=1 '; 
    
        --------function to create search query------------------
        function fun_search_query(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number
            ) return varchar2 is
            p_wheretxt varchar2(4000);
        begin
            --------------equals------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
    
            --------------likes------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.name',p_name);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.title',p_title);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.revision',p_revision);
            
            return const_sqltxt||p_wheretxt;
        end;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_update_user_id in number) is 
        begin
            if p_id > 0 then 
                update drawing set
                    name = p_name,
                    title = p_title,
                    revision = p_revision,
                    type_id = p_type_id,
                    update_user_id = p_update_user_id,
                    update_date = sysdate
                where id = p_id;
                commit;
            else
                insert into drawing(id,name, title, revision, type_id, update_date, update_user_id)
                values (seq_drawing.nextval,p_name, p_title, p_revision, p_type_id, sysdate, p_update_user_id);
                commit;
            end if;
        end;
    
        --------search procedure------------------
        procedure prc_search(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor) is 
                sqltxt varchar2(4000);
                p_sqltxt varchar2(4000);
                p_sqltxt_page varchar2(4000);
                p_startpage number;
                p_maxpage number;
                p_ordersqltxt varchar2(1000);
        begin
            p_startpage := p_pagesize*(p_page-1);
            p_maxpage := p_pagesize*p_page;
            p_sqltxt := fun_search_query(p_name, p_title, p_revision, p_type_id);
            p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'tbl.id');
            p_sqltxt_page := 'select * from (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||
                    'where rnum<=:max_row_to_fetch and rnum > :min_row_to_fetch ' || p_ordersqltxt;
            open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;
        end;
    
        --------count procedure------------------
        procedure prc_count(
            p_name in varchar2,
            p_title in varchar2,
            p_revision in varchar2,
            p_type_id in number,
                p_pagesize in number,
                p_recordset out types.ref_cursor) is 
            p_sqltxt varchar2(4000);
        begin
            p_sqltxt := fun_search_query(p_name, p_title, p_revision, p_type_id);
            p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) pagecount from ('||p_sqltxt||') ';
            open p_recordset for p_sqltxt using p_pagesize;
        end;
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor ) is 
        begin
            if p_id > 0 then
                open p_recordset for
                const_sqltxt || ' and tbl.id = :p_id' using p_id;
            end if;
        end;
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number) is 
            t_var number;
        begin
            select count(1) into t_var from drawing where id = p_id;
            if t_var > 0 then
                delete from drawing where id = p_id;
            commit;
            end if;
        end;
    
    end pkg_drawing;
    /
        

    Прежде чем рассматривать код генератора приведем код класса таблицы, который используется в генераторе:

    class DBTable
    {
        //Название таблицы
        public string TableName;
    
        //Количество полей в таблице
        public int FieldCount;
    
        //Массив с названиями полей таблицы
        public string[] FieldName;
    
        //Массив с названиями типов полей таблицы
        public string[] FieldTypeName;
    
        //Массив с указанием длины строковых полей таблицы
        public string[] FieldLength;
    
        //Часть запроса для сортировки по умолчанию
        public string defaultOrderString;
    
        //Часть пути для сохранения сгенерированного кода
        public string filesPath;
    
    }
        

    А теперь приведем код класса CodeGenerator. Пояснения по коду даны в комментариях.

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Text;
    using System.IO;
    
    namespace PLSQLCodeGenerator
    {
        class CodeGenerator
        {
            //Константы tab1, tab2, tab3, tab4 предназначены для установки отступов
            //в начале строк генерируемого приложения.
            public const string tab1 = "    ";
            public const string tab2 = "        ";
            public const string tab3 = "            ";
            public const string tab4 = "                ";
    
            //В этих константах хранятся часто применяемые участки кода.
            public const string p_recordset = " p_recordset out types.ref_cursor ";
            public const string add_equal_m1 = "p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1";
            public const string add_like = "p_wheretxt := p_wheretxt || pkg_lib.fun_add_like";
    
            //Здесь хранятся окончания имен для генерируемых файлов
            //В трех файлах сохраняются запросы на создание объектов,
            //спецификация и тело пакета.
            public const string obj_filename = @"Objects.txt";
            public const string package_spec_filename = @"PackageSpec.txt";
            public const string package_body_filename = @"PackageBody.txt";
    
            //Сокращенное имя таблицы по умолчанию.
            public const string tbl = "tbl";
    
            //Процедура для вывода сгенерированного результата в файл.
            public void PutResult(List<string> pList, String filePath)
            {
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
                using (StreamWriter sw = File.CreateText(filePath))
                {
                    foreach (string str in pList)
                    {
                        sw.WriteLine(str);
                    }
                }
            }
    
            //Возвращается текст списка переменных
            //в виде: "p_number, p_title, p_revision, p_type_id",
            //используется при генерации тела пакета.
            public string GetParamListMain(DBTable table)
            {
                string result = "";
                for (int i = 0; i < table.FieldCount; i++)
                {
                    result = result + "p_" + table.FieldName[i];
                    if (i < table.FieldCount - 1) result = result + ", ";
    
                }
                return result;
            }
    
            //К тексту списка переменных добавляются:
            //в начало - следующее значение последовательности
            //в конец - текущая дата и идентификатор пользователя.
            //Используется при генерации запроса вставки строки.
            //Результат получается следующего вида:
            //"seq_drawing.nextval,p_number, p_title, p_revision, p_type_id, sysdate, p_update_user_id".
            public string GetParamListAll(DBTable table)
            {
                return "seq_" + table.TableName + ".nextval," + GetParamListMain(table) +
                   ", sysdate, p_update_user_id";
            }
    
            //Возвращается текст списка полей таблицы
            //в виде: "number, title, revision, type_id".
            //Используется при генерации запросов SELECT и INSERT.
            public string GetFieldListMain(DBTable table)
            {
                string result = "";
                for (int i = 0; i < table.FieldCount; i++)
                {
                    result = result + table.FieldName[i];
                    if (i < table.FieldCount - 1) result += ", ";
                }
                return result;
            }
    
            //К тексту списка полей таблицы добавляются
            //поля идентификатора таблицы, даты последнего обновления,
            //а также идентификатора пользователя.
            //Результат получается следующего вида:
            //"id,number, title, revision, type_id, update_date, update_user_id".
            public string GetFieldListAll(DBTable table)
            {
                return "id," + GetFieldListMain(table) + ", update_date, update_user_id";
            }
    
            //Создается список параметров, применяемый
            //в сигнатурах функций и процедур как в теле,
            //так и в спецификации пакета.
            //Результат получается следующего вида:
            //        p_number in varchar2,
            //        p_title in varchar2,
            //        p_revision in varchar2,
            //        p_type_id in number
            public List<string> GetParametersDeclaration(DBTable table, bool is_last)
            {
                //Переменная result имеет тип List<string> и является набором строк.
                //В ней сохраняется сгенерированный код, который впоследствии
                //может быть выведен в файл.
                List<string> result = new List<string>();
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = tab2;
                    temp += "p_" + table.FieldName[i] + " in " + table.FieldTypeName[i];
                    if (i < table.FieldCount - 1 || !is_last) temp += ",";
                    result.Add(temp);
                }
                return result;
            }
    
            //Формируется запрос на создание таблицы.
            //Результат возвращается в виде набора строк.
            public List<string> GetCreateTable(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add("create table " + table.TableName + " ( ");
                result.Add("id" + tab3 + "number primary key,");
                //В цикле для каждого поля таблицы
                //формируется код объявления.
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = "";
                    temp = table.FieldName[i] + tab3 + table.FieldTypeName[i];
                    if (!(table.FieldLength[i] == ""))
                    {
                        temp += "(" + table.FieldLength[i] + ")";
                    }
                    temp += ",";
                    result.Add(temp);
                }
                //Всегда добавляются
                //поля update_date и update_user_id.
                result.Add("update_date" + tab3 + "date,");
                result.Add("update_user_id" + tab3 + "number");
                result.Add(")");
                return result;
            }
    
            //Формирование запроса на создание последовательности Oracle.
            public List<string> GetCreateSequence(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add("create sequence seq_" + table.TableName);
                result.Add("start with 1");
                result.Add("maxvalue 999999999999999999999999999");
                result.Add("minvalue 1");
                result.Add("nocycle");
                result.Add("nocache");
                result.Add("noorder");
                return result;
            }
    
            //В одном файле сохраняются запросы
            //на создание таблицы и последовательности.
            public void SaveCreateObjects(DBTable table)
            {
                List<string> result = new List<string>();
                //В набор строк добавляется запрос на создание таблицы.
                result.AddRange(GetCreateTable(table));
                //Запросы будут разделены пустой строкой.
                result.Add("");
                //В набор строк добавляется запрос на создание последовательности.
                result.AddRange(GetCreateSequence(table));
                PutResult(result, table.filesPath + obj_filename);
            }
    
            //Функция для создания кода спецификации пакета.
            public List<string> GetPackageSpecification(DBTable table)
            {
                List<string> result = new List<string>();
                //Объявление спецификации пакета.
                result.Add("create or replace package pkg_" + table.TableName + " as ");
    
                //Объявление функции, формирующей текст запроса.
                result.Add("");
                result.Add(tab1 + "--------function to create search query------------------");
                result.Add(tab1 + "function fun_search_query(");
                result.AddRange(GetParametersDeclaration(table, true));
                result.Add(tab1 + ") return varchar2;");
    
                //Объявление процедуры сохранения записи.
                result.Add("");
                result.Add(tab1 + "--------procedure to save------------------");
                result.Add(tab1 + "procedure prc_save(p_id number,");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_update_user_id in number);");
    
                //Объявление процедуры поиска записей по заданным критериям.
                result.Add("");
                result.Add(tab1 + "--------search procedure------------------");
                result.Add(tab1 + "procedure prc_search(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_page in number,");
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_order_by varchar2,");
                result.Add(tab2 + "p_order_type varchar2,");
                result.Add(tab2 + "p_recordset out types.ref_cursor);");
    
                //Объявление процедуры подсчета количества записей по заданным критериям.
                result.Add("");
                result.Add(tab1 + "--------count procedure------------------");
                result.Add(tab1 + "procedure prc_count(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_recordset out types.ref_cursor);");
    
                //Объявление процедуры выборки одной строки по заданному идентификатору.
                result.Add("");
                result.Add(tab1 + "--------show one item procedure ------------------");
                result.Add(tab1 + "procedure prc_show_by_id(p_id number, " + p_recordset + ");");
    
                //Объявление процедуры удаления строки.
                result.Add("");
                result.Add(tab1 + "--------delete procedure ------------------");
                result.Add(tab1 + "procedure prc_delete(p_id number);");
    
                //Конец спецификации пакета
                result.Add("");
                result.Add("end pkg_" + table.TableName + ";");
                result.Add("/");
                
                //Возвращается набор строк, результат работы процедуры.
                return result;
            }
    
            //Сохранение спецификации пакета в отдельном файле.
            public void SavePackageSpecification(DBTable table)
            {
                PutResult(GetPackageSpecification(table), table.filesPath + package_spec_filename);
            }
    
            //Здесь формируется код функции создания запроса.
            public List<string> GetSearchFunction(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------function to create search query------------------");
                result.Add(tab1 + "function fun_search_query(");
                result.AddRange(GetParametersDeclaration(table, true));
                result.Add(tab2 + ") return varchar2 is");
    
                result.Add(tab2 + "p_wheretxt varchar2(4000);");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "--------------equals------------------------");
    
                //В цикле формируется код вида:
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
                for (int i = 0; i < table.FieldCount; i++)
                {
                    if (table.FieldTypeName[i] == "number")
                    {
                        result.Add(tab2 + add_equal_m1 + "('" + tbl + "." + table.FieldName[i] + 
                           "',p_" + table.FieldName[i] + ");");
                    }
                }
                result.Add("");
                result.Add(tab2 + "--------------likes------------------------");
    
                //В цикле формируется код вида:
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.number',p_number);
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.title',p_title);
                //p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.revision',p_revision);
                for (int i = 0; i < table.FieldCount; i++)
                {
                    if (table.FieldTypeName[i] == "varchar2")
                    {
                        result.Add(tab2 + add_like + "('" + tbl + "." + table.FieldName[i] + 
                           "',p_" + table.FieldName[i] + ");");
                    }
                }
    
                result.Add(tab2);
                result.Add(tab2 + "return const_sqltxt||p_wheretxt;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры поиска записей.
            public List<string> GetSearchProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------search procedure------------------");
                result.Add(tab1 + "procedure prc_search(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_page in number,");
                result.Add(tab2 + "p_pagesize in number,");
                result.Add(tab2 + "p_order_by varchar2,");
                result.Add(tab2 + "p_order_type varchar2,");
                result.Add(tab2 + "p_recordset out types.ref_cursor) is ");
    
                result.Add(tab3 + "sqltxt varchar2(4000);");
                result.Add(tab3 + "p_sqltxt varchar2(4000);");
                result.Add(tab3 + "p_sqltxt_page varchar2(4000);");
                result.Add(tab3 + "p_startpage number;");
                result.Add(tab3 + "p_maxpage number;");
                result.Add(tab3 + "p_ordersqltxt varchar2(1000);");
    
                result.Add(tab1 + "begin");
                result.Add(tab2 + "p_startpage := p_pagesize*(p_page-1);");
                result.Add(tab2 + "p_maxpage := p_pagesize*p_page;");
                result.Add(tab2 + "p_sqltxt := fun_search_query(" + GetParamListMain(table) + ");");
                result.Add(tab2 + "p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'" 
                   + tbl + "." + table.defaultOrderString + "');");
                result.Add(tab2 + "p_sqltxt_page := 'select * from 
                   (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||");
                result.Add(tab4 + "'where rnum<=:max_row_to_fetch and rnum >
                :min_row_to_fetch ' || p_ordersqltxt;");
                result.Add(tab2 + "open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры сохранения записи.
            public List<string> GetSaveProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------procedure to save------------------");
                result.Add(tab1 + "procedure prc_save(p_id number,");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab2 + "p_update_user_id in number) is ");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "if p_id > 0 then ");
                result.Add(tab3 + "update " + table.TableName + " set");
    
                //В этом цикле генерируется часть запроса UPDATE вида:
                //  number = p_number,
                //  title = p_title,
                //  revision = p_revision,
                //  type_id = p_type_id,
                for (int i = 0; i < table.FieldCount; i++)
                {
                    string temp = tab4;
                    temp += table.FieldName[i] + " = p_" + table.FieldName[i] + ",";
                    result.Add(temp);
                }
    
                result.Add(tab4 + "update_user_id = p_update_user_id,");
                result.Add(tab4 + "update_date = sysdate");
                result.Add(tab3 + "where id = p_id;");
                result.Add(tab3 + "commit;");
                result.Add(tab2 + "else");
                result.Add(tab3 + "insert into " + table.TableName + 
                "(" + GetFieldListAll(table) + ")");
                result.Add(tab3 + "values (" + GetParamListAll(table) + ");");
                result.Add(tab3 + "commit;");
                result.Add(tab2 + "end if;");
    
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры подсчета количества записей.
            public List<string> GetCountProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------count procedure------------------");
                result.Add(tab1 + "procedure prc_count(");
                result.AddRange(GetParametersDeclaration(table, false));
                result.Add(tab3 + "p_pagesize in number,");
                result.Add(tab3 + "p_recordset out types.ref_cursor) is ");
                result.Add(tab2 + "p_sqltxt varchar2(4000);");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "p_sqltxt := fun_search_query
                (" + GetParamListMain(table) + ");");
                result.Add(tab2 + "p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) 
                pagecount from ('||p_sqltxt||') ';");
                result.Add(tab2 + "open p_recordset for p_sqltxt using p_pagesize;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры выборки одной записи.
            public List<string> GetShowOneItemProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------show one item procedure ------------------");
                result.Add(tab1 + "procedure prc_show_by_id(p_id number, 
                " + p_recordset + ") is ");
    
                result.Add(tab1 + "begin");
    
                result.Add(tab2 + "if p_id > 0 then");
                result.Add(tab3 + "open p_recordset for");
                result.Add(tab3 + "const_sqltxt || ' and " + tbl + ".id =
                 :p_id' using p_id;");
                result.Add(tab2 + "end if;");
    
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Формируется код процедуры удаления записи.
            public List<string> GetDeleteProcedure(DBTable table)
            {
                List<string> result = new List<string>();
                result.Add(tab1 + "--------delete procedure ------------------");
                result.Add(tab1 + "procedure prc_delete(p_id number) is ");
                result.Add(tab2 + "t_var number;");
                result.Add(tab1 + "begin");
                result.Add(tab2 + "select count(1) into t_var from " + 
                table.TableName + " where id = p_id;");
                result.Add(tab2 + "if t_var > 0 then");
                result.Add(tab3 + "delete from " + table.TableName + 
                " where id = p_id;");
                result.Add(tab2 + "commit;");
                result.Add(tab2 + "end if;");
                result.Add(tab1 + "end;");
                return result;
            }
    
            //Функция для создания кода тела пакета.
            public List<string> GetPackageBody(DBTable table)
            {
                List<string> result = new List<string>();
                //Начало пакета.
                result.Add("create or replace package body pkg_" + 
                table.TableName + " as ");
    
                //Генерация запроса и константы, в которой она будет сохранена.
                result.Add(tab1 + "const_sqltxt constant varchar2(1000 char):= 'select '||");
                result.Add(tab4 + "' " + GetFieldListAll(table) + "'|| ");
                result.Add(tab4 + "' from " + table.TableName + " " +
                 tbl + " where 1=1 '; ");
    
                //Добавление к телу пакета кода функции, возвращающей запрос.
                result.Add("");
                result.AddRange(GetSearchFunction(table));
    
                //Добавление к телу пакета кода процедуры, сохраняющей запись.
                result.Add("");
                result.AddRange(GetSaveProcedure(table));
    
                //Добавление к телу пакета кода процедуры, выполняющей поиск записей.
                result.Add("");
                result.AddRange(GetSearchProcedure(table));
    
                //Добавление к телу пакета кода процедуры, возвращающей количество записей.
                result.Add("");
                result.AddRange(GetCountProcedure(table));
    
                //Добавление к телу пакета кода процедуры, возвращающей одну запись.
                result.Add("");
                result.AddRange(GetShowOneItemProcedure(table));
    
                //Добавление к телу пакета кода процедуры, удаляющей запись.
                result.Add("");
                result.AddRange(GetDeleteProcedure(table));
    
                //Конец тела пакета.
                result.Add("");
                result.Add("end pkg_" + table.TableName + ";");
                result.Add("/");
    
                return result;
            }
    
            //Сохранение тела пакета в одном файле.
            public void SavePackageBody(DBTable table)
            {
                PutResult(GetPackageBody(table), table.filesPath + package_body_filename);
            }
    
            //Конструктор, вызывающий процедуры для генерации кода
            //создания объектов, спецификации и тела пакета
            //и сохранения их в трех файлах.
            public CodeGenerator(DBTable table)
            {
                SaveCreateObjects(table);
                SavePackageSpecification(table);
                SavePackageBody(table);
            }
        }
    }
        

    А теперь посмотрим на код, который будет сгенерирован для таблицы equipment. Поля таблицы equipment следующие:

  • id (идентификатор),
  • tag_number (номер ярлыка оборудования),
  • model (наименование модели),
  • description (краткое описание назначения оборудования),
  • type_id (идентификатор типа оборудования, является внешним ключом).
  • Следующий код можно добавить в событие кнопки для настольного приложения или событие запуска консольного приложения:

    //Объявление таблицы, его названия и количества полей
    DBTable table = new DBTable();
    table.TableName = "equipment";
    table.FieldCount = 4;
    
    //Метаданные для простоты примера заполняются напрямую в коде.
    //Однако их можно считывать и из базы данных или файла XML.
    table.FieldName = new string[table.FieldCount];
    table.FieldTypeName = new string[table.FieldCount];
    table.FieldLength = new string[table.FieldCount];
    
    table.FieldName[0] = "tag_number";
    table.FieldTypeName[0] = "varchar2";
    table.FieldLength[0] = "100 char";
    
    table.FieldName[1] = "model";
    table.FieldTypeName[1] = "varchar2";
    table.FieldLength[1] = "100 char";
    
    table.FieldName[2] = "description";
    table.FieldTypeName[2] = "varchar2";
    table.FieldLength[2] = "200 char";
    
    table.FieldName[3] = "type_id";
    table.FieldTypeName[3] = "number";
    table.FieldLength[3] = "";
    
    table.defaultOrderString = "id";
    
    //Указывается путь и часть названия файла вида:
    //"A:\Result\equipment_" для вывода результатов.
    //К нему впоследствии добавляется часть названия 
    //файла в зависимости от типа сгенерированного кода:
    //A:\Result\equipment_Objects.txt
    //A:\Result\equipment_PackageSpec.txt
    //A:\Result\equipment_PackageBody.txt
    table.filesPath = @"A:\Result\" + table.TableName + "_";
    
    //Вызывается конструктор для осуществления генерации кода
    CodeGenerator cg = new CodeGenerator(table);
        

    После запуска будет сгенерировано три файла.

    Файл equipment_Objects.txt:

    create table equipment ( 
    id            number primary key,
    tag_number            varchar2(100 char),
    model            varchar2(100 char),
    description            varchar2(200 char),
    type_id            number,
    update_date            date,
    update_user_id            number
    )
    
    create sequence seq_equipment
    start with 1
    maxvalue 999999999999999999999999999
    minvalue 1
    nocycle
    nocache
    noorder
        

    Файл equipment_PackageSpec.txt:

    create or replace package pkg_equipment as 
    
        --------function to create search query------------------
        function fun_search_query(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number
        ) return varchar2;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_update_user_id in number);
    
        --------search procedure------------------
        procedure prc_search(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor);
    
        --------count procedure------------------
        procedure prc_count(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_pagesize in number,
            p_recordset out types.ref_cursor);
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor );
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number);
    
    end pkg_equipment;
    /
        

    Файл equipment_PackageBody.txt:

    create or replace package body pkg_equipment as 
        const_sqltxt constant varchar2(1000 char):= 'select '||
                    ' id,tag_number, model, description, type_id, update_date, update_user_id'|| 
                    ' from equipment tbl where 1=1 '; 
    
        --------function to create search query------------------
        function fun_search_query(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number
            ) return varchar2 is
            p_wheretxt varchar2(4000);
        begin
            --------------equals------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_equal_m1('tbl.type_id',p_type_id);
    
            --------------likes------------------------
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.tag_number',p_tag_number);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.model',p_model);
            p_wheretxt := p_wheretxt || pkg_lib.fun_add_like('tbl.description',p_description);
            
            return const_sqltxt||p_wheretxt;
        end;
    
        --------procedure to save------------------
        procedure prc_save(p_id number,
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_update_user_id in number) is 
        begin
            if p_id > 0 then 
                update equipment set
                    tag_number = p_tag_number,
                    model = p_model,
                    description = p_description,
                    type_id = p_type_id,
                    update_user_id = p_update_user_id,
                    update_date = sysdate
                where id = p_id;
                commit;
            else
                insert into equipment(id,tag_number, model, description, type_id, update_date, 
                 update_user_id)
                values (seq_equipment.nextval,p_tag_number, p_model, p_description,
                 p_type_id, sysdate, p_update_user_id);
                commit;
            end if;
        end;
    
        --------search procedure------------------
        procedure prc_search(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
            p_page in number,
            p_pagesize in number,
            p_order_by varchar2,
            p_order_type varchar2,
            p_recordset out types.ref_cursor) is 
                sqltxt varchar2(4000);
                p_sqltxt varchar2(4000);
                p_sqltxt_page varchar2(4000);
                p_startpage number;
                p_maxpage number;
                p_ordersqltxt varchar2(1000);
        begin
            p_startpage := p_pagesize*(p_page-1);
            p_maxpage := p_pagesize*p_page;
            p_sqltxt := fun_search_query(p_tag_number, p_model, p_description, p_type_id);
            p_ordersqltxt := pkg_lib.fun_sorting_query(p_order_by,p_order_type,'tbl.id');
            p_sqltxt_page := 'select * from (select s1.*, rownum rnum from ('||p_sqltxt|| p_ordersqltxt|| ') s1) ' ||
                    'where rnum<=:max_row_to_fetch and rnum > :min_row_to_fetch ' || p_ordersqltxt;
            open p_recordset for p_sqltxt_page using p_maxpage,p_startpage;
        end;
    
        --------count procedure------------------
        procedure prc_count(
            p_tag_number in varchar2,
            p_model in varchar2,
            p_description in varchar2,
            p_type_id in number,
                p_pagesize in number,
                p_recordset out types.ref_cursor) is 
            p_sqltxt varchar2(4000);
        begin
            p_sqltxt := fun_search_query(p_tag_number, p_model, p_description, p_type_id);
            p_sqltxt := 'select count(1) cnt, ceil(count(1)/:p_pagesize) pagecount from ('||p_sqltxt||') ';
            open p_recordset for p_sqltxt using p_pagesize;
        end;
    
        --------show one item procedure ------------------
        procedure prc_show_by_id(p_id number,  p_recordset out types.ref_cursor ) is 
        begin
            if p_id > 0 then
                open p_recordset for
                const_sqltxt || ' and tbl.id = :p_id' using p_id;
            end if;
        end;
    
        --------delete procedure ------------------
        procedure prc_delete(p_id number) is 
            t_var number;
        begin
            select count(1) into t_var from equipment where id = p_id;
            if t_var > 0 then
                delete from equipment where id = p_id;
            commit;
            end if;
        end;
    
    end pkg_equipment;
    /
        

    Для того чтобы сгенерировать 170 строк программного кода, потребовалось создать генератор объемом более 500 строк, то есть в три раза больше. Может показаться, что генерировать код сложнее, чем писать вручную. Однако, если вам надо разработать еще 250-300 схожих пакетов, то это будет около 50000 строк кода. И здесь становится совершенно ясно, что гораздо удобнее и быстрее разработать генератор, ввести описания всех таблиц и сгенерировать за короткое время все 50000 строк программного кода. И это будет стандартный шаблонный код, который практически не содержит ошибок.

    Вернуться к учебному плану