Группа SoftWeyr
Объектно-ориентированный интерфейс
к DBF-файлам
версия 0.95 beta
OVERVIEW
Объектно-ориентированный интерфейс к DBF-файлам позволяет
из программы, написанной на Turbo Pascal 6.0 и выше работать с
DBF-файлами, пользуясь привычными понятиями языка xBase.
(xBase - обобщенное название для dBase, FoxBase и Clipper)
Основные преимущества:
1. Возможность открыть произвольное количество объектов баз
данных (аналог рабочей области в xBase)
2. Возможность работы с базой данных, хранящейся не только в
файле DOS, но и в EMS, в памяти или любом месте, к которому
можно обратиться как к потоку с прямым доступом.
3. В отличие от большинства подобных продуктов
поддерживаются фильтры (аналог SET FILTER TO)
4. Поддерживаются индексированные базы данных.
5. Объектно-ориентированная идеология позволяет легко
добавлять новые типы индексов (которые у каждого
xBase-совместимого продукта свои) и Memo-полей.
Направления совершенствования (можете присоединяться)
1. Индексы других типов
(модули dBaseIII.pas, dBaseIIIp.pas, dBaseIV.pas и
Clipper.pas)
2. Поддержка .MDX файлов FoxPro
3. Поддержка вычисляемых индексных выражений
3. Объекты-наследники от TDBF, работающие с 3-й нормальной
формой
КОМПЛЕКТ ПОСТАВКИ
Система поставляется в исходных текстах, поскольку разные
версии TP имеют разный формат библиотек. Мы могли бы,конечно
как делают некоторые, поставлять версии TPU для TP 6.0 и TP
7.0, но что будет делать бедный пользователь у которого
окажется версия 7.5 или 8.0 и не окажется нашего адреса? Кроме
того у Borland Pascal 7.0 три формата модулей - TPU,TPP и TPW
(кстати, в Turbo Pascal for Windows 1.5 и 1.0 наша библиотека
тоже должна работать, хотя мы и не пробовали).
Итак, в комплект поставки входят:
DBASE.PAS - модуль, содержащий объект TDBF и объекты,
TIndex и TMemofile, реализующие абстрактную концепцию работы с
индексами и файлами Memo.
FOXPRO.PAS - реализация индексов для IDX-файлов FOXPRO
и реализация Memo для FPT-файлов.
Почему FoxPro? Да потому, что у нас было описание форматов
для нее. Если кто напишет интерфейс к NDX и MDX файлам dBase
или NTX Clipper, и положит его туда, где я смогу до него
дотянуться, я буду только рад, да и все остальные пользователи
- тоже.
УСЛОВИЯ РАСПРОСТРАНЕНИЯ
Как и вся продукция, поступающая на mailserver'ы под маркой
SoftWeyr, данная библиотека не является ShareWare, она
FreeWare, т. е. автор далек от мысли требовать какие-либо
деньги даже с тех, кто использует ее для добывания скудного
прокорма (а сколь скуден прокорм программиста в б.СССР автор
знает по собственному опыту). С другой стороны, автору не
понравится если вы будете
1. Удалять его CopyRight
2. Распространять библиотеку кусками (При распространении
должны быть присутствовать по крайней мере три файла -
DBASE.PAS, FOXPRO.PAS и DBOBJECT.DOC, хотя последний можно
распространять и отдельно) Особо не приветствуется
распространение библиотеки в откомпилированном виде без
приложения исходных текстов
3. Вносить исправления и изменения так, что нельзя понять где
авторский текст, а где нет. Желательно все доделки
реализовывать в виде отдельных файлов, а если уж такой
возможности нет, выделять комментарием начало и конец вашей
вставки и оставлять в виде комментариев то, что вы удалили.
И последнее, большая просьба - буде кто найдет, а тем паче
исправит ошибку или сделает общественно полезную доделку - типа
реализации другого формата индекса или MEMO, пришлите это мне.
(но только не в виде TPU, пожалуйста, а в исходном виде.)
Вообще, я надеюсь, что время от времени будут появляться
новые версии, во всяком случае появится версия 1.00 не beta,
так что распространяя доделки в виде TPU вы не только привяжете
пользователей к определенной версии Turbo Pascal, но и к
определенной версии dBase.tpu, что совсем некорректно, ибо пока
она beta.
ИЕРАРХИЯ ОБЪЕКТОВ
+---------+
Objects.TPU | TObject |
+---+-----+
+-------+------+-----------------+
- - - - - - - - - | - - - - - - -| - - - - - - - - | - - - - -
+--+---+ +--+----+ +--+-------+
DBase.TPU |TDBF | | TIndex| | TMemoFile|
+--+---+ +--+----+ +---+------+
+---+---------+ | |
| TFilteredDBF|+ - |- - - - - - - - - | - - - -+
+---+---------+ +--+--+ +---+---+
+---+--------+ ||TIDX | | TFPT | |
| TIndexedDBF| +-----+ +-------+
+------------+ |FOXPRO.TPU |
ОПИСАНИЕ ОБЪЕКТОВ
1. TDBF - объект реализующий в программе базу данных.
Многим пользователям будет достаточно одного этого объекта,
так как часто программе не нужны ни фильтры, ни индексы, а
нужно просто тупо и в лоб прочитать DBF-файл.
Поля
S:PStream - Указатель на поток, откуда берутся данные.
Читать это поле вам может потребоваться только
для того, чтобы узнать статус потока в случае
возникновения ошибки
HeaderSize - размер заголовка DbF-файла. Сам не знаю, почему
это поле не private
RecSize - физический размер записи (вместе с признаком
удаления). Тоже по смыслу внутреннее дело объекта
RecCount : LongInt - общее количество записей в файле.
Установка фильтров и индексов с опцией UNIQUE на
значение этого поля не влияет.
FieldCount : Byte - количество полей в базе данных
SkipDeleted : Boolean - если True, то включен режим
SET DELETED OFF. Просьба не изменять напрямую.
для этого есть процедура SetDeleted
Mode:Byte - содержит код режима доступа к БД. Одно из трех
dbOpen - база открыта на чтение/запись
dbReadOnly - база открыта только на чтение
dbCreate - база в процессе создания. доступны
только операции добавления полей и создания.
RecNo:LongInt - номер текущей записи (физической) Изменять
методами Go, Skip и Next
VersionID: Byte - номер версии DBF файла. Если он не равер 3,
то файл имеет поля типа Memo и нужно создать
соответствующий объект TMemoFile, если вы хотите
иметь доступ ко всей информации в файле.
Memo:PMemoFile - указатель на объект TMemoFile, связанный с
данным DBF-файлом. Инициализируется путем
выполнения присваивания после инициализации
объекта, если открыт сущетсвующий файл и перед
выполенением методов Create/CopyStru если
создается новый файл, содержащий поля типа Memo
Методы:
constructor Init (St:PStream);
Инициализирует объект. Единственный параметр -
указатель на поток. Если поток открыт в режиме
stOpenRead, Mode получит значение dbReadOnly, если
поток пуст, dbCreate.
В случае возникновения ошибок, код ошибки
заносится в глобальную переменную dbError и
вызывается Fail. Поэтому, если вы размещаете
объект в динамической памяти, проверьте, не равен
ли nil указатель на него. Если это так, то
произошла ошибка и базу данных вы не открыли.
(см. раздел Constructor error recovery в Turbo
Pascal Programmers Guide)
Доступ к структуре БД
Function Field(n:Integer):String; - Все кто работал с dBase
знают, эта функция возвращает имя поля по его
номеру.
Function FieldNo( FName:String):byte; - А это обратная
операция. И если структура базы данных задано не
жестко, она вам понадобится - ведь обращение к
полям происходит по номеру, а не по имени.
(если же структура задана жестко, рекомендую
описать целые константы, соответствующие именам
полей и равные их номерам. Это нагляднее). В
случае, если поле с указанным именем не найдено,
возвращает 0.
Function FieldType(n:integer):Char; - Возвращает тип поля, т.е
букву N,C,L или M.
Function FieldLen(n:integer):byte;
{------ Возвращает длину поля}
Function FieldDec(n:integer):byte;
{------ Возвращает число знаков после запятой. Для
всех полей кроме N всегда возвращает 0
Чтение значений из базы данных.
В данном разделе параметр n задает номер поля. Читать
можно только поля текущей записи
Function GetField(n:Integer):string;
Возвращает строковое представление любого
поля кроме Memo. Для Memo возвращает строковое
представление адреса в Memo-файле
Function GetIntField(n:integer):LongInt;
Возвращает целое представление числового поля с
Dec=0
или адрес поля MEMO в файле
Function GetRealField(n:integer):Float;
Возвращает вещественное представление числового
поля тип Float равен Real, если вы откомпилировали
dBase.pas с опцией N- и Extended,если с N+. Но пусть
вас это не волнует. Он совместим по присваиванию с
любым вещественным типом и вы можете работать с тем,
который вам нравится. Модуль dbase нигде не требует
параметров-переменных типа Float, а только параметры
значения и возвращаемые результаты функций.
Function GetBooleanField(n:integer):Boolean;
Возвращает значение логического поля}
Вызов любой из этих функций для поля неправильного типа
приведет к тому, что в переменной dbError появится значение
кода ошибки, а функция вернет 0, false или пустую строку.
Специального интерфейса с полями даты нет. Их надо
рассматривать как частный случай строковых полей. Можно было,
конечно,написать кое-что на эту тему, но с одной стороны в
Turbo Professional есть мощнейший модуль TPDate, где все на эту
тему уже написано, а с другой - не хочется, чтобы наш модуль
его требовал при компиляции - вдруг у кого-то нет, а этому
кому-то поля даты и не нужны.
Изменение полей БД.
Procedure PutField(n:Integer;Value:String);
Изменяет строковое поле
Procedure PutIntField(n:Integer;Value:LongInt);
Изменяет целое поле, т.е. поле Numeric с числом
десятичных цифр равным 0,или заносит ссылку на запись
в файле Memo в поле Memo
Procedure PutRealField(n:Integer;Value:Float);
изменяет любое числовое поле
Procedure PutBooleanField(n:Integer;Value:Boolean);
Изменяет логическое поле
Изменения будут храниться в буфере до тех пор пока вы не
уйдете из данной записи или не вызовите деструктор. Тогда они
запишутся на диск.
Procedure PutMemoField
Эти процедуры могут возвращать ошибки в глобальной
переменной DBError. Если код ошибки больше 0, то произошла
серьезная неприятность и надо что-то делать, если меньше, то
это мелочи - строка не уместилась в поле и была обрезана.
Подробнее - см. в разделе "Коды ошибок".
Навигация по базе данных
Procedure Go(ARecNo:LongInt);virtual; переход к записи по
физическому номеру. Если база данных фильтрована,
то текущей становится первая удовлетворяющая
фильтру запись после указанной.
Вместо номера записи можно использовать константы
Top и Bottom, что делает работу с TDBF еще более
похожей на xBase. Go делает удаленную запись
текущей независимо от значения SkipDeleted
Procedure Skip(Count:LongInt);virtual;
Перемещает указатель на Count записей (вперед или
назад - зависит от знака Count). В режиме Set
Deleted Off удаленные записи не считаются. Записи не
удовлетворяющие фильтру не считаются. В
индексированном файле перемещение происходит в
порядке индекса.
Procedure Next - то же, что Skip(1), только быстрее.
Function Eof:Boolean;virtual; - возвращает True, если достигнут
конец файла
Удаление/восстановление
Procedure Delete - помечает текущую запись как удаленную.
Procedure Recall - снимает с текущей записи отметку для
удаления.
Function IsDeleted:Boolean - возвращает True, если текущая
запись помечена для удаления.
Procedure SetDeleted(On:Boolean); - переключает режимы Set
Deleted On и Set Deleted Off.
SetDeleted(True)= Set Deleted On
SetDeleted(False)= Set deleted off
Для физического удаления записей нужно перезаписать весь
файл.
Добавление записей
На самом деле для ввода новой информации нет нужды в
специальных методах - установите курсор на конец файла и
записывайте данные. Новая запись создастся автоматически. Но
если она не одна, то придется заводить новые записи явным
образом.
Procedure InsertBlank(Before:boolean);virtual;
{------- Вставляет пустую запись после/перед текущей}
Procedure AppendBlank;
{------- Вставляет пустую запись в конец файла}
procedure AppendFrom(P:PDBF);
{------ Копирует в данный файл записи из другого.
Копируются только те поля, которые совпадают по имени
и типу. По длине и числу десятичных знаков они могут
не совпадать и кода ошибки вы скорее всего при этом
не получите, так как копирование следующей записи его
затрет. А вполне может случиться так, что данные
последней записи влезут в новые поля, а данные
предпоследней- нет. Копирование производится с учетом
текущего фильтра базы - источника, ее режима Deleted
и порядка, заданного ее индексом.
ПРОЧЕЕ
Destructor Done;virtual; - сохраняет последние изменения,
уничтножает все буферы, индексы и т.п., закрывает файл.
Не забывайте вызвать!
Procedure Zap; уничтожает все данные, оставляя только заголовок
файла. резервной копии не создает.
СОЗДАНИЕ ФАЙЛОВ
Если вы пытаетесь открыть несуществующий файл, то объект
TDBF создастся и его поле Mode будет иметь значение dbCreate.
Для того, чтобы с базой данных можно было работать, нужно
создать структуру файла и записать его заголовок.
Простейший способ - скопировать структуру другого файла с
помощью
procedure CopyStru(P:PDBF);
Этот метод скопирует описание всех полей и создаст
заголовок, переведя базу в режим dbOpen.
Eсли вам нужно создать базу данных с собственной структурой,
то вам придется воспользоваться методами
Procedure AddField(Name:String;Typ:char;LEn,dec:byte);
который добавляет одно поле с указанными свойствами
и
Procedure Create; который записывает заголовок и переводит базу
в режим dbOpen.
Например, скопировать первые 10 полей базы P в базу B
(P и B - указатели типа PDBF) можно так:
p:=New(PDBF,Init(New(PDosStream,Init('OLDFILE.DBF',
stOpenRead)));
b:=New(PDBF,Init(New(PDosStream,Init('NewFile.DBF',
stCreate)));
With P^ do
For i:=1 to 10 do
B^.AddField(Field(i),FieldType(i),FieldLen(i),
FieldDec(i));
B^.Create;
B^.AppendFrom(P);
Dispose(P,Done);
Dispose(B,Done);
Объект TFilteredDBF
Этот объект реализует базу данных, в которой доступны не
все записи, а только те, которые удовлетворяют некоторым
условиям. Эти условия задаются с помощью пользовательской
функции, описанной как
function MyFilter(var D:TDBF):Boolean;far;
Функция-фильтр должна быть глобальной в вашей программе или
модуле. В качестве параметра она получает объект, коий ей
фильтруется и может пользоваться методами GetXXXfield для
доступа к его полям.
Она должна возвращать True если текущая запись удовлетворяет
условию и False - если нет.
Установка фильтра производится с помощью метода
procedure SetFilterTo(AFilter:TFilter); где
тип TFilter описан как function(var D:TDBF):Boolean;
В качестве фактического параметра передается идентификатор
функции.
Такой метод передачи параметра позволяет на этапе
компиляции проверить соответствие функции требуемому виду. При
несоответсвтии типа функции вы получите сообщение Type
mismatch, а если функция локальная или не объявлена как Far, то
Invalid procedure or function reference.
В случае, если фильтр не был установлен, или был сброшен
процедурой RemoveFilter, то все записи доступны.
Объект TIndexedDBF
Отличается от TFilteredDBF тем, что записи в базе данных
упорядочены с помощью индексного файла. Индексных файлов может
быть несколько, при этом упорядочение задается только одним
(последним из заданных) а при изменении базы обновляются все.
деструктор этого объекта считает, что все индексы размещаются
в динамической памяти и вызывает для них Dispose.
Установка индекса производится методом
procedure SetIndexTo(P:PIndex);
Сделать уже открытый индекс текущим можно методом
procedure SetOrderTo(n:Integer);
(индексом номер 1 считается текущий, 2 - предыдущий и т.д.)
Индекс - объект - наследник абстрактного объекта TIndex
Объект TIndex
Это абстрактный объект, реализующий метод работы с индексами,
требующийся TIndexedDBF.
Он имеет поле Typ типа IndexType которое может иметь два
значение itpString и itpNumeric,поле Next - указатель на
следующий индекс в списке и методы:
Function First:LongInt;virtual;
Возвращает номер первой по порядку индекса. Используется в
TIndexedDBF.GO(Top)
Function NextKey:LongInt;virtual;
Возвращает следующий номер записи после текущего или -1 если
текущее значение последнее.
Function PrevKey:LongInt;virtual;
возвращает номер предыдущей записи или -1, если запись первая
Далее идут две функции поиска. Совместить поиск по строковому
и числовому ключу, увы не удалось. Поэтому для любого из
индексов одна из этих функций будет производить поиск, а вторая
заносить в dbError значение dbInvalidType. Обе функции должны
возвращать номер записи, ближайшей к указанному ключу и
помещать в поле Exact значение True, если найдена запись с
точно совпадающим ключом и False, если такой записи нет.
Это единственные методы объекта TIndex, которые вам надо
вызывать непосредственно. Все остальные методы представляют
собой интерфейс с объектом TDBF
Function SeekStr(Key:String):LongInt;virtual;
Поиск - в случае строкового индекса}
Function SeekNum(Key:Float):LongInt;virtual;
Поиск - в случае числового индекса}
Procedure UpdateKey;virtual; эта процедура вызывается при уходе
из записи. Она должна обновить значение ключа, если ключевое
поле изменилось, вставить новую запись в индекс, если
добавлена новая запись и удалять , если запись удалена.
Объект TMemoFile
Это тоже абстрактный объект, реализующий работу с файлом
типа Memo.
Он не имеет полей, но имеет следующие методы
function GetMemoSize(Offset:LongInt):word;virtual;
Возвращает размер текста указанного поля Memo для поиска
нужного поля в параметре Offset передается содержимое поля в
файле dBF;
Procedure Get(Offset:LongInt;var P);virtual;
Записывает в блок памяти, на который указывает
указатель P содержимое указаного поля Memo. Формат записи
соответствует структре TMemoRec из модуля Editors Turbo Vision
- сначала слово длины, а потом сам текст.
Function Put(var P):LongInt;virtual; -записывает в файл
текст Memo. Возвращает число, которое следует поместить в
соответствующее поле DBF-файла.
Procedure Free(Offset:LongInt);virtual; - освобождает участок
в файле Memo, добавляя его в список свободных.
Function GetDBFID:Byte;virtual; возвращает байт типа базы
данных, который нужно поместить в заголовок файла DBF.
Этот байт должен иметь значение
$83 для Memo в формате Dbase III + или FoxBase+, (расширение
Memo-файла DBT)
$F5 для Memo в формате FoxPro (FPT) и
$8B для Memo в формате dBase IV
СООБЩЕНИЯ ОБ ОШИБКАХ
В модуле dBase определена глобальная переменная dbError,
куда практически каждая операция помещает код завершения. Этот
код может быть 0 - ошибок нет, отрицательный - что-то не так,
но операция выполнена и положительный - операцию выполнить не
удалось. Такое кодирование облегчает при необходимости
игнорирование несущественных ошибок.
Для облегчения работы с кодами, каждому из них соответствует
символическая константа с длинным, но удобочитаемым названием.
Итак, ошибки бывают:
dbFieldEmpty=-1;
Метод GetXXXField обнаружил пустое поле, возвращено
значение по умолчанию (0 - для числовых поле, False - для
логических. Для строки это вообще нормальная ситуация- пустая
строка).
dbDataTooLong=-2;
Попытка PutField строку, превышающую длину поля. Она
обрезана по границе поля.
dbRecordNotDeleted=-3;
Попытка Recall запись, не помеченную для удаления. Ничего
не сделано, но результат тот, которого вы добивались.
dbAlreadyDeleted=-4;
Наоборот, попытка повторно удалить запись.
Все, что далее - более серьезно:
dbInvalidMode=1;
Попытка добавить поле в существующую базу данных или,
наоборот, писать данные (или читать) в создаваемую. Попытка
изменить файл, открытый только для чтения.
dbInvalidField=2;
Попытка обратиться к несуществующему полю или найти в
базе данных поле с длиной имени более 11 символов.
dbInvalidRecNo=3;
Попытка обратиться к записи с очень большим или
отрицательным номером
dbInvalidFieldName=4;
Попытка использовать русские буквы в имени поля
dbInvalidType=5;
Неправильный тип поля (PutRealField в строковое поле или
что-нибудь в этом роде
dbInvalidData=6; Данные не соответствуют типу поля.
Например буква в числовом поле.
dbLimitExeeded=7; Данные не умещаются в числовое поле.
Ничего не записывается.
dbBaseEmpty=8; Попытка читать из пустой базы данных
dbRecordTooLarge=9; Попытка добавить в базу данных поле,
вместе с которым длина записи превысит 4 К
dbFieldAlreadyExist=10; Попытка добавить поле имя
которого совпадает с уже существующим.
dbInvalidIndexNum=11; В SetOrderTo передано отрицательное
число или число, превышающее количество открытых индексов.
dbInvalidIndex=12; Зарезервировано для использования
наследниками от TIndex
dbUndefinedIndex =13;
dbMemoNotOpen=14; Попытка обратиться к Memo полю, не
инициализировав объект типа TMemoFile и/или не поместив ссылку
на него в поле Memo объекта TDBF.
Замечания по beta-версии 0.90
В версию 0.95 beta данной библиотеки включен модуль
FOXPRO.PAS, в котором не реализованы функции обновления
индекса. При попытке изменить ключевое поле или удалить запись
программа будет слетать по ошибке abstract method call. Данную
особенность ни в коем случае не следует расценивать как
стремление автора содрать деньги за полную версию. Просто я еще
не дописал эту вещь, но уже имел неосторожность
разрекламировать ее в Relcom, после чего был завален просьбами
ее прислать. Поэтому получайте пока AS IS.
По моему мнению даже в таком урезаном виде модуль FOXPRO
полезен, ибо позволяет работать с индексами FOXPRO в режиме
ReadOnly, а это тоже иногда бывает нужно. Модуль FOXPRO, вернее
то, что в нем работает, тестировался с индексными файлами,
созданными FOXPRO ver 1.00 US/Canadion Edition и Foxpro 2.5B
for DOS
Сведений о том, чьи зарегестрированные тороговые марки
использованы в тексте, не привожу, бо лень.
С вопросами, предложениями и дополнениями обращаться по
адресу
(relcom) vitus@agropc.msk.su
тел. (095) 230-80-61 (р ~ с 9:00 до 17:00)
ФИДО нет
|