Интерфейс Clipper - Pascal

TURBO PASCAL + CLIPPER

                    +-----------------------------------+
                    |          П.В. Кукурузова          |
                    | "Компьютеры+Программы", 1(2)/1993 |
                    +-----------------------------------+

------------------------------------------------------------------------------
Полное или частичное воспроизведение или размножение каким бы то ни было
способом материалов, опубликованных в настоящем издании, допускается только
с письменного разрешения ИИЦ "Компьютеры+Программы". ;)))))))))))))
------------------------------------------------------------------------------

Поддержка баз данных во многих случаях требует реализации достаточно сложных алгоритмов, ориентированных на использование сложных структур данных, динамической памяти, взаимодействия с сервисом операционной системы. Большинство систем управления базами д анных (СУБД) не предоставляют программисту таких возможностей. Это обусловлено спецификой языков манипулирования данными (ЯМД) , ядро которых составляют операции взаимодействия с базой данных. Для решения такой проблемы необходим межязыковый интерфейс - между языком СУБД и некоторым универсальным языком, на котором программист может эффективно реализовать требуемые операции.

Способы реализации интерфейса

     Реализация интерфейса в основном выполняется следующими способами:
 - взаимодействие с внешним подпроцессом (старт самостоятельной задачи);
 - взаимодействие ЯМД с объектными модулями, разработанными вне СУБД;
 - взаимодействие из внешнего языка с ядром СУБД.
     В   первом   случае   приложение,  разрабатываемое  в  рамках  СУБД  (или
интегрированной  среды,  включа  ющей  СУБД),  вызывает внешний ЕХЕ-модуль, со
зданный  в  произвольной  среде.  Перед  вызовом необ ходимо подготовить г ими
воспользоваться.  ЕХЕ-модуль  обрабатывает  эти данные и формирует результирую
щие  данные,  которые  испол ьзуются основным про цессом. Тривиальным способом
передачи  данных  яв  ляется  использование  текстового  файла  с  заранее  со
гласованным  форматом.  При такой схеме взаимодей ствия расходуется достаточно
много  времени  и памя ти (дисковой или оперативной). Во мног их случаях объем
передаваемых  данных  может быть сравним с объемом базы данных, что ставит под
сомнение целе сообразность взаимодействия по тако й схеме.
     Во  втором случае процесс обмена данными весьма сложен. Объектный модуль,
написанный  на  каком-ли  бо  языке программирования, должен иметь доступ к об
ласти  данных  программы,  написанной на ЯМД. Если доступ к данным невозможен,
необходимы  действия по передаче данных аналогично первому случаю. Реали зация
взаимодействия  по  второй схеме возможна дале ко не во всех случаях. Если при
формировании ЯМД- приложения не используется этап компоновки или ис пользуется
нестандартный   компоновщик,  то  реализа  ц  ия  такой  схемы  взаимодействия
невозможна.  К  тому  же, не все языки программирования порождают стан дартные
объектные мод ули, например. Turbo Pascal.
     Отличительной  чертой  третьей  схемы  взаимо  действия  является то, что
основная   программа  со  здается  на  одно  м  из  языков  программирования и
взаимодействуете  ядром  СУБД,  предоставленным  программисту в виде некоторой
библиотеки  про  ц  едур  и функций. Каждая из процедур реализует определенную
операцию  доступа  к  базе  данных.  При такой схеме данные передаются в качес
тве  параметров  процедур и функций. К сожалению, для большинства СУБД ядро не
выделено  в  до  ступную  всем  языкам  программирован ия библио теку. В таких
случаях  программисты  разрабаты вают собственные библиотеки процедур, реализу
ющих   требуемые  функции  ядра  СУБД.  Теперь  речь  идет  лишь  о  косвенном
взаимодействии,  т.к.  в  таких  условиях обеспечивается возможность взаимодей
ствия  прилож  ений,  написанных  на  универсальных языках программирования, с
базами данных, под держиваемыми ЯМД-приложениями.


                     

Взаимодействие Turbo Pascal и Clipper

