home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
modula2
/
library
/
dbf_file
/
dbf.doc
< prev
next >
Wrap
Text File
|
1991-10-26
|
104KB
|
2,839 lines
Modula-Tools
dBase Toolkit
Copyright 1989, 1990, 1991 by David Albert
All rights reserved
Table of Contents Table of Contents _________________
License/Registration 1
Introduction 3
Database Basics 4
DBF Module 7
NDX Module 18
BuildNDX Module 28
Modula Tools Utilities 33
Appendices
A DBF technical specs 34
B NDX technical data 37
C Multi-user programming 39
Legalese Legalese ________
Copyright Copyright
The software documented herein is copyrighted and all rights
are reserved by David Albert.
Warranty Warranty
Neither Digital Engineering nor David Albert assume any
responsibility for damages or difficulties arising from use of
this product. For registered users only, if the original
diskettes or manual are found to be defective within 90 days
of the date of purchase, Digital Engineering will replace them
upon proof of purchase. This software is distributed on an
'as is' basis, without any warranty express or implied except
as set forth above. In particular, Digital Engineering and
David Albert disclaim all warranties of merchantability or
fitness for any particular purpose. The user waives all
claims against Digital Engineering and David Albert for
special, direct, indirect, or consequential damages arising
out of or in connection with the use or performance of this
software product.
Introduction - Page 1
Registration Registration ____________
This software is provided as shareware. This allows you to try the
software without purchasing it. You may use this software for up
to 30 days free of charge. Thereafter, you must register and pay
for it.
A great deal of work has gone into the development of this product
and the accompanying manual. As a software developer, you should
recognize and protect this work by respecting the distribution
agreement. The pricing is extremely reasonable and will encourage
further development of this package. It will also assure you of
future updates and bug fixes.
Several additions and expansions are under development for this
package including an applications generator. In order to provide
you with technical support and to keep you informed about updates
and new releases, you should send in this registration form.
Technical support and upgrades are ONLY available to registered
users, so please fill out and send yours in today.
Registration Form
Name : ____________________________________________
Company: ____________________________________________
Address: ____________________________________________
City : _______________ State: ___ Zip:___________
Computer Brand : __________________________________
Processor : 8088 80286 80386 80486
Modula-2 compiler: __________________________________
Modula-Tools ver.: __________________________________
Pricing: Students: $10.00
Personal/Company: $30.00
Commercial Use: $50.00
VA Residents add sales tax
Introduction - Page 2
Introduction Introduction ____________
Congratulations on your selection of the Modula-Tools dBase
toolkit. With this toolkit and your Modula-2 compiler, you will be
able to create sophisticated database applications for single and
multi-user environments with ease.
The Modula-Tools dBase toolkit provides three basic library
modules: DBF, NDX, and BuildNDX. The DBF module provides access to
dBase III, III+, and IV data files. The NDX module provides access
to dBase III, III+, and IV index files. The BuildNDX module allows
your programs to build sophisticated indices much faster than you
can with dBase.
There are numerous advantages to using the dBase toolkit. These
include:
- Compatibility with dBase industry standard.
- Easy of use.
- Flexibility
- Full Multi-user support.
- High-level databasing language.
- Compatibility with most popular Modula-2 compilers.
Naturally, the biggest advantage is the ability to produce advanced
database applications with the powerful Modula-2 programming
language.
This manual assumes you understand the basics of databasing and
Modula-2. Experience with dBase* is useful, but not required. If
you have not worked with databases previously, you should read the
overviews carefully and refer to the appendix for a suggested list
of introductory reading materials.
On the following pages, you will find a general overview of
databasing as well as detailed explanations of the library modules.
Each exported procedure is outlined, followed by a detailed
explanation of its functioning. More technical information can be
found in the appendices.
We believe the dBase toolkit will provide the best database
solution for your applications. However, we are always looking for
ways to improve. If you find an area that needs improvement, or
perhaps a feature that should be added, or maybe even (gasp!) a
bug, please don't hesitate to contact us.
* dBase III, III+, and IV are registered trademarks of Ashton Tate
IBM is a registered trademark of International Business Machines
Introduction - Page 3
Database Basics
What is a database?
A database is a collection of pieces of information of the
same general type. For example, a telephone book is a
database. Each entry in a phone book is of the same type:
Name, Address, and Telephone number. Computer databases are
similar; each entry in a computer database is of the same
general format. There can be an unlimited number of entries,
as long as they have the same structure.
How Are Databases Used?
Most computer applications require some sort of databasing.
For example, accounting programs must store records of each
check written and bill received. Inventory programs must keep
records of orders received, current stock, vendors, clients,
etc. A computer bulletin board needs to keep a database of
its users, files, messages, etc.
Database Semantics
Before continuing, let's examine some database terminology.
An entire database (such as a computerized telephone book) is
called a database file. Each entry in a database is called a database file
data record. Within each record are data fields. In a record fields
telphone directory, each person occupies a record; each
record has three fields: name, address, and telephone number.
Database Tools
In order to work with databases, you must be able to create
database files, store information in them, and retrieve
information from them. Since most general purpose languages
such as Modula-2 do not provide an easy way to do this, other
tools are required.
Special languages such as dBase and Paradox have been created
to facilitate working with databases. However, these
languages lack the speed, power, and flexibility of Modula-2.
With the Modula-Tools dBase toolkit, you get the best of both
worlds: speed and power, plus the ability to easily create
and work with industry standard database files.
Database Basics - Page 4
A more detailed example
A typical business program might need to store a list of
customers and keep records of their names, addresses,
purchases, credit, etc. This list of customers would be
referred to as the customer database. A record would be kept customer database
for each customer. You could visualize such a database as
follows:
Customer Customer Customer Customer
Record Record Record Record etc.
1 2 3 4
Each customer record can be seen as a fill-in-the-blanks type
form with the same questions asked of each customer, and the
same space allowed for their answers. Each blank on the form
is called a field. In this example, there are 9 fields for
each customer record:
Field Description Type Length ______ ___________ ____ ______
1 NAME Customer's name Characters 30
2 ADDRESS address Characters 30
3 CITY city Characters 15
4 STATE state Characters 2
5 ZIP Zip code Characters 10
6 PHONE Phone number Characters 15
7 PURCHASED Total purchased Numeric 12
8 BALANCE Total balance due Numeric 12
9 CREDIT Credit limit Numeric 12
Notice that each field has a name, fixed length, and type.
When information is entered into a customer record, it must be
of the correct type, and must fit in the space alloted. For
example, you can only enter numbers up to 12 digits in length
into the BALANCE field.
Each customer record will occupy exactly 138 spaces (the sum
of the lengths of each field). What changes from customer to
customer is the data contained in those fields.
Notice that each customer record occupies the same amount of
space regardless of how much of that space is used. This
makes it easy to calculate the size of a database file. For
example. If there were 1,000 customers in this database, it
would occupy 138,000 bytes.
Database Basics - Page 5
Storing and Retrieving
The purpose of a database is to allow you to store, retrieve,
and manipulate data quickly and easily. This means you must
have a way to access a database file, add new records to it,
modify existing records, and quickly find any record you
desire. The Modula-Tools dBase toolkit makes this easy.
More Terminology
Before you can use a database file, you must open it for open
general access. Once the file is opened, you can append new append
records to the end of the file, or retrieve existing records
from it. Note that new records are always added to the end of ___
a data file.
To retrieve a data record, you must specify the database the
record is in (eg. the customer database) and the record number record number
of the record you wish to retrieve. Once you have retrieved a
data record, you can display or modify the information in it.
If modified, you must place the updated record back in the
data file.
When you have finished accessing a database file, you should
close it to be certain sure all of your changes have been close
saved.
Fields and Buffers
When the dBase Toolkit retrieves a record from a database
file, it is initially placed in a temporary storage area
called a data buffer. All data going into or out of a data buffer
database file must pass through this buffer. The dBase
toolkit automatically creates and maintains a buffer for each
database file you work with.
The purpose of the buffer is to isolate your program from the
details and inner workings of the database file.
Database Basics - Page 6
DBF Module __________
The DBF module provides a complete set of procedures for working
with dBase III, III+, and IV database files. The procedures
provided will allow you to access (open) a database file, add data
records to it, retrieve and modify existing records, etc. The DBF
module insulates you from the details of working with database
files allowing you to concentrate on the application being created.
The database files used by the DBF module are completely compatible
with those created and used by Ashton Tate's dBase III, III+, and
IV. dBase data files have been in use for many years and have
become the industry standard for data storage. Numerous other
database systems use the dBase file format and a host of third
party utilities are available for them. By using the dBase format,
all of these utilities become available for your applications.
DBF Usage _________
The basic unit of the DBF module is the database file. You must
declare a variable of type DBFile for each database file you will
use. For example:
VAR CustomerFile : DBFile;
This variable, known as the file variable, is used whenever you
which to reference the data file. For example, to open (initially
access) a database file you would use the statement:
OpenDBF(CustomerFile, 'CUSTFILE.DBF');
When this statement is run, the DBF module searches for a file
named CUSTFILE.DBF, opens it and reads the structure of the data
file. Information, such as the number and types of each field,
record size, number of records, etc. is digested and stored by the
DBF module. If the file does not exist, or is not a valid
database, an error is flagged.
Opening the file also creates, in memory, an area to store customer
data records known as the record buffer. Whenever a record is read
from the file, it is placed in this buffer. For example, to read
the third customer record from the data file, you would use the
statement:
GetRec(CustomerFile, 3);
This reads the third customer record, and stores it in the record
buffer. You can then access individual fields within the record
from this buffer. For example, to get the customer's name from the
record buffer:
VAR CustName : ARRAY [0..29] OF CHAR;
DBF Module - Page 7
GetField(CustomerFile, 'NAME', CustName);
This copies the customer's name from the record buffer to the
variable CustName where it can be displayed, edited, and
manipulated as desired. Likewise, to put information into a
customer field, you would use the statement:
PutField(CustomerFile, 'NAME', CustName);
This copies the contents of the variable CustName into the NAME
field in the record buffer. Once the record in the buffer has been
modified, it should then be written back to disk with the
statement:
PutRec(CustomerFile, 3);
This places the contents of the record buffer back into the data
file on disk. If you do not do this, the changes made to that
record will be lost when your program finishes. When you are done
working with a data file, you should close it with the statement:
CloseDBF(CustomerFile);
At the end of this chapter, a short example program is provided
which demonstrates how these procedures are used together.
On the following page is the DBF definition module. This lists
each procedure and type that is provided by the DBF module. To use
these procedures, you import the DBF module into your application
program with the following statement:
IMPORT DBF;
You may then use any procedure listed by preceding it with the DBF.
prefix. For example:
DBF.GetRec(CustomerFile, 3);
This is best illustrated in the example program at the end of this
chapter.
The remainder of this chapter explains in detail each procedure
offered by the DBF module. For technical information about the
structure of a DBF database file, refer to appendix A at the end of
this manual.
DBF Module - Page 8
DBF Definition Module _____________________
DEFINITION MODULE DBF;
CONST
MaxFields = 200;
MaxRecLen = 4000;
ErrOpen = 1; ErrSeek = 5; ErrMemory = 9;
ErrClose = 2; ErrLock = 6;
ErrRead = 3; ErrUnLock = 7;
ErrWrite = 4; ErrBadDBF = 8;
TYPE DBFile;
VAR OK : BOOLEAN;
Error : CARDINAL;
MultiUser : BOOLEAN;
PROCEDURE AddRec (D : DBFile);
PROCEDURE CloseDBF (VAR D : DBFile);
PROCEDURE Deleted (D : DBFile): BOOLEAN;
PROCEDURE DelRec (D : DBFile);
PROCEDURE Encrypted (D : DBFile) : BOOLEAN;
PROCEDURE FieldData (D : DBFile; FieldName:ARRAY OF CHAR;
VAR Type : CHAR;
VAR Len, Dec : CARDINAL);
PROCEDURE FieldName (D : DBFile; FieldNum : CARDINAL;
VAR FieldName : ARRAY OF CHAR);
PROCEDURE FileName (D : DBFile; VAR Name : ARRAY OF CHAR);
PROCEDURE GetField (D : DBFile; FieldName: ARRAY OF CHAR;
VAR TheField : ARRAY OF CHAR);
PROCEDURE GetRec (D : DBFile; RecNum : LONGCARD);
PROCEDURE GetRecBuf (D : DBFile; Buf : ADDRESS);
PROCEDURE HasMDX (D : DBFile) : BOOLEAN;
PROCEDURE Incomplete(D : DBFile) : BOOLEAN;
PROCEDURE LockRec (D : DBFile; RecNum : LONGCARD);
PROCEDURE OldField (D : DBFile; FieldName: ARRAY OF CHAR;
VAR TheField : ARRAY OF CHAR);
PROCEDURE OpenDBF (VAR D : DBFile; FileName : ARRAY OF CHAR);
PROCEDURE PutField (D : DBFile; FieldName: ARRAY OF CHAR;
TheField : ARRAY OF CHAR);
PROCEDURE PutRec (D : DBFile; RecNum : LONGCARD);
PROCEDURE PutRecBuf (D : DBFile; Buf : ADDRESS);
PROCEDURE RecCount (D : DBFile) : LONGCARD;
PROCEDURE RecNo (D : DBFile) : LONGCARD;
PROCEDURE RecSize (D : DBFile) : CARDINAL;
PROCEDURE UnDelRec (D : DBFile);
PROCEDURE UnLockRec (D : DBFile; RecNum : LONGCARD);
END DBF.
DBF Module - Page 9
DBF Exports - Detail ____________________
TYPE DBFile;
Your application must declare a variable of this type for each
data file you will use. For example, to use a customer data
file and an inventory data file, declare the following two
variables:
VAR CustomerFile, InventoryFile : DBFile;
The DBFile type is an opaque data type. This means that the
details of its structure are hidden from your application.
This is done so that changes can be made in future releases
without affecting compatibility.
VAR OK : BOOLEAN;
After any DBF procedure is called, this variable is set to
indicate whether the operation was successful. Your
application should check OK after each procedure call and make
sure the operation was successful (OK = TRUE). If an error
occurred, OK will be set to FALSE, and the you should give the
user an error message and close any open files before you
terminate your program.
VAR Error : CARDINAL;
If an operation fails, this variable returns the exact nature
of the failure. For example, if OpenDBF fails, OK will return
FALSE, and the value in Error will provide more specific
details as to the cause of the failure. Error = 8 (Bad DBF)
would indicate that the datafile is probably damaged or not a
dBase data file. The current error codes are as follows:
ErrOpen = 1; ErrSeek = 5; ErrMemory = 9;
ErrClose = 2; ErrLock = 6;
ErrRead = 3; ErrUnLock = 7;
ErrWrite = 4; ErrBadDBF = 8;
More error codes will be added in future releases to provide
more specific information.
VAR MultiUser : BOOLEAN;
If your program will be running in a multi-user environment
such as a network or multi-user operating system, you should
set this variable TRUE. When MultiUser is TRUE, the DBF
module will take special measures when accessing data files to
make certain no damage is caused be two users simultaneously
making changes. The default setting for MultiUser is FALSE.
For more information on multi-user operation, see appendix D.
DBF Module - Page 10
PROCEDURE AddRec(D : DBFile);
AddRec adds the current contents of the data record buffer to
the end of the file on disk.
PROCEDURE CloseDBF(VAR D : DBFile);
CloseDBF makes certain all changes to the file are saved to
disk, closes the specified data file, and dissolves the record
buffer. This procedure should always be called when your
program finishes with a data file.
PROCEDURE Deleted(D : DBFile) : BOOLEAN;
If the current record is marked for deletion, this function
returns TRUE; otherwise, it returns FALSE.
PROCEDURE DelRec(D : DBFile);
Marks the current record for deletion by placing an '*' in the
first byte of the record. Note that if the current record is
already marked for deletion, this procedure has no effect.
PROCEDURE Encrypted(D : DBFile) : BOOLEAN; (DBASE IV Only)
dBase IV provides a facility for encrypting (coding) data
records for security purposes. If a data file's records are
encrypted, this function will return TRUE; otherwise, it
returns FALSE.
PROCEDURE FieldData(D : DBFile; FieldName : ARRAY OF CHAR;
VAR Type : CHAR;
VAR Len, Dec : CARDINAL);
This function returns the type, length, and number of decimal
places allocated for the field specified in FieldName. It's
purpose is to allow the programmer to get information about
the data field's definition.
PROCEDURE FieldName(D : DBFile; FieldNum : CARDINAL;
VAR FieldName : ARRAY OF CHAR);
This function will return the field name of a particular
field. For example, to find the name of field number 3, the
procedure would be called as follows:
VAR FieldName : ARRAY [0..10] OF CHAR;
FieldName(CustomerFile, 3, FieldName);
Note that the maximum field name length is 11 characters and
that no white-space (blanks, tabs, etc.) is allowed in a field
name.
DBF Module - Page 11
PROCEDURE FileName(D : DBFile; VAR Name : ARRAY OF CHAR);
This procedure returns the name of the file as it was opened
or created. For example:
VAR FName : ARRAY [0..40] OF CHAR;
CustomerFile : DBFile;
OpenDBF(CustomerFile, 'CUSTFILE');
FileName(CustomerFile, FName);
At the end of this sequence, FName will contain the string
'CUSTFILE.DBF'.
PROCEDURE GetField(D : DBFile; FieldName : ARRAY OF CHAR;
VAR TheField : ARRAY OF CHAR);
GetField gets the data from a particular field in the record
buffer and copies it into a variable. Fields are referred to
by field name. All fields are stored as ARRAYs OF CHAR in
dBase data files (see appendix A). If you wish to convert a
numeric field to one of the standard Modula-2 numeric types,
you must first read it in as a string, and then convert it
using one of the conversion routines provided with your
compiler. For example:
VAR ZipStr : ARRAY [0..8] OF CHAR;
ZipNum : LONGCARD;
Done : BOOLEAN;
GetField(CustomerFile, 'ZIP', ZipStr);
ZipNum := StrToCard(ZipStr, 10, Done);
Date fields are also stored as strings in the format YYYYMMDD.
This format is convenient for most date operations.
PROCEDURE GetRec(D : DBFile; RecNum : LONGCARD);
GetRec loads in the record specified from the data file on
disk into the record buffer. If the record cannot be found or
an error occurs during the read, OK is set to FALSE. For
example, to load the third record from the customer data file:
GetRec(CustomerFile, 3);
PROCEDURE GetRecBuf(D : DBFile; Buf : ADDRESS);
Once a record is in the record buffer, it is usually accessed
a field at a time via the GetField and PutField procedures.
However, GetRecBuf provides a means for copying the entire
record from the record buffer to a user specified buffer.
DBF Module - Page 12
This is particularly useful in two circumstances. It allows
the programmer to copy records without having to deal with
individual fields. It also allows a record to be copied
directly into a standard Modula-2 record variable.
Note that it is important that the user buffer is large enough
to accommodate the entire record or else unknown (and almost
certainly bad) things will happen. If the record size is
unknown, play it safe and allocate a 4K buffer, the maximum
size for a dBase record. An example of how this procedure is
used follows:
TYPE CustRecType = RECORD
DeletedFlag : CHAR;
Name : ARRAY [0..29] OF CHAR;
Address : ARRAY [0..29] OF CHAR;
City : ARRAY [0..14] OF CHAR;
State : ARRAY [0..1] OF CHAR;
Zip : ARRAY [0..9] OF CHAR;
Phone : ARRAY [0..14] OF CHAR;
Purchased : ARRAY [0..11] OF CHAR;
Balance : ARRAY [0..11] OF CHAR;
END;
VAR CustomerFile: DBFile;
CustomerRec : CustRecType;
OpenDBF(CustomerFile, 'CUSTFILE.DBF', TRUE);
GetRec(CustomerFile, 3);
GetRecBuf(CustomerFile, ADR(CustomerRec));
PROCEDURE HasMDX(D : DBFile) : BOOLEAN; (dBase IV Only)
dBase IV has finally included the much needed feature of
multi-keyed index (MDX) files. These files will be supported
in a future release of the dBase Toolkit. If a data file has
a multi-key index file associated with it, this procedure will
return TRUE; otherwise, it will return FALSE.
PROCEDURE Incomplete(D : DBFile) : BOOLEAN;
Incomplete returns TRUE if the last write to the data file was
not successfully completed. This is usually caused by a power
outage or other catastrophic failure. In situations in which
data integrity is crucial, this procedure should be called to
check for damage to the data file. If it returns TRUE, your
program should instruct the user to restore their last backup.
DBF Module - Page 13
PROCEDURE LockRec(D : DBFile; RecNum : LONGCARD); (Multi-user)
LockRec allows a user to 'lock' a particular record so that no
other user has access to it. Locking a record is usually done
to prevent two users from making changes to it simultaneously
which would result in one user's changes being lost. This
procedure is ignored in single-user environments. For more
information on multi-user programming, refer to Appendix D on
multi-user programming.
PROCEDURE OldField(D : DBFile; FieldName : ARRAY OF CHAR;
VAR TheField : ARRAY OF CHAR);
Occasionally, a user makes changes to a record and then wishes
to undo those changes. This procedure returns the contents of
a field as it was originally read into the record buffer,
before any changes were made. Note however that once the
record is written to disk, changes are final.
PROCEDURE OpenDBF(VAR D : DBFile; FileName: ARRAY OF CHAR);
OpenDBF searches the disk for the data file specified in
FileName. If the file is found, it is opened, and the file
header is read. The file header contains information about
the structure of the file including record size, number of
records, number of fields, and the type of each field. This
information is read and stored in the file variable. OpenDBF
also allocates (creates) the file's record buffer. If the
MultiUser variable is set to TRUE, then several users can open
the file at the same time. If MultiUser is set to FALSE, the
file is locked by the first who opens it. No other user can
open the file until the user who first locked it closes the
file.
PROCEDURE PutField(VAR D : DBFile; FieldName : ARRAY OF CHAR;
TheField : ARRAY OF CHAR);
PutField places the contents of TheField into the file
variables record buffer in the field specified by FieldName.
For example, to store the value '12345' in the zip code field
in the customer file, the procedure call would be:
PutField(CustomerFile, 'ZIP', '12345');
PROCEDURE PutRec(VAR D : DBFile; RecNum : LONGCARD);
PutRec takes the record in the record buffer and writes it to
disk at the record number specified. For example, to write
the current record to disk as the 10th record, the procedure
call would be:
PutRec(CustomerFile, 10);
DBF Module - Page 14
PROCEDURE PutRecBuf(VAR D : DBFile; Buf : ADDRESS);
PutRecBuf copies the contents of a separate (user provided)
record buffer into the file variable's record buffer, usually
as a prelude to writing the record to the disk. See GetRecBuf
for more details. For example to write the record contained
in CustRec as the 3rd record in the Customer file, the
procedures would be:
PutRecBuf(CustomerFile, CustRec);
PutRec(CustomerFile, 3);
PROCEDURE RecCount(D : DBFile) : LONGCARD;
Returns the number of records currently in the data file.
PROCEDURE RecNo(D : DBFile) : LONGCARD;
Returns the number of the current record (the last record read
from or written to the data file).
PROCEDURE RecSize(D : DBFile) : CARDINAL;
Returns the size of each data record. This is useful for
calculating the size of a data file, or for anticipating the
amount of disk space required for a particular operation. See
related procedure GetRecBuf.
PROCEDURE UnDelRec(D : DBFile);
The opposite of the DelRec procedure, UnDelRec places a space
in the first byte of the current record. If the record was
marked for deletion with an '*' in the first position,
UnDelRec will remove the asterisk. If the current record is
not marked for deletion, this procedure has no effect.
PROCEDURE UnLockRec(D : DBFile; RecNum : LONGCARD);
The opposite of the LockRec procedure, UnLockRec removes the
lock placed on a record so that it becomes accessible to other
users. For more information on record locking, refer to the
appendix on multi-user operation.
DBF Module - Page 15
Manipulating Records ____________________
Two procedures, GetRecBuf and PutRecBuf, are provided for
manipulating records as a unit rather than a field at a time.
These commands copy an entire record from the data file into a user
provided buffer. This is particularly useful for such operations
as quickly copying records from one file to another. These
commands can also be used to read a record into a standard Modula-2
record structure. (See the example in GetRecBuf).
However, it should be noted that there is an advantage in dealing
with fields rather than records as the basic data unit. When
working with data by field name, if changes are made to a data file
such as adding new fields, your application program will not need
to be modified or re-compiled. However, programs that deal with
records as a unit will need to be modified each time the record
structure changes.
DBF Module - Page 16
Sample Program Using DBF Module _______________________________
MODULE CustomerReport;
IMPORT DBF, IO;
VAR CustFile : DBF.DBFile;
RecNum : LONGCARD;
PROCEDURE DisplayCustRec;
VAR Field : ARRAY [0..29] OF CHAR;
BEGIN
DBF.GetField(CustFile, 'NAME', Field); (* Get NAME field *)
IO.WrStr(Field); (* Display it *)
IO.WrLn; (* Go to next line. *)
DBF.GetField(CustFile, 'ADDR', Field); (* Get ADDR field *)
IO.WrStr(Field); (* Display it *)
IO.WrLn;
(* Continue for all fields. *)
END DisplayCustRec;
BEGIN
DBF.Open(CustFile, 'CUSTFILE.DBF'); (* Open data file *)
IF NOT DBF.OK THEN (* If Open not OK, *)
IO.WrStr('Error: file not found.'); (* display an *)
IO.WrLn; (* error message *)
HALT; (* and quit now. *)
END;
RecNum := VAL(LONGCARD, 1); (* Start at rec 1 *)
WHILE DBF.OK AND (* While no errors, *)
(RecNum <= DBF.RecCount(CustFile)) DO (* and more records *)
DBF.GetRec(CustFile, RecNum); (* Get record *)
DisplayCustRec; (* Display record*)
INC(RecNum); (* Bump rec num. *)
END; (* Continue for all *)
DBF.Close(CustFile); (* Close when done *)
END CustomerReport;
DBF Module - Page 17
NDX Module __________
The NDX library module provides complete access to dBase III, III+,
and IV index files. Included are procedures to open, close,
search, and update a standard dBase NDX file. Specific information
on each item exported by the NDX module is provided on the
following pages. However, in order to better understand the usage
of this module, you should first read the overview provided below.
Overview ________
An index file does for a data file pretty much what an index does
for a book: it allows you to quickly find a particular topic based
on a 'key' word or phrase. To use the index in a book, you quickly
search the index for the topic you are interested in, get the page
number, and then go read that page. An index file works the same
way.
For example, a customer data file might contain 1000 customer
records. In each customer's record are fields for the customer's
name, address, phone number, zip code, etc. To find a customer's
record, you could search each record until you found the desired
customer, but that might take a long time (what if there were a
million customers?).
It would certainly help to have an alphabetical list of customer
names, and their corresponding record numbers. With such a list
(an index), you could use a binary search to quickly locate the
name and record number of any customer. To find a customer in a
file of one thousand customers would require examining only ten
names. In a file of one million, you would need to examine only
twenty names!
The NDX module allows you to create and use such indices. For
example, you could create a customer index file based on the NAME
field; you might call this index 'CUSTNAME.NDX'. The index file
would contain an alphabetical list of the customer names and their
record numbers. Each time a new customer were added to the data
file, you would insert the name and record number into the index,
keeping it up-to-date.
This index could be used to rapidly locate any customer. It could
also be used to list the customers in alphabetical order creating a
customer report. An index could also be created called
'CUSTZIP.NDX' which would keep an ordered list of zip codes and
their corresponding customer record numbers. Such a list could be
useful for marketing and mailing purposes.
You can visualize such an index as a list of key fields in
alphabetical order. Each key has with it the record number of the
customer record which it came from. For example:
NDX Module - Page 18
Customer Name Index
Index Customer Name (Key) Record Number
Pointer
--> Astra, Inc. 5
Candi's Computer Center 2
Digital Engineering 1
Fashions by Diana 6
HoshMed Research 3
Janisoft 4
Laura's Hiking Supplies 8
Len's Literature 7
etc.
Because the list is arranged alphabetically, it is easy to find the
name you are looking for. With the name is the record number
allowing you to quickly pull up the associated record.
The compatibility of the NDX index files with dBase index files
allows your applications to be used together with dBase to produce
more sophisticated applications than are possible with dBase alone.
For example, sophisticated data entry and query screens can be
written in Modula-2 with features not available to dBase
programmers. However, reports and custom additions are often
quicker and easier to program using dBase. The DBF and NDX modules
make it easy to build hybrid programs with the ease of dBase and
the power of Modula-2.
NDX Usage _________
The NDXFile is the basic unit of the NDX module. You must declare
a variable of this type for each index file you will use. For
example:
VAR CustNameIndex : NDXFile;
This variable is known as the index file variable and is used
whenever you reference the index. For example, to open an index
file:
OpenNDX(CustNameIndex, 'CUSTNAME.NDX');
When this statement is executed, the NDX module searches the disk
for a file named CUSTNAME.NDX. If found, NDX opens it and reads
the structure of the index file. Information about the index file
is digested and stored.
The index pointer (the current position in the index) is set to the
beginning of the file when the file is opened. The Next key and
Prev key procedures allow you to get the next and previous keys
NDX Module - Page 19
(relative to the current index pointer). For example:
RecNum := Next(CustNameIndex);
This reads the next key (alphabetically) in the index, and returns
the number of the data record containing that key. The DBF module
can then be used to retrieve that record.
The Find procedure can be used to quickly locate any name or even a
partial name. For example:
RecNum := Find(CustNameIndex, 'Laura');
This will search for a key which is equal to or larger than
'Laura'. In our example, the search would stop at "Laura's Hiking
Supplies" and would return the record number of 8.
When your application is done working with an index file, it should
be closed with the statement:
CloseNDX(CustNameIndex);
This makes sure any changes to the index file are saved to disk,
and that the file is safely closed before the program finishes.
To summarize, using an index file involves
1) Declaring an index file variable,
2) Opening the index file
3) Searching for key(s)
4) Loading the corresponding record(s)
5) Closing the index file.
The NDX module is intended for use in conjunction with the DBF
module. At the end of this chapter, a short example program is
provided to demonstrate how the NDX and DBF procedures are used
together. The remainder of this chapter will explain in more
detail the use of each procedure provided by the NDX module.
NDX Module - Page 20
NDX Definition Module _____________________
DEFINITION MODULE NDX;
CONST MaxKeyLen = 100;
ErrOpen = 1; ErrSeek = 5; ErrMemory = 9;
ErrClose = 2; ErrLock = 6;
ErrRead = 3; ErrUnLock = 7;
ErrWrite = 4; ErrBadNDX = 8;
TYPE NDXFile;
VAR OK : BOOLEAN;
Error : CARDINAL;
FOUND : BOOLEAN;
MultiUser : BOOLEAN;
Retries : CARDINAL;
PROCEDURE OpenNDX (VAR I : NDXFile; Name : ARRAY OF CHAR);
PROCEDURE CloseNDX (VAR I : NDXFile);
PROCEDURE CreateNDX(VAR I : NDXFile; FileName : ARRAY OF CHAR;
KeyField : ARRAY OF CHAR;
KeyType : CHAR;
KeyLen : CARDINAL);
PROCEDURE AddKey (I:NDXFile; Key:ARRAY OF CHAR; Ptr:LONGCARD);
PROCEDURE DelKey (I:NDXFile);
PROCEDURE Find (I:NDXFile; Key:ARRAY OF CHAR) : LONGCARD;
PROCEDURE Next (I:NDXFile) : LONGCARD;
PROCEDURE Prev (I:NDXFile) : LONGCARD;
PROCEDURE GoTop (I:NDXFile);
PROCEDURE GoBottom (I:NDXFile);
PROCEDURE BOF (I : NDXFile) : BOOLEAN;
PROCEDURE EOF (I : NDXFile) : BOOLEAN;
PROCEDURE KeyField (I : NDXFile;
VAR Field : ARRAY OF CHAR);
PROCEDURE Unique (I : NDXFile) : BOOLEAN;
END NDX.
NDX Module - Page 21
NDX Exports - Detail ____________________
TYPE NDXFile;
Your application must declare a variable for each index file
it will use. The variables must be of type NDXFile. For
example, if you will be accessing the customer file by name,
you would declare the following variable:
VAR CustNameIndex : NDXFile;
The NDXFile type is an opaque data type. This means that the
details of its structure are hidden from your application.
This is done so that changes can be made in future releases
without affecting compatibility.
VAR OK : BOOLEAN;
After any NDX procedure is called, this variable is set to
indicate whether the operation was successful. Your
application should check OK after each procedure call and make
sure the operation was successful (OK = TRUE). If an error
occurred, OK will be set to FALSE and you should give the user
an error message and close any open files before you terminate
your program.
VAR Error : CARDINAL;
If an operation fails, this variable returns the exact nature
of the failure. For example, if OpenNDX fails, OK will return
FALSE, and the value in Error will provide more specific
details as to the cause of the failure. Error = 8 (Bad NDX)
would indicate that the index file is probably damaged or not
a dBase NDX file. The current error codes are as follows:
ErrOpen = 1; ErrSeek = 5; ErrMemory = 9;
ErrClose = 2; ErrLock = 6;
ErrRead = 3; ErrUnLock = 7;
ErrWrite = 4; ErrBadNDX = 8;
More error codes will be added in future releases to provide
more specific information.
VAR FOUND : BOOLEAN;
After a search procedure (Find, Next, or Prev) this variable
is set to indicate whether the key searched for was found. If
FOUND is FALSE after a Next or Prev operation, then the end of
the file has been hit. If FOUND is FALSE after a Find
procedure, then an exact match was not found.
NDX Module - Page 22
VAR MultiUser : BOOLEAN;
If your program will be running in a multi-user environment
such as a network or multi-user operating system, you should
set this variable TRUE. When MultiUser is TRUE, the NDX
module will take special measures when accessing index files
to make certain no damage is caused be two users
simultaneously making changes. The default setting for
MultiUser is FALSE.
VAR Retries : CARDINAL;
When the NDX module is running in multi-user mode, it must
frequently place locks on the index file to prevent two users
from performing conflicting operations. If one user has the
index locked and a second user attempts to gain access, the
second user must wait and try again. The NDX module will
automatically wait and retry a fixed number of times. That
number is held in the variable Retries, and may be read and
modified by the application program.
PROCEDURE AddKey(I : NDXFile; Key : ARRAY OF CHAR; Ptr : LONGCARD);
AddKey inserts a new key into the index. Whenever a new
record is added to a data file, the key field(s) for that
record should be added to their corresponding index files. If
an existing record's key field is changed, the old key should
first be deleted, and then AddKey should be called to insert
the updated key.
PROCEDURE BOF(I : NDXFile) : BOOLEAN;
This procedure indicates when the index pointer is at the top
(beginning) of the index.
PROCEDURE CloseNDX(VAR I : NDXFile);
CloseNDX makes certain all changes to the index file are saved
to disk, updates the index header, and closes the specified
index file. This procedure should always be called when your
program finishes working with an index file.
PROCEDURE CreateNDX(VAR I : NDXFile; FileName : ARRAY OF CHAR;
KeyField : ARRAY OF CHAR;
KeyType : CHAR;
KeyLen : CARDINAL);
This procedure is used to create a new NDX file. To create
the file, the procedure must be given the name for the new
file, the key field name (expression), the type of the key
(<C>haracter, <N>umeric, <D>ate, or <L>ogical), and the length
of the key. For example, to create a new index file for the
NDX Module - Page 23
customer database based on the name field (which is a
character field of length 30) the following statement might be
used:
CreateNDX(CustName, 'CUSTNAME.NDX', 'NAME', 'C', 30);
Creating an index will overwrite any existing index with the
same name. The newly created index will contain no keys.
PROCEDURE DelKey(I : NDXFile);
The delete key procedure deletes the last key found from the
index. If the last search operation was not successful (FOUND
= TRUE), the DelKey procedure will do nothing.
PROCEDURE EOF(I : NDXFile) : BOOLEAN;
This procedure indicates when the index pointer is at the
bottom (end) of the index.
PROCEDURE Find(I : NDXFile; Key : ARRAY OF CHAR) : LONGCARD;
The Find key procedure quickly searches the index for the
specified key. The search method is a modified binary search
requiring no more than 20 accesses to find one key in an index
of one million keys. If an exact match isn't found, the first
key that is higher than the key specified is returned. The
value returned is the number of the record in the data file
containing the key higher than or equal to the key specified.
An example of how the Find procedure might be used to find the
customer Laura's Hiking Supplies is as follows:
RecNum := Find(CustName, "Laura's Hiking Supplies");
IF FOUND THEN
DBF.GetRec(CustomerFile, RecNum);
END;
The variable FOUND will be set if an exact match is made.
PROCEDURE GoBottom(I : NDXFile);
The GoBottom procedure moves the file pointer to the bottom
(end) of the index file. After this procedure is run, the
Prev key procedure will return the last (largest) key in the
index.
PROCEDURE GoTop(I : NDXFile);
The GoTop procedure moves the index file pointer to the top
(beginning) of the index file. After this procedure is run,
NDX Module - Page 24
the Next key procedure will return the first (smallest) key in
the index.
PROCEDURE KeyField(I : NDXFile; VAR Field : ARRAY OF CHAR);
The KeyField procedure returns the key field expression (the
name of the key field) used by the index.
PROCEDURE Next(I : NDXFile) : LONGCARD;
The Next key procedure finds the next sequential key in the
index and returns the number of the record in the data file
containing that key. When the end of the file is hit, this
procedure will return a null (Zero) pointer. For example, to
list each customer record in alphabetical order, the code
might be as follows:
GoTop(CustName);
WHILE NOT EOF(CustName) DO
RecNum := Next(CustName);
IF FOUND THEN
DBF.GetRec(CustomerFile, RecNum);
DisplayCustRec;
END;
END;
As is the case in the Find key procedure, the FOUND variable
will reflect the success of the Next key operation.
PROCEDURE Prev(I : NDXFile) : LONGCARD;
The Prev key procedure finds the previous sequential key in
the index and returns the number of the record in the data
file containing that key. When the beginning of the file is
hit, this procedure will return a null (Zero) pointer. It can
be used to search backwards alphabetically through a file.
For example, to list the customer file in reverse alphabetical
order, the code fragment might be:
GoBottom(CustName);
WHILE NOT BOF(CustName) DO
RecNum := Prev(CustName);
IF FOUND THEN
DBF.GetRec(CustomerFile, RecNum);
DisplayCustRec;
END;
END;
As is the case with the Find and Next procedures, the FOUND
variable reflects the success of this procedure in finding the
NDX Module - Page 25
previous key.
PROCEDURE Unique(I : NDXFile) : BOOLEAN;
If an index created by dBase or one of its clones has
specified that the index should only contain unique keys (i.e.
no duplicate keys should be allowed), this procedure will
return TRUE. This can be used to maintain the unique key
requirement in your applications.
NDX Module - Page 26
NDX Sample Program __________________
MODULE NDXTest;
IMPORT IO, DBF, NDX;
VAR CustomerFile : DBF.DBFile;
CustNameIdx : NDX.NDXFile;
Name : ARRAY [0..29] OF CHAR;
RecNum : LONGCARD;
PROCEDURE MakeNameIdx;
BEGIN
NDX.CreateNDX(CustNameIdx, 'CUSTNAME.NDX', 'NAME, 'C', 30);
RecNum := VAL(LONGCARD, 1);
WHILE RecNum <= DBF.RecCount(CustomerFile) DO
DBF.GetRec(CustomerFile, RecNum);
DBF.GetField(CustomerFile, 'NAME', Name);
NDX.AddKey(CustNameIdx, Name, RecNum);
INC(RecNum);
END;
END MakeNameIdx;
PROCEDURE DisplayCustomer;
VAR FieldBuf : ARRAY OF CHAR;
BEGIN
DBF.GetField(CustomerFile, 'NAME', FieldBuf);
IO.WrStr(FieldBuf); IO.WrLn;
DBF.GetField(CustomerFile, 'ADDR', FieldBuf);
IO.WrStr(FieldBuf); IO.WrLn;
(* Same for all fields: get field and display it. *)
END DisplayCustomer;
BEGIN
DBF.OpenDBF(CustomerFile, 'CUSTOMER.DBF'); (* Open DBF *)
MakeNameIdx; (* Create NDX *)
REPEAT (* Main loop! *)
IO.WrStr('Enter name of customer.'); (* Get name of *)
IO.RdStr(Name); (* cust to find*)
IF Name # '' THEN (* If not blank*)
RecNum := NDX.Find(CustNameIdx, Name); (* Find name *)
IF NDX.FOUND THEN (* If found, *)
DBF.GetRec(CustomerFile, RecNum); (* Get record*)
DisplayCustRec; (* Display it*)
ELSE (* Otherwise, *)
IO.WrStr('Customer not found.'); (* show msg. *)
END; (* Continue *)
END; (* loop till no*)
UNTIL (SearchName = ''); (* name entered*)
DBF.CloseDBF(CustomerFile); (* Close DBF, *)
NDX.CloseNDX(CustNameIdx); (* Close NDX *)
END NDXTest. (* Done! *)
NDX Module - Page 27
BuildNDX Module _______________
The BuildNDX library module provides procedures which allow you to
quickly generate complex indices for database files. Using a
modified QuickSort technique, and processing large numbers of
records at a time, the BuildNDX module builds indices much faster
than dBase III+ can.
The indices built are completely compatible with those created by
dBase III+ or IV and may be used with either or with the NDX module
from the previous chapter. Specific information on each item
exported by the BuildNDX module is provided on the following pages.
However, in order to better understand the usage of this module,
you should first read the overview provided in the NDX module
chapter as well as the overview below.
Overview ________
When working with large databases, it is important to be able to
rearrange and work with the data records in a variety of orders.
For example an application might need to list all customers in the
customer file organized by city and within each city list the
customers alphabetically.
To do this, a new index needs to be generated with a key consisting
of the CITY and NAME fields concatenated. This index could be
generated using only the NDX module by first creating an empty NDX
file, and then entering a loop that reads each customer record one-
at-a-time, concatenating the CITY and NAME fields from each
customer record and adding the new key to the new index file. For
example:
VAR RecNum : LONGCARD;
Name : ARRAY[0..29] OF CHAR;
City : ARRAY[0..9] OF CHAR;
NewKey : ARRAY[0..39] OF CHAR;
NDX.OpenNDX(CityNameNDX, 'CITYNAME.NDX',
'CITY+NAME', 'C', 40);
FOR RecNum := 1 TO DBF.NumRecs(CustFile) DO
DBF.GetRec(CustFile, RecNum);
DBF.GetField(CustFile, 'NAME', Name);
DBF.GetField(CustFile, 'CITY', City);
Str.Concat(NewKey, City, Name);
NDX.AddKey(CityNameNDX, NewKey, RecNum);
END;
NDX.CloseNDX(CityNameNDX);
For small files, this would work fine. However, if there were
several thousand customer records, this method would be very slow.
BuildNDX Module - Page 28
MakeNDX _______
The BuildNDX module provides several procedures to aid in rapidly
creating indices. The main procedure is MakeNDX which requires
only the name of the database file, the name for the new index
file, the field(s) to use in constructing the new index, and
optionally the key length. For example, to create the index
described above would require only the following statement:
MakeNDX('CUSTFILE', 'CITYNAME', 'CITY+NAME', 0);
Note that the key-field parameter lists the name of the key
field(s) to use in generating the index. More than one field can
be concatenated with the '+' sign. Note too that if the key length
parameter is set to 0, the MakeNDX procedure will automatically set
the length as required to hold the key(s).
The MakeNDX procedure is able to generate indices more rapidly than
other techniques by performing most of its sorting in memory rather
than on disk. When the procedure is called, it loads as much of
the specified data file as possible into memory buffers.
For each data record in memory, MakeNDX calls the Filter procedure
(see below) to decide whether to include that record in the index
it is creating. If the filter procedure returns TRUE, the record is
to be included. If it returns FALSE, the record is skipped and the
next one examined. The DefaultFilter procedure always returns
TRUE, causing all records to be included in the index.
When the filter procedure returns true, MakeNDX calls the Key
procedure (see below) to extract the key data from the data record.
The key data is added to the index, then the next record is
examined. The DefaultKey procedure returns the field(s) selected
in the MakeNDX procedure's KeyField parameter.
Once all records have been filtered, their keys extracted and added
to the index, they are sorted via the Quicksort algorithm, and the
dbase compatible B-tree is built. Finally, the index is closed and
is ready for use.
A good examples of the use of the MakeNDX procedure is found in the
MAKENDX utility program. This program allows you to create dBase
compatible indices from the DOS command line using only the MakeNDX
procedure.
BuildNDX Module - Page 29
Filter Procedure ________________
Some applications may require more sophisticated indices. For
example, a program might need to generate a listing of customers by
city as above, but showing only customers with balances over one
thousand dollars. You could accomplish this using the index
created above and then retrieving each customer's record and
checking their balance before deciding whether to print them or
not. For Example:
VAR RecNum : LONGCARD;
BalStr : ARRAY[0..15] OF CHAR;
Balance: LONGREAL;
BuildNDX.MakeNDX('CUSTFILE', 'CITYNAME', 'CITY+NAME', 0);
NDX.OpenNDX(CityName, 'CITYNAME');
WHILE NOT (NDX.EOF(CityName)) DO
RecNum := NDX.Next(CityName);
DBF.GetRec(CustFile, RecNum);
DBF.GetField(CustFile, 'BALANCE', BalStr);
StrToCard(BalStr, Balance);
IF Balance > 1000.00 THEN
PrintRec;
END; (* If balance > 1000 *)
END; (* While not at end of index *)
NDX.CloseNDX(CityName);
The above method would work. However, if there were very few
customers with balances over $1000, most of the program's time
would be spent retrieving and examining the records of those
customers that need not be printed. A better solution would be to
create an index such that it only contained the records that
belonged in the listing i.e. to filter out those records which
don't belong.
To accomplish this, the BuildNDX module provides for filter
procedures. You can create your own filter procedure to specify
which database records should be included in an index and then tell
the BuildNDX module to use your filter procedure when building new
indices.
Filter procedures are always of the type FilterType as defined in
the BuildNDX module. i.e. of the format:
PROCEDURE procname () : BOOLEAN;
Your filter procedure will simply get information about the data
record currently being processed and based on that information will
return TRUE or FALSE indicating whether the record should be
included in the index or not.
BuildNDX Module - Page 30
For example, the above program fragment could be re-written as
follows:
VAR RecNum : LONGCARD;
PROCEDURE BalanceFilter() : BOOLEAN;
VAR BalStr : ARRAY [0..15] OF CHAR;
Balance: LONGREAL;
BEGIN
BuildNDX.GetField('BALANCE', BalStr);
StrToCard(BalStr, Balance);
RETURN (Balance > 1000.00);
END;
BEGIN
BuildNDX.Filter := BalanceFilter;
BuildNDX.MakeNDX('CUSTFILE', 'CITYNAME', 'CITY+NAME', 0);
NDX.OpenNDX(CityName, 'CITYNAME);
WHILE NOT (NDX.EOF(CityName)) DO
PrintRec;
END; (* While not at end of index *)
NDX.CloseNDX(CityName);
END;
In this program fragment, a filter procedure: BalanceFilter is
defined. This procedure extracts the Balance field from each data
record it examines and determines whether the record belongs in the
index based on the balance being over or under $1000, returning
TRUE or FALSE accordingly.
The BuildNDX module is told to use this filter procedure when
creating new indices by the statement:
BuildNDX.Filter := BalanceFilter;
Note that this statement is not calling the BalanceFilter
procedure. It is storing the address of the BalanceFilter
procedure in the variable Filter.
When the MakeNDX procedure is called, as it processes each record,
it calls the filter procedure indicated by the variable Filter. If
the filter procedure called returns true, the key field is
extracted from the record and added to the index being built. If
it returns false, the record is skipped and no key is added.
You can use the procedures Deleted and GetField, exported by
BuildNDX, to decide whether to include a record or not. Use
GetField to extract the field information from the record currently
being processed then use that information to decide whether the
record should be included in the index or not.
BuildNDX Module - Page 31
Advanced Keys _____________
It is sometimes necessary to generate index keys that are more
complex than a single field or even multiple fields concatenated.
To accomodate these complex keys, the BuildNDX module allows for
key procedures. Similar to filter procedures, key procedures are
called once for each record processed by the MakeNDX procedure.
However, unlike filter procedures, a key procedure is passed a key
buffer and returns a key.
The key procedure can access information and field(s) in the
current data record via the GetField and Deleted procedures
exported by the BuildNDX module. Using this and any other data,
the key procedure should construct a key value for the current
record and return it in the key buffer passed.
A key procedure is always of the type KeyExpType as exported by the
BuildNDX module i.e. of the format:
PROCEDURE procname (VAR key : ARRAY OF CHAR);
Your key expression will extract field data from the record
currently being processed (using the GetField procedure) and will
generate a key for this record. The DefaultKeyExp procedure simply
extracts and returns the field(s) specified in the KeyName
parameter when MakeNDX was called.
To install your Key procedure, use the statement:
BuildNDX.KeyExp := procname;
BuildNDX Module - Page 32
DBF Toolkit - Utilities _______________________
To make database development easier, some utility programs are
included to create and edit dBase compatible database files and
dBase compatible indices:
MAKEDBF.EXE
Using easy menu driven techniques, you can create new
databases and modify the structure of existing ones. To
execute the program type:
MAKEDBF filename
MAKENDX.EXE
From the command line, generate an index for a given database
file. You can specify single or multiple (concatenated)
fields for the key field(s). To create an index, type:
MAKENDX dbfname ndxname keyfield
or
MAKENDX dbfname ndxname key1+key2+key3...
Many other utilities are available both as shareware and in the
public domain for working with dBase compatible database files. If
there is sufficient demand, other utilities including a file
browser will be released in the next version of the Modula-Tools
dBF toolkit. Suggestions and contributions are welcome.
Utilities - Page 33
DBF Data Files
The data files used by the DBF module are 100% compatible with
industry standard dBase III, III+, and IV data files. DBF data
files contain three major sections: the DBF header, field array,
and data records.
DBF File Structure
DBF Field Data
Header Array Records
Header
The DBF Header is located at the beginning of the file and is
32 bytes long. It contains information about the length of
the header (including the field array), the length of each
data record, whether there are associated Memo or MDX files,
etc. The header structure is outlined below:
Position Type Description
0 SHORTCARD Has Memo File (03H or 83H)
1 3 BYTES Last update date
4 LONGCARD Number of Records
8 CARDINAL Header len (incl. field array)
10 CARDINAL Record length
12 2 BYTES Reserved for future use
14 BOOLEAN Incomplete transaction flag
15 BOOLEAN Encrypted file flag
16 12 BYTES Reserved for network use
28 BOOLEAN Has associated MDX file
29 3 BYTES Reserved for future use
Field Array
Each field record is 32 bytes long and contains a complete
description of one of the data fields. For example, if each
data record contains 8 fields, the field array will have 8
field records. The field record array will occupy 256 bytes
(8 * 32), and the total file header length, including the DBF
header will be 288 bytes (256 + 32). Thus the number of
fields in a data file can be calculated as:
NumFields := (DBFHeaderLength - 32) DIV 32;
Appendix A - Page 34
Each field record is structured as follows:
Position Type Description
0 11 BYTES Field Name
11 CHAR Field type ('C', 'N', 'L', 'D')
12 4 BYTES Reserved for future use
16 SHORTCARD Field length
17 SHORTCARD Field decimal places
18 2 BYTES Reserved for future use
20 SHORTCARD Work Area ID
21 11 BYTES Reserved for future use
Data Records
Each data record starts with a single character that indicates
whether the record has been marked for deletion. If the
record is to be deleted, the character is an asterisk;
otherwise, it is a space. The balance of each data record
contains the data fields as listed in the field array. All
field data is stored in string format. For example:
Numeric field ' 123.45'
Date field '19900704'
Character field 'Test '
Logical field 'T'
Numeric data is stored as characters right adjusted in the
field. The field length is defined in the field array record
and includes decimal point and decimal places (if any).
Date fields are stored using 8 characters in the format
YYYYMMDD.
Character data is stored left adjusted with any extra space
filled with blanks. The DBF module automatically trims the
trailing blank spaces from the field when it is read and
terminates it with a null character for Modula-2
compatibility. When placing a Modula-2 string into a DBF
field, the DBF module adds the required spaces.
Logical (boolean) fields are stored as a single character,
either a 'T' or a 'F'.
The length of each record is defined in the DBF header and
includes the character to indicate whether the record is
deleted or not.
The number of records in a file is also listed in the DBF
header. 32 bit Long cardinals are used for record numbers, so
dBase files can hold a virtually unlimited number of records,
limited only by disk space.
Appendix A - Page 35
General
Although it may seem that this method of storing data is
cumbersome compared to simply declaring a Modula-2 record
structure and a file of that record type, there are several
advantages to the DBF method. The first and most apparent is
compatibility with an industry standard. Due to the
prevalence of dBase and its clones in the business world,
almost every product dealing with databases will support the
DBF file format. There are numerous report generators and
utilities designed to work with dBase data files; with the
DBF module, all of these third party products become available
to support your applications.
Another advantage of the DBF file format is that it makes it easier
to make changes to the data file without requiring program changes.
Since the record structure is defined in the DBF file and not hard-
coded into your program, fields can by added or moved without any
program changes. Since many applications involve large numbers of
separate programs and modules, this can save a great deal of time.
Limitations - (for dBase compatibility)
Maximum Field length 255
Maximum Number of Fields 200
Maximum Record length 4000
Maximum Number of Records Unlimited
Appendix A - Page 36
NDX Index Files
The index files created and used by the NDX module are compatible
with the NDX files used by DBase III, III+, and IV with exceptions
noted at the end of this appendix. Use of the dBase NDX format for
your applications indices will allow you to freely access your data
from both Modula-2 and dBase or one of its clones without requiring
re-indexing.
Like the DBF files, NDX files begin with a header containing
information about the format of the index including the key field,
length, root page pointer, number of keys per index page, etc.
Following the NDX header are the actual pages which form the B tree
index.
NDX File Structure
NDX Index
Header Pages
Header
The NDX Header is located at the beginning of the file and is
512 bytes (one page) long. It contains information about the
key field expression, the length of the key, the key type
(numeric or not), etc. The NDX header structure is outlined
below:
Position Type Description
0 LONGCARD Root page number
4 LONGCARD Next free page number
8 4 BYTES Reserved for future use
12 CARDINAL Key field length
14 CARDINAL Number of keys per page
16 CARDINAL Numeric key flag (1 = numeric)
18 CARDINAL Key size (including pointers)
20 CARDINAL Reserved for future use
22 CARDINAL Unique keys flag (1 = unique)
24 100 BYTES Key field expression
124 388 BYTES Unused
Appendix B - Page 37
Index Pages
After the NDX header, which occupies the first 512 bytes of
the file, come the actual index pages. Each page is 512 bytes
long and is structured as follows:
Position Type Description
0 CARDINAL Number of keys in page
2 2 BYTES Unused
4 508 BYTES NDX Keys
The pages are not stored in any particular order, but are
allocated one-at-a-time as needed. Pages are numbered
starting at 1 (page 0 contains the NDX header). The pages are
organized into a B+ Tree structure which allows them to be
quickly searched for any particular key.
Index Keys
Within each page are 1 or more index keys. Each key has three
components:
Position Type Description
0 LONGCARD Page pointer (0 for leaf pages)
4 LONGCARD Record pointer
8 KeyLen Bytes Actual Key field
The page pointer is used in the node pages to point to the
page containing keys lower than that contained in the key
field. The record pointer contains the number of the record
that supplied the key in the key field.
General
Ashton Tate chose to use a traditional B+ tree structure for
their NDX files. Accordingly, the NDX module does the same.
For a more efficient index structure, see the appendix
pertaining to the IDX module. It is beyond the scope of this
article to explain B+ tree structure and usage. However, for
more information you should refer to Donald Knuth's 'The Art
of Computer Programming' or Namir Shammas' 'The Turbo Pascal
Toolbook.'
Limitations
dBase NDX files allow the user to create index files based on
complex expressions involving the data file fields and the
dBase programming language. The NDX module does not currently
support this feature. For the time being, NDX key expressions
must consist of a single field name from a data file.
The unique key field in the NDX header is also ignored.
Appendix B - Page 38
Multi-user Programming
Overview ________
Most applications for personal computers operate in a single-user
environment (i.e. only one user at a time per computer system).
However, databases, particularly for business applications
frequently require several users to be simultaneously performing
updates and additions. For example, a large firm might require
several people accessing and updating inventory information at the
same time. Under these circumstances certain problems can develop.
The problems usually occur when two users simultaneously make
changes to the same record. In this case, one users changes will
be lost. To help prevent this, functions such as file and record
locking are provided by DOS. When a file is locked, only one user
can access it. DOS will not allow a second user to open the file
until the first user has closed it.
A more elegant mechanism in DOS allows users to lock a selected
area within a file such as a data record. Only one user at a time
can lock each area. This feature allows programs to lock only
small areas of a file while the rest remains available to others.
Clearly this is a better solution.
Record locking is used extensively by the DBF, NDX, and IDX modules
to prevent users from damaging data and index files. In fact, most
aspects of multi-user databasing are handled automatically by the
modules. The locking feature is also available to applications
programs via the LockRec and UnLockRec procedures in the DBF
module.
If your programs are run in a multi-user environment, you must be
careful to prevent multi-user conflicts. These can easily occur
when more than one user is working with the same data file. For
example:
Two sales people at Dataworks Widget Factory are receiving orders.
Sales Person A finds and loads the widget inventory record into his
record buffer and sees that 17,000 widgets are in stock. Sales
Person B loads the same record into hers and sees the same thing.
Sales Person A then sells 7,000 widgets to his customer. He
updates the inventory and saves the new updated inventory record
showing only 10,000 widgets. Sales Person B, not realizing that A
has reduced the available inventory believes that 17,000 are still
available. She sells 12,000 widget to her customer and reduces the
number of widgets in her record buffer accordingly. She saves her
changes, overwriting the changes saved by A.
Appendix C - Page 39
Such a situation would be a disaster. At the end of the above
sequence, the widget record would incorrectly indicate 5,000
widgets still available, and one of the orders could not be
shipped. To avoid such a nightmare, the application program must
control access to the widget record; allowing only one user at a
time to make changes. This is done via the LockRec and UnLockRec
procedures. Returning to the above example this is the way things
should have been handled:
1. A Finds Widget Record
2. A Locks Record
3. A Reads Record
4. B Finds Widget Record
5. B tries to lock record but can't since A locked it
6. B keeps trying
7. A makes changes to record
8. A writes record to file
9. A unlocks record
10. Now B locks record
11. B Reads record
etc.
In the above sequence, the application program locks a record
before it reads it. If the record cannot be locked, it means
another user has already locked it. In such a circumstance, the
program should ask the user if he or she wishes to wait or try
again. The DBF module will automatically retry the lock a certain
number of times before indicating that the lock failed. The number
of times it will retry is controlled by the variable: Retries.
Locking records can create some problems of its own if the
programmer is not careful. A particularly nasty situation known as
deadlock can occur when locking is incorrectly used. Deadlock
results when two users need to access the same two records. For
example, two users need to access and modify records 1 and 5:
1. User A locks record 1
2. User B locks record 5
3. User A tries to lock 5 but can't because B has it
locked, so A must wait.
4. User B tries to lock 1 but can't because A has it
locked, so B must wait.
5. Endless loop.
A good way to avoid deadlock is to always lock records in a fixed
order so that both users would first try to lock 1. Only one user
would succeed in locking 1 and the other would have to wait, but no
deadlock would occur.
A great deal has been written about concurrency in database
operation. If you will be writing multi-user applications, it
would be wise to purchase and read a good book covering this topic.
Appendix C - Page 40
Technical information _____________________
All locking in the DBF, NDX, and IDX modules is done via the
standard DOS record and file locking functions (Interrupt 21h
Functions 3Dh and 5Ch). To use the multi-user features of the
modules, you must be running DOS 3.0 or higher, and SHARE must be
loaded. Most networks and multi-user operating systems support
these functions allowing your applications to run on a wide range
of systems.
If your programs may run in a multi-user environment, you should
set the variable 'MultiUser' from each module to TRUE. This will
enable automatic locking for data and index files and will prevent
files from becoming corrupted.
It should be noted that multi-user support adds substantial
overhead to most database operations when enabled. Therefore if
your program will be run only in a single-user environment, you
should keep the variable 'MultiUser' set to FALSE. Future
enhancements to this package will increase the speed of multi-user
access and will add automatic detection of multi-user access.
Two variables in each module control the functioning of the multi-
user mode. The boolean variable MultiUser enables and disables
multi-user access to data files. The variable Retries controls the
number of times a lock will be attempted before it fails. The
default value for MultiUser is FALSE. The default value for
Retries is 500.
Two procedures LockRec and UnLockRec from the DBF module allow your
applications to explicitly control the locking of records. If
several users will be accessing the same data file, it is prudent
to use these procedures to prevent conflicts (see overview above).
It is important to understand that in some systems, locking a
record may not prevent other users from reading it, only from _______
locking it. Therefore it may be possible to read a record that is
locked by another user, but not to lock it until the first user has
unlocked it.
Appendix C - Page 41