CodeBase User Guide
CodeBase User Guide
0 TM
User’s Guide
No part of this publication may be reproduced, or transmitted in any form or by any means without
the written permission of Sequiter Software Inc. The software described by this publication is
furnished under a license agreement, and may be used or copied only in accordance with the terms
of that agreement.
The manual and associated software is sold with no warranties, either expressed or implied,
regarding its merchantability or fitness for any particular purpose. The information in this manual
is subject to change without notice and does not represent a commitment on the part of Sequiter
Software Inc.
Introduction
CodeBase is a high performance database engine for C, C++, Delphi and
Visual Basic programmers.
CodeBase is compatible with dBASE IV, FoxPro and Clipper file formats.
CodeBase allows you to create, access or change their data, index or memo
files. Applications written in CodeBase can seamlessly share files with
concurrently running dBASE IV, FoxPro and Clipper applications.
CodeBase has four different interfaces depending on programming language.
CodeBase supports the C++, C, Visual Basic languages and Pascal under
Delphi. In addition, these interfaces can be used under a variety of operating
systems, including DOS, Windows 3.1/95/NT or OS/2 and UNIX, depending
on the language.
CodeBase is scalable to your needs, which means that it can be used in a
stand-alone, multi-user or client-server configuration.
CodeBase has many useful features including a full set of query and relation
functions. These functions use Query Optimization to greatly reduce the time
it takes to perform queries and generate reports. CodeBase also has logging
and transaction capabilities, which allow you to control the integrity of a
database easily.
This User's Guide discusses the CodeBase Visual Basic API (Application
Program Interface). The CodeBase Visual Basic API allows the programmer
to write applications using CodeBase in Visual Basic. It is necessary to have
Microsoft Visual Basic for Windows and some knowledge of how to program
in Visual Basic.
Refer to the Getting Started booklet for information on installing CodeBase,
running the example programs and contacting Sequiter Software.
6 CodeBase
How To Use This manual was designed for both novice and veteran database
programmers. The CodeBase 6.0 Getting Started booklet details how to
This Manual
execute an example CodeBase application. This booklet is recommend
reading for all users of CodeBase. The "Database Concepts" chapter of this
guide discusses database terminology. Even if you are an expert database
programmer, you should at least skim this chapter because some of the terms,
such as tags and indexes, may be used differently than you expect. The
remaining chapters deal with the basics of writing database applications with
the CodeBase library. For your convenience, all example programs illustrated
in this manual can be found on disk.
Manual This manual uses several conventions to distinguish between the various
Conventions language constructs. These conventions are listed below:
CodeBase classes, functions, structures, constants, and defines are always
shown highlighted as follows:
e.g. d4create , CODE4, r4eof, E4DEBUG
dBASE expressions are always contained by two double quotes ( " " ).
e.g. " 'Hello There ' + FIRST_NAME"
You should note that the containing quotes are part of the Basic syntax rather
than part of the dBASE expression.
dBASE functions are displayed in upper case and are denoted by a trailing set
of parenthesis.
e.g. UPPER() , STR(), DELETED()
Basic language functions and constructs are shown in bold typeface.
e.g. For, Print, Left$
Database Concepts 7
1 Database Concepts
If you are not familiar with the concept of a database, simply think of it as a
collection of information which has been organized in a logical manner. The
most common example of a database is a telephone directory. It contains the
names, phone numbers, and addresses of thousands of people. Each listing in
the phone book corresponds to one record, and each piece of information in
the record corresponds to one field. The phone book excerpt below illustrates
this concept.
Record # 5
CodeBase CodeBase uses the industry standard .DBF data files. This standard allows
for several types of fixed width fields and one type of variable length field.
File Format
8 CodeBase
Fields The .DBF standard uses four attributes to describe each field. These
attributes are Name, Type, Length, and Decimal. They are listed below.
For detailed information on field attributes, please refer to the FIELD4INFO
structure as described in the CodeBase Reference Guide under d4create.
• Name: This simply refers to the name that will be used to identify the
field. Each field name can be a maximum of 10 characters, and each
field name must be unique within a data file and must consist of
alphanumeric or underscore characters. The first character of the name
must be a letter.
• Type: The type of the field determines what kind of information should
be stored in the field. There are eight different types that can be
specified for any given field. They are Character, Numeric, Floating
Point, Date, Logical, Memo, Binary and General.
• Length: This attribute refers to the number of characters or digits that
can be stored in the field.
• Decimal: This attribute applies only to Numeric and Floating Point
fields. It specifies the number of digits after the decimal point.
For a more in-depth discussion of the various field types and their attributes,
refer to the "Field Functions" section of the Reference Guide.
Records A record consists of all information associated with one entry in a data file.
Typically, every line in a phone book would correspond to a new record.
Each data file record can be identified by its record number, which indicates
its physical position in the data file.
In addition, each record includes an extra byte of storage that is used as a
deletion flag. When a record is no longer needed in a data file, it can b
'flagged' for later removal by setting its deletion flag to true (non-zero). A
record that is marked for deletion is not physically removed from the file
until it undergoes a process known as packing. This method gives one the
opportunity to recall any record scheduled for deletion by simply setting the
deletion flag to false before packing the file.
Indexes & An index is a way to sort data file information without actually reorganizing
Tags the data itself. The index at the back of a book is a good metaphor for data
file indexes. The various topics of the book are sorted alphabetically for
easy lookup, and each entry has a corresponding reference to the physical
page number where the infomation can be found.
Indexes are also smaller than data files, and subsequently faster to sort and
manipulate, because they are based on only a portion of the record, usually
just a field value, rather than the entire record itself. This value is called the
index key. The key can be a single field value of a conbination of field
values. For example, if a phone book data file has the fields LAST_NAME
and FIRST_NAME, then an index key could be built based on a
combination of the two fields. A valid expression for the key would be
"LAST_NAME + FIRST_NAME".
Database Concepts 9
to cause only a subset of the data file records to have corresponding key
entries in the index. Filters are discussed further in this chapter.
Besides containing a key for each valid record, the index also contains the
physical record number that corresponds to each key. Thus, when you use
an index to seek a certain value, the corresponding record can be located
from the data file using the record number information. Here is how an
index file might look, based on a key expression of the field NAME.
Figure 1.2 Data and Index Representation
Physical Order:
3 ALBERTSON BRITAIN
4 FOREST CHINA
5 BAKER BRAZIL
5 BAKER
1 DAVIDSON
4 FOREST
2 SMITH
10 CodeBase
2 DataBase Access
Now that you have been introduced to the concepts of database management,
you're ready to start programming some simple database programs using
Visual Basic. This chapter take you through the details of manipulating the
data files by explaining how to create and open data files, store and retrieve
data, and how records are added and deleted.
To run any of the tutorial programs, refer to the Getting Started booklet.
Tutorial source code is installed into subdirectory "EXAMPLES".
The incorrect example assumes d4seek returns -1 or True upon success, thus
when zero is returned by d4seek to indicate a CodeBase success the Else
statement is executed instead of reporting that the seek value was found. In
the correct version, the return code is compared to the constant r4success to
determine whether the seek succeeded. Watch for this type of situation when
writing applications.
The exceptions to this rule are the CodeBase functions that must return
pointers. These functions returns zero when the function fails and usually set
the CODE4.errorCode to a negative value.
Logical Parameters Many CodeBase functions accept logical values as parameters. In these cases,
a true value corresponds to the value 1, while a false value corresponds to 0.
Thus, the value zero can represent both success and failure in functions
depending on the return type and it can also represent false when used as a
logical parameter.
Functions and A final note about our use of the word function to describe CodeBase
Subs
routines. In Visual Basic, those routines that do not return a value are known
as subroutines and those that do return a value are refered to as functions.
For the sake of consistency, the CodeBase Reference Guide and User's Guide
refer to all routines as functions regardless of whether they return a value or
not. Each function listed in the Reference Guide has a "usage" section that
indicates whether the function returns a value.
CodeBase CodeBase structures are used for storing information and for referencing
Structures objects such as data files and fields. CodeBase structures follow the same
naming conventions as CodeBase functions except that they are all upper
case.
CodeBase In addition to CodeBase structures and functions, you can also use CodeBase
Constants constants. Most constants are integers which are used for return values and
error codes. These constants have names which usually start with r4, or e4.
Some examples include r4success, r4locked, e4memory, and e4open.
These constants are listed in more detail in "Appendix A: Error Codes" and
"Appendix B: Return Codes" of the CodeBase Reference Guide. All
constants are defined in CODEBASE.BAS and can be included in any of your
projects.
Overview The next section introduces some the of the important CodeBase structures
and examines how they are related to data files.
DataBase Access 13
CODE4
DATA4
Record PRODUCTS.DBF
Current Record
Number Data File
PRODUCT PRO_ID COST
1 Widgit 2001 14.95
The DATA4 The DATA4 structure is used to reference a particular data file. When a data
structure file is opened, the information for the DATA4 structure is allocated and
stored in the CODE4 structure. When you call a function that operates on a
data file, you specify which data file you want by passing the function a
pointer to its DATA4 structure. ( For convenience, the terms "pointer to its
DATA4 structure" and "DATA4 pointer" will be used interchangeably. ) Note
that more than one DATA4 structure can reference the same open data file.
However, since the information for the structure is stored within the CODE4
structure, each new structure constructed with code4data contains the same
information, including the same current record and optimization settings.
14 CodeBase
The record buffer Each data file has a memory area associated with it called the record buffer,
which is used to store one record from the data file. The record that is stored
in the record buffer is called the current record. For example, when the data
file is positioned to the top record, record number one becomes the current
record and its contents are read from disk and stored in the record buffer.
Access to the record is then performed through the buffer, which is really just
a copy of the disk data.
Changes made to the record buffer using the field functions are automatically
written to the data file before any new current record is read from disk.
The FIELD4 A FIELD4 structure is used to reference a particular field in the record buffer.
structure When the data file is opened, a FIELD4 structure is automatically allocated
for each of its fields. When you call any function that uses a specific field of
the record buffer, you specify which field by passing a FIELD4 pointer.
Access to the FIELD4 pointer is obtained with the d4field function.
Learning As explained in the Getting Started book, the code samples for all the
CodeBase subsequent chapters in the User's Guide can be found in the project
USER.MAK.
Each example presented in this guide has a name, which corresponds to an
entry in a List Box that is presented when USER.MAK is run. Double
clicking an entry runs the example code. Any appropriate output is listed to
the screen.
The VB API Some of the example code presented here deals with the Visual Basic API
itself, such as filing list boxed, displaying data and so forth. These details are
not discussed because they are not directly related to the functionality of
CodeBase. If you have any questions about these sections, refer to the Visual
Basic Programmer's Guide.
An Example At this point, you are ready to write your first useful program that uses the
CodeBase CodeBase library. Every time USER.MAK is started, a Main() routine is
Program executed before USER.FRM is shown. This routine is used to both initialized
the library and to free up system resources when the Form is closed. Use this
method for your own applications. It will help prevent the types of errors that
occur when functions like code4initUndo are called twice in an application.
Main() is only listed once for ths first example, but remember that for all the
examples it is being called whenever the project is started. This function can
be found in USER.BAS.
PROGRAM The first example program is called ShowData. This example opens a data
ShowData file and prints its contents to the screen. Following the code listing is a brief
explanation of how the program works. Don't worry if some things aren't
totally clear after reading the explanation. The details will be explained
further in this chapter and the rest of the guide.
DataBase Access 15
Option Explicit
Global cb As Long 'Used to store CODE4 pointer
Global db As Long 'Used to store DATA4 pointer
Global rc As Integer 'Used as general return code
Sub Main()
cb = code4init( ) 'Initialize CodeBase
If cb = 0 Then
MsgBox "code4init( ) failed"
Exit Sub
Else
Form1.Show( 1 ) 'Show Form 1 modally
End If
End Sub
If db = 0 Then
MsgBox "Open Failed"
rc = code4errorCode( cb, 0 ) 'Reset error code
Exit Sub
End If
rc = d4top(cb)
Explanation : The next section contains a brief explanation of the ShowData program,
which is followed by a more detailed explanation.
The first step required in any CodeBase application is the initialization of the
library. This step must occur before you attempt to call any other functions.
Failing to do so will lead to unpredictable results. Initialization is
accomplished through code4init.
code4init returns a long integer pointer to an internal CODE4 structure that
CodeBase creates and initializes. In most cases, this return value should be
stored in a variable that has a global application scope.
Global cb As Long 'Used to store CODE4 pointer
...
If cb equals zero after code4init has been called, an error has occurred and
the application will end; otherwise USER.FRM is shown. When 'ShowData'
is double-clicked, d4open is called to open the data file. The second
parameter of d4open specifies which data file to open, in this case
SHOWDATA.DBF. If db equals zero, an error has occurred and the
subroutine is exited.
db = d4open( cb, fPath + "SHOWDATA" )
If db = 0 Then
MsgBox "Open Failed"
rc = code4errorCode( cb, 0 )
Exit Sub
End If
If the file is opened without error, the program then moves to the top of the
file by calling d4top, which loads the first record into the record buffer.
rc = d4top( db )
Next, a For/Next loop is used to load each subsequent record into the record
buffer. The loop terminates when it has iterated through all the records in the
file. The number of records in the file is returned d4recCount.
For i = 1 To d4recCount( db )
Inside this For loop, a second For loop is used to iterate through the fields by
incrementing a counter from one to the total number of fields in the data file.
The number of fields in the data file is obtained by a call to d4numFields.
For j = 1 To d4numField( db )
After the inner loop has completed, one record has been printed to the screen.
The next record is then loaded in the record buffer with the d4skip function.
Print
rc = d4skip( db )
Next i
If everything has executed properly, the final step is to close the data file with
the d4close function.
rc = d4close( db )
DataBase Access 17
When the form is closed, control will return to the Main( ) routine, which
uninitializes the library by calling code4initUndo. This function is
important for freeing any resources that CodeBase has allocated.
rc = code4initUndo( cb )
Initializing Since the CODE4 structure contains random data when the program starts,
the CODE4 structure must be initialized before any CodeBase functions can
The CODE4
be used.
Structure
Code fragment cb = code4init( )
from ShowData
When the CODE4 structure is initialized, all of the flags and member
variables are set to their default values. The code4init function should
normally only be called once in the application.
After code4init has been called, you can then change the value of the CODE4
structure's flags. Refer to the CodeBase Members and Functions in the
Reference Guide for an explanation on all the members of the CODE4
structure and how to change their default values.
Opening Only after a CODE4 structure is initialized, can data files be opened with
d4open.
Data Files
Code fragment db = d4open( cb, fPath + "SHOWDATA" )
If db = 0 Then
from ShowData MsgBox "Open Failed"
d4open accepts the CODE4 pointer as its first parameter and a string
containing the path and file name of a data file as its second. If the specified
file name does not contain an extension, the default extension of ".DBF" is
used. If no path is specified, the current directory is searched.
Obtaining a DATA4 If the file is located, it is opened and the address to an internal DATA4
pointer structure is returned. This address is used as a reference to the data file.
Almost every function that starts with d4 takes a DATA4 pointer as a
Note parameter. This pointer specifies which data file the function should
operate on.
Checking for errors If the file does not exist, or if CodeBase detects any other error, an error
message is displayed and a zero is returned by d4open. In addition,
CODE4.errorCode will be set to a negative value such as e4open. This
error code must be reset to zero before other CodeBase function calls can be
made.
Code fragment rc = code4errorCode( cb, 0 )
Exit Sub
from ShowData End If
18 CodeBase
After the data file has been successfully opened, the current record number is
set to '-1' to indicate that a record has not yet been read into the record buffer.
Closing Before your application completes, it is important that any open data files are
closed. This ensures that any changes to the data file are updated correctly.
Data Files
There are two functions that you can use. They are d4close and
code4close. The d4close function closes the data file whose DATA4
pointer was provided as a parameter. The function also closes any index files
(see the "Indexing" chapter) or memo files associated with the data file.
Code fragment rc = d4close( db )
from ShowData
The function code4close closes all open data, index, and memo files. This
function takes a CODE4 pointer as its only parameter. code4initUndo will
also close any open data files.
Moving CodeBase provides ten functions for moving between records in the data file:
d4top, d4bottom, d4skip, d4go, d4seek, d4seekDouble, d4seekN,
Between
d4seekNext, d4seekNextDouble and d4seekNextN. These functions
Records change the current record by loading a new record into the record buffer.
• The d4top function sets the current record to the first record of the data
file. The d4bottom performs a similar function for the last record.
• The d4skip function moves a specified number of records above or below
the current record. This function must have a current record in order to
function correctly.
• The d4go function loads the record buffer with the record whose record
number was provided as a parameter.
• The d4seek, d4seekDouble and d4seekN functions locate the first
record in a sorted order that matches a search key. These functions are
described in detail in the "Indexing" chapter.
• The d4seekNext, d4seekNextDouble and d4seekNextN functions are
similar to the d4seek functions except that they locate the next record in
a sorted order that matches a search key. These functions are described in
detail in the"Indexing" chapter.
Scanning A common task in database programming is to display a list of records. The
Through The ShowData sample code illustrated this by using the number of records as the
Records outer loop counter. Another way this same task can be accomplished is by
using the return codes from the positioning functions. The following code
fragment demonstrates this idea.
DataBase Access 19
d4top d4top is called, before the For loop, to position the data file to a valid record.
This function causes the first record in the data file to be loaded into the
record buffer. If the function is successful, it returns a value of r4success.
d4skip At the end of each iteration through the For loop, d4skip is called. d4skip
allows you to skip either forwards or backwards through the data file. The
numeric argument is used to specify how many records are skipped. If the
number is positive, you move towards the end of the data file; if it is negative
you move back towards the top. In this example the value is 1, which
indicates that the next record should be loaded. The d4skip function also
returns a value of r4success when it executes successfully.
The For loop is terminated when rc is no longer equal to r4success. This
happens when either d4skip or d4top encounters an error or the end of the
data file.
Accessing Before any information can be stored to or retrieved from a field, you must
first obtain a pointer to its FIELD4 structure. There are two ways to
Fields
accomplish this: the first involves knowing the field's position in the data file,
and the second requires that you already know the field's name.
'FIELD4 pointers
Dim fName&, lname&, address&, age&, birthdate&
Dim marriage&, amount&, comment&
'TAG4 pointers
Dim nameTag&, ageTag&, amtTag&, birthTag&
Do
lnameStr = f4str( lname )
fnameStr = f4str( fname )
addressStr = f4str( address )
ageVal = f4int( age )
birthdateStr = f4str( birthdate )
marriedStr = f4str( married )
amtVal = f4double( amount )
commentStr = f4memoStr( comment )
code4close( cb )
Referencing Each field in the data file has a unique field number. Field numbers range
By Field from one to the total number of fields in the data file. This value denotes the
Number field's physical order in the record. The d4fieldJ function uses a field's field
number to return a pointer to the field's FIELD4 structure.
Iterating through Iterating through the fields in a record is a common use of this method of field
the fields
referencing. Before you can do this, d4numFields is called to determine the
number of fields in the data file. A For loop is then used to iterate through
each field to generically obtain a FIELD4 pointer.
Code fragment For j = 1 To d4numFields( db )
fldPtr = d4field( db, j )
from ShowData Print f4memoStr( fldPtr )
Referencing If you know ahead of time what the names of the fields are, you can obtain a
By Field field's FIELD4 pointer using its name. This is the method used in the CustList
Name program.
The d4field function returns a pointer to a FIELD4 structure of the field
whose name is specified. This is demonstrated in the following section of
code.
Code fragment Dim fName&, lName&, address&, age&, birthDate&,
from CustList Dim married&, amount&, comment&
Retrieving The Basic language has many different data types such as Integers, Strings,
Doubles and Longs. As a result, the CodeBase provides a number of functions
A Field's
that retrieve the contents of fields and automatically perform any necessary
Contents conversions to the Basic data type.
All the field functions accept a FIELD4 pointer that specifies which field is to
be operated upon.
Retrieving If you need to access the field's contents in the form of a string, you can use
field contents f4str or f4memoStr. They both return a copy of the field's contents.
as Strings
Each time a f4str or f4memoStr function is called, the internal
Note CodeBase buffer is overlaid with data from the new field.
Consequently, if the field's value needs to be saved, it is necessary
for the application to make a copy of the field's contents.
The f4str function can be used on any type of field except Memo fields.
f4memoStr works on all types of fields including Memo fields. The reason
for having two almost identical functions is that Memo fields require special
handling. If you know a field is not a Memo field, it is appropriate to use the
f4str function instead, since it is slightly faster.
To enable the program to work on any type of field, the ShowData program
uses f4memoStr function.
Code fragment fldPtr = d4field( db, j )
Print f4memoStr( fldPtr )
from ShowData
Creating Not only does CodeBase allow you to access existing files, you can also
create new files. This is accomplished by a single function call to
Data Files
d4createData.
PROGRAM The NewList program creates a new data file if one of the same name does
NewList not already exist. It then appends several new records and assigns values to
the fields in each record. Note that this example and the ones that follow use
subroutines that are located in the file USER.BAS.
USER.BAS
'FIELD4INFO Structures
Dim field4Info() As FIELD4INFO
'Other vars
Dim ageVal As Integer, amtVal As Double
Dim fnameStr As String, lnameStr As String
Dim addressStr As String, marriedStr As String
Dim birthdateStr As String, commentStr As String
Function cbError( )
If code4errorCode( cb, 0) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Function OpenDataFile%( )
db = d4open( cb, fPath + "DATA1" )
If db = 0 Then
InitField4 'Initialize field info
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError( ) Then Exit Function
End If
Sub PrintRecordsNewList( )
Dim x As Integer
x = 12
DataBase Access 23
Do
lnameStr = f4str( lname ) 'Copy the contents of the field
fnameStr = f4str( fname )
addressStr = f4str( address )
ageVal = f4int( age )
birthdateStr = f4str( birthdate )
marriedStr = f4str( married )
amtVal = f4double( amount )
commentStr = f4memoStr( comment )
rc = d4skip( db, 1 )
Loop While rc = r4success
End Sub
Sub InitField4( )
fieldInfo(1).fname = "F_NAME"
fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
fieldInfo(2).fname = "L_NAME"
fieldInfo(2).ftype = r4str
fieldInfo(2).flength = 10
fieldInfo(2).fdecimals = 0
fieldInfo(3).fname = "ADDRESS"
fieldInfo(3).ftype = r4str
fieldInfo(3).flength = 15
fieldInfo(3).fdecimals = 0
fieldInfo(4).fname = "AGE"
fieldInfo(4).ftype = r4num
fieldInfo(4).flength = 2
fieldInfo(4).fdecimals = 0
fieldInfo(6).fname = "MARRIED"
fieldInfo(6).ftype = r4log
fieldInfo(6).flength = r4logLen
fieldInfo(6).fdecimals = 0
fieldInfo(7).fname = "AMOUNT"
fieldInfo(7).ftype = r4num
fieldInfo(7).flength = 7
fieldInfo(7).fdecimals = 2
fieldInfo(8).fname = "COMMENT"
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If
rc = d4append( db )
End Sub
FIELD4INFO The FIELD4INFO structure is an integral component of the data file creation
Structures process. This structure contains the information which defines a field.
The FIELD4INFO structure contains a member for each of the field's four
attributes. These members are described below:
• name This is a string containing the name of the field. This name must
be unique to the data file and any characters after the first ten are ignored.
Valid field names can only contain letters, numbers, and/or underscores.
The first character of the name must be a letter.
• type This member contains an abbreviation for the field type. CodeBase
supports Character, Date, Floating Point, Logical, Memo, Numeric,
Binary and General field types. Valid abbreviations are the character
constants 'C', 'D', 'F', 'L', 'M', 'N', 'B' and 'G'. In addition you can also use
the equivalent CodeBase constants: r4str, r4date, r4float, r4log,
r4memo, r4num, r4bin and r4gen, respectively.
• len This integer value determines the length of the field.
• dec This integer member determines the number of decimal places in
Numeric or Floating Point fields.
Refer to d4create in the CodeBase Reference Guide for more detailed
information on field attributes.
FIELD4INFO Before a data file can be created, an array of FIELD4INFO structures must be
arrays defined. Each element of this array describes a single field.
An example FIELD4INFO array is defined below:
Code fragment Sub InitField4( )
from NewList ReDim fieldInfo( 1 to 8 ) as FIELD4INFO
fieldInfo(1).fname = "F_NAME"
fieldInfo(1) .ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
...
fieldInfo(8).fname = "COMMENT"
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
26 CodeBase
Creating The The actual creation of the data file is performed by the d4createData
Data File function. This function uses the information in a FIELD4INFO array as the
field specifications for a new data file. d4createData only creates a data file
and possibly a memo file. The function d4create builds a data file and
production index file as well. The "Indexing" chapter discusses the use of
d4create and building indexes.
The second parameter of the d4createData function is a string containing a
file name. This file name can contain any extension, but if no extension is
provided, the default ".DBF" extension is used. Like the d4open function,
the current directory is assumed if no path is provided as part of the file name.
If the data file is successfully created, a pointer to its DATA4 structure is
returned. If for any reason the data file could not be created, zero is returned
instead and CODE4.errorCode is set accordingly. The DATA4 pointer that is
returned from d4createData or d4create can be passed on to other data
functions such as d4top and d4skip.
The CODE4.safety The CODE4.safety flag determines whether any existing file is replaced when
flag CodeBase attempts to create a file. If the file exists and the CODE4.safety
flag is set to its default value of true (an integer value of 1), the new file is not
created. When this flag is set to false (an integer value of 0), the old file and
its index and memo files are replaced by the newly created file and its
associated files. If the new file is not created because the file already exists
and CODE4.errCreate is true, an error message is generated. The value of
CODE4.safety can be changed by calling the function code4safety.
As with all CODE4 members, the CODE4.safety flag can only be
changed after code4init has been called.
WARNING
Opening Or It is often the case that you want to open a data file if it exists or create it if it
Creating does not. The following section of code demonstrates how this can be
accomplished:
Code fragment save1 = code4errOpen( cb, 0 )
save2 = code4safety( cb, 0 )
from NewList
. . .
If db = 0 Then
InitField4
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError( ) Then Exit Function
End If
DataBase Access 27
The If the data file does not already exist, d4open will normally generate an error
CODE4.errOpen message when it fails to open the file. This error message can be suppressed
flag
by changing the CODE4.errOpen flag. When this flag is set to true (its
default value), an error message is generated when CodeBase is unable to
open a file. If this flag is set to false (zero), CodeBase does not display this
error message. It does however still return a zero DATA4 pointer, which can
be tested. This is the recommended method for opening a data file if it exists
and creating it if it does not. The value of CODE4.erroOpen can be changed
by calling code4errOpen.
Adding New There are two methods that can be used to add new records to the data file.
They both append a new record to the end of the data file.
Records
Appending A If all you need to do is add a blank record to the bottom of the data file,
Blank Record d4appendBlank should be used.
rc = d4appendBlank( db )
This function adds a new record to the data file, blanks out all of its fields,
and makes the new record the current record.
The Append The second method involves three steps. This method is more efficient and
Sequence should be used when you intend to immediately assign values to the new
records fields. Using this method results in less disk writes and therefore
improves performance.
starting the The first step is performed by the d4appendStart function. This function sets
append sequence
the current record number to zero to let CodeBase know that a new record is
about to be appended. Additionally, d4appendStart temporarily disables
automatic flushing for the current record, so that if changes need to be
aborted, the record is not flushed to disk.
Code fragment rc = d4appendStart( db, 0 )
from NewList
The second step involves assigning values to the fields. After the
d4appendStart function is called, the record buffer contains a copy of the
previous current record. If no changes are made to the record buffer, a copy
of this record is appended. If you prefer an empty record buffer, a call to
d4blank will clear it.
rc = d4appendStart( db, 0)
rc = d4blank( db )
Appending the The final step is accomplished by d4append. This function causes a new
record
record to be physically added to the end of the data file and its key values to
be added to any open index tags.
28 CodeBase
Assigning Just as there are several CodeBase functions for retrieving information from
fields, there are corresponding functions for storing information in fields.
Field
Values
CODE4.lockEnforce In multi-user configurations, only one application should edit a record at a
time. One way to ensure that only one application can modify a record is to
explicitly lock the record before it is changed. To ensure that a record is
locked before it is changed, set CODE4.lockEnforce to true (non-zero) by
calling code4lockEnforce. When CODE4.lockEnforce is true (non-zero)
and an attempt is made to modify an unlocked record using a field function or
d4blank, d4changed, d4delete or d4recall, an e4lock error is generated
and the modification is aborted.
An alternative method of ensuring that only one application modifies a record
is to deny all other applications write access to the data file. In this case
explicit locking is not required, even when CODE4.lockEnforce is true, since
only no outside applications can write to the data file. Write access can be
denied to other applications by passing OPEN4DENY_WRITE or
OPEN4DENY_RW to code4accessMode before the data file is opened.
Refer to the "Multi-User Applications" chapter of this guide for more
information.
Copying f4assign and f4memoAssign are two functions that copy strings to fields.
Strings To The f4assign function works an any type of field except Memo fields, while
Fields f4memoAssign works on all types.
Copying strings to If the length of the assigned string is less than the size of the field, the field's
Character fields
contents are left justified, and the remaining spaces are filled with blanks. If
the string is larger than the field, its contents are truncated to fit. When an
assignment is done with a memo field, the length is automatically adjusted to
accommodate any value.
Code fragment Call f4assign(fname,fNameStr)
NewList rc = f4memoAssign(comment,commentStr)
Copying f4assign and f4memoAssign can also store strings into Numeric fields, but
strings to Numeric
generally this is not a good idea as it is very easy to format the information
fields
incorrectly. A correctly formatted number is right justified, zero-padded right
to the number of decimals in the field, and space-padded left for any unused
units.
The functions f4assign and f4memoAssign store the information
Note exactly as it appears in the string. This can cause problems when
they are used to store non-numeric data into Numeric type fields. It
is recommended that the functions f4assignInt, f4assignDouble or
f4assignLong be used to store data in Numeric fields.
DataBase Access 29
Copying strings to Any strings that are copied to Date type fields should be exactly eight
Date fields
characters long and should contain a date in standard format
(CCYYMMDD). For details please refer to the "Date Functions"
chapter.ate4 Class Ch
Copying strings to The only strings that should be copied to Logical fields are "T", "F","t",
Logical fields
"f","Y", "N", "y" or "n".
Code fragment If marriedVal Then
Call f4assign( married,"T" )
from NewList Else
Call f4assign( married,"F" )
End If
Storing Instead of worrying about the formatting of strings containing numerical data,
Numeric Data you can let CodeBase perform the formatting for you. f4assignDouble,
f4assignInt and f4assignLong automatically convert and format numerical
data.
Assigning double You can assign a double value to a field using f4assignDouble. This
values to a field function can automatically format the numerical value according to the field's
attributes. For example, if the field has a length of six and has two decimal
places, and the double value of 34.12345 assigned to it, the field will contain
" 34.12" after f4assignDouble is called. If the f4assignDouble function
is used on a Character field, it assumes the field has no decimals and right
justifies its contents.
Code fragment Call f4assignDouble( amount, amtVal )
from NewList
Assigning integer f4assignInt and f4assignLong are used to copy and format integer and long
values to a field
values to fields. Right justification is used, and if the field is of type Numeric,
any decimal places are filled with zeros.
Code fragment Call f4assignLong( age, ageVal )
from NewList
Removing CodeBase provides a two level method for removing records. You can delete
a record by simply changing the status of its deletion flag to true. The records
Records
that are flagged for deletion can then be physically removed by "packing" the
data file.
PROGRAM The Deletion program demonstrates the effects of deleting, recalling, and
Deletion packing records.
USER.BAS
If status Then
display = Str$( recno ) + " - DELETED"
Else
display = Str$( recno ) + " - NOT DELETED"
End If
Form1.Print "Record # " + display
End Sub
Sub PrintRecordsStatus( )
rc = d4top( db )
Do While rc = r4success
recno = d4recNo( db )
status = d4deleted( db )
Call PrintDeleteStatus( status, recno )
rc = d4skip( db, 1 )
Loop
Form1.Print
End Sub
Function CreateDeleteFile%( )
Dim save As Integer
fieldInfo(2).fname = "MEMO"
fieldInfo(2).ftype = r4memo
fieldInfo(2).flength = r4memoLen
fieldInfo(2).fdecimals = 0
If db = 0 Then
rc = code4errorCode( cb, 0 )
CreateDeleteFile = False
Else
CreateDeleteFile = True
End If
End Function
rc = d4go( db, 3 )
Call d4recall( db )
PrintRecordsStatus
rc = d4pack( db )
rc = d4memoCompress( db ) 'Remove deleted records
PrintRecordsStatus 'Compress memo File
rc = code4close( cb )
Explanation: This program creates the data file (or re-creates, if it exists) and then appends
five records. The program then displays the deletion status of all the records:
DataBase Access 31
Record numbers one, three and four are then marked for deletion and the
deletion status of the records are displayed again.
Deletion output Record # 1 - DELETED
part 2 Record # 2 - NOT DELETED
Record # 3 - DELETED
Record # 4 - DELETED
Record # 5 - NOT DELETED
Record number three is recalled (ie. the deletion mark is removed) and the
deletion status of the records are displayed.
Deletion output Record # 1 - DELETED
part 3 Record # 2 - NOT DELETED
Record # 3 - NOT DELETED
Record # 4 - DELETED
Record # 5 - NOT DELETED
Finally the data file is packed. Displaying the record status shows that record
one was removed from the data file. Record two now occupies the space of
record one and record three occupies record two and so on.
Deletion output Record # 1 - NOT DELETED
part 4 Record # 2 - NOT DELETED
Record # 3 - NOT DELETED
Since the record numbers are contiguous and always start at one,
Note the record number for a specific record may have changed after the
data file has been packed
Determining Each record has its own deletion flag. The status of the deletion flag of the
The Deletion current record can be checked using d4deleted.
Status
Code fragment status = d4deleted( db )
from Deletion
d4deleted returns a true (non zero) value when the current record has been
marked for deletion.
Marking A d4delete is used to set the current record's deletion status to true. The next
Record For time the data file is packed, the record is physically removed from the file and
Deletion any reference to it in any open index file is also removed.
Code fragment Call d4delete( db )
from Deletion
Recalling If the data file has not yet been packed, any deleted records can be recalled
Records back to non-deleted status. This is accomplished by a call to d4recall.
Call d4recall( db )
Packing The Physically removing records from the data file is called packing. If your data
Data File file is quite large, this process can be quite time consuming. That is why
records are first marked for deletion and then physically removed. The
cumulative time necessary for removing individual records would be
substantially greater than removing the many "deleted" records at one time.
Packing the data file is performed by d4pack.
Consider opening the file exclusively before packing to keep others
Note from accessing the data file while packing is occurring. Otherwise,
the data may appear as corrupted to other users for the duration of
the pack.
Compressing When d4pack removes a record from the data file, it does not remove the
The Memo corresponding memo entry (assuming that there is at least one Memo field).
File This results in unreferenced memo entries. Unreference memo entries cause no
difficulties in an application except that they simply waste disk space. To
remove any wasted memo file space, the memo file can be compressed by
calling d4memoCompress. Again, since compressing a memo file can
consume a large amount of time, it may be appropriate to compress a data file
sometime other than when the data file is packed
Data File CodeBase contains a set of functions that provide information about the data
files, their records and fields.
Information
PROGRAM This program displays information about a data file whose name is provided
DataInfo in an Input Box when the program is run. It displays the data file's alias,
record count, record width, and the attributes of its fields.
USER.BAS
Function cbError( )
'Check and reset error code
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
USER.FRM List1_DblClick( )
y = 17
List2.Visible = True
List2.Clear
rc = code4close( cb )
The data Each data file that you have open has a character string label called the alias.
file alias
The alias is mainly used for qualifying field names in dBASE expressions or
looking up a DATA4 pointer using code4data.
d4alias returns a string containing the data file's alias. When the file is
opened, the alias is set to the same name as the data file without the extension.
This string is valid as long as the data file is open.
Code fragment for Print "Alias: "; d4alias( db )
DataInfo
The alias of a data file can be changed by using d4aliasSet. This would be
required in a situation where the same file is being opened twice or if two data
files in different directories have the same file name. In either case, open the
first data file and change its alias to something other than the file name and
then open the second data file. In this way CodeBase can distinguish the data
files based on their differing aliases.
Finding the number An important data file statistic is the record count, which can be obtained by
of records
calling d4recCount. This function does not take into account the deleted
status of any records, nor any filter condition of a open tag.
rCount = d4recCount( db )
Determining the Another useful function is d4recWidth. This function returns the length of
record width
the record buffer. This value is the total length of all the fields plus one byte
for the deletion flag. This function may be used to save copies of the record
buffer.
Code fragment for Print "Record Length: "; d4recWidth( db )
DataInfo
34 CodeBase
Field If you have access to a field's FIELD4 pointer, you can retrieve the field
Information attributes.
Field name f4name returns a pointer to the field's name. This pointer is valid as long as
the data file is open.
Code fragments List2.AddItem f4name( fldRef )
from DataInfo
Field type f4type returns the ASCII value of the field type:
List2.AddItem Chr$( f4type( fldRef ) )
Field length and The length of the field and the number of decimals are returned by f4len
decimals
(f4memoLen) and f4decimals respectively. Both functions return integers.
List2.AddItem Str$( f4len( fldRef ) )
List2.AddItem Str$( f4decimals( fldRef ) )
Advanced This section documents how to copy the structure of an existing file and use
the information to create a new file with the same structure.
Topics
Copying Data You may occasionally require a new data file that has an identical structure to
File an existing data file.
Structures To satisfy this need CodeBase provides the function d4fieldInfo, which
returns a copy of the FIELD4INFO array of an existing file. NOTE that this
function returns a pointer to the field information that is already in the native
'C' format of the DLL. This pointer can NOT be passed to d4create since it
is expecting the Visual Basic version of a FIELD4INFO array. Instead pass
this pointer ByVal to the function d4createCB, which is the actual low level
function that is called by d4create after it converts the Visual Basic
FIELD4INFO information into 'C' data.
Refer to the discussion of i4tagInfo in the "Indexing" chapter. This function
returns an array of index infomation, which can be used to create index files.
The FIELD4INFO array that is returned by d4fieldInfo is allocated
Note dynamically and should therefore be deallocated after you are
finished using it. The u4free function can be used for this purpose.
PROGRAM The CopyData program retrieves a file name from a Input Box and uses that
CopyData file's structure to create a new empty file called COPY.DBF.
USER.BAS
Function cbError( )
If code4errorCode( cb, 0 )< 0 Then
cbError = True
Else
cbError = False
End If
End Function
USER.FRM List1_DblClick( )
rc = code4close( cb )
3 Indexing
The purpose of a database is to organize information in a useful manner. So
far you have seen how the information is divided into fields and records. Now
it is time to order the records in purposeful ways. One approach is to
physically sort records in your data files. Unfortunately, maintaining data file
sorts involves continually shuffling large amounts of data. In addition, it is
only possible to maintain a single sort order using this method.
A preferable method is to leave the records in the data file in their original
order and store the sorted orderings in a separate file called an index file.
When you create an index file, you are effectively sorting the data file. Index
files are efficiently maintained and you can have an unlimited number of
sorted orderings continually available.
Indexes & Each index file can contain one or more sorted orderings. These sorted
orderings are identified by a tag. That is, each index file tag corresponds to a
Tags
single sorted ordering. CodeBase supports four types of index files. Their
attributes and differences are described below:
The difference between indexes and tags are illustrated in Figure 3.2.
38 CodeBase
PRODUCTS.DBF
PRODUCTS.MDX COST.MDX
Index File
Index File PRO_TAG ID_TAG COST_TAG
Dludge 1023 4.57
Tags
Creating An Creating an index file is similar to creating a data file; in fact you can even
create both at the same time. There are two types of index files that you can
Index File
create: production indexes and non-production indexes.
PROGRAM The following is the program listing for NewList2. This modified version of
NewList2 the NewList program, creates an index file along with the data file.
USER.BAS Declarations and Functions
'Other vars
Dim ageVal As Integer, amtVal As Double
Dim fnameStr As String, lnameStr As String
Dim addressStr As String, marriedStr As String
Dim birthdateStr As String, commentStr As String
'Structure arrays
Global fieldInfo() As FIELD4INFO, tagInfo() As TAG4INFO
Sub InitField4( )
fieldInfo(1).fname = "F_NAME"
fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
fieldInfo(2).fname = "L_NAME"
fieldIn fo(2).ftype = r4str
40 CodeBase
fieldInfo(2).flength = 10
fieldInfo(2).fdecimals = 0
fieldInfo(3).fname = "ADDRESS"
fieldInfo(3).ftype = r4str
fieldInfo(3).flength = 15
fieldInfo(3).fdecimals = 0
fieldInfo(4).fname = "AGE"
fieldInfo(4).ftype = r4num
fieldInfo(4).flength = 2
fieldInfo(4).fdecimals = 0
fieldInfo(5).fname = "BIRTH_DATE"
fieldInfo(5).ftype = r4date
fieldInfo(5).flength = r4dateLen
fieldInfo(5).fdecimals = 0
fieldInfo(6).fname = "MARRIED"
fieldInfo(6).ftype = r4log
fieldInfo(6).flength = r4logLen
fieldInfo(6).fdecimals = 0
fieldInfo(7).fname = "AMOUNT"
fieldInfo(7).ftype = r4num
fieldInfo(7).flength = 7
fieldInfo(7).fdecimals = 2
fieldInfo(8).fname = "COMMENT"
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
Sub InitTag4( )
tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).descending = r4descending
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
End Sub
InitField4
InitTag4
If cbError() Or db = 0 Then
CreateDataFile = False
Exit Function
End If
End Function
If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If
rc = d4append( d b )
End Sub
Sub PrintRecordsNewList( )
Dim x As Integer
x = 12
Do
lnameStr = f4str( lname ) 'Copy the contents of the field
fnameStr = f4str( fname )
addressStr = f4str( address )
ageVal = f4int( age )
birthdateStr = f4str( birthdate )
marriedStr = f4str( married )
amtVal = f4double( amount )
commentStr = f4memoStr( comment )
rc = d4skip( db, 1 )
Loop While rc = r4success
End Sub
Call AddNewRecord( "Sarah", "Webber", "132-32 St.", 32, "19610523", True, 147.99,
"New Customer")
Call AddNewRecord( "John", "Albridge", "1232-76 Ave.", 55, "19381212", False,
98.99, "" )
PrintRecordsNewList
Print
'And so on...
The Just as the attributes of a field in the data file are defined by a FIELD4INFO
TAG4INFO structure, the attributes of a tag in an index file are specified by a TAG4INFO
Structure structure. This structure contains the following five members:
The TAG4INFO The first two members must be defined for every TAG4INFO structure. The
members last three members are used to specify optional properties of the tag which
will be discussed later in this chapter.
• name This is a string containing the name of the tag. The name may be
composed of letters, numbers and underscores. Only letters and
underscores are permitted as the first character of the name. This member
cannot be the zero length string.
When using FoxPro or .MDX formats, the name must be unique to the
data file and have a length of ten characters or less excluding the
extension. If you are using the .NTX index format, then this name can
include a tag name with a path. In this case, the tag name within the path
is limited to eight characters or less, excluding the extension.
• expression This is a string containing the tag's index expression. This
expression determines the sorting order of the tag. (see the "Index
Expression" section of this chapter for details) This member cannot be a
zero length string.
• filter This is a string containing the tag's filter expression. If this
member is not explicitly set, there is no filter for the tag.
• unique This integer determines how duplicate keys are treated. See the
"Unique Keys" section of this chapter for more details. If this member is
not explicitly set, the tag is non-unique.
• descending This integer determines if the index should be generated in
descending order. If this member is not explicitly set, the tag is in
ascending order. Set this member to r4descending if descending order is
desired.
Indexing 43
Before the index file can be created, an array of TAG4INFO structures must
be defined. Each element of this array denotes a single tag.
The TAG4INFO Before the index file can be created, an array of TAG4INFO structures must
array be defined. Each element of this array denotes a single tag.
An example TAG4INFO array is defined below.
Code fragment ReDim tagInfo( 1 to 4 ) As TAG4INFO
from NewList2 tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
tagInfo(2).name = "AGE_TAG"
tagInfo(2).expression = "AGE"
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).descending = r4descending
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
Using d4create The most common method for creating an index file is to create it when the
To Create data file is created. In this case the index will be a production index. If you
Index Files recall, the d4create accepts four parameters, only three of which were used
in the last chapter. The fourth parameter is a pointer to a TAG4INFO array
which is used for creating a production index file. This is illustrated in the
following code segment:
Code fragment db = d4create( cb, fPath + "DATA1", fldInfo(), tagInfo() )
from NewList2
Using i4create The other method for creating index files involves the use of the i4create
To Create function. In addition to a TAG4INFO array, this function also accepts a
Index Files string containing a file name. An index file is created with this file name. If
no extension is specified, an appropriate extension for the file compatibility
used ( .MDX, .CDX, or .NTX) is provided.
For example, the program NewList2 could be modified to use i4create
instead.
Dim ind As Long
...
i4create returns a pointer to the index file's INDEX4 structure. The pointer is
used by many low level index functions.
44 CodeBase
Creating Normally, i4create does not produce production indexes, even if the file name
Production Indexes
that you provide is the same as the data file's. i4create can be used to create
with i4create
production indexes in the following manner. The data file must be opened
exclusively before i4create is called. This can be achieved by setting the
CODE4.accessMode to OPEN4DENY_RW before the data file is opened.
After the data file is opened or created exclusively, then i4create is used to
create a production index by passing a zero length string as the file name
parameter. This is illustrated as follows:
Dim ind As Long
Maintaining CodeBase automatically updates all open index files for the data file. When a
Index Files record is added, modified, or deleted, the appropriate tag entries for all of the
open index tags are modified automatically. In general application
programming, there is no need to manually add, delete, or modify the tag
entries. CodeBase handles all of the key manipulation in the background,
letting you concentrate on application programming.
Code fragment Dim cb As Long, db As Long, nameFld As Long
rc = code4init( )
db = d4open( cb, fPath + "DATAFILE" )
nameFld = d4field( db, "NAME" )
rc = d4top( db )
Call f4assign( nameFld, "ROBERT WILKHAM" )
rc = d4skip( db, 1 )
Opening Opening index files can be accomplished by one of two functions. If the data
file has a production index, d4open automatically opens it. Other index files
Index Files
may be opened using function i4open.
Dim cb As Long, db As Long, ind As Long
cb = code4init( )
db = d4open( cb, fPath + "DATAFILE")
Disabling the There may be occasions when you prefer to leave the production index file
automatic opening
closed. You can disable the automatic opening of production index files by
of production
indexes setting flag CODE4.autoOpen to false (zero). The production index can then
be opened like any other index file:
Indexing 45
cb = code4init( )
rc = code4autoOpen( cb, 0 )
Referencing Before a tag is used, you must first obtain a pointer to its TAG4 structure.
When calling tag functions, this pointer specifies which tag the function
Tags
operates on. There are six functions that can be used to retrieve a tag's TAG4
pointer: d4tag, d4tagDefault, d4tagNext, d4tagPrev, d4tagSelected and
t4open.
PROGRAM The program ShowData2 is a modified version of ShowData. This version
ShowData2 displays the records of the data file in natural order and according to any tags
in its production index.
USER.BAS
Sub GenericPrint( )
rc = d4top( db )
USER.FRM List1_DblClick( )
If db = 0 Then
rc = code4errorCode( cb, 0 ) 'Reset error code
Exit Sub
End If
Do Until tagPtr = 0
Referencing If you know the tag's name, you can retrieve its TAG4 pointer by using
By Name d4tag. This function looks through all the open index files of the specified
data file for a tag matching the name. This method is used by program
NewList2.
Code Fragment 'Get TAG4 pointers
nameTag = d4tag( db, "NAME_TAG" )
from NewList2 ageTag = d4tag( db, "AGE_TAG" )
amtTag = d4tag( db, "AMT_TAG" )
Obtaining the The default tag is the currently selected tag. If there are no tags selected, the
Default Tag default tag is the first tag in the first opened index file. You can obtain a
pointer to the default tag's TAG4 structure by calling function d4tagDefault.
Dim tag1 As Long, db As Long
Iterating The functions d4tagNext and d4tagPrev are used for iterating through the
Through The tags.
Tags
d4tagNext The function d4tagNext returns the next tag after the specified tag. The first
tag of the first index is returned by passing a null pointer (0&) as the tag
parameter. If the last tag in the index file is passed into the tag parameter, the
first tag in the next index file is returned.
Code fragment tagPtr = d4tagNext( db, 0& )
from ShowData2 Do Until tagPtr = 0
.
.
.
tagPtr = d4tagNext( db, tagPtr )
Loop
d4tagPrev The function d4tagPrev works like d4tagNext except that it starts at the last
tag of the last index file and works its way toward the first tag of the first
index file.
Selecting Initially a data file does not have a selected tag and is therefore in natural
order. Natural order is the order in which the records were added to the data
Tags
file. When you want to use a particular sort ordering, select a tag by calling
d4tagSelect.
Selecting Natural If you want to return to natural order simply pass a null pointer (0&) to
Order
d4tagSelect instead of a TAG4 pointer.
Obtaining the The function d4tagSelected obtains the currently selected tag. It behaves
currently selected
like d4tagDefault except that it returns zero when no tag is selected.
tag
Indexing 47
The Effects Once a tag is selected, the behaviour of d4top, d4bottom, and d4skip
Of Selecting changes. Functions d4top and d4bottom then set the current record to the
a Tag first and last data file entries respectively, using the tag's sort ordering.
Similarly, d4skip skips using the tag ordering instead of the natural ordering.
Tag Filters A tag filter is used to obtain a subset of the available data file records. The
subset is based on true/false conditions calculated from a dBASE expression.
Only records that pass through the filter have entries in the tag. A tag filter is
created when the tag is created and cannot later be changed without
destroying and recreating the tag.
PROGRAM In program ShowData3, when the index file is created, several filters are
ShowData3 present.
USER.BAS
'Structure arrays
Global fieldInfo() As FIELD4INFO
Global tagInfo() As TAG4INFO
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Sub InitField4( )
fieldInfo(1).fname = "F_NAME"
fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
fieldInfo(2).fname = "L_NAME"
fieldInfo(2).ftype = r4str
fieldInfo(2).flength = 10
fieldInfo(2).fdecimals = 0
fieldInfo(3).fname = "ADDRESS"
fieldInfo(3).ftype = r4str
fieldInfo(3).flength = 15
fieldInfo(3).fdecimals = 0
fieldInfo(4).fname = "AGE"
fieldInfo(4).ftype = r4num
fieldInfo(4).flength = 2
fieldInfo(4).fdecimals = 0
fieldInfo(5).fname = "BIRTH_DATE"
fieldInfo(5).ftype = r4date
fieldInfo(5).flength = r4dateLen
fieldInfo(5).fdecimals = 0
fieldInfo(6).fname = "MARRIED"
fieldInfo(6).ftype = r4log
fieldInfo(6).fle ngth = r4logLen
fieldInfo(6).fdecimals = 0
fieldInfo(7).fname = "AMOUNT"
fieldInfo(7).ftype = r4num
fieldInfo(7).flength = 7
fieldInfo(7).fdecimals = 2
fieldInfo(8).fname = "COMMENT"
48 CodeBase
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
Function OpenDataFile%( )
Dim save As Integer
If db = 0 Then
InitField4
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError() Then
OpenDataFile = False
Exit Function
End If
End If
End Function
If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If
rc = d4append( db )
End Sub
Function CreateIndexFile%( )
tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
tagInfo(2).name = "AGE_TAG"
tagInfo(2).expression = "AGE"
tagInfo(2).filter = "AGE > 40"
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).filter = "AMOUNT > 100"
tagInfo (3).descending = r4descending
Indexing 49
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
tagInfo(4).filter = ""
End Function
USER.FRM List1_DblClick( )
If d4recCount( db ) = 0 Then
Call AddNewRecord( "Sarah", "Webber","132-32 St.",32,"19610523", True, 147.99,
"New Customer")
Call AddNewRecord( "John", "Albridge", "1232-76 Ave.", 55, "19381212", False,
98.99, "" )
End If
rc = code4close( cb )
Filter A filter expression is a dBASE expression that returns a Logical result and is
Expressions used as a tag filter. The expression is evaluated for each record as its tag entry
is updated. If the filter expression evaluates to true, an entry for that record is
included in the tag. If it evaluates to false, that record's tag entry is omitted
from the tag.
50 CodeBase
PRODUCTS.DBF COST.MDX
Record COST_TAG
Number PRODUCT PRO_ID COST
4.57
1 Widgit 2001 14.95 Filter Expressions
12.21
2 Giszmo 321T 19.47
“COST < 20.00”
14.95
3 Dludge 1023 4.57
19.47
4 Shimly A323 32.97
Creating A The tag filter is specified, when the index is initially created, through the
Tag Filter TAG4INFO.filter structure member. If this member is a zero length string or
if it is not initialized, then no records are filtered. The following code segment
shows several tags with filters:
Code Fragment tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
from ShowData3 tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
tagInfo(2).name = "AGE_TAG"
tagInfo(2).expression = "AGE"
tagInfo(2).filter = "AGE > 40"
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).filter = "AMOUNT > 100"
tagInfo(3).descending = r4descending
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
The first tag has a filter expression of ".NOT. DELETED()". This filters out
any records that have been marked for deletion. The second tag's filter
expression is "AGE > 40". This excludes any record which has an AGE field
value of less than or equal to 40.
Unique The fourth member of the TAG4INFO structure, unique, determines how
duplicate keys are handled. This member can have four possible values: 0,
Keys
r4uniqueContinue, e4unique and r4unique.
Indexing 51
It is often desirable to ensure that no two index keys are the same. To meet
this requirement, you can specify that a tag must only contain unique keys.
As a result, when a new key is to be added to the tag, a search is first made to
see if that key already exists in the tag. If it does exist, the new entry is not
added. CodeBase provides three methods of handling the addition of non-
unique keys: r4uniqueContinue, e4unique and r4unique.
The descriptions of the values for the TAG4INFO.unique member are as
follows:
• 0 Duplicate keys are allowed.
• r4uniqueContinue Any duplicate keys are discarded. However, the
record continues to be added to the data file. In this case, there may not
be a tag entry for a particular record.
• e4unique An error message is generated if a duplicate key is
encountered.
• r4unique The data file record is not added or changed. Regardless, no
error message is generated. Instead a value of r4unique is returned.
Record
Number PRODUCT
PRO_TAG
1 Foobar
2 Gizmo
Dunsel Tag with
Unique Keys
3 Gizmo
Foobar
4 Dunsel
Gizmo
5 Gizmo
6 Foobar
Although a specific unique code for a tag is specified when the tag
is created, only a true/false flag, which indicates whether a tag is
WARNING unique or non-unique, is actually stored on disk. No information on
how to respond to a duplicate key for unique key tags is saved.
As a result, when a tag is re-opened, the value contained in
CODE4.errDefaultUnique is used to set the tag's unique code. If the
tag was designed to be r4unique or e4unique, then the default value
of r4uniqueContinue must be changed before any records are
modified or appended.
This change can be accomplished in two ways. The first involves
setting the CODE4.errDefaultUnique to another value, in which case
all indexes opened subsequently will have this value for their unique
code. The function that sets this unique code is
code4errDefaultUnique. This setting has no effect on non-unique
tags.
The second method is to set the unique code for one or more tags
individually after the index file has been opened. Pass the
appropriate unique code to the function t4uniqueSet. This function
only has an effect on unique tags.
Seeking One of the most useful features of a database is the ability to find a record by
providing a search key. This process is referred to as seeking. When a seek
is performed, the search key, which is usually a string, is compared against
the index keys in the selected tag. When a match occurs, the corresponding
record is loaded in the record buffer.
PROGRAM The Seeker program demonstrates seeks on the various types of tags. When
Seeker it performs the seeks, it displays the record that was found and the return
value that was returned from the seek.
USER.BAS
'FIELD4 structure pointer
Dim fname As Long, lname As Long, address As Long
Dim age As Long, birthdate As Long, married As Long
Dim amount As Long, comment As Long
'Structure arrays
Global fieldInfo() As FIELD4INFO
Global tagInfo() As TAG4INFO
If marriedVal Then
Call f4assign( married,"T" )
Indexing 53
Else
Call f4assign( married,"F" )
End If
rc = d4append( db )
End Sub
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
InitField4
InitTag4
If cbError() Or db = 0 Then
CreateDataFile = False
Exit Function
End If
End Function
Sub InitField4( )
fieldInfo(1).fname = "F_NAME"
fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
fieldInfo(3).fname = "ADDRESS"
fieldInfo(3).ftype = r4str
fieldInfo(3).flength = 15
fieldInfo(3).fdecimals = 0
fieldInfo(4).fname = "AGE"
fieldInfo(4).ftype = r4num
fieldInfo(4).flength = 2
fieldInfo(4).fdecimals = 0
fieldInfo(5).fname = "BIRTH_DATE"
fieldInfo(5).ftype = r4date
fieldInfo(5).flength = r4dateLen
fieldInfo(5).fdecimals = 0
fieldInfo(6).fname = "MARRIED"
fieldInfo(6).ftype = r4l og
54 CodeBase
fieldInfo(6).flength = r4logLen
fieldInfo(6).fdecimals = 0
fieldInfo(7).fname = "AMOUNT"
fieldInfo(7).ftype = r4num
fieldInfo(7).flength = 7
fieldInfo(7).fdecimals = 2
fieldInfo(8).fname = "COMMENT"
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
Function InitTag4( )
tagInfo(2).name = "AGE_TAG"
tagInfo(2).expression = "AGE"
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).descending = r4descending
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
End Function
USER.FRM List1_DblClick( )
'TAG4 pointers
Indexing 55
If d4recCount( db ) = 0 Then
Call AddNewRecord( "Sarah", "Webber", "132-32 St.", 32, "19610523", True,
147.99, "New Customer" )
Call AddNewRecord( "John", "Albridge", "1232-76 Ave.", 55, "19381212",
False, 98.99, "" )
End If
'Get TAG4 pointers
nameTag = d4tag( db, "NAME_TAG" )
amtTag = d4tag( db, "AMT_TAG" )
birthTag = d4tag( db, "DATE_TAG" )
'The following code finds all the occurrences of John Albridge in the tag
Loop
rc = code4close( cb )
Performing There are six functions in the CodeBase library that perform seeking: d4seek
Seeks and d4seekNext may seek for character data, d4seekDouble and
d4seekNextDouble may seek for numeric data and d4seekN and
d4seekNextN may seek for binary data.
d4seek with d4seek accepts a string as the search key and performs a seek using the
character data default tag, which is the selected tag if one is selected. If there is no selected
tag, the default tag is the first tag of the first opened index. d4seek can
perform seeks using Character, Numeric or Date tags.
Code fragment Call d4tagSelect( db, nameTag )
from Seeker rc = d4seek( db, "Sarah Webber " )
PrintRecordSeek( rc )
56 CodeBase
d4seek on Date When performing seeks using Date tags, d4seek must be passed an eight
tags character string containing a date in the standard "CCYYMMDD" format.
See the "Date Functions" chapter for details on using the date functions.
Code fragment Call d4tagSelect( db, birthTag )
rc = d4seek( db, "19381212" )
from SEEKER PrintRecordSeek( rc )
d4seek on Numeric When d4seek uses a Numeric tag and the search key is a string, the string is
tags converted into double value before searching. As a result, you do not have to
worry about formatting the character parameter to match the key value.
Code fragment Call d4tagSelect( db, amtTag )
from SEEKER rc = d4seek( db, "250.75" )
PrintRecordSeek( rc )
d4seekDouble on d4seekDouble accepts a double value as the search key and this function
Numeric Tags should only be used on numeric keys. All numeric tags, regardless of the
number of decimals, store their key entries as double values. When seeking
on numeric tags, it is more efficient to seek with a double value than a
character representation.
Code fragment Call d4tagSelect( db, amtTag )
rc = d4seekDouble( db, 35.75 )
from SEEKER PrintRecordSeek( rc )
d4seekN on Binary Use the d4seekN function to seek on character tags that contain binary data.
Data This seek function can be used to seek without regard for nulls, since len
specifies the length of the search key.
Code fragment Dim key As String
key = "\0\0000111\010";
Partial When a seek is performed, the tag is positioned to the place in the tag where
Matches the search key should be located. If this position lies between two index keys
(ie. the tag was not exactly found), then a partial match has occurred. The
record whose index key is directly after the "correct" position is loaded into
the record buffer. When this happens, a value of r4after is returned by the
d4seek functions and r4entry or r4after is returned by the d4seekNext
functions. Consider the following three seeks.
Code fragment Call d4tagSelect( db, nameTag )
from SEEKER rc = d4seek( db, "Sarah Webber " )
PrintRecordSeek( rc )
No Match If there are no index keys in the tag or if the search key's position is after the
last index key, then the d4seek and d4seekNext functions return r4eof.
Seeking On Performing seeks on compound index keys is slightly more difficult. Before a
Compound seek can be performed on a tag with a compound index expression, a search
Keys key must be created in a similar manner. For example, if the index
expression for the tag is "F_NAME + L_NAME", an example is as follows:
Call d4tagSelect( db, nameTag) ;
The first part of the search key, "Sarah ", matches the format of field
F_NAME and is padded out with blanks to the length of that field ( 10 ). The
second part, "Webber ", matches field L_NAME, which also has a length of
10. The total size of the string is 20 characters.
The various parts of the search key must be padded out to the full
length of their respective fields. Failure to do so may cause an
WARNING incorrect seek.
A similar strategy is used when seeking on compound index keys built with
fields of different types. If the index expression is "PRODUCT +
STR(COST, 7, 2)" where 'product' is a Character field of length eight and
Cost is a Numeric field, then a valid search key is "Dunsel 1234.21". Since
the STR() function converts Numeric values to Character values, the search
string is constructed with the first eight characters matching the PRODUCT
field and the "1234.21" matching the return value of STR().
An easy way to build compound search strings is to use the Visual Basic
Space$ function to create a space filled string and then use the Mid$ function
to insert the various search components into their proper space within the
string. The search string 'Sarah Webber' in the previous example could have
been built as follows:
Building compound Dim size1 As Integer, size2 As Integer
If Not OpenDataFile() Then Exit Sub
search values
size1 = f4len( d4field( db, "F_NAME" ) )
size2 = f4len( d4field( db, "L_NAME" ) )
You should note the difference between the dBASE "+" and "-"
operators when they are applied to Character values. The "+"
WARNING includes any trailing blanks when the Character values are
concatenated. For example if the index expression is "L_NAME +
F_NAME", the search key should be "Krammer David ". The
"-" removes any trailing blanks from the first string. The second
string is concatenated and the previously removed blanks are
added to the end of the concatenated string. As a result, if the index
expression is "L_NAME - F_NAME", the search key should be
"KrammerDavid ".
60 CodeBase
Group Files In Clipper, the .NTX indexes must be manually opened each and every time
the data file is opened. This causes programming time to be increased, as
well as having a good chance of forgetting to open an index file. dBASE IV
and FoxPro use compound index files (more than one tag in a file) and
production index files (automatically opens when data file opens) to avoid
these problems.
CodeBase has introduced group files in order to compensate for this limitation
of the .NTX file format. A group file allows you to use the same function
calls when using .NTX index files as you would when using .CDX and
.MDX index files.
This is accomplished by emulating production indexes and multiple tags per
index file. The same database that was shown in Figure 3.2 is depicted in
Figure 3.5 using .NTX index files and group files.
Creating A group file is automatically created when using i4create or d4create with
Group Files .NTX index compatibility. In addition, a group file can be created for
existing index files using a text editor. Simply create a file with a file name
extension of ".CGP". Then you enter the names of the index files to be
grouped together. Enter one index file name per line.
GROUP FILE For example, here is the actual contents of group file 'PRODUCTS.CGP'
PRODUCTS.CGP
from Figure 3.5:
PRO_TAG
P_NUM_TAG
Bypassing Because group files are unique to CodeBase, index files generated with
Clipper do not have group files. Under these conditions, it may be more
Group Files
convenient for you to bypass the group files and access the .NTX group files
directly instead of creating your own group files.
Indexing 61
PRODUCTS.DBF
COST.CGP
Record COST_TAG.NTX
Number PRODUCT PRO_ID COST
Disabling The When using .NTX index compatibility, CodeBase automatically tries to open
Auto Open a group file when a data file is opened. If you have tried to open a data file
that does not have a group file, you will encounter the following error:
Error #: -60
Opening File
File Name:
DATA1.CGP
This results from CodeBase being unable to locate a group file. This
behavior can be disabled by setting the CODE4.autoOpen flag to false
(zero).
Creating You can create .NTX files without a corresponding group file by passing a
Index Files zero length string as the file name to i4create. As documented earlier, the tag
names also become file names.
PROGRAM This program demonstrates how .NTX index files can be created without
NoGroup group files.
62 CodeBase
USER.BAS
'Structure arrays
Global field4Info() As FIELD4INFO
Global tagInfo() As TAG4INFO
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Sub InitField4( )
fieldInfo(1).fname = "F_NAME"
fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0
fieldInfo(3).fname = "ADDRESS"
fieldInfo(3).ftype = r4str
fieldInfo(3).flength = 15
fieldInfo(3).fdecimals = 0
fieldInfo(4).fname = "AGE"
fieldInfo(4).ftype = r4num
fieldInfo(4).flength = 2
fieldInfo(4).fdecimals = 0
fieldInfo(5).fname = "BIRTH_DATE"
fieldInfo(5).ftype = r4date
fieldInfo(5).flength = r4dateLen
fieldInfo(5).fdecimals = 0
fieldInfo(6).fname = "MARRIED"
fieldInfo(6).ftype = r4log
field Info(6).flength = r4logLen
fieldInfo(6).fdecimals = 0
fieldInfo(7).fname = "AMOUNT"
fieldInfo(7).ftype = r4num
fieldInfo(7).flength = 7
fieldInfo(7).fdecimals = 2
fieldInfo(8).fname = "COMMENT"
fieldInfo(8).ftype = r4memo
fieldInfo(8).flength = r4memoLen
fieldInfo(8).fdecimals = 0
End Sub
Function InitTag4( )
tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
tagInfo(2).name = "AGE_TAG"
tagInfo(2).expression = "AGE"
tagInfo(3).name = "AMT_TAG"
tagInfo(3).expression = "AMOUNT"
tagInfo(3).descending = r4descending
tagInfo(4).name = "DATE_TAG"
tagInfo(4).expression = "BIRTH_DATE"
End Function
Indexing 63
Function OpenDataFile%( )
If db = 0 Then
InitField4
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError() Then
OpenDataFile = False
Exit Function
End If
End If
End Function
USER.FRM List1_DblClick( )
InitTag4
rc = d4close( db )
rc = code4autoOpen( cb, save1 )
rc = code4safety( cb, save2 )
Opening A single .NTX index file can also be opened without using group files.
Index Files
The following example program contains code segments that are
specific to CodeBase libraries built with.NTX index file compatibility.
WARNING
PROGRAM This program demonstrates how .NTX index files can be opened without
NoGroup2 using a group file.
64 CodeBase
USER.BAS
Function cbError( )
'See NoGroup source listing
End Function
Sub InitTag4( )
'See NoGroup source listing
End Sub
Function OpenDataFile%( )
'See NoGroup source listing
End Function
USER.FRM
InitTag4
rc = d4close( db )
rc = code4autoOpen( cb, save1 )
rc = code4safety( cb, save2 )
The function that provides this functionality is t4open. It opens the specified
file and returns the TAG4 pointer of its single tag.
Code nameTag = t4open( db, "NAME" )
addrTag = t4open( db, "ADDRESS" )
fragment from ageTag = t4open( db, "AGE" )
NoGroup2
Alternatively, i4open may be used to open an .NTX index file. When doing
so, the .NTX file name extension must be provided.
Indexing 65
Reindexing Index files become out of date if they remain closed when their data file is
modified. Alternatively, a computer could be turned off in the middle of an
update. When an index is out of date, the data file may contain records that
do not have corresponding tag entries, or the index file may have tag entries
that specify the wrong record.
To rectify this problem, it is sometimes necessary to reindex the index files.
You could accomplish this in two ways. First you could delete the index file
from your system and then recreate the index, or you could open an existing
index file and call one of the reindex functions.
Reindexing If you suspect one or all of your index files may be out of date, you can use
All Index the d4reindex function. This function reindexes any index files that are
Files associated with that data file and are currently open. This includes both
production and non-production index files.
rc = d4reindex( db );
Reindexing A If you have several index files but only one is out of date, you can reindex just
Single Index that index file by using the i4reindex function. This function reindexes the
index file corresponding to the INDEX4 pointer you pass to it. All tags
associated with the INDEX4 structure are automatically updated.
Dim db As Long, ind As Long
rc = i4reindex( ind )
You can obtain the index's INDEX4 pointer either from the i4open or
i4create functions, or by using d4index with the index file's name.
Advanced The next section contains details on copying the index file structures and
determining the current index key.
Topics
Copying You may occasionally require a new index file that has an identical structure
Index File as an existing index file. This situation usually arises when you are making a
Structures copy of a data file.
As a parallel function to d4fieldInfo, CodeBase provides the function
i4tagInfo, which returns the TAG4INFO array of an existing index file in its
native 'C' format. You can pass this array pointer ByVal to d4createCB or
i4createCB to create a new index file.
The TAG4INFO returned by i4tagInfo is allocated dynamically and
should therefore be deallocated after you are finished using it. The
WARNING u4free function may be called to free up the memory for this
purpose.
PROGRAM Program CopyData2 uses the structure from the data and index files to create
CopyData2 a new data and index file. An Input Box is used to retrieve the name of the file
to be copied.
66 CodeBase
USER.BAS
Function cbError( )
USER.FRM List1_DblClick( )
If cbError( ) Then
rc = code4close( cb )
Exit Sub 'Check for error
End If
rc = code4close( cb )
rc = code4autoOpen( cb, save1 ) 'Reset to initial status
rc = code4safety( cb, save2 )
Queries and Relations 67
Relations In its simplest form, a relation is a connection between two data files that
specifies how the records from the first data file are used to locate one or
more records from the second.
Master & Slave The data file that controls the others is called the master. The controlled data
Data File
files are called slaves. A master can have any number of slaves which in turn
can be masters of other slaves.
Master Expression To create a relation , you usually provide two pieces of information. The first
& Slave Tag
is the master expression, and the second is a tag based on the slave data file
called the slave tag.
The purpose of the master expression is to generate an index key called the
lookup key. The master expression is evaluated for each record in the master
data file and the resulting value is that record's lookup key. A record in the
slave is then found by performing a seek using the lookup key on the slave
tag.
The most common type of master expressions are ones which only contain the
name of a field from the master data file. The slave tag ordering is usually
based on an identical field in the slave data file. Figure 4.1 shows a relation
of this type.
68 CodeBase
In this example, the master expression is "ID" and the slave tag is the
tag STU_ID_TAG. The index expression for the slave tag is
"STU_ID". If, for example, the current record for the master data
file is set to record number 4, the master expression would then
evaluate to "423232". The lookup on the slave data is then
performed, locating record number 3.
Composite Related records from the master and slaves data files are collectively
Records referred to as a composite record. The composite record consists of
the fields from the master along with the fields from the slave data
files. The following is a composite record from the previous relation
example:
Queries and Relations 69
STUDENT.DBF
ENROLL.DBF
654321 Ken Hirshfeld 30
157932 ECON102
123345 Sandra Donaghey 32
234533 CMPT389
873454 Barry Webber 22 ENROLL->C_CODE
423232 MATH114
423232 Harvey Tyler 43
423232 CMPT411
463722 James Miller 34
234533 MATH114
234533 David Krammer 25
125753 CMPT411
534452 Bernie McFarland 22
423232 MATH115
835543 Douglas Samoil 39
423232 Harvey Tyler 43 423232 MATH114
873454 CMPT201
ID F_NAME L_NAME AGE
153543 Ron Watson 22
765343 MATH114
858343 George Dean 43
ENROLL->STU_ID 125753 ECON102
157932 Albert Miller 34
876097 CMPT411
876097 Scott Greig 23
534452 CMPT201
345742 Brian Perron 24 COMPOSITE RECORD
234533 CMPT411
336544 Allan Racine 29
865422 Cameron Calvert 30
125753 Reginald Page 24
874632 Eric Lane 41
765343 Upali Shivji 32
When using fields from the composite record in a dBASE expression, you
must precede any field names from slave data files with the field name
qualifier. The field name qualifier is the name of the data file followed by the
"->" characters. This avoids any potential naming conflicts caused by data
files with field names in common.
For example, " ENROLL->STU_ID = 876987" is a dBASE expression
containing fields from the slave data file. This expression could be used as
part of a query (discussed later in this chapter).
Composite The composite data file is the set of composite records that are produced by
Data Files the relation. Figure 4.3 lists the composite data file for the example relation.
70 CodeBase
STUDENT.DBF
654321 Ken Hirshfeld 30
The composite data file does not physically exist on disk. It is merely a
way of viewing the information in the data files of the relation.
For example, when the relation is applied to record number 3 of the master
data file the following composite record is generated:
Queries and Relations 71
By default, if the relation does not find a match in the slave data file, the
slave's fields in the composite record are left as blanks. For example record
number 1 in the master data file does not have a corresponding slave record,
so the generated composite record is:
Composite Data File Record # 1
Complex Although having a relation between two data files is quite useful, it is often
necessary to set relations between one master and several slaves, or to set
Relations
relations between the slaves and other data files.
Single Master, Relations with one master and several slaves are very similar to the single
Multiple Slave master, single slave relations. In this case there is a master expression and
Relations slave tag for each slave, and the composite record consists of fields from all
the data files. CodeBase permits any number of slaves for a single master.
The only limitations are the resources of your system.
Multi-Layered There are many situations that require a master with a relation to a slave,
Relations which in turn has a relation to other data files. CodeBase supports an
unlimited number of these multi-layered relations and will automatically
perform lookups in any associated slave data files.
The Top Master A master data file is a master only in the context of a specific relation. The
data file can also be slave in a different relation. If the data file is not a
slave in any other relation, it is called a top master.
The Relation Set A relation set consists of a top master and all other connected relations.
There must be exactly one top master in a relation set.
Figure 4.4 describes a new relation between the ENROLL.DBF data file
and the new data file COURSE.DBF. It shows the entire relation set and
points out the various relations between the data files.
72 CodeBase
ENROLL.DBF
STU_ID C_CODE COURSE.DBF
CODE TITLE
423232 CMPT411
CMPT401 Software Engineering
234533 CMPT389
CMPT389 Intro to Databases ...
423232 MATH114
157932 ECON102
C__CODE CODE_TAG CMPT272 Programming Con ...
234533
CMPT411
CMPT389
Master of COURSE
336544 Allan Racine 29
423232 MATH114
865422 Cameron Calvert 30
125753 Reginald Page 24 157932 ECON102
Relation Set
Relation There are three basic types of relations. They are exact match, scan and
approximate match relations. The type of relation determines what record or
Types
records are located during a lookup.
Exact Match An exact match relation permits only a single match between the master and
Relations slave data files. Both one to one and many to one relations are exact match
relations. In a one to one relation, there is only one record in the master that
matches a single record in the slave. In a many to one relation, there are one
or more records in the master that match a single slave record.
If there are multiple slave records that match a single master record, then a
composite record is only generated for the first slave record, as illustrated
below:
In the above example, there are three records in the slave data file that are
matches for record number 4 of the master. Because this is an exact relation a
composite record is made from only the first matching record of the slave data
file.
74 CodeBase
Scan Relations In a scan relation, if there are multiple slave records for a master record,
there is one composite record in the extended data file for each of the
matching slave records. Both one to many and many to many relations are
scan relations. In a one to many relation, each record in the master may
have multiple matching slave records. A many to many relation is the same
as one to many except that different master records may match the same
slave record.
Approximate The final type of relation is the approximate match relation. This is similar to
Match the exact match relation in that it only permits one match for a master record.
Relations The only difference is the way it behaves when an exact match is not found in
the slave data file. If the match fails, the slave record whose index key
appears next in the slave tag is used instead. Approximate match relations are
generally quite rare and are usually used only when a range of records are
represented by a single high value.
Queries and Relations 75
EMP_FILE.DBF BENEFIT.DBF
EMP_NAME YEARS YEARS BENEFIT
ADAMS, J. 6 5 25000
ADAMS, L. 15 10 35000
COOK, P. 2 15 50000
FRANK, B. 3 20 75000
HENKE, D. 20 999 100000
MOORE, A. 25
Creating Relations are created during the execution of your application. Before you
can create a relation, you must have opened all the data files and any index
Relations
files that are used in the relation set.
PROGRAM Program Relate1 sets up a scan type relation between the STUDENT.DBF
Relate1 and ENROLL.DBF data files that were illustrated in Figure 4.1. This
program lists all of the records in composite data file.
USER.BAS
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Function OpenFileRelate1( )
student = d4open( cb, fPath + "STUDENT" )
enrollment = d4open( cb, fPat h + "ENROLL" )
Function SetRelation1( )
master = relate4init( student )
76 CodeBase
If master = 0 Then
SetRelation1 = False
Exit Function
End If
Sub PrintRecRelate1( )
Dim relation As Long, dbf As Long
Static recNo As Long
relation = master
Form1.List2.AddItem ""
recNo = recNo + 1
End Sub
Sub ListRecsRelate1( )
rc = relate4top( master )
Do While rc = r4success
PrintRecRe late1
rc = relate4skip( master, 1 )
Loop
End Sub
USER.FRM List1_DblClick( )
'Relate1 sample code
If Not OpenFileRelate1( ) Then Exit Sub
List2.Visible = True
List2.Clear
ListRecsRelate1
rc = code4unlock( cb )
rc = relate4free( master, 0 )
rc = code4close( cb )
The RELATE4 The RELATE4 structure contains the control information about a single data
Structure file in a relation. Every data file that is in the relation set must have its own
RELATE4 structure.
A data file's RELATE4 structure contains information such as what data file
(if any) is the master of this data file and what type of relation exists between
the data file and its master.
In a single relation set, a data file can only have one RELATE4 structure
associated with it.
Specifying The first step for creating a relation entails specifying which data file will be
The Top the top master of the relation set. This step is accomplished by a call to
Master relate4init, which creates and returns a pointer to the RELATE4 structure
for that data file.
Queries and Relations 77
At this point, you have a complete relation set consisting of one data file --
the top master. Why would you want a relation set with just a single data
file in it? A single data file relation set may be used when a query involves
only the one data file.
Adding Slave Adding slave data files to a relation is accomplished by a call to
Data Files relate4createSlave. To do this, there are four pieces of information that
you must specify.
The master's First, the master data file's RELATE4 structure must be provided. This can
RELATE4 pointer either be the RELATE4 pointer returned from relate4init or from a previous
call to relate4createSlave.
The slave's DATA4 The second piece of information is a pointer to the slave's DATA4 structure.
pointer
A data file should only be used once in a relation set. The result of
using a data file more than once in a relation set is undefined and
WARNING unpredictable.
The master The master expression is the third piece of information needed when adding a
expression
slave. This dBASE expression is evaluated using the current record of the
master data file to produce a lookup key.
A TAG4 pointer The final piece of information is a TAG4 pointer to one of the slave's tags.
from the slave data During a lookup, a search for the lookup key (generated by the master
file
expression) is made using this tag.
After the slave has been added to the relation set relate4createSlave returns
a pointer to the slave's RELATE4 structure. You should save this pointer in
a variable if you plan on changing the default relation type or on adding
slaves to the slave data file.
Code fragment slave = relate4createSlave(master, enrollment, "ID", idTag)
from RELATE1
In the above code fragment, master is the RELATE4 pointer returned from
the call to relate4init and enrollment is the DATA4 pointer of the slave data
file. The master expression is "ID". As a result, the lookup key is the
contents of the ID field from the master data file. The lookups are performed
using a tag from the slave data file. This tag is identified by idTag.
78 CodeBase
Creating CodeBase allows an alternative method of performing lookups that does not
Slaves use tags from the slave data file. Instead of having the master expression
Without Tags evaluate to a lookup key, it evaluates to a record number. This record
number specifies the physical record number of the slave record retrieved
from the slave data file.
To use this method, simply pass null for the TAG4 parameter of
relate4createSlave. This method is mainly useful on static unchanging
slave data files that are never packed. This method has the advantage of
being faster and more efficient than performing seeks on a tag. The following
is a example of this method.
slave = relate4createSlave(master, enrollment, "RECNO()", 0& )
Setting The The default type of relation is an exact match relation. You can change a
relation's type by calling relate4type. This function takes a RELATE4
Relation
pointer and an integer specifying the type of the relation. The valid integer
Type values are specified by the following defined CodeBase constants:
• relate4exact (Default) This specifies an exact match relation.
• relate4approx This specifies an approximate match relation.
• relate4scan This specifies a scan relation.
Since the master data file may have multiple slaves each using a different
type of relation, it is the slave's RELATE4 pointer that is passed to the
relate4type function. In Relate1 the RELATE4 pointer slave is passed to
the relate4type function.
Code fragment rc = relate4type( slave, relate4scan )
from RELATE1
Setting The Sometimes a record in the slave data file cannot be located from a master
Error Action data file record. This occurs when you are using an exact type relation and
there is no match for the lookup key, or when the relation is using direct
record lookup and the generated record number does not exist in the slave
data file. Under either of these circumstances, there are several ways in
which CodeBase can react. relate4errorAction specifies which method is
used and it accepts a pointer to the slave's RELATE4 structure and an
integer that specifies the type error action. The valid error action codes are
as follows:
• relate4blank (Default) This means that when a slave record cannot be
located, it becomes a blank record.
• relate4skipRec This code means that the entire composite record is
skipped as if it did not exist.
• relate4terminate This means that a CodeBase error is generated and
the CodeBase relate function, usually relate4skip, returns an error code.
Moving CodeBase provides three functions for moving through the records in the
Through The composite data file. They are relate4top, relate4bottom and relate4skip.
Composite These functions are used mainly for obtaining composite records when a
query is performed.
Data File
Listing The When used together, these three functions allow you iterate through all of the
Composite composite data file. The following code segment demonstrates this process:
Data File
Code fragment Sub ListRecsRelate1( )
rc = relate4top( master )
from Relate1 Do While rc = r4success
PrintRecRelate1
rc = relate4skip( master, 1 )
Loop
End Sub
Finding relate4top sets the current record of the master data file to the first record in
the first record in
the composite data file. If there are slaves in the masters slave family,
the Composite data
file lookups are automatically performed. If there are no composite records in
the composite data file, r4eof is returned.
Skipping through For each iteration of the Do loop, relate4skip is used to move to the next
records
record in the composite data file. Just as in the d4skip function,
relate4skip's second parameter specifies the number of records to be
skipped. When there are no more records in the composite data file, r4eof is
returned.
Call relate4top or relate4bottom before relate4skip is used.
WARNING
Skipping backwards through the composite data file is also permitted. Since
skipping backwards requires extra internal overhead, you must either call
80 CodeBase
Queries Once a relation has been set up, you can then perform queries upon the
composite data file. A query allows you to retrieve selected records from the
And Sorting
composite data file by specifying a query expression. Essentially the query
specifies a subset of the composite data file, which is called the query set, by
filtering out any composite records that do not meet the search criterion.
In addition to performing queries, you can also specify the order in which the
composite records are presented.
When a query is specified, CodeBase uses its Query Optimization, whenever
possible, to minimize the number of necessary disk accesses. This greatly
improves performance when retrieving composite records from the query set.
PROGRAM Program Relate2 demonstrates how queries can be performed on a single
Relate2 data file.
USER.BAS
Dim j As Integer
Function cbError( )
If code4errorCode( cb, 0 ) < 0 T hen
cbError = True
Else
cbError = False
End If
End Function
Form1.List2.AddItem ""
recNo = recNo + 1
End Sub
Form1.List2.AddItem ""
rc = relate4free(relation, 0 )
End Sub
USER.FRM List1_DblClick( )
'Relation2 sample code
List2.Visible = True
List2.Clear
rc = code4close( cb )
The Query To generate a query, all you need to provide is a dBASE expression, called
Expression the query expression, which evaluates to a Logical value. This expression is
used to determine whether a composite record should be included in the
query. If the query expression evaluates to .TRUE. then the record is kept,
otherwise it is discarded from the query.
Setting the query The query expression is specified for a relation set by the relate4querySet
expression
function. This function takes a pointer to the top master's RELATE4
structure and specifies the query expression for the entire relation set.
The query expression can reference fields from different files in the relation
set. When a query is made, the default data base used is the master. This
means that the field will automatically be associated with the master data
base. To refer fields of different data files use the file's alias followed by an
arrow (->) and the field name.
For example, suppose a relation consists of a master data file called
FATHER.DBF and a slave data file called SON.DBF. Assume that each file
has a field called NAME.
For these data files, the following queries are equivalent:
relate4querySet( relation, "FATHER->NAME = 'Ben Nyland'") ;
relate4querySet( relation, "NAME = 'Ben Nyland'") ;
The Sort The sort expression of a relation is very similar to an index expression.
Expression They are both dBASE expressions, and are both used to determine the sort
ordering.
The difference is that the sort expression determines the sort order of the
query set and you are allowed to use fields from any data file in the relation
set.
Setting the sort The sort expression is specified for a relation set by relate4sortSet. This
expression
function takes a pointer to the top master's RELATE4 structure and specifies
the sort ordering for the entire query set.
The sort expression can contain field names from any data file in the relation
set. It is required that any fields, except those from the top master, are
qualified by the data file's alias.
relate4sortSet( relation, "L_NAME + F_NAME + CLASSES->C_CODE");
Like the query expression, the sort expression can be changed at any time,
although relate4top or relate4bottom must then be called before
relate4skip can be called.
Performing Queries can be performed on relation sets of any size, including those
Queries on consisting of a single data file. The following figures illustrate two queries.
Relation Sets They are on the composite data file shown in Figure 4.3.
Query Set
Query Set
As long as the query and sort expressions are not changed, further calls to
relate4top and relate4bottom do not cause the query set to be regenerated.
Regenerating You can force CodeBase to completely regenerate the query set by calling
the Query Set relate4changed. This forces CodeBase to discard any buffered information
and regenerate the query set based on the current state of the relation set's
data files. After relate4changed is called, relate4top or relate4bottom
must be called before any further calls to relate4skip may be made.
Queries On The steps involved in performing queries on multi-layered relation sets are
Multi-Layered the same as on a relation set containing a single data file. You create your
Relation Sets relations, set the types of the relations, specify query and sort expressions
and retrieve the composite records from the query set.
PROGRAM Program Relate3 creates a multi-layered relation between the STUDENT,
Relate3 ENROLL and CLASSES data files and then performs queries on it.
USER.BAS
'Structure pointers
Dim student As Long, enrollment As Long 'DATA4
Dim classes As Long
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Function OpenFileRelate3( )
84 CodeBase
OpenFileRelate3 = True
End Function
Function SetRelation3( )
ClassRel = relate4init( classes )
enrollRel = relate4createSlave( classRel, enrollment, "CODE", codeTag )
studentRel = relate4createSlave( enrollRel, student, "STU_ID_TAG", idTag )
Sub PrintRecRelate3( )
Form1.List2.AddItem f4str( firstName )
Form1.List2.AddItem f4str( lastName )
Form1.List2.AddItem f4str( studentId )
Form1.List2.AddItem f4str( studentAge )
Form1.List2.AddItem ""
End Sub
Form1.List2.AddItem ""
querySet = querySet + 1
End Sub
USER.FRM List1_DblClick( )
'Relate3 sample code
List2.Clear
List2.Visible = True
Queries and Relations 85
rc = code4unlock( cb )
rc = relate4free ( classRel, 0 )
rc = code4close( cb )
Lookups The method illustrated above for retrieving composite records from the
composite data file is well suited for performing queries and generating
On
reports, but has unnecessary overhead if all you want to do is perform
Relations lookups to slave data files. As a result CodeBase provides an alternative
method for performing lookups that is independent of the query set and sort
orderings.
PROGRAM Program Relate4 sets up an exact match type relation between the
Relate4 STUDENT.DBF and ENROLL.DBF that were illustrated in Figure 4.1.
This program performs some seeks and lookups.
USER.BAS
'Structure pointers
Dim student As Long, enrollment As Long 'DATA4
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Function OpenFileRelate4( )
student = d4open( cb, fPath + "STUDENT" )
enrollment = d4open( cb, fPath + "ENROLL" )
OpenFileRelate4 = True
End Function
Function SetRelation4( )
master = relate4init( student )
slave = relate4createSlave( master, enrollment, "ID", idTag )
Sub PrintRecRelate4( )
Form1.Print f4str( first Name )
Form1.Print f4str( lastName )
Form1.Print f4str( studentId )
Form1.Print f4str( studentAge )
Form1.Print f4str( classCode )
Form1.Print ""
End Sub
USER.FRM List1_DblClick( )
'Relate4 sample Code
' <-----15------><-------15---->
Call SeekRelate4( "Tyler Harvey " )
rc = code4close( cb )
Performing A Performing lookups is quite simple. First you locate the record in the master
Lookup data file that you wish to perform the lookup from, using the normal d4top,
d4bottom, d4go, d4seek, d4seekDouble, d4seekN, d4seekNext,
d4seekNextDouble, d4seekNextN and d4skip functions. The lookup is
then accomplished by a call to relate4doAll. This function is passed a
RELATE4 pointer of a master data file and it performs lookups on the slave
data files of that master. In addition, it travels down the relation set
performing lookups on the slave's slaves.
The following function performs a seek on a data file and then performs the
lookups on it's slaves.
Code fragment Sub SeekRelate4( key As String )
rc = d4seek( student, key )
from Relate4 rc = relate4doAll( master )
End Sub
Iterating As an aid in writing generic reusable code, CodeBase provides the function
relate4next, which allows you to iterate through the relations in a relation
Through
set.
The
This function takes a RELATE4 pointer ( passed by reference ) and changes
Relations it to a pointer to the next RELATE4 structure in the relation set.
Queries and Relations 87
The relate4next function changes the value of the long pointer that
is passed in as a parameter. Consequently, you should make a
WARNING copy of this pointer if you need it later.
The following code segment displays all of the records from all of the
relations in the relation set.
Code Fragment Sub PrintRecRelate1( )
Dim relation As Long, dbf As Long
from Relate1 Static recNo As Long
relation = master
Form1.List2.AddItem ""
recNo = recNo + 1
End Sub
88 CodeBase
Query Optimization 89
5 Query Optimization
What is Query Query Optimization is an application invisible method of returning query
Optimization results at high speed. The CodeBase Relation/Query modules uses Query
Optimization to analyze the query condition and return the matching set of
records with a minimum of disk accesses.
This is accomplished by comparing the query expression to the tag sort
orderings of the top master tag. If part of the query matches the tag
expression, the tag itself is used to filter out records that do not match the
expression.
This results in lightning quick performance, even on the largest data files,
since very few records are actually physically read. For example, if there
were an index built on PRODUCT and the query was:
PRODUCT = 'GIZMO'
The Query Optimization would automatically use the index file to quickly
seek to the appropriate record and, using the PRODUCT tag, and skip to
retrieve all the records where PRODUCT is equal to 'GIZMO'. Once this
is no longer the case, Query Optimization ignores the remaining records.
It makes little difference whether the data file has 500 records or 500,000
records, since the same number of disk reads are performed in each case.
Complex Query Optimization can also work on more complex expressions involving
expressions
the .AND. and .OR. operators. In these cases, Query Optimization breaks
down the whole expression into sub-expressions that can be optimized. For
example, the two sub-expressions in the query:
L_NAME = "SMITH" .AND. F_NAME = "JOE"
could both be optimized if there were two tags, one based on the expression
L_NAME, the other on F_NAME. CodeBase could still partially optimize
the query if there was a tag on either of the two sub-expressions. Even a
partially optimized query can lead to very fast performance.
90 CodeBase
When is Query To start with, the Query Optimization capabilities are built into the
Optimization CodeBase relate/query module, the report module and into CodeReporter.
used Your applications can use the Query Optimization provided a few simple
conditions are met.
Requirements To ensure that the Query Optimization is used to return your queries, the
of Query following steps must be taken:
Optimization
1. Insure that the Master data file's index file(s) are open. In general, the
more tags open on the master data files, the greater the chance that
query optimization can take effect.
2. Specify a query expression that contains in whole or in part, a top
master tag key compared to a constant.
3. The query expression is set using relate4querySet. Query
optimization is only effective when the query expression involves the
top master data file.
The following tables illustrate some of the situations where query
optimization is used automatically, and where it cannot be used. The
information in the tables is based on a data file that has the following fields
and tags.
Query Optimization 91
USER.BAS
Sub InitField4( )
ReDim fieldInfo( 1 to 11 ) As FIELD4INFO
field4Info(1).fname = "L_NAME"
field4Info(1).ftype = r4str
field4Info(1).flength = 10
field4Info(1).fdecimals = 0
field4Info(2).fname = "F_NAME"
field4Info(2).ftype = r4str
field4Info(2).flength = 10
field4Info(2).fdecimals = 0
field4Info(3).fname = "COMPANY"
field4Info(3).ftype = r4str
field4Info(3).flength = 10
field4Info(3).fdecimals = 0
field4Info(4).fname = "AGE"
field4Info(4).ftype = r4num
field4Info(4).flength = 2
field4Info(4).fdecimals = 0
field4Info(5).fname = "DT"
field4Info(5).ftype = r4date
field4Info(5).flength = r4dateLen
field4Info(5).fdecimals = 0
field4Info(6).fname = "AMOUNT"
field4Info(6).ftype = r4num
field4Info(6).flength = 7
field4Info(6).fdecimals = 2
field4Info(7).fname = "ADDRESS"
field4Info(7).ftype = r4str
field4Info(7).flength = 15
field4Info(7).fdecimals = 0
field4Info(8).fname = "COMMENT"
field4Info(8).ftype = r4memo
field4Info(8).flength = r4memoLen
field4Info(8).fdecimals = 0
field4Info(9).fname = "PRODUCT"
field4Info(9).ftype = r4str
field4Info(9).flength = 10
field4Info(9).fdecimals = 0
field4Info(10).fname = "ID"
field4Info(10).ftype = r4str
field4Info(10).flength = 5
field4Info(10).fdecimals = 0
field4Info(11).fname = "PHONE"
field4Info(11).ftype = r4str
field4Info(11).flength = 8
field4Info(11).fdecimals = 0
End Sub
Sub InitTag4()
ReDim tag4Info(1 To 10 ) As TAG4INFO
tagInfo(1).name = "L_NAME_TAG"
tagInfo(1).expression = "L_NAME"
tagInfo(2).name = "COMPANY_TAG"
tagInfo(2).expression = "UPPER(COMPANY)"
tagInfo(3).name = "AGE_TAG"
tagInfo(3).expression = "AGE"
tagInfo(4).name = "DT_TAG"
tagInfo(4).expression = "DT"
tagInfo(5).name = "AMNT_TAG"
tagInfo(5).expression = "AMOUNT"
tagInfo(5).filter = ".NOT.DELETED()"
tagInfo(6).name = "ADDR_TAG"
tagInfo(6).expression = "ADDRESS"
tagInfo(6).filter = "DELETED().AND..NOT.DELETED()"
tagInfo(7).name = "COMMENT_TAG"
92 CodeBase
6 Date Functions
The date functions allow you to convert between a variety of date formats and
to perform date arithmetic. The date functions also allow you to retrieve the
various components of a date, such as the day of the week, in numeric form.
Date The format of a date string is represented by a date picture string. A date
picture is a string containing several special formatting characters and other
Pictures
characters. The special formatting characters are:
• C Century. A 'C' represents the first digit of the century. If two 'C's
appear together, then both digits of the century are represented.
Additional 'C' s are not used as formatting characters.
• Y Year. A 'Y' represents the first digit of the year. If two 'Y's appear
together, they represent both digits of the year. Additional 'Y' s are not
used as formatting characters.
• M Month. One or two 'M's represent the first or both digits of the
month. If there are more than two consecutive 'M's, a character
representation of the month is returned.
• D Day. One or two 'D' s represent the first or both digits of the day of
the month. Additional 'D' s are not used as formatting characters.
• Other Characters Any character which is not mentioned above is
placed in the date string when it is formatted.
For example, if the date August 4 1994 is converted to a date string using the
date picture "MM/DD/CCYY", the resulting date string is "08/04/1994". If
the same date is converted with the date picture "MMMMMMMM DD, YY"
the resulting date string is "August 04, 94".
Date Dates can be stored in many formats. The two used by CodeBase are the
standard format and the Julian day format.
Formats
PROGRAM Program Date uses several of the date functions to calculate the number of
Date days until Christmas and until your next birthday.
USER.BAS
' theDate has already passed for this year, so add a year
If julianDate < julianToday Then
theYear = theYear + 1
theDate = Format$(theYear) + Format$(aMonth, "00") + Format$(aDay, "00")
julianDate = date4long( theDate )
End If
Form1.Print "There are " + Str$( days ) + " days until " + title
Form1.Print "which is a " + dow + " this year"
Form1.Print ""
End Sub
USER.FRM List1_DblClick( )
'Date sample code
Do
birth = InputBox$( request, "Date", "Jan 01" )
If birth = "" Then Exit Sub
Call date4init( standard, birth, "MMM DD\CCYY" )
Loop While Not ValidDate( standard )
Standard Dates are stored in the data file in "CCYYMMDD" format. This is known
Format as the standard format. The f4str function returns a date from a Date field
in this standard format. f4assign performs the opposite operation by storing
a standard format date string to the data file.
If f4assign is passed a date string in anything other than standard
format, indexing and seeking will not be performed correctly.
WARNING
Julian Day Another important date format is the Julian day format, which is stored as
Format long integer. A Julian day is defined as the number of days since Jan. 1,
4713 BC. Having the date accessible in this format lets you perform date
arithmetic. Two dates can be subtracted to find the number of days
separating them, or an integer number of days can be added to a date.
Converting Since dates are only stored in the data file in standard format, conversion
Between functions have been provided for converting between standard and julian day
Formats format, as well as converting between standard and any other format.
The interrelationships between the data file, date formats and date functions
are illustrated by Figure 6.1.
Date Functions 97
Database File
f4str
f4assign
date4format date4long
“CCYYMMDD” Julian Day
Other formats Standard long int
Formats format
date4init date4assign
Allows different Funcions that Julian format is
formats of dates use this format used for date
to be displayed to date4cdow arithmetic.
the user.
date4cmonth
eg.
“MM/DD/YY” date4day
“12/02/92” date4dow
eg.
“MMM DD/YY” date4month
“OCT 12/92” date4today
date4year
As mentioned above, dates are stored and retrieved from the data file using
the functions f4assign and f4str.
Date strings in standard format can be converted to Julian day format by
using the date4long function. The inverse of this function is date4assign,
which converts a Julian date back into a standard date string.
code fragment from julianToday = date4long( todayStandard )
Date
To convert from a date string in any other format to standard format, the
date4init function is used. This time the date picture string is used to
specify what format the original string is in. If any part of the date is
missing, the missing portion is filled in using the date January 1, 1980.
code fragment from Call date4init( standard, birthdate, "MMM DD/CCYY" )
Date
98 CodeBase
Testing For You can test for invalid dates using the date4long function. If the date
string, in standard format, is not a valid date, the date4long function will
Invalid
return -1. If it is passed a zero length string, the function returns zero. The
Dates following example function tests for valid dates:
Code fragment Function ValidDate( dateS As String )
from Date Dim rcl As Long
Other Date There are several other date functions that perform various date related
tasks:
Functions
Days and There are two functions that return the day or month portion of the date
Months as string as a character strings. They are:
Characters date4cdow The day of the week in character form is returned.
date4cmonth The day of the month in character form is returned.
Code fragment dow = date4cdow( date )
from Date
Days, Months There are four functions that return a portion of the date string as integer
and Years as values:
Integers • date4day The day of the month is returned as an integer value from 1
to 31.
• date4dow The day of the week is returned as an integer value from 1 to
7.
• date4month The month of the year is returned as an integer value from
1 to 12.
• date4year. Returns the century/year portion of the date as an integer
value.
code fragment from Call HowLongUntil(date4month(standard), date4day(standard),
"your next birthday")
Date
7 Memory Optimizations
Memory optimization is accomplished by buffering portions of data, index and
memo files in memory. This improves performance because physically
accessing the disk takes longer than accessing memory. In addition, reading
and writing several records at once is considerably quicker than reading and
writing individual records separately.
Memory optimization is more effective in some operating environments than
others. This is because some operating systems already do some memory
optimizations at the operating system level and some hard disks do memory
optimizations at the hardware level.
Regardless, when memory is available, proper use of CodeBase memory
optimizations will always improve performance. This is because CodeBase
memory optimizations are designed specifically to optimize database
operations. In addition, in network applications, they can reduce requests to
the network server.
Using Using memory optimization is quite easy. All you have to do is turn it on.
Memory CodeBase has defaults for determining which files should be optimized and
Optimizations whether the files should be optimized for reading and/or writing. If you
wish, you can override the defaults for any particular file(s).
Specifying Any file that you can open with a CodeBase function can be optimized. This
Files To includes data files, index files and memo files. Each file opened by
Optimize CodeBase has two flags associated with it. The first specifies whether the
file is optimized. When a file is optimized, the second flag specifies whether
the file is optimized for both reading and writing or just reading.
There are two ways of setting a file's optimization flags. You can use the
CODE4 default settings when the file is opened or you can explicitly set
them.
Changing When the file is opened, its optimization flags are set from the
The Default CODE4.optimize and CODE4.optimizeWrite flag settings. Valid settings
Settings for CODE4.optimize are:
• OPT4EXCLUSIVE (Default) Read optimize files when the files are
opened exclusively or when the read-only attribute is set. Otherwise do
not read-optimize the file.
• OPT4OFF Do not read optimize the file.
• OPT4ALL Same as OPT4EXCLUSIVE except that shared files are also
optimized.
Valid settings for CODE4.optimizeWrite are:
• OPT4EXCLUSIVE (Default) Write optimize files when they are opened
exclusively. Otherwise do not write optimize.
• OPT4OFF Do not write optimize.
100 CodeBase
The following code segment opens three data files and changes their
optimization flag settings. When memory optimization is activated, the first
data file is read optimized, the second is read/write optimized, and the third
has no optimization at all.
Sub OptTest( )
Dim cb&, db1&, db2&, db3&, rc%
cb = code4init( )
End Sub
Overriding A file's optimization flags can be changed at any time by calling one of the
The Default following functions: d4optimize or d4optimizeWrite. The d4optimize and
Settings d4optimizeWrite change the optimization flags of a data file and all of its
open index and memo files. Following is the above example converted to use
the d4optimize and d4optimizeWrite functions.
Sub OptTest( )
Dim cb&, db1&, db2&, db3&, rc%
cb = code4init( )
End Sub
Activating When the code4optStart function is called, the files whose optimization
The flags are set are memory optimized. Memory optimization is disabled by
Optimizations code4optSuspend.
rc = code4optStart( cb ) ;
. . .
rc = code4optSuspend( cb ) ;
Memory Optimizations 101
Refreshing You can also force a refresh of the optimization buffers. The d4refresh
The Buffers causes any buffered portion of the data, index or memo file to be discarded
and reread into the buffer from disk. Finally, the d4refreshRecord rereads
the current record from the disk.
Memory When using memory optimization, you can limit the amount of memory
Requirements which CodeBase uses for this purpose by setting code4memStartMax. The
natural question is what is an appropriate maximum? In general, the best
maximum is the amount of memory which is likely to be available. For more
information please refer to the "CodeBase Members and Functions" chapter
of the Reference Guide.
Since most applications run under a number of hardware environments where
varying amounts of memory are available, the application should assume that
all extra available memory was used by CodeBase memory optimization.
Therefore, the application should allocate its own memory before calling
code4optStart, or after calling code4optSuspend. If you must allocate
memory when the optimization is active, use u4allocFree. If this function
fails to allocate memory, it will free memory from the CodeBase memory
buffers and try again. These techniques make CodeBase memory
optimization a lower priority.
If you expect very little memory to be available for CodeBase memory
optimization, you should probably just not use it.
When To Use Using memory optimization is not especially difficult. The most difficult
Memory part about memory optimization is knowing when to use it.
Optimization
Single User The single user case is the most straight forward. Essentially, a single user
application can safely memory read and write optimize all files. If an
application explicitly flushes to disk by calling CodeBase flushing functions,
write optimizations are ineffective. In all other single user cases write
optimization is useful for improving performance.
When writing single user applications with memory optimization, it is a good
idea to set CODE4.accessMode to OPEN4DENY_RW. This way
CodeBase performs memory optimizations by default.
Multi-User Whether or not to use memory optimization in a multi-user or multi-tasking
environment is a more difficult decision. This is because memory
optimization interferes with the multi-user sharing of information. On the
other hand, the speed improvements resulting from the use of memory
optimization can be even more dramatic because accessing a network server
can be slower than accessing a local drive. In addition, memory optimization
causes the server to be used less which can improve response time for other
users.
102 CodeBase
CodeBase only allows write optimization when the entire data file is
Note locked or when it is opened exclusively. This restriction is
necessary in order to guarantee that index and memo files are not
corrupt after they have been flushed.
8 Multi-User Applications
A multi-user application can take many forms. For example, several people
could be entering data, over a local area network, into the same data file at the
same time. Another possibility is one person entering data while several
others look at the data file.
When using CodeBase, you have many options in how you design the multi-
user aspects of your application. For example, you can lock data areas before
you read them or you can read them without locking. You can choose memory
optimizations to improve performance or you can write/read directly to/from
disk in order to ensure information is current. The exact options you choose
depend on the requirements of the application and hardware resources
available.
Recommended The following CODE4 flag settings are often appropriate when you are
CODE4 Flag writing multi-user applications. Unless otherwise specified, the discussions
Settings in the following sections assume that these settings are being used. For
details on the effects of changing these settings, please refer to last sections
of this chapter.
• CODE4.accessMode = OPEN4DENY_NONE (default) Files are
opened in non-exclusive mode. This means that other applications can
share the files with your application and have read and write access.
• CODE4.readOnly = 0 (default) Opens files in read/write mode. This
allows you to read and write records to and from the data file.
• CODE4.readLock = 0 (default) There is no automatic record locking
when a record is read.
• CODE4.lockAttempts = WAIT4EVER (default) If a lock fails,
CodeBase will keep retrying the lock until it succeeds.
• CODE4.lockEnforce = 1 An error is generated if an attempt is made to
modify an unlocked record with a field function or d4blank,
d4changed, d4delete or d4recall. This member variable must be set
explicitly set to true (non-zero), since the default setting is false (zero).
Automatic Since locking and unlocking are time consuming operations (in the same
Record order of magnitude as a write to disk), CodeBase functions that write a
Locking record to disk lock that record without unlocking it afterwards. This
prevents redundant unlocking calls and allows you the option of leaving the
record locked.
The only functions that modify records and perform automatic locking are:
d4append, d4appendBlank, d4flush, d4flushRecord and d4write.
Normally, you do not want to leave the record locked after these operations.
To unlock the record, a call can be made to d4unlock:
rc = d4append( db ) ;
rc = d4unlock( db ) ;
Automatic In addition to functions that automatically lock a data file record, there are
Data File CodeBase functions that automatically lock the entire data file. These are
Locking d4memoCompress, d4pack, d4reindex, d4zap and i4reindex. It is
strongly recommended that not only that the data file be locked, but that the
file be opened exclusively before performing these operations. Please refer
to the section on CODE4.accessMode in the CodeBase Reference Guide.
These functions leave the data file locked after they finish executing. As a
result, if the data file was opened non-exclusively, these functions should be
immediately followed by a call to d4unlock.
106 CodeBase
Automatic As a rule, CodeBase functions that move from an old record to a new record
Unlocking that requires a lock, automatically remove any locks on the data file
Of Records according to code4unlockAuto. These functions are d4bottom, d4go,
d4goEof, d4positionSet, d4seek, d4seekDouble, d4seekN,
d4seekNext, d4seekNextDouble, d4seekNextN, d4skip, and d4top.
Refer to code4unlockauto in the Reference Guide for more information on
how the automatic unlocking works.
The only exception to this rule is when the new record is already locked. In
that case, no unlocking is performed.
Common If you follow the advice about calling d4unlock after calling functions that
automatically lock records and data files and are aware of functions that
Multi-User
perform automatic unlocking, you should have little difficulty when writing
Tasks multi-user applications.
To help you write multi-user applications, examples of common tasks
performed by multi-user applications are provided below.
PROGRAM Application Multi, which is perhaps simplistic, illustrates some basic
Multi operations which are present in almost any multi-user application: adding a
record, modifying a record, finding a record, and reporting. It handles its
multi-user aspects in a manner which is appropriate for many applications.
To run this application on a network, copy the files NAMES.* from the
samples directory to a network drive and modify the 'fPath' variable found in
the Form_Load event of USER.FRM to point to the network path. A multi-
user scenerio can be simulated by running two instances of the application,
in which case SHARE.EXE must be loaded first.
Multi assumes that a data file "NAMES.DBF" is present along with a
production index file containing a tag named "NAME". Data file
"NAMES.DBF" also contains a field named "NAME".
USER.BAS
rc = d4appendStart( dbf, 0 )
Call f4assign( field, buf )
rc = d4append( dbf )
rc = d4unlock( dbf )
End Sub
If rc = r4success Then
MsgBox "Record Found"
Else
MsgBox "Not Found. Return code = " + S tr$( rc )
End If
End Sub
If rc = r4locked Then
MsgBox "Record Locked. Unable to edit"
Else
buf = InputBox("Enter Replacement Record", "ModifyRecord" )
Call f4assign( field, buf )
rc = d4flus h( dbf )
rc = d4unlock( dbf )
End If
Form1.List2.Clear
rc = code4optStart( cb )
rc = d4optimize( db, OPT4ALL )
rc = d4top( db )
Do While rc = r4success
Form1.List2.AddItem "Rec. #: " + Str$( d4recNo( db ) )
Form1.List2.AddItem f4str( field )
Form1.List2.AddItem ""
rc = d4skip( dbf, 1 )
Loop
End Sub
rc = d4top( db )
Do
Cls
rc = code4errorCode( c b, 0 )
Print "Record #: "; d4recNo( db ); Tab( 25 ); "Name: "; f4str( fName )
108 CodeBase
Print
choice$ = InputBox( opt$, "Multi" )
Opening Files It is especially important to check for any file open errors in multi-user
applications because there is a chance that the file might be opened
exclusively by another application.
Code fragment db = d4open( cb, fPath + "NAMES" )
If code4errorCode( cb, 0 ) < 0 Then Exit Sub
from Multi
Control Most data file editing software has some kind of loop where the end user can
Loops enter various editing and possibly reporting commands. The Multi
application is no exception. It allows you to 'add', 'find', 'list', 'modify' or
'exit'.
The start of the loop sets the CodeBase error code to zero. An error can
occur if the user tries to modify a record before any have been added.
rc = d4top( db )
Do
Cls
rc = code4errorCode( cb, 0 )
Print "Record #: "; d4recNo( db ); Tab( 25 ); "Name: "; f4str( fName )
Print
choice$ = InputBox( opt$, "Multi" )
Adding In a single user situation, new records may simply be appended with a call to
Records d4append. The multi-user situation is exactly the same, except that after
appending the record d4unlock should be called (as in the addRecord
function above). This call is necessary because the newly appended record
remains locked after d4append completes.
Sub AddRecord( dbf As Long, field As Long )
Dim buf As String
rc = d4appendStart( dbf, 0 )
Call f4assign( field, buf )
rc = d4a ppend( dbf )
rc = d4unlock( dbf )
End Sub
If rc = r4success Then
MsgBox "Record Found"
Else
MsgBox "Not Found. Return code = " + Str$( rc )
End If
End Sub
Modifying The field functions are used to assign values to the fields, and when
Records appropriate, the changes are flushed to disk. Explicit flushing can be
accomplished by calling d4flush.
After flushing a modified record in a multi-user situation, an application
should call d4unlock. When d4flush is called, the changed record gets
written to disk. In order to accomplish this, d4flush locks the record and
leaves it locked. Consequently, the call to d4unlock is suggested once the
modified record is no longer required, in order to give other users an
opportunity to modify the record.
The call to d4flush is not strictly necessary. This is because d4unlock is
smart enough to recognize when the record buffer has changed. In this case,
d4unlock will lock the record, flush the record, and then unlock the record.
Code fragment Sub ModifyRecord( dbf As Long, field As Long )
Dim buf As String
from Multi Dim oldLockAttempts As Integer
If rc = r4locked Then
MsgBox "Record Locked. Unable to edit"
Else
buf = InputBox("Enter Replacement Record", "ModifyRecord" )
Call f4assign( field, buf )
rc = d4flush( dbf )
rc = d4unlock( dbf )
End If
Locking the In the MULTI.C example, the record must be explicitly locked before it is
Record before
modified by a field funcition, since the CODE4.lockEnforce member is set
modifying
to true (non-zero). This serves to ensure that only one application can edit a
record at a time. The example sets the CODE4.lockAttempts to (int) 1, to
cause d4lock to immediately return r4locked if the record is already locked
by another user, in which case the modifyRecord function is aborted.
Adhering to this locking procedure will prevent the following undesirable
scenerio from occurring.
Consider the case where two users of the application decide to modify the
same record at the same time. When this happens, one application may write
out its changes to disk before the second. The second application then writes
to the disk, overwriting the first application's changes. Neither application
knows that this has happened. User two makes changes based on an out of
date version of the record, and user one loses all changes.
Multi-User This next section deals with using the memory optimization functions in a
Optimizations multi-user application. The optimizations can be used for both appending
large quantities of records and for efficiently generating lists of records.
Listing Function listData from the program Multi is an example of reporting.
Records Notice that memory optimization is turned on at the start of the routine and
turned off at the end of the routine. This speeds up the report. In addition,
when a network is being used it minimizes requests to the server.
In a multi-user application, the use of memory optimization is recommended
when reporting but is strongly discouraged when editing. Refer to the
"Memory Optimizations" chapter for additional information.
Notice that there are no unlocking function calls. As explained under
"Finding Records" section (above), when a record is read using the data file
functions no automatic locking takes place. Consequently there is no need
for any unlocking.
Code fragment Sub ListData( dbf As Long, field As Long )
Dim file As String, oldOpt As Long
from Multi
file = d4alias( dbf )
Form1.List2.Clear
rc = code4optStart( cb )
rc = d4optimize( db, OPT4ALL )
rc = d4top( db )
Do While rc = r4success
Form1.List2.AddItem "Rec. #: " + Str$( d4recNo( db ) )
Form1.List2.AddItem f4str( field )
Form1.List2.AddItem ""
rc = d4skip( dbf, 1 )
Loop
End Sub
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
USER.FRM List1_DblClick( )
'Append sample code
rc = code4optStart( cb )
rc = code4lockAttempts( cb, 1 )
rc1 = d4lockFile( dbTo )
rc2 = d4lockFile( dbFrom )
rc = d4top( dbFrom )
Do While rc = r4success
rc = d4appendStart( dbTo, 0 )
Call f4assignField( infoTo, infoFrom )
rc = d4append( dbTo )
rc = d4skip( dbFrom, 1 )
Loop
rc = d4unlock( dbTo )
112 CodeBase
rc = d4unlock( dbFrom )
rc = code4close( cb )
rc = code4optStart( cb )
Note that code4optStart is called after the files are opened. This is because
a call to code4optStart may use up the available memory. In this case, the
calls to d4open could be slowed down as optimization was repeatedly
suspended and re-invoked inside d4open. Internally, d4open calls
u4allocFree when it allocates memory. When memory is not available,
u4allocFree temporarily suspends memory optimization in an attempt to
allocate the requested memory.
This example illustrates the only situation in which write optimization should
be considered in a multi-user application. When writing to a file with write
optimizations enabled, the file should be locked.
With the file locked and write optimization enabled, the repeated appending
goes considerably faster. The trade off is that if any other application
attempts to read the records being appended, the other application could, at
worst, generate CodeBase errors or could read garbage information. To
avoid this, you can open the files exclusively or do not use write
optimization.
In this specific case, the read optimization is a very good idea. This is
because there is no chance that information being returned could be out of
date because the file is locked. The speed improvements in this example, as
a result of the read optimization, are considerable.
This application skips sequentially through data file "FROM_DBF" using
memory optimization. Consequently, it is important to use function d4skip
rather than d4go because function d4skip detects the sequential reading and
does special performance optimizations.
Since the data files are locked going into the loop, they stay locked
throughout the entire loop with no automatic unlocking occurring.
Multi-User Applications 113
Once the records have been appended, the files are unlocked and closed.
When data file "TO_DBF.DBF" is unlocked its changes are automatically
flushed to disk.
Code fragment rc = d4unlock( dbTo )
rc = d4unlock( dbFrom )
from Append
Print "Record count TO_DBF: "; d4recCount( dbTo )
rc = code4close( cb )
The calls to d4unlock are not strictly necessary in this example because
code4close does flushing and unlocking automatically. Alternatively,
code4unlock could have been called to unlock both files at once.
Avoiding When locking multiple data files at once, you need to be careful to avoid
deadlock. Deadlock happens when one application waits for a second
Deadlock
application to unlock something and the second application waits for the first
application to unlock something else. Since they are both waiting for each
other, they both wait forever. This program avoids deadlock by changing
CODE4.lockAttempts from the default of unlimited retries to one try. If the
lock attempt fails, the program exits.
Code fragment rc = code4lockAttempts( cb, 1 )
rc1 = d4lockFile( dbTo )
from Append rc2 = d4lockFile( dbFrom )
Group Locks Sometimes it is desirable to lock many items as a group. In this case, either
all items are locked or no items are locked. This functionality will help
minimize the chance of deadlock. CodeBase supplies functions that will put
an item on a queue so that the whole queue can be locked as a group. The
functions d4lockAdd, d4lockAddAppend, d4lockAddFile, d4lockAddAll
and relate4lockAdd put their respective items on a queue to be locked.
These functions do NOT lock any items. The items are locked by a call to
code4lock. If any item in the queue fails to be locked, the successful locks
are removed, code4lock returns r4locked and the queue remains intact for a
future lock attempt by a subsequent call to code4lock. When code4lock
succeeds, all the locks are in place and the queue is emptied. Refer to
code4lock in the Reference Guide for more details.
Exclusive The simplest type of multi-user applications are those that open all of their
files in exclusive access mode. If any other application tries to open a file
Access
which has been opened exclusively by another user, the application gets a file
open error.
The big disadvantage of this method is that no other applications can use
files that you are using. This is not a recommended method of creating
multi-user applications.
The main use of opening files exclusively is when you are performing
packing, zapping, indexing or reindexing. This prevents other applications
from generating errors or garbage output if they use the file while one of
these activities is occurring.
Opening Files Whether a file is opened exclusively is determined by the current status of
Exclusively the CODE4.accessMode flag. This flag specifies what access OTHER
users have to the current file. CODE4.accessMode has three possible
values:
• OPEN4DENY_NONE Open the database files in shared mode. Other
users have read and write access. This is the default value.
• OPEN4DENY_WRITE Open the database files in shared mode. The
application may read and write to the files, but other users may only
open the file in read only mode and may not change the files.
• OPEN4DENY_RW Open the database files exclusively. Other users
may not open the files.
This flag can be changed any time after the CODE4 structure has been
initialized with code4init. Changes to the flag only affect those files that are
opened thereafter. Therefore if the access mode must be altered for an open
file, the file must be closed and then reopened.
rc = code4accessMode( cb, OPEN4DENY_RW )
db = d4open( cb, "DATAFILE" ) 'Open exclusively
rc = d4close( db )
If a file cannot be opened exclusively due to the fact that some other
application already has it open, the file open fails. When this happens, and
the CODE4.errOpen flag is set to true , an error message is generated.
Read Only If your application reads information from a file and never modifies it, you
can open the file in read only mode. This mode must be used when you only
Mode
have read access to a file. This is a common situation on a network where a
database is shared among many users, but only a few users have the
authority to make changes to it.
Opening Files Whether a file is opened in read only mode is determined by the current
In Read Only status of the CODE4.readOnly flag. If the flag is set to its default value of
Mode false (zero), files are opened in read / write mode. If this flag is set to true
(non zero), files are opened in read only mode.
Code fragment rc = code4readOnly( cb, 0 )
from Multi
Lock When a lock is performed on a file, the possibility exists that some other
application has already locked that file or a portion of the file. When this
Attempts
occurs, what CodeBase does next is determined by the status of the
CODE4.lockAttempts flag.
The default value of CODE4.lockAttempts is WAIT4EVER. This indicates
that CodeBase will keep trying to establish a lock until it succeeds. The
advantage of this method is that it simplifies programming because the
application can assume that all lock attempts succeed. Unfortunately, this
can increase the chances of deadlock bugs being present in an application. In
addition, if applications attempt locking for long periods of time, users can
be left waiting.
Code fragment rc = code4lockAttempts( cb, 1 ) 'Try lock once
from Multi
9 Performance Tips
Programmers are always looking for ways to improve the performance of their
applications. This chapter provides the programmer with tips on how to
achieve the highest performance when building applications with CodeBase.
Memory The prudent use memory optimization can enhance the performance of an
Optimization application. For details on when to use memory optimization refer the
"Memory Optimizations" chapter of this guide. Write optimization is most
useful when making multiple updates on files, such as appending many
records at once. In general, when memory optimization is used, d4skip is
faster than d4go.
Memory The CODE4 member variable CODE4.memStartMax specifies the
Requirements
maximum amount of memory allocated for memory optimization.
Theoretically, the more memory that CodeBase can use for memory
optimization the greater the improvement in performance. In practise, if
CODE4.memStartMax is too large, CodeBase may not be able to allocate
all of the requested memory causing the memory optimization to work less
efficiently. In some operating systems all requested memory is allocated; but
as virtual memory, which is counter-productive to memory optimization. On
the other hand, if CODE4.memStartMax too small, the potential benefits of
memory optimization will not warrant the overhead.
Observing the The improvement in performance can be observed by using code4optAll.
performance
code4optAll ensures that memory optimization is fully implemented by
improvement
locking and fully read and write optimizing all opened data, index and memo
files. A comparison of the performance can then be made between the
application that employs memory optimization with one that does not.
Functions that can Memory optimization acts by buffering portions of files in memory and thus
reduce
decreases the number of disk accesses and improves performance.
performance
Therefore, frequent calls to CodeBase functions that read data from disk will
negate the effects of read optimization. For this reason, functions such as
d4refresh and d4refreshRecord should not be called repeatedly while
memory optimization is invoked. Frequent flushing also reduces the benefits
of write optimization.
Locking Locking can take as long a disk access, so repeated record locks are
inefficient. It is more efficient to lock an entire file when multiple updates are
necessary. The best way to lock a file that requires many modifications is to
use d4lockAddAll with code4lock, or call d4lockAll.
A CODE4 setting that can influence performance is CODE4.readLock.
When CODE4.readLock is set to true (non-zero), each record is locked
before it is read, which can reduce performance. It is useful to set
CODE4.readLock to true when the records are to be modified.
118 CodeBase
Appending Whenever possible, it is more efficient to append many records at once rather
than one at a time. When appending many records, it is sometimes faster to
close the index files first, append the records and then reindex. The files
must be locked before the records may be appended and the most efficient
method is to call d4lockAddAll and code4lock, or d4lockAll, as discussed
above. Batch appending can also take advantage of write optimization,
which will improve performance.
Time Certain CodeBase functions are inherently time consuming and this aspect
Consuming should be considered when incorporating them into an application. Avoid
Functions calling time consuming functions repeatedly, since this can reduce
performance. Some functions are meant to be used for debugging purposes
and should not be used in the final user application.
The following functions reindex index files, which is a time consuming
procedure, so they should be used with care. d4reindex and i4reindex can
be called to explicitly reindex files. Both d4pack and d4zap automatically
reindex the associated index files when they are called.
d4pack and d4zap are time consuming functions even when there are no
tags to reindex. These functions physically remove records from a data file
and then reorganize the remaining records, which is a time consuming
process.
d4check is a function that determines whether an index file has been
corrupted, which is useful for debugging. This function can take as long as
reindexing and therefore application performance can be hindered.
Queries and CodeBase uses Query Optimization to greatly increase the performance of
Relations queries in relation sets. To ensure that the Query Optimization can be used
by CodeBase, the query expression must have a corresponding tag
expression. See the "Query Optimization" chapter in this guide for more
details on how to ensure that the query optimization will be invoked.
Another important issue to consider when manipulating relations is how to
specify the sort order of the query set. The most efficient manner will depend
on the size of the database. There are three ways in which a sorted order
may be specified for the query set.
1. The sorted order can be determined by the selected tag in the master data
file.
2. If there is no selected tag for the master data file, then the natural order
of query set is used.
3. The function relate4sortSet can be used to specify the sorted order.
Performance Tips 119
If there is a tag that specifies the same sort order as the relate4sortSet
expression, then use the tag to specify the sort order when the query set is
almost the same size as the data file. If the query set is almost the same size
as the data file, then relate4sortSet is less efficient when compared with the
tag sorted order or the natural order. Use relate4sortSet when the query set
is small compared to the size of the data file. This will result in better
performance when compared with the selected tag ordering.
If there is no tag that specifies a desired sort order, then use relate4sortSet
to sort the query set. Using relate4sortSet will be faster than creating a
new tag to specify the sort order, regardless of the size of the query set.
General Tips The following section discusses miscellaneous issues that may influence the
performance of an application.
• Be sure to use the field functions to manipulate fields for the best results.
Avoid using the expression module to perform calculations on fields,
otherwise the application will execute at a slower rate.
• It is recommended that all necessary tags be constructed when the index
file is created with either d4create or i4create. This method is more
efficient than adding new tags by calling i4tagAdd later in the
application.
• File access is quicker when the file is opened exclusively, since no future
record or file locks will be necessary.
• The CODE4 member variables that determine how many memory blocks
are allocated should be defined at the beginning of the program and
should not be changed during the program. This allows CodeBase to
share memory pools efficiently. Set the following CODE4 settings at the
beginning of the application.
CODE4.memStartData
CODE4.memStartIndex
CODE4.memStartBlock
CODE4.memStartLock
CODE4.memStartMax
CODE4.memStartTag
120 CodeBase
Transaction Processing 121
10 Transaction Processing
At times it is necessary to have a group of actions executed as a unit. In this
case, either all actions are completed or none of the actions are completed.
To illustrate this, consider the everyday example of transferring funds from
one bank account to another. There are two steps to this process: first one
account has the money debited and then the second account has the sum
credited. A failure could occur after the debit but before the credit, in which
case the money would be removed from the first account but the second
account remains unchanged, resulting in lost funds. In this type of situation,
one would like an all or nothing response, where either both the debit and
credit are completed or nothing happens and the accounts remain unchanged.
Transactions A transaction is one way of treating a group of actions as a whole. The scope
of a transaction is delimited by a specified "start" and "end" between which
the actions are treated as a unit. A transaction has the all or nothing
property, so that either all the actions within the scope of a transaction are
completed or none are completed. When a transaction has successfully
completed all the operations within its scope, it is "committed". A
transaction can be aborted and all the changes that were made up until the
failure can be reversed. This constitutes a "rollback". After the transaction
has terminated, the changes are permanent and can not be reversed.
PROGRAM The following program shows how a simple bank transfer can be
Transfer accomplished using a CodeBase transaction.
USER.BAS
Option Explicit
Global cb As Long
Global db As Long
Global rc As Integer
Dim acctNo As Long, balance As Long
Dim acctTag As Long, balTag As Long
Sub Main( )
cb = code4init( )
If cb = 0 Then
MsgBox "code4init() failed"
Exit Sub
Else
Form1.Show( 1 )
End If
rc = code4initUndo( cb )
End Sub
Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function
Function OpenDataFileTransfer( )
rc = code4tranStart( cb )
End Sub
Sub PrintRecords( )
rc = d4top( db )
While rc = r4success
Form1.Print "----------- -------------------------------"
Form1.Print "Account Number: " + Str$(f4long( acctNo ))
Form1.Print "Balance : " + Str$(f4double( balance ))
rc = d4skip( db, 1 )
Loop
Form1.Print "
End Sub
Transaction Processing 123
USER.FRM List1_DblClick( )
'Transfer sample code
rc = code4close( cb )
In the Transfer program, a transfer takes place when there is a debit from
one account followed by a credit to another, as shown by the following
function calls.
Code fragment for rc1 = Debit( fromAcct, amt )
rc2 = Credit( toAcct, amt )
Transfer
These two operations must both be successful in order for the transfer to
succeed. Therefore, these two actions must be contained within a transaction
where either both the debit and the credit succeed or neither will be
completed and the database will not be changed. In CodeBase, a transaction
is delimited by the functions code4tranStart and code4tranCommit, which
initiate and terminate the transaction respectively.
rc = code4tranStart( cb )
rc = code4tranCommit( cb )
Events may occur that cause failure during a transaction. For example, if
there is a power failure or the hardware crashes during a transaction, one
would want to be able to reverse any changes that had occurred before the
failure. Programmers must anticipate and test for possible failure points
within a transaction. For instance, the example program Transfer checks to
see if both specified accounts exist in the database and a failure occurs if
either account did not exist. When the program encounters a failure of this
sort, it calls code4tranRollback to abort the transaction and restore the
database to its original state.
If the transfer succeeds, the transaction is completed by code4tranCommit,
which commits the changes to the database. After the transaction has been
committed, code4tranRollback can NOT be used restore the database to
the state that existed before the transaction was started. An error is
generated if code4tranRollback is called after code4tranCommit.
Code fragment If rc1 = r4success And rc2 = r4success
rc = code4tranCommit( cb )
from Transfer Else
rc = code4tranRollback( cb )
End If
124 CodeBase
Logging in All modifications that are made while an application is running, which are
outside the scope of a transaction, can be automatically recorded in the log
the Stand-
file. Automatic logging is useful when a back up copy of all changes is
Alone Case required. There are times when logging is not necessary; for example, when
copying data files. In the stand-alone configuration, the logging can be
turned off to save time and disk space. The following discussion applies
only to logging in the stand-alone configuration.
code4log The CODE4 member variable CODE4.log is used to specify whether
changes to the data files are automatically recorded in a log file. This setting
can be changed by using the function code4log. Changing this setting only
affects the logging status of the files that are opened subsequently. The files
that were opened before the setting was changed retain the logging status that
was set when the file was opened. CODE4.log has three possible values:
• LOG4ALWAYS The automatic logging takes place for all the data
files for as long as the application is running. The logging can NOT be
turned off for the current data file by calling d4log.
• LOG4ON The automatic logging takes place for all the data
files for as long as the application is running. In this case, d4log can be
used to turn the logging off and on for the current data file. CodeBase
uses this value as the default.
• LOG4TRANS Only the changes made during a transaction are
automatically recorded in the log file. Logging may be turned on and off
for the current data file by calling d4log.
d4log When a file is opened with CODE4.log set to either LOG4ON or
LOG4TRANS, d4log can be used to turn the logging off and on for the
current data file. d4log only has an effect on logging while the data file is
open and it has no effect on the logging that is done during a transaction.
Pass false (zero) to d4log, to turn the logging off and pass it true (non-zero)
to turn the logging back on. d4log also returns the previous logging setting.
If -1 is passed to d4log the current logging status is returned. The possible
return values are as follows:
• r4logOn This return value means that logging is turned on for the
data file.
Transaction Processing 125
• r4logOff This return value means that either the logging is turned off
for the data file or that logging in general is not enabled.
• <0 An error has occurred.
turning logging off 'Logging of changes takes place
'for all data files opened subsequently
Log files In the stand-alone configuration, a log file must exist or be explicitly created
before any automatic logging or transaction logging can take place. If a log
file does not exist, code4tranStart will generate an error.
The log file may be manually opened or created by using code4logOpen
and code4logCreate respectively. code4logOpen and code4logCreate
both take the CODE4 pointer, the name of the log file and user identification
as parameters. If a zero length string is passed as the file name, then the
default file name "C4.LOG" is used. code4logOpen and code4logCreate
must be called before d4open or d4create.
Generally, one would like to open a log file if it exists, otherwise create a
new log file. This concept is the same as discussed in the "Opening or
Creating" section of the "Database Access" chapter in this guide.
Code fragment Function OpenLogFile( )
from Transfer rc = code4errOpen( cb, 0 )
rc = code4safety( cb, 0 )
If rc = r4noOpen Then
rc = code4logCreate( cb, "", "user1" )
End If
If Not cbError( ) Then OpenLogFile = True
End Function
Locking Every time a data file is being modified one must consider which locking
procedure is appropriate. Locking is necessary step in preserving the
integrity of the database. CodeBase has both automatic and explicit locking
features, as discussed in the "Multi-User Applications" chapter in this guide.
Automatic locking While a transaction is in progress, any automatic locking that is required
during a
during a transaction is performed, but CodeBase acts as though the
transaction
code4unlockAuto is set to LOCK4OFF, so no automatic unlocking will
occur.
Automatic locking and unlocking can occur when a transaction is committed
or rolled back. In both cases, record buffer flushing may be required. The
locking and unlocking procedure follows that of d4flush. If a new lock is
needed, everything is unlocked according to code4unlockAuto and then the
lock is placed. Otherwise, if no new locks are required then nothing is
unlocked. It may be necessary to explicitly unlock files after the transaction
is committed or rolled back depending on the code4unlockAuto setting.
Automatic locking Whether using the automatic or explicit locking one must decide on a value
vs. Explicit locking
for the CODE4 member variable CODE4.lockAttempts. This member has
a default value of WAIT4EVER, which may result in deadlock in a multi-
user environment. To prevent deadlock, set CODE4.lockAttempts to a
reasonable finite number.
The example program Transfer sets CODE4.lockAttempts to a finite value
and takes advantage of the automatic locking properties of CodeBase. When
d4seek is called during the transaction, the current record may need to be
flushed before the new record can be loaded into the recorded buffer.
Normally, if a new lock is required, everything is unlocked according to
code4unlockAuto and then the lock is placed, but the seek takes place
during a transaction so no unlocking is performed.
Code fragment rc = code4lockAttempts( cb, 5 )
from Transfer
Index
functions, 11
structures, 12, 13
—.— CodeBase Functions
code4close, 18, 113
.CDX. See Index Files
code4init, 17, 26, 114
.CGP. See Group Files
code4initUndo, 18
.DBF. See Data Files
code4lock, 114, 117, 118, 126
.MDX. See Index Files
code4logCreate, 125, 126
.NTX. See Index Files
code4logOpen, 125, 126
code4logOpenOff, 125, 126
—A— code4optStart, 100, 101, 112
code4optSuspend, 100, 101
Appending Records. See Records code4tranCommit, 123, 124
Automatic Opening code4tranRollback, 123, 124
index files. See Index Files, production indexes code4tranStart, 123, 124, 125
code4unlockAuto, 106, 126
Composite Data Files. See Relations
—B— moving between composite records, 79
Composite Record. See Relations
Buffering. See Memory Optimizations
Constants
CodeBase constants, 12
—C— Converting Date Formats. See Date Operations
Current Record. See Data Files
CCYYMMDD. See Date Operations
Character Fields
assigning values to, 28 —D—
creating, 25
Data File Functions
retrieving contents, 21
d4alias, 33
Clipper
d4aliasSet, 33
index files, 37
d4append, 27, 105, 109
locking protocol, 104
d4appendBlank, 27, 105
CODE4, 13, 17
d4appendStart, 27
Flag setting functions
d4bottom, 18, 47, 86, 106, 116
code4accessMode, 17
d4close, 16, 18
code4autoOpen, 44, 61
d4create, 25, 26, 43, 60, 119, 125
code4errDefaultUnique, 52, 92
d4createCB, 34
code4errOpen, 27, 115
d4delete, 28, 31, 103, 105
code4lockAttempts, 105, 107, 109, 110, 113,
d4deleted, 31
115, 126
d4flush, 105, 109
code4lockDelay, 118
d4go, 18
code4lockEnforce, 28, 103, 105, 110, 116, 126
d4lockAdd, 114
code4log, 124
d4lockAddAll, 114, 117, 118, 126
code4memStartMax, 117, 119
d4lockAddAppend, 114
code4optimize, 99, 101, 112
d4lockAddFile, 114
code4optimizeWrite, 99, 112
d4lockAll, 117, 118, 126
code4readLock, 105, 109, 116, 117
d4log, 124
code4readOnly, 105, 115
d4memoCompress, 32, 105
code4safety, 26
d4numFields, 16, 20
initializing, 17
d4open, 16, 17, 26, 27, 44, 112, 125
multi-user settings, 105
d4optimize, 100
uninitializing, 18
d4optimizeWrite, 100
Code4 Members and Functions
d4pack, 105
lock, 114
d4position, 116
CodeBase
d4recall, 28, 32, 103, 105
constants, 12
d4recCount, 33, 78
130 CodeBase
d4recWidth, 33 date4cmonth, 98
d4refresh, 101, 117 date4day, 98
d4refreshRecord, 101, 117 date4dow, 98
d4reindex, 65, 105, 118 date4init, 97
d4seek, 18, 55, 57, 58, 86, 106, 116, 126 date4isleap, 98
d4seekDouble, 18, 55, 56, 57, 86, 106, 116 date4long, 97
d4seekN, 18, 55, 56, 86, 106, 116 date4month, 98
d4seekNext, 18, 55, 57, 58, 86, 106, 116 date4today, 98
d4seekNextDouble, 18, 55, 57, 58 date4year, 98
d4seekNextN, 18, 55, 57, 58, 86, 106, 116 Date Operation
d4skip, 16, 18, 19, 47, 86, 112, 117 leap year, 98
d4tagSelect, 46 Date Operations, 95
d4top, 18, 19, 47, 86, 106, 116 assigning values to date fields, 29, 96
d4unlock, 105, 106, 109, 113, 126, 127 CCYYMMDD, 96
d4write, 105 converting between date formats, 96
d4zap, 105, 118 date formats, 95
Data Files, 7 date pictures, 95
alias, 33 day of the month, 98
alias, changing, 33 day of the week, 98
buffering. See Memory Optimizations julian day format, 96
closing, 18 retrieving contents from date fields, 21, 96
composite. See Relations retrieving the current day, 98
compressing memo files, 32 standard format, 96
creating, 22, 25, 26 testing for valid dates, 98
current record, 14 years, 98
deletion flag. See Deletion Flag Date Pictures. See Date Operations
exclusive access. See Exclusive Access dBASE Expressions. See Reference Guide
field structure, 14, 25, 34 filter expression, 49, 50
index files, creating with, 43 index expression, 39
locking. See Locking master expression, 67, 77
master. See Relations query expression, 81
moving between records, 18 sort expression, 82
natural order. See Natural Order dBASE Functions, 39
opening, 17, 26 DELETED(), 50
packing, 32 STR(), 59
queries. See Queries UPPER(), 39, 92
read only mode. See Read Only Mode dBASE IV
record buffer, 14 descending order tags, 42
record count, 33 index files, 37
record number. See Record Number index filters, 47
relations. See Relations locking protocol, 104
slaves. See Relations dBASE operators
top master. See Relations '+', '-', 59
DATA4, 13, 27, 33, 77 Deadlock. See Multi-User
obtaining the pointer to, 13, 17, 26 Deleting records. See Records
Data4 Functions Deletion Flag, 8, 29
lockAdd, 114 Descending Order. See Tags
lockAddAll, 114
lockAddAppend, 114 —E—
lockAddFile, 114
Databases. See Data Files e4unique, 51
Date Fields Error Functions
assigning values, 29, 96 error4exitTest, 18
creating, 25 Exact Matches
retrieving contents, 21, 96 seeks. See Seeking
Date Formats. See Date Operations Exclusive Access
Date Functions opening files exclusively, 114, 116
date4assign, 97
date4cdow, 98
Index 131
—P—
—M—
Packing. See Data Files
Master Data File. See Relations Partial Matches
Memo Fields
relations. See Relations, approximate match
assigning values to, 28
seeks. See Seeking
creating, 25
Performance Issues
Memo File
efficient appending, 118
buffering. See Memory Optimizations
efficient locking, 117
compressing, 32
efficiently sorting a query set, 83
exclusive access. See Exclusive Access functions that reduce performance, 117
locking. See Locking general tips, 119
Memory memory optimization, 117
buffering. See Memory Optimizations time consuming functions, 118
dynamic allocation, 34, 65
Index 133