Рассмотрим способы организации взаимодейст вия с базами данных, представленых в формате сис темы Clipper, и з программ, разрабатываемых в Turbo Pascal. Первая схема, помимо собственных недостатков, приобретает еще од с внутренним ме ханизмом распределения динамической памяти в Turbo Pascal. ЯМД-приложение и Pascal-приложе- ния будут требовать память из непересекающихся об ластей, что существенно ограничит доступную каж дому из приложений динамическую память. Использование второго подхода принципиально невозможно, т.к. Turbo Pascal имеет собственный формат представления объектного кода. Целесообразно ориентироваться на третий вари ант взаимодействия. Учитывая тот факт, что Clipper не имеет отдельно выделенного ядра , оформленного в виде библиотеки для Turbo Pascal, необходимо ори ентироваться на разработку собственной библиотеки. Такая библиотека разраб отана автором и рассматри вается в настоящей статье. При этом преследуются следующие цели: - обсудить требования к гипотетической библиотеке, выполняющей функции ядра СУБД Clipper; - рассмотреть модуль DEFILE в качестве готового продукта, выполняющего такие функции.

Функции библиотеки поддержки баз данных

Рассмотрим, какие возможности Clipper целесооб разно предос тавить программистам, работающим на Turbo Pascal. Для этого необходимо оценить потребность Pascal-приложений в функциях взаимодействия с базами да нных (БД). Из Turbo Pascal имеет смысл разрабатывать инфор мационные системы, если обработка данных требует: -использования достаточно сложных вычислительных процедур, для которых необходима высокая скорость числовых операций, использование сложных структур данных, управление динамической памятью и т.п.; - построения специализированного (возможно, графическ ого) интерфейса с пользователем; -разработки системных процедур, требующих прямого взаимодействия с сервисом операционной системы; - интеграции разнояз ыковых фрагментов в рамках Turbo Pascal. Целесообразно также избежать избыточности кода приложений, п орождаемого Clipper. Clipper-приложе- ние в себе содержит полное ядро СУБД и интерпретатор ЯМД, необходимые для интерпретирующего режима выполнения приложе ний. Такой подход имеет как недо статки, так и достоинства. В частности, интерпретирую щий режим п озволяет поддерживать весьма удобный ме ханизм макроподстановок. Избежать избыточности кода приложений можно, исключив из рассмотрения все интерактивные коман ды и все команды, требующие интерпретации. Базовый набор команд ЯМД Clipper является доста точн о удобным и полным. Целесообразно организовать Call-интерфейс таким образом, чтобы каждой базовой команде ЯМД соответствовала определенная процедура или функция в рамках специального модуля для Turbo Pascal. В качестве такого модуля в дальнейшем будем р ассматривать DBFILE. Команды ЯМД Clipper можно разбить на следующие группы: - создания и реорганизации баз данных (create, modify. Index oil, reindex, copy structure); - подготовки базы данных к использованию в приложении, включая организацию виртуальны х представлений (use, set index, set filter, set relation); - наполнения данными и модификации данных в БД (append (blank), replace, delete, recall, pack, zap); - до ступа к данным на физическом и логическом уровнях (go to, go top, go bottom, skip, fin d). Концепция рабочих областей, достаточно хорошо используемая в рамках ЯМД Clipper, для Pascal-прило- жений будет не очень удобной и совсем ненаглядной. По этому иденти фикацию файла-операнда для каждой из команд можно выполнять по символьному имени ра нее открытого файла данных. Далее рассмотрим реализацию модуля поддержки баз данных DBFILE. Программисты, желающие раз работать собственный аналогичный продукт, смогут учест ь достоинства и недостатки (с их точки зрения) в новых разработках для Turbo Pas cal или в разработках для других языков программирования.

Представление о базе данных

