Рассмотрим пример генерации кода пакета для одной таблицы. Пакет будет содержать функциональность для выполнения базовых и стандартных операций над записями в таблице. Это поиск и фильтрация записей, обновление, вставка и удаление строки. То есть все те операции, которые необходимо выполнять над практически любой таблицей в любой базе данных. Возьмем в качестве примера немного 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
Пакеты будут содержать процедуры для поиска, обновления, вставки, удаления записей из таблицы. В
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 <. |
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
Пакеты будут содержать процедуры для поиска, обновления, вставки, удаления записей из таблицы. В
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 <. |
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 строк программного кода. И это будет стандартный шаблонный код, который практически не содержит ошибок.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.