Глава 5. Разработка серверной части


Соглашения

Системные имена модулей серверной части должны быть ограничены шестнадцатью символами и состоять из букв латинского алфавита, символа подчеркивания "_" и цифр, причем первым символом в имени должна быть буква.

Все файлы процедур модуля и файл компоновки оформляются любым текстовым редактором в OEM–формате символов. Примером редактора может служить встроенный редактор утилиты FAR (File and ARchive Manager) или редактор MultiEdit любой версии. Для Windows-версии MultiEdit необходимо использовать шрифт "Terminal".

Файл проекта модуля

Файл проекта является основным источником информации о разрабатываемом модуле серверной части и содержит как настройки соединения с БД, так и сведения о входящих в модуль отдельных файлов с программным кодом встроенных процедур. Файл имеет произвольное название с расширением .PRJ . Содержание файла проекта разделено на секции (на примере файла Kernel.prj

[LoginInfo]
Server=OFFICE
Database=ONTARIO
Login=sa
Password=pwd
[Files]
File1=Kernel
File2=Objects
File3=Users
File4=Wgroups
File5=Classes

В секции LoginInfo описываются параметры соединения с сервером БД. Вместо списка параметров можно записать строку LoginFile=<имя_файла>. В этом случае данный файл должен содержать секцию LoginInfo со списком параметров регистрации с сервером БД. Это бывает удобно при использовании одной и той же конфигурации для нескольких модулей. Имя файла может содержать полный или относительный путь. В секции Files приводится список файлов процедур (без расширений), входящих в проект. Число файлов может быть любым, а порядок их трансляции на сервер БД учитывается и определяется номером файла ( 1, 2, ... и т.д. ). Наименьшим номером является “1”. Файлы процедур могут находиться в каталоге, отличном от того, в котором находится сам файл проекта. В этом случае необходимо описать путь к файлу относительно каталога проекта или полный путь.

Пример использования файла конфигурации:

Файл проекта (kernel.prj):

[LoginInfo]
LoginFile=..\spp.cfg
[Files]
File1=Kernel
File2=Objects
File3=Users
File4=Wgroups
File5=Classes

Файл конфигурации (spp.cfg):

[LoginInfo]
Server=OFFICE
Database=ONTARIO
Login=sa
Password=pwd

Файл процедур

Файл процедур содержит исходный код встроенных процедур БД и должен иметь расширение .SQL . Код процедур оформляется следующим образом:

CREATE PROCEDURE <имя процедуры>
AS
. . .
SQL–код
GO

Допускаются комментарии в стиле Transact–SQL ( “/* текст */”и “-- текст” ). Файл рекомендуется предварять заголовком, где описаны название, назначение, версия, дата создания и последней модификации и автор файла процедур и модуля, в который он входит, например:

--==========================================================================--
--                         ONTARIO System                                     --
--                           version 1.0                                      --
--                                                                           --
--                        Module: System Kernel                               --
--                        Submodule: System Procedures                        --
--                                                                           --
--                        Created: 12/12/1997                                 --
--                        Developed by Author Name                             --
--                        (c) 1997 all right reserved                         --
--==========================================================================--

Трансляция процедур

Трансляция процедур происходит следующим образом: необходимо вызвать утилиту SPP.EXE (Stored Procedures Precompiler), входящую в состав поставки системы с исходным программным кодом, с одним параметром – имя и расширение файла проекта из каталога, где находится этот самый файл проекта. При удачном завершении трансляции процедур на сервер БД создаются копии файлов процедур с расширением .BAK. В дальнейшем транслятор анализирует исходный файл и BAK–копию по процедурам и транслирует только те из них, где код претерпел изменения.

Создание SQL–скрипта процедур модуля

Для создания полного SQL–скрипта всех процедур, входящих в модуль необходимо запустить трансляцию проекта с дополнительным вторым параметром “SQL”.

SPP.EXE <имя файла проекта> SQL

Результирующий файл будет содержать код процедур всех файлов, входящих в проект с проверками их наличия/удаления и установки прав доступа на выполнение. Файл будет иметь то же имя. что и файл проекта модуля, но с расширением .OMP (ONTARIO Module Procedures).Таким образом, OMP-файл является по сути скриптом всех процедур модуля без определения структур данных (таблиц, индексов и др.) и вызовов процедур инициализации модуля (создания классов и др.).

Файл компоновки

Файл компоновки представляет собой шаблон для создания полного SQL–скрипта установки/снятия/обновления модуля. Файл имеет наименование, совпадающее с наименованием файла проекта и расширение .LNK.Файл состоит из соответствующих секций и включаемых в них ранее подготовленных файлов SQL–скриптов, например:

[ModuleInfo]
Name=kernel
Description=System Kernel
Version=1.0
Dependencies=

[Install]
#include file1
#include file2
. . .
#include fileN

[Uninstall]
#include file1
. . .

#include fileM

В секции ModuleInfo необходимо занести информацию о модуле: системное наименование, описание (на языке пользователя), номер версии и зависимости от уже установленных модулей. В строке Dependencies необходимо перечислить через запятую или пробел системные имена тех модулей, наличие которых необходимо для правильной установки данного модуля. Зависимость от системного ядра (модуль “kernel”можно не указывать.

Включаемые файлы представляют собой либо файл SQL–скрипта процедур модуля (.OMP, либо файл SQL–скрипта описания структур данных и/или команд инициализации/снятия/обновления, который должен иметь расширение .OMD (Ontario Module Database). Для единообразия оформления модулей необходимо давать .OMD–файлам имена соответственно INSTALL.OMD, UNINST.OMD. В конструкции #include необходимо явно указывать имя и расширение файла. Порядок включения файлов в секции Install и Uninstall явно определяет порядок их расположения внутри секции компонуемого в дальнейшем SQL–скрипта.

Стандартная компоновка представляет собой набор файлов:

[Install]
#include install.omd
#include <имя_модуля>.omp
#include init.omd

[Uninstall]
#include uninst.omd

В файле INSTALL.OMD описывается создание таблиц, индексов, просмотров и других структур для модуля. Следующий .OMP–файл содержит все процедуры модуля. Файл INIT.OMD представляет собой SQL–скрипт инициализации данных для модуля, например, регистрация классов и создание предопределенных объектов или заливку данных.

Файл UNIST.OMD также представляет собой SQL–скрипт, в котором описывается удаление структур данных, удаление классов и встроенных процедур. В качестве примера можно взять любой модуль.

Компоновка модуля для установки

Для компоновки и создания распространяемой версии модуля (релиза) необходимо подготовить файл компоновки. Далее следует запустить утилиту SPP.EXE с параметрами:

SPP.EXE <имя файла проекта> BUILD

В результате будет создан файл для программы установки (DBSETUP.EXE) с именем файла проекта и расширением .OSM (ONTARIO Setup Module).

Особенности программирования модулей серверной части

Системные папки и объекты

Системными папками и объектами являются такие объекты БД, которые должны быть доступны из любого участка программного кода по их уникальному имени, независимо от OID. Прежде всего это папки различного уровня дерева, которые должны раскрываться пользователю при возможности выбора того или иного объекта в интерфейсной части, например папка “Справочники”. Это также может быть некоторый зарезервированный объект, который используется, как некоторый предопределенный и неизменяемый по смыслу и назначению, например фирма с названием “Корпорация” или пользователь “Администратор”, несмотря на то, что само название и другие атрибуты объекта могут изменяться.

Все системные объекты защищены от удаления пользователем, даже если он имеет явно указанные права на удаление.

Системные объекты создаются в процедуре инициализации модуля внесением записи в таблицу SFolders. Вот пример создания папки "Целевые назначения" из комплекта справочников.

SELECT @OID = isnull( ( SELECT SFID FROM SFolders WHERE SFName = 'RefBooks' ), 0 )
if ( @OID = 0 ) begin
  EXECUTE spCreateObj @OID OUTPUT, 'TDBOFolder', 0, 'Справочники', ''
  INSERT INTO SFolders ( SFID, SFName ) VALUES ( @OID, 'RefBooks' )
end
EXECUTE spCreateObj @NewOID OUTPUT, 'TDBOFolder', @OID, 'Целевые назначения', ''
INSERT INTO SFolders ( SFID, SFName ) VALUES ( @NewOID, 'Targets' )

В данном примере показано создание папки “Целевые назначения” с системным именем Targets, внутри имеющейся или отсутствующей, но создаваемой в этом случае, папки “Справочники”. В дальнейшем при указании имени Targets в свойстве SFolders компонента, связанного с объектом БД (TDBObjGrid, TDBOField), интерфейс выбора объекта (Проводник) открывается для просмотра поддерева с корнем в данной вершине.

Декларирование ссылочной целостности для объектов связанных классов

Аналогом триггеров для событий создания, модификации или удаления в реляционной СУБД являются в ONTARIO процедуры обработки событий для объектов связанных классов. Положим, объект класса “А” имеют свойство–ссылку на объект класса “В”. Как правило, при реализации это означает, что в таблице расширений класса "А" есть поле, содержащее OID объекта или поля содержащие OID объектов класса "В". Тогда процедура, обрабатывающее некоторое событие, происходящее с объектом класса “В” или его объектами его подклассов по отношению к объекту класса “А” или его подклассам будет выглядеть следующим образом:

CREATE PROCEDURE <Имя класса А>_<Имя события>_<Имя класса Б>
  @OID int
AS
if <Условия проверки> begin
  RAISERROR( <Текст сообщения>, 11, 1 )
  RETURN 1
end
GO

где @OID – идентификатор объекта класса В, а <имя события> – один из трех символов, обозначающих событие, произошедшее с объектом: “C”–Create, “S”–Save или Update, “D”–Delete. Генерация ошибки вызовет откат транзакции процесса и вывод сообщения об ошибке в клиентском приложении.

Вызов механизма событий реализован в соответствующих методах базового класса TDBObject_Create, TDBObject_Save и TDBObject_Delete, поэтому при переопределении этих методов заботиться о вызове процедуры-обработчика события не надо. Если необходимо реализовать проверки при срабатывании других методов класса, то в коде метода должен стоять вызов системной процедуры запуска механизма вызовов обработчика событий:

if @RetVal = 0 begin
  execute @RetVal = spEventHandler @OID, "Х"
end
return @RetVal

где @OID – идентификатор объекта, метод которого выполняется, а "Х" - одно из трех обозначений событий, перечисленных выше. При этом, выполнение такого метода должно быть заключено в транзакцию.

Например, таким образом будет выглядеть ограничение на удаление тех объектов классов “Базовая единица измерения” (TDBOMeasure) и их потомков (“Производная единица измерения”–TDBOMeasureSec), которые используются в качестве атрибутов для объектов класса “Товар” () или его потомков

CREATE PROCEDURE TDBOGood_D_TDBOMeasure
  @OID int
AS
if EXISTS( SELECT MeasureID FROM Goods WHERE MeasureID = @OID ) begin
  RAISERROR( 'Единица измерения используется в учете товаров', 11, 1 )
  RETURN 1
end
GO

Использование настроек “по умолчанию”

Настройки “по умолчанию” являются объектами системы и предназначены для хранения значений некоторых системных констант, например, “валюта по умолчанию” или “число дней выполнения заказа”. Все настройки хранятся в БД и имеют интерфейс в виде SP–процедур для их создания и получения значения.

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

Настройки располагаются в папке “Значения по умолчанию” папки “Система” и доступны для редактирования из клиентского приложения.

Для работы со значениями "по умолчанию" используется API, описанный в Приложении 4.

TDBODefault_CreateInstance
TDBODefault_AddValue
TDBODefault_GetValue

TDBODefault_DelValue

Пример создания настройки “Валюта по умолчанию” и добавление значения “Рубль РФ” для всей корпорации.

select @CorpID = SFID from SFolders where SFName = "Corporation"
select @CurrID = OID from Objs where upper( Name ) like "%РУБЛЬ%" and BaseClass = "TDBOCurrency"
execute TDBODefault_CreateInstance @OID output, "Валюта по умолчанию", "Currency", 5
execute TDBODefault_AddValue @OID = @OID, @SubjID = @CorpID, @IntVal = @CurrID

Создание собственных процедур поиска объекта при вводе текста в поле TDBOField

Поиск при вводе текста в поле компонента TDBOField осуществляется в следующем порядке:

Приложение вызывает найденную процедуру поиска и обрабатывает полученное результирующее множество. Если не найдено ни одного объекта по данным критериям поиска, выдается ошибка. Если найден один объект, то он появляется в поле DBOField. Если найдено более одного объекта, выдается список, из которого пользователь может выбрать нужный объект.

Изначально в ядре присутствует только процедура TDBObject_Find, которая будет вызываться во всех случаях. Если Вам необходимо изменить условия поиска, например, искать не по полю “наименование”, а по полю “расширение” или выдавать другой, более расширенный набор колонок для выбора нескольких объектов, то возможны два варианта:

  1. Создать процедуру поиска для данного класса объектов с именем <имя класса>_Find;
  2. Если такая процедура уже есть, а необходимо иметь несколько вариантов поиска в разных полях ввода для одного и того же класса объектов, то можно создать процедуру с любым именем и присвоить свойству “FindStoredProcName” данного компонента DBOField ее имя:

MyDBOField.FindStoredProcName := ‘TDBObject_MyEspeciallyFind’;

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

Пример процедуры поиска объекта базового класса:

create procedure TDBObject_Find
  @Name varchar( 255 ),
  @Class varchar( 16 )
as

  declare @UserID int
  execute spGetUserID @UserID output

  if ( substring( @Name, datalength( @Name ), 1 ) != "%" ) begin
    select @Name = @Name + "%"
  end
  select @Name = upper( @Name )

  create table #Cls(
    OID int not null,
    Class varchar( 16 ),
    Lev int not null
  )
  insert into #Cls ( OID, Class, Lev )
    execute spRetSubClasses @Class

  select "OID" = O.OID,
         "Class" = O.BaseClass,
         "Наименование" = O.Name,
         "Расширение" = O.Ext,
         "Дата" = O.CreationDate,
         "Статус" = O.State,
         "Сумма" = O.Summ
    from Objs O, #Cls C
    where upper( O.Name ) like @Name and
          O.BaseClass = C.Class and
          exists( select *
                    from Access
                    where OID = O.OID and
                          AccRead = 1 and
                          ( UserID = @UserID or
                            UserID = any( select GroupID
                                            from User2Group
                                            where UserID = @UserID )
                          )
                )            
    order by 2
  drop table #Cls
go

Создание состояний документа и обработка их изменения

Каждый объект в системе, являющийся подклассом абстрактного документа (TDBODoc) имеет свойство "Состояние документа" и метод "Изменить состояние документа". При выполнении из клиентского приложения этой операции вызывается процедура TDBODoc_ChangeState, которая, в зависимости от класса документа, вызывает процедуру <имя класса>_OnChangeState. Общие требования к оформлению процедуры следующие:

create procedure <имя класса>_OnChangeState
  @OID int,
  @StateID int,
  @OldStateID int

где @OID - ID документа, который меняет состояние, @StateID - ID состояния (объект класса TDBODocState), в которое переводится документа и @OldStateID - ID состояния, из которого переводится документ.

В процедуре необходимо определить по @StateID системное название состояния (смотри свойство "Системное название состояния" класса TDBODocState) и произвести в зависимости от смыслового значения идентифицированного состояния необходимые действия. Например, в обработке изменения состояния приходной накладной (см. процедуру TDBOInInv_OnChangeState в файле InInv.sql модуля StorDoc) на "Закрыто", необходимо занести в буфер проводки (OpLog_AddRecord) и провести их (OpLog_DoPass). Если существуют какие-либо ограничения по переводу из одного состояния в другое, то это отслеживается путем сравнения состояний @StateID и @OldStateID.

Каждое состояние является по сути псевдонимом одного из трех предопределенных в системе базовых состояний, в котором может находится документ: 0-черновик, 1-в работе, 2-закрыт. Эти состояния характеризуют степень проведения хозяйственных по данному документу. Так, документ в базовом состоянии "0" не проводит никаких хозяйственных операций (проводок), в состоянии "1" проводки означают планируемые операции (например, резервирование товара или ожидание прихода), а в состоянии "3" - свершившиеся хозяйственные операции, влияющие на баланс.

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

Например, так создаются состояния для документа класса "Приходная накладная":

select @StateID = isnull( ( select SFID from SFolders where SFName = 'DocStates' ), 0 )
execute TDBODocState_Create 'Черновик'        , @StateID, 'InInvInitial'     , 0, 'TDBOInInv'
execute TDBODocState_Create 'В работе'        , @StateID, 'InInvProcess'     , 1, 'TDBOInInv'
execute TDBODocState_Create 'Закрыт'          , @StateID, 'InInvFinal'       , 2, 'TDBOInInv'
execute TDBODocState_Create 'Закрыт (розница)', @StateID, 'InInvFinalNotNDS' , 2, 'TDBOInInv'

 


Содержание Глоссарий Предыдущая Следующая