База данных рассматривается как множество логи чески взаимосвязанных между собой файлов да нных и их индексов. Файлы данных имеют известный СЕР-формат, поддерживаемый , помимо Clipper, еще системами dBase, Foxbase, FoxPro и другими. Физически файл данных состоят из заголовка файла и множества записей, представленных в символьном формате. Заг оловок файла содержит определение структуры записей файла, которые состоят из набора полей (атрибутов). Длина записи в рамках файла данных фиксирована. Каждое поле определяется десятисимвольным именем и типом своих значений. Допу скается использование по лей со следующими типами: - символьный (Character); - числовой (Numeric); - логический (Logical); - даты (Date); - текстовый (Меmo). При использовании текстовых полей наряду с DBF-файлом поддерживается одноименный файл с расшире нием ОВТ, содержащий значения тексто вых полей переменной длины. Значение текстового поля в рамках DBF-файла - это адрес значения в ОВТ-файле. В рамках DBT-файла память распределяется блоками по 512 байтов. Значение текстового поля может занимать несколько последовательно расположенных блоков. В рамках Turbo PascaL структура заголовка DBF-файла может быть описана следующим образом:
type
         _FILE_HEADER = record {32}
              Version      : byte; {03h dBase-III
                                    83h dBase-III и есть DBT-файл}

              DateUpdate   : record Y,M,D : byte end;
                             {Дата последней модификации}
              NumOfRecords : longint; {число записей в файле}
              HeaderSize   : word; {размер всего заголовка файла
                                   (с описанием полей)}

              RecordSize   : longint; {размер записи}
              Ignored      : array [1..18] of byte;
         end; {_FILE_HEADER}

         _MEMO_HEADER = record {512-первый блок DBT-файла}
              FreePage     : longint; {размер файла}
              Ignored      : array [1..508] of byte;
         end; {_MEMO_HEADER}

         _FIELD_HEADER= record {32}
              FiName       : array [1..10] of char; {Имя поля}
              FiEnd        : char; {00h}
              FiType       : char; {Тип поля C/N/D/L/M}
              FiOfs        : longint; {Смещение поля в записи}
              FiLength     : byte; {Длина значения поля}
              FiDecimals   : byte; {Десятичных цифр}
              Ignored      : array [1..14] of Byte;
         end; {_FIELD_HEADER}

         _FIELD_LIST       : array [1..256] of _FIELD_HEADER

         _DBF_HEADER  = record
              FileHeader   : _FILE_HEADER;
              MemoHeader   : _MEMO_HEADER;
              Fields       : ^_FIELD_HEADER;
         end; {_DBF_HEADER}
Заголовок файла содержит 32 байта общей инфор мации и определение полей, заканчивающееся симво лом ODh. Определение каждого поля занимает 32 байта. Таким образом, размер заголовка файла HeaderSlze = 32 + ( число полей) * 32 + 1. При создании файла значение этого поля рассчитывается на основе числа полей в файле. Для существующего файла опре делить число полей в нем можно по обратной зависи мости на основе длины заголовка. Фрагмент загрузки заголовка DBF-файл а может иметь вид:
var
         DBF               : _DBF_HEADER;
         F                 : file;
     begin
         {$I-} Reset (F,1); {$I+}
         if IOResult <> 0 then ...
         {$I-} BlockRead (F, DBF,32); {$I+}
         if IOResult <> 0 then ...
         with DBF, DBF.FileHeader do begin
              GetMem (Fields, HeaderSize-33);
              {$I-} BlockRead (F, Fields^, HeaderSize-33);
              if IOResult <> 0 then ...
В начале каждой записи файла поддерживается код - нобайтовое поле логического удаления записи (" " или "*" - не удалена или удалена). Поэтому смещение (FiOfs) значения первого поля в записи равно единице, а длина записи (RecordSlze) на единицу больше суммы длин всех полей (FiLength). Например, для того, чтобы прочитать запись с номером RecNo, необходимо выполнить следующие действия:
{$I-}
     with DBF, DBF.FileHeader do begin
          Seek (F, HeaderSize+Pred(RecNo)*RecordSize);
          if IOResult <> 0 then ...
          BlockRead (F, RecBuf^, RecordSize);
          if IOResult <> 0 then ...
     end; {with}
     {$I+}
Получить доступ к полю с номером No можно, ис пользуя указатель: Ptr(Seg(RecBuf^),Ofs(RecBuf^)+FiOfs[No])^ В конце файла всегда присутствует символ 1Ah. Та ким образом, длина файла данных равна: HeaderSize + RecordSize*NumOfRecords+1. Для поиска данных по ключу и для виртуальной упорядоченности записей по ключу используются индексные файлы - поисковые структуры на основе В-деревьев. Clipper поддерживает NTX-формат индексных файлов. Индекс состоит из заголовка и записей-узлов разме ром 1024 байта. Заголовок индекса может быть описан в Turbo Pascal следующим образом:
type
          _NTX_HEADER = record {1024}
                _0600       : word; {0600h}
                Stay        : word; {Состояние буфера индекса
                         (необходимо для сетевой обработки индексов)}

                RootNode    : longint; {Смещение корневого узла}
                FFNode      : longint; {Смещение первого узла в списке
                         свободных записей-узлов дерева}

                ItemLength  : word; {Длина элемента узла}
                KeyLength   : word; {Длина значения ключа}
                _0000       : word; {0000h}
                MaxNumOfKey : word; {max число ключей в узлах}
                MinNumOfKey : word; {min число ключей в узлах}
                Expression  : array [0..255] of char;
                         {Выражение образования ключа, например:
                         'field1+field2+field3+field4+field5'}

                Unique      : boolean;
                         {Флаг уникальности значений ключа в индексе}
                Ignored     : array [1..761] of byte;
          end; {_NTX_HEADER}
Узел индекса состоит из таблицы смещений эле- ментов в узле и спи ска собственно элементов. Элемент узла индекса состоит из трех полей: - смещение записи узла-потомка (4 байта); - номер записи файла данных, содержащей ключ (4 байта); - значение ключа (KeyLength байтов). Каждый узел, кроме корня, может содержать от MinNumOfKey до MaxNumOfKey=2*MlnNumOfKey ключей. Все узлы-листики (не имеющие потомков) распо ложены на одном уровне в дереве. Узел, не являющийся листиком и содержащий m ключей, имеет (m+l) -го потомка. Поэтому в узле, содержащем m ключей, всегда задействовано (m+l) элементов. Ключи в узле упорядочены по возрастанию значений. Значения ключа строятся по значениям полей DBF-файла и имеют такой же формат пред ставления. Значения ключей в узлах дерева всегда упорядочены таким образом, что их значения в подде реве некоторого э лемента не превышают значение ключа элемента. Структуру узла удобно определить не в виде ста тического типа, а виде набора процедур и функций, обеспечивающих все операции с элементами и полями элементов узлов. Для поиска значения ключа (и номера соетветсвующей записи в файле данных) можно использовать следующую схему:
function FindRecNo (var NTX : _NTX_HEADER;
                         var F : file; Key : string) : longint;
                         {Возвращается номер записи файла данных,
                         содержащий значение ключа Key.
         function GetKey - возвращает значение ключа элемента
         function GetRecNo - возвращает номер записи файла данных
         function GetNodeOfs - возвращает смещение узла-потомка}

     type
         NTX_NODE = array [0..511] of word;
     var
         NodeRec : _NTX_NODE; {Буфер записи-узла}
         NodeOfs : longint;   {Смещение записи-узла}
         L, R, ItemNo : word;
     begin
         FindRecNo := 0; {Эначение Key не найдено}
         NodeOfs   := NTX.RootNode; {Поиск от корня по ветви}
         while NodeOfs > 0 do begin {пока узел-не листик}
               {$I-} Seek (F, NodeOfs); {$I+ Читаем узел}
               if IOResult = 0 then begin
                  {$I-} BlockRead (F, NodeRec,1024); {$I+}
                  if (IOResult=0) and (NodeRec[0]>0) then begin
                  {Используя бинарный поиск, ищем ключ в узле}
                  L := 1;
                  R := NodeRec[0];
                  repeat
                    ItemNo := (L+R) shr 1;
                    if Key = GetKey (NodeRec, ItemNo) then begin
                       FindRecNo := GetRecNo (NodeRec, ItemNo);
                       exit
                     end else if Key < GetKey (NodeRec, ItemNo) then
                         R := Pred (ItemNo)
                     else L := Succ (ItemNo);
                  until L < R;
                  NodeOfs := GetNodeOfs (NodeRec, ItemNo);
               end else NodeOfs := 0;
                  {$I+}
         end; {while}
     end; {FindRecNo}
┌────────────────────────────────────────────────────────────────────────────┐ │ ТАБЛИЦА смещений элементов в узле содержит значения смещений элементов в │ │ рамках узла │ │ │ │ +0 dw Число ключей в узле (=n) │ │ +2 dw Смещение значения первого элемента в узле (=i1) │ │ +2n dw Смещение значения n-го элемента в узле (=in) │ │ +2n+2 dw Смещение значения (n+1)-го элемента в узле (=ix) │ │ +i1+0 dd Смещение узла-потомка для 1-го ключа │ │ +i1+4 dd Номер записи файла данных для 1-го кляча │ │ +i1+8 db ? dup Значение 1-го ключа │ │ +i2+0 dd Смещение узла-потомка для 2-го ключа │ │ ............ │ │ +in+0 dd Смещение узла-потомка для n-го ключа │ │ +in+4 dd Номер записи файла данных для n-го ключа │ │ +in+8 db ? dup Значение n-го ключа │ │ +ix+0 dd Смещение узла-потомка (корня поддерева, │ │ содержащего значения ключей │ └────────────────────────────────────────────────────────────────────────────┘ Для разработки CALL-интерфейс а очень важно правильно решить вопрос о выборе уровня представления о БД , необходимого и достаточного для пользователя при разработке приложений. Детальный уровень пред ставления о БД предполагает сделать доступными программисту все поля дескрипторов файлов данных и индексов. Это позволит разрабатывать приложения, очень эффективно работающие с БД. Но такие возможности требуют от программиста знания всех тонкостей взаимо действия с БД не только со стороны Pascal, но и со стороны Clipper, чтобы избежать разработки приложений, не преднамеренно разрушающих БД. Для разработки надежных приложений в рамках Turbo Pascal программисту целесообразно предоставить точно такие же операции и уровень представления о БД, как и в рамках Clipper.

Создание и реорганизация базы данных

Для создания и реорганизаций файлов данных необходимо определить требуемую структуру файла данных - полное определение характеристик полей. Структуру файла можно определить в виде четырех массивов: - массив имен полей (array [1 ..] of string [10]); - массив типов полей (array [1 ..] of char); - массив длин полей (array [1 ..] of byte); - массив числа знаков в дробной части для числовых полей (array 1 1 . . 1 of byte). Элем енты массива с одним номером определяют па раметры некоторо го поля. Процедуре создания файла данных (Create) в качестве параметров передаются имя файла данных, число полей и указатели на перечисленные массивы.
var
     MyStructure = record
                 NumOfFi : word;
                 FiName : array [1..MaxFields] of string [10];
                 FiType : array [1..MaxFields] of Char;
                 FiLength : array [1..MaxFields] of Byte;
                 FiDecimals : array [1..MaxFields] of byte;
     end; {MyStructure}

     begin
     ... {структура файла определена в MyStructure}
     with MyStructure do
     Create ('C:\MYDIR\MYFILE.DBF', NumOfFi,
              @FiName, @FiType, @FiLength, @FiDecimals);
     if dbf_error <> Ok then ...
     Use ('MYFILE',ExlusiveOff);
     ...........
При создании файла рассчитываются все параметры заголовка файла, создается DBF- и, при наличии Memo- полей, DBF-файл с пустой структурой (только заголовки). Реорганизация файла данных предполагает добавление или удаление имеющихся полей и изменение параметров полей. Очень важно при этом сохранить в файле данных как можно больше информации. Для реорганиза ции файла данных необходимо еще дополнительно определить массив исходны х имен по лей. т.е. поле добавляется в файл и заполняется пус тым значением, если для него исходное имя не опреде лено. Если исходное имя поля определено, то значения нового поля буд ут скопированы из значений исходного. Если существующее поле файла данных не присутсву- ет в новой структуре, то оно будет удалено. В составе DBFILE процедура реорганизации файла данных описана, как:
procedure Modify (DBFPath : string;
                       NF      : word;
                       AFNPtr, ASFNPtr,
                       AFTPtr, ALFPtr, ADPPtr : pointer);
При создании индекса (CreateIndex) достаточно определить, для какого файла создается индекс, какое выражение образования значений ключа он имеет и допускает ли индекс хранение неуникальных значений ключа.
... (* CreateIndex *)
    Use('MYFILE',ExlusiveOff);
    if dbf_error <> Ok then ...
    CreateIndex('MYFILE','MYINDEX','NAME+BIRTHDAY',UniqueOn);
    if dbf_error <> Ok then ...
Перестроить существующий индекс можно с по мощью процедуры Relndex, указав имя файла данных и имя перестраиваемого индекса. Процедуры индексирования должны обладать до статочно большой скоростью. Методы скоростного ин дексирования специфичны и их реализация доставляет массу проблем. Индексирование выполняется с исполь зованием достаточно большого буфера в оперативно й памяти (по умолчанию в DBFILE - 64К). На первом этапе во временный файл выталкиваютс я упорядоченные блоки значений ключей со ссылками на соответствующие записи файла данных. Создание тако го временного файла выполняется за один просмотр фай ла данных. При этом для загрузки записей файла данных используется тот же самый буфер. На втором эт апе выполняется слияние блоков вре менного файла с упорядоченными значениями ключа. При выполнении слияния очередной по значению ключ выталкивается в один узлов стр оящегося В-дерева. В памя ти (в том же буфере) хранятся узлы одной изменяющей ся ветви дерев а. При наполнении очередного узла ключами он выталкивается в файл поисковой структуры. Например, при выполнении индексирования в среде Foxbase объем операций на этапах отображается на дисплее, как 'ХХХ key generated' и 'ХХХ key Indexed'.

Подготовка базы данных к работе и образование виртуальных представлений

Для подготовки базы данных к использованию в не котором приложении необходимо открыть требуемые файлы данных и их индексы. Команда ЯМД Clipper:
use <file> [indexes <index-list>][exclusive]
в рамках DBFILE разбита на три процедуры - открыть файл данных (Use), открыть индекс для файла данных (UseIndex) и установить один на открытых индексов в качестве главного (SetIndex):
(* Use *)
     ...
     Use('C:\MYDIR\MYFILE.DBF',ExclusiveOff);
     if dbf_error <> Ok then ...
     UseIndex('MYFILE','MYINDEX1');
     if dbf_error <> Ok then ...
     UseIndex('MYFILE','MYINDEX2');
     if dbf_error <> Ok then ...
     SetIndex('MYFILE','MYINDEX1',false,false);
Bсe открытые индексы автоматически модифицируются при добавлении записей в файл данных и их обновлении. Главный индекс обеспечивает поиск записей по ключу и определяет логический порядок следования записей в файле данных в соответствии с упорядоченностью значений ключа в индексе. Под виртуальным представлением можно понимать совокупность определенным образом связанных между собой файлов данных. Для каждого из файлов можно установить фильтр и главный индекс. Межфайловые связи строятся на основе индексов. При установленном фильтре все записи, не удовлетворяющие условию фильтра, исключаются из рассмотрения. В ЯМД CLIPPER условие фильтра интерпретируется. В рамках TURBO PASCAL в качестве фильтра можно использовать функцию типа boolean, возвращающую значение true в том случае, если запись удовлетворяет фильтру. При загрузке очередной записи файла данных автоматически опрашивается фильтр-функция. Запись, не удовлетворяющая фильтр-функции, пропускается. Фильтр-функция может иметь вид:
{$F+}
     function MyFilter : boolean;
     var
         BirthDay : string[8]; {date}
     begin
         GetField ('MYFILE','BIRTHDAY',BirthDay);
         {Получить значение поля BIRTHDAY текущей записи}
         if BirthDay = '21.06.63' then MyFilter := true
         else MyFilter := false;
     end; {MyFilter}
Установить фильтр для файла MYFILE достаточно просто:
(* SetFilter *)
     ...
     Use('C:\MYDIR\MYFILE.DBF',ExclusiveOff);
     if dbf_error <> Ok then ...
     SetFilter('MYFILE',@@MyFilter,false);
     ...
В дальнейшем при обращении к файлу данных до ступны будут только те записи, у которых поле BIRTHDAY = '21.06.63'. В DBFILE реализован точно такой же механизм поддержки межфайловых связей, как в ЯМД CLIPPER. Файлы связываются при указании ключевого выражения исходного файла и индекса зависимого файла. При этом предполагается, что заданное выражение и выражение индекса совпадают по сути.
(*  SetRelation  *)
     Use('C:\MYDIR\MYFILE1.DBF',ExclusiveOff):
     if dbf_error <> OK then ...
     Use ('C:\MYDIR\MYFILE2.DBF',ExclusiveOff);
     if dbf_error <> OK then ...
     ...
     SetRelation('MYFILE2','NAME+BIRTHDAY','MYFILE1','MYINDEX1',false);
     ...
В дальнейшем при перемещении по записям файла MYFILE2 автоматически будет отыскиваться присоеди няемая запись в файле MYFILE1 . Для этого по значениям текущей записи MYFILE2 на основе выражения NAME+BIRTHDAY будет сформирован ключ присоединяемой записи файла MYFILE1, которая будет найдена при использовании индекса MYINDEXI (для файла MYFILE 1 ).

Доступ к данным

Для каждого от крытого файла данных поддержива ется указатель текущей записи, значением которого яв ляется номер загруженной эаписи файла данных либо О. Поля текущей записи файла данных доступны в прило жениях. Взаимодействие между файлом данных и приложе нием выполняетс я через буфер текущей записи, память под который выделяется автоматически при открытии файла данных. Для обмена информацией между переменными PASCAL-приложения и полями текущей записи файла данных в DBFILE определены две процедуры GetField (из поля в переменную) и PutField (из переменной в поле). При этом типы переменных должны контролироваться программистом и удовлетворять следующим соглашениям: ┌───────────────────────────────────────────────────────────────────────┐ │ Тип поля Длина поля Дробная часть Тип переменной │ ├───────────────────────────────────────────────────────────────────────┤ │ Character 1 - char │ │ Character 2..255 - string[2..255] │ │ Numeric 1..9 0 longint │ │ Numeric 9..13 0 real │ │ Numeric 1..13 1..11 real │ │ Date - - string[8] │ │ Logica - - boolean │ │ Memo - - pointer │ └───────────────────────────────────────────────────────────────────────┘ Доступ к записи файла данных сводится к перемещению указателя текущей записи. Физический доступ осуществляется по номеру записи:
var                                         (*Go*)
           Birthday  :  string[8];
           Go('MYFILE',15); { К записи с номером 15 }

           if dbf_error <> OK  then  ...
           GetField('MYFILE','BIRTHDAY',Birthday);
           Write('#15',Birthday);
           .....
Логический доступ выполняется по значению ключа или в порядке возра стания/убывания значений ключа главного индекса с помощью процедур (Find, GoTop, GoBottom, Skip). Для поиск а по ключу необходимо определить зна чения всех ключевых полей главного индекса.
(*Find*)
           ...
           SetIndex ('MYFILE','MYINDEX',false,false);
           ...
           ReadLn(Name);
           PutField ('MYFILE','NAME',Name);
           Find ('MYFILE');

           if dbf_error <> Ok then ...
           GetField ('MYFILE','BIRTHDAY',BirthDay);
           GetField ('MYFILE','PHONE',Phone);
           Write (Name,':',BirthDay,' ',Phone);
           ...
Последовательный просмотр записей файла данных выполняется аналогично ЯМД Clipper.
(*GoTop/Skip*)
           GoTop('MYFILE'):
           while dbf_error  =  OK  do  begin
                 GetField('MYFILE','NAME',Name);
                 GetField('MYFILE','PHONE',Phone);
                 Writel(Name,'  ',Phone);
                 Skip('MYFILE',1):
           end; {while}
При последовательном логическом доступе к записям файла данных все записи, помеченные к удалению, но не удовлетворяющие фильтру, пропускаются. Если файлы связаны между собой с помощью SetRelallon, то процедуры GetFleld в рассмотренном примере можно применять к любому из связанных файлов.

Модификация данных

Для добавления за писи в файл данных необходимо определить все поля записи с помощью PutField и вы полнить процед уру Append.
(* Append*)
             Readt(Name);
             PutField('MYFILE','NAME',Name);
             Readln(Phone);
             PutField('MYFILE','PHONE',Phone);
             ....
             Append('MYFILE');
             if dbf_error <> OK then ...
Для обновления полей текущей записи файла данных используется такая же схема и процедура Replace. Удалить и восстановить текущую запись можно с помощью процедур Delete, Recall. Физическое удале ние всех помеченных к удалению записей выполняется процедурой Расk. При модификации записей файла данных авто матически модифицируются все открытые ин дексы файла.

Сетевое взаимодействие

Координация работы с базой данных со стороны не скольких приложений обеспечивается механизмом блокировок. Блокировки выполняются ср едствами се тевого сервиса MS DOS (функция 5Ch прерывания 21h). Большинство средств се тевого программного обеспечения поддерживают эту интерфейсную точку входа и благодаря этому CLIPPER работоспособен практически во всех видах сетей. Механизм блокировок по ддерживается с помощью функций FLock (блокировка файла), Rlock (блокировка текуще й записи), возвращающих true при удачном вы полнении. Разблокирование файла или его записи вы полняется процедурой Unlock. В рамках MS DOS используется абсолютная блокировка (на запись и чтение). Это весьма неудобно. В CLIPPER блокировка выполняется только на запись (обновление), а чтение заблокированной записи/файла выполняется свободно. Выполняется это достаточно оригинально. При блокировании записи с номером N науровне MS DOS блокируется один байт файла со смещением 1 ,000,000,000 + N. Весь файл данных с записями, расположенными в файле до 1,000,000,000 байта оказывается доступным для чтения. Из другого Clipper-приложения заблокировать эту же запись с номером N (при использовании такого же подхода) невозможно. Интерес представляет также реш ение пробле мы использования буфера виртуальной памяти для индексов. Каждое из приложений в своей памяти поддерживает буфер для хранения записей узлов ин- декса. Использовать такой б уфер можно лишь в том случае, когда состояние данных в буфере соответст вует состоянию данных в файле индекса. Для распознава ния такой ситуации в заголовке индекса хранится код состояния Stay. При обновлении индекса каким-либо из приложений код состояния у величивается на едини цу. Этого оказывается достаточно для избежания кол лизий при использования виртуальной памяти в каж дом из приложений. Приложения, написанные с использованием DBFILE, и Clipper-приложения обладают полной со вместимостью при использова нии одной базы данных. Концепция сетевого взаимодействия, реализо ван ная в Clipper, обладает недостатком, который связан с тем, что управление блокировками должно быть реали зовано в каждом из приложений прикладными про граммистами. Не управляя блокировкам и, достаточно тяжело избежать ситуации, известной под названием "тупика". FLock('FILE1') FLock('FILE2') не выполнить Приложение 1 ┌──────┴───────────────────────────┴────────────────────────── │ FLock('FILE2') FLock('FILE1') не выполнить │ Приложение 2 │ ├─────┴─────────────────────────────┴───────────────────────── │ │ Параллельное выполнение приложений │ └────────────────────────────────────────────── Такая ситуация обеспечивает "за висание" обоих приложений на бесконечно долгий срок. Прилож ение 1 сможет заблокировать FILE2 только после того, как Приложение 2 освободит этот файл. Но этого не про изойдет потому, что Приложение 2 находится в анало гичном ожидании. Для избежания таких си туаций рекомендуется вы полнять групповую блокировку все х файлов, требуе мых для выполнения некоторой модифицирующей опе рации. Групповая блокировка завершается удачно, ес ли все файлы удалось заблокировать. Если хотя бы один из файлов не удалось заблокиро вать, то необходи мо разблокировать все удачно заблок ированные и че рез некоторый период времени попытаться повторно выполнить эту же групповую блокировку.

Соответствие команд ЯМД Clipper и процедур DBFILE

Практически все команды ЯМД Clipper могут быть реа лизованысредствами DBFILE. Ниже приведена таблица, показывающая, на основе каких процедур и функций DBFILE можно достигнуть тех же результатов, что и при использовании базовых команд ЯМД Clipper. ┌──────────────────────────────────────────────────┐ │ CLIPPER DBFILE │ ├──────────────────────────────────────────────────┤ │ AFIELD() FIELDINFO+FIELDSINFO │ │ APPEND BLANK CLEARFIELDS+APPEND │ │ APPEND FROM APPENDFROM │ │ CANCEL QUIT │ │ CLOSE INDEXES UNUSEINDEXES │ │ COPY STRUCTURE TO COPYSTRUCTURE │ │ COPY TO COPYTO │ │ CREATE CREATE │ │ DELETE DELETE │ │ DELETED() DELETED() │ │ EOF() EOF() │ │ FCOUNT() FILEINFO │ │ FIELD/FIELDNAME() FIELDSINFO │ │ FIND FIND │ │ FLOCK FLOCK │ │ FOUND() NOT EOF() │ │ GO/GOTO GO │ │ GOBOTTOM GOBOTTOM │ │ GOTOP GOTOP │ │ INDEX ON CREATEINDEX │ │ INDEXKEY() INDEXINFO │ │ PACK PACK │ │ QUIT QUIT │ │ RECALL RECALL │ │ RECNO() RECNO() │ │ REINDEX REINDEX │ │ REPLACE REPLACE │ │ RLOCK()/LOCK() RLOCK() │ │ SET FILTER TO SETFILTER │ │ SET INDEX TO USEINDEX+SETINDEX │ │ SET ORDER TO SETINDEX │ │ SET RELATION TO SETRELATION │ │ SET UNIQUE ON/OFF SETINDEX │ │ SKIP SKIP │ │ UNLOCK UNLOCK │ │ USE USE+USEINDEX UNUSE │ │ ZAP ZAP │ └──────────────────────────────────────────────────┘

Направления развития

Реализацию DBFILE для новых версий Turbo Pascal (Turbo Pascal 7.0 и Borland Pascal 7.0) целесообразно оформить в структурах объектно-ориентированного подхода и в виде динамически подключаемой библиотеки (DLL, Dinamic Link Library). Это не только позволит эффективно использовать модуль в DOS-приложениях и Windows-приложениях, но и обеспечит возможность использования DBFILE из других языков программирования, например из C++. Необходимо также обеспечить поддержку новых форматов индексных файлов, используемых в Clipper версии 5.0 и выше.
Дата публикации: 26.03.1994