0% found this document useful (0 votes)
72 views

CodeBase User Guide

CodeBase 6.0 is a high-performance database engine compatible with dBASE IV, FoxPro, and Clipper file formats, designed for C, C++, Delphi, and Visual Basic programmers. The User's Guide provides comprehensive instructions on database concepts, access, indexing, queries, and performance optimization, along with examples and API details for Visual Basic. It emphasizes the importance of understanding database terminology and offers insights into creating, accessing, and managing data files effectively.

Uploaded by

sam johnson
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
72 views

CodeBase User Guide

CodeBase 6.0 is a high-performance database engine compatible with dBASE IV, FoxPro, and Clipper file formats, designed for C, C++, Delphi, and Visual Basic programmers. The User's Guide provides comprehensive instructions on database concepts, access, indexing, queries, and performance optimization, along with examples and API details for Visual Basic. It emphasizes the importance of understanding database terminology and offers insights into creating, accessing, and managing data files effectively.

Uploaded by

sam johnson
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 134

CodeBase 6.

0 TM

User’s Guide

The Visual Basic Engine For Database Management


Clipper Compatible
dBASE Compatible
FoxPro Compatible

Sequiter Software Inc.


© Copyright Sequiter Software Inc., 1988-1996. All rights reserved.

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.

CodeBase™ and CodeReporter™ are trademarks of Sequiter Software Inc.

Borland C++® is a registered trademark of Borland International.

Clipper® is a registered trademark of Computer Associates International Inc.

FoxPro® is a registered trademark of Microsoft Corporation.

Microsoft Visual C++® is a registered trademark of Microsoft Corporation.

Microsoft Windows® is a registered trademark of Microsoft Corporation.

Microsoft Visual Basic® is a registered trademark of Microsoft Corporation.

OS/2® is a registered trademark of International Business Machines Corporation.


Contents
Introduction ...........................................................................................................5
How To Use This Manual ................................................................................................6
1 Database Concepts............................................................................................7
CodeBase .......................................................................................................................7
File Format .....................................................................................................................7
2 DataBase Access .............................................................................................11
CodeBase Components ................................................................................................11
Learning CodeBase ......................................................................................................14
The VB API...................................................................................................................14
An Example CodeBase Program...................................................................................14
Initializing The CODE4 Structure ..................................................................................17
Code fragment from ShowData.....................................................................................17
Opening Data Files .......................................................................................................17
Closing Data Files.........................................................................................................18
Moving Between Records .............................................................................................18
Accessing Fields ...........................................................................................................19
Retrieving A Field's Contents .......................................................................................21
Creating Data Files .......................................................................................................22
Adding New Records.....................................................................................................27
Assigning Field Values..................................................................................................28
Removing Records .......................................................................................................29
Data File Information ....................................................................................................32
Advanced Topics ..........................................................................................................34
3 Indexing............................................................................................................37
Indexes & Tags.............................................................................................................37
Index Expressions.........................................................................................................39
Creating An Index File ..................................................................................................39
Maintaining Index Files .................................................................................................44
Opening Index Files......................................................................................................44
Referencing Tags..........................................................................................................45
Selecting Tags .............................................................................................................46
Tag Filters ....................................................................................................................47
Unique Keys .................................................................................................................50
Seeking ........................................................................................................................52
Group Files ...................................................................................................................60
Bypassing Group Files ..................................................................................................60
Reindexing....................................................................................................................65
Advanced Topics ..........................................................................................................65
4 Queries And Relations .....................................................................................67
Relations.......................................................................................................................67
Complex Relations........................................................................................................71
Relation Types..............................................................................................................73
Creating Relations ........................................................................................................75
Setting The Relation Type.............................................................................................78
Moving Through The Composite Data File ....................................................................79
Queries And Sorting......................................................................................................80
Accessing The Query Set..............................................................................................83
Queries On Multi-Layered Relation Sets .......................................................................83
Lookups On Relations...................................................................................................85
Iterating Through The Relations ....................................................................................86
2 CodeBase

5 Query Optimization ..........................................................................................89


What is Query Optimization ..........................................................................................89
When is Query Optimization used.................................................................................90
How To Use Query Optimization...................................................................................93
6 Date Functions .................................................................................................95
Date Pictures ................................................................................................................95
Date Formats................................................................................................................95
code fragment from Date ..............................................................................................97
code fragment from Date ..............................................................................................97
code fragment from Date ..............................................................................................97
Testing For Invalid Dates ..............................................................................................98
Other Date Functions....................................................................................................98
code fragment from Date ..............................................................................................98
7 Memory Optimizations......................................................................................99
Using Memory Optimizations ........................................................................................99
Memory Requirements................................................................................................101
When To Use Memory Optimization ...........................................................................101
8 Multi-User Applications ..................................................................................103
Locking .......................................................................................................................103
Creating Multi-User Applications .................................................................................104
Common Multi-User Tasks..........................................................................................106
Multi-User Optimizations.............................................................................................110
Avoiding Deadlock ......................................................................................................113
Exclusive Access ........................................................................................................114
Read Only Mode.........................................................................................................115
Code fragment from Multi ...........................................................................................115
Lock Attempts.............................................................................................................115
Automatic Record Locking ..........................................................................................116
Enforced Locking ........................................................................................................116
9 Performance Tips...........................................................................................117
Memory Optimization..................................................................................................117
Memory Requirements................................................................................................117
Observing the performance improvement ...................................................................117
Functions that can reduce performance ......................................................................117
Locking .......................................................................................................................117
Appending ..................................................................................................................118
Time Consuming Functions ........................................................................................118
Queries and Relations.................................................................................................118
General Tips ...............................................................................................................119
10 Transaction Processing ...............................................................................121
Transactions ...............................................................................................................121
Logging in the Stand-Alone Case ................................................................................124
Logging in the Client-Server case ...............................................................................126
Locking .......................................................................................................................126
Index .................................................................................................................129
Introduction 5

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.

Name Field Address Field Phone # Field

Smith John 897 Elm Street 555-3456


Stevens Dave 456 Oak Lane 555-1234
Trumble Al 123 Maple Ave 555-4567
Tuna Peter 955 Pine Ridge 555-2345
Tunner Sam 634 Spruce Ave 555-6789
Victor Paul 344 Knottwood Blvd 555-6423

Record # 5

Figure 1.1 Phone Book Example

As shown in this example, each data file (also known as a table) is a


collection of one or more fields (also known as a tuple). Each of these fields
has a set of characteristics that determine the size and type of data to be
stored. Collectively, these field descriptions make up the structure of your data
file.

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

In addition to including field values, key expressions can include a number of


dBASE expression functions and operators. For example, the '+' operator
concatenates two character fields together, or adds numeric values together.
The dBASE function STR() , converts a non-character field into a string.
The DELETED() function returns true if the record is marked for deletion.
Here is a typical expression using these functions and operators:
"LAST_NAME + STR(NUM_FLD) + DTOS(DATE_FLD)"

The previous expression combines a character field with the string


representation of a Numeric field and a Date field.
In addition, dBASE IV, FoxPro and Clipper indexes can be built with 'filters'
using expressions such as this,
"LAST_NAME = "SMITH" .AND. .NOT. DELETED()"

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:

Rec. No. NAME COUNTRY


1 DAVIDSON CANADA

2 SMITH UNITED STATES

3 ALBERTSON BRITAIN

4 FOREST CHINA

5 BAKER BRAZIL

Index File: key = NAME

Rec. No. KEY VALUE


3 ALBERTSON

5 BAKER

1 DAVIDSON

4 FOREST

2 SMITH
10 CodeBase

The most common use of indexed is to implement quick lookups of one or


more records. Another advantage to index files is that you cna build as
many as you need for any data file.
There are several formats of index files that CodeBase supports.
• ".MDX" (dBASE IV)
• ".NTX" (Clipper)
• ".CDX" (FoxPro)
The ".CDX" and ".MDX" allow you to have multiple tags in each index file,
and to have production indexes. A production index is an index file that is
opened automatically when the associated data file is opened. The ".NTX"
format limits you to one tag per index file, and does not allow for production
indexes. CodeBase does, however, provide this functionality through the use
of group files. Refer to the "Indexing" chapter for more details on index file
formats.
CodeBase uses the term tag to refer to any sort ordering, whether it be an
index within a multiple index file, or a single index file. The term selecting a
tag means putting an open index into use.
Filters Filters are used to obtain a subset of the available data file records. The
subset is based on true/false conditions calculated from data file field
information. Only records that pass through the filter have entries in the tag.
This allows for fast access to a data subset. Refer to the "Indexing" chapter
for details on using filters.
Relations A relation is a connection between two or more data files. A relation
determines how records in related data files can be found from a record in
the current data file. Refer to the "Queries And Relations" chapter for
details on relations.
DataBase Access 11

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".

CodeBase In addition to the elements of standard Basic programs, every CodeBase


Components program will contain CodeBase functions, structures, and return codes and
other constants.
CodeBase All data manipulation is performed by CodeBase functions that can be
Functions identified by the format of their names. These names are mixed case lower
case starting with one to six letters followed by the number four. The leading
letters indicate the module of the function while the remaining portion of the
name describes its purpose. For example, functions that manipulate data files
start with d4, while all the date functions start with date4.
Function Success CodeBase functions return -1 to indicate an error and 0 to indicate success,
and Failure
which is the opposite of the Visual Basic convention of using -1 to indicate
success or a true condition and 0 for failure or the false condition. For this
reason it is advisable to test the return values from CodeBase functions using
the return code constants documented in Appendices A and B of the
Reference Guide and defined in CODEBASE.BAS.
For example, the function d4seek, which does a look up in an index tag,
returns r4success (zero) if the seek is successful and -1 if an error has
occurred. The following are incorrect and correct examples of how to use this
function.
Incorrect: If d4seek( db, "JONES" ) Then
Print "We found Him"
Else ...

Correct: If d4seek( db, "JONES" ) = r4success Then


Print "We found Him"
Else ...
12 CodeBase

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

FIELD4 FIELD4 FIELD4

Gismo 321 19.47


Record Buffer

Record PRODUCTS.DBF
Current Record
Number Data File
PRODUCT PRO_ID COST
1 Widgit 2001 14.95

2 Giszmo 321T 19.47

DATA4 3 Dludge 1023 4.57


Records
4 Shimly A323 32.97

5 Dunsel 2145 12.21


DATA4
6 Foobar 1046 34.00

Figure 2.1 CodeBase Structures


The CODE4 Foremost of the CodeBase structures is the CODE4. This structure contains
Structure settings and information used by most of the other CodeBase functions. All
CODE4 elements are accessed through code4... functions, such as
code4safety and code4optimize. This structure is created internally and is
initialized to its default values when code4init is called.

Most applications only require one CODE4 structure. If you are


Note using more than one CODE4 structure, you will most likely be
wasting memory resources.

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

Declarations and Main() routine from USER.BAS

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

rc = code4initUndo( cb ) 'Close everything and free resources

End Sub

USER.FRM List1_DblClick( ) Event


'ShowData sample code

Dim fldPtr As Long

db = d4open( cb, fPath + "SHOWDATA" ) 'Open File

If db = 0 Then
MsgBox "Open Failed"
rc = code4errorCode( cb, 0 ) 'Reset error code
Exit Sub
End If

rc = d4top(cb)

For i = 1 To d4recCount(db) 'Loop through records


For j = 1 To d4numFields(db) 'Loop through each field
fldPtr = d4fieldJ(db, j) 'Get Field Pointer
Print f4mem oStr(fldPtr) 'Print field contents
Next j
Print
rc = d4skip(db, 1) 'Blank lines between recs.
Next i 'Go to next record

rc = d4close(db) 'Close file

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
...

cb = code4init( ) 'Initialize CodeBase


16 CodeBase

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"

If an error does occur, CodeBase sets CODE4.errorCode to a


Note negative value corresponding to the error that occurred. Other
CodeBase functions will not respond until this error code is reset to
zero. This is accomplished through the code4errorCode function.

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 )

This counter is used in conjunction with the d4fieldJ function to obtain a


pointer to a field's FIELD4 structure. The FIELD4 pointer is passed to
f4memoStr, which in turn returns a pointer to the contents of that field so it
can be printed out.
fldPtr = d4fieldJ( db, j )
Print f4memoStr( fldPtr )
Next j

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

An alternative to checking the CODE4.errorCode is to call error4exitTest.


It checks for any errors and terminates the program if one has occurred. On
the other hand, if you did not want to exit the program, you can check to see
if db is equal to zero.
error4exitTest( cb )

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

Scanning through ...


rc = d4top( db )
records If rc <> r4success Then Exit Sub
Do
For i = 1 To d4numFields( db )
Print f4str( d4fieldJ( db, i ) )
Next i
rc = d4skip( db, 1 )
Loop While rc = r4success
...

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.

PROGRAM This program lists the customer information contained in the


CustList CUSTLIST.DBF data file. Remember that as usual, this code is based on the
fact that code4init has already been called in sub Main() and the CODE4
pointer is stored in the global variable cb, which is of type long integer.
USER.FRM
Sub List1_DblClick( )

'FIELD4 pointers
Dim fName&, lname&, address&, age&, birthdate&
Dim marriage&, amount&, comment&

'TAG4 pointers
Dim nameTag&, ageTag&, amtTag&, birthTag&

'Field value holders


Dim ageVal%, amtVal#
Dim fnameStr$, lnameStr$, addressStr$, marri edStr$
Dim birthdateStr$, commentStr$

'CustList code example

db = d4open( cb, fPath + "DATA1" ) 'Open File

'Check and reset the error code


If code4errorCode( cb, 0 ) < 0 Then Exit Sub

fName = d4field( db,"F_NAME" ) 'Get field pointers


lName = d4field( db,"L_NAME" )
address = d4field( db,"ADDRESS" )
age = d4field( db,"AGE" )
birthDate = d4field( db,"BIRTH_DATE" ) 'Date field
married = d4field( db,"MARRIED" ) 'Logical
amount = d4field( db,"A MOUNT" ) 'Numeric
comment = d4field( db,"COMMENT" ) 'Memo
20 CodeBase

If d4top( db ) <> r4success Then Exit Sub

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 )

Print "Name :"; fnameStr; " "; lnameStr


Print "Address :"; addressStr; " "
Print "Age :"; ageVal; Space(5); "Married: "; marriedStr
Print "Amount :"; amtVal
Print "Comment :"; %s\n",commentStr
Print
rc = d4skip( db, 1 )
Loop While rc = r4success

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&

... data file is opened ....


fName = d4field( db,"F_NAME" )
lName = d4field( db,"L_NAME" )
address = d4field( db,"ADDRESS" )
age = d4field( db,"AGE" )
birthDate = d4field( db,"BIRTH_DATE" )
married = d4field( db,"MARRIED" )
amount = d4field( db,"AMOUNT" )
comment = d4field( db,"COMMENT" )
DataBase Access 21

In this code fragment, a FIELD4 pointer is obtained and stored in separate


long integer variables for each field of the record.
When the data file is closed, all FIELD4 pointers for the data file become
invalid.

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

The CustList program uses f4str on non-Memo fields.


Code fragment lnameStr = f4str( lname )
fnameStr = f4str( fname )
from CustList
Using f4str and
f4memoStr
On Character fields Character fields are returned by f4str and f4memoStr as left justified strings
padded out with blanks to the full length of the field.
On Date fields When these functions are used on Date fields, they return the date in the form
of an eight character string. Please refer to the "Date Functions" chapter for
more information.
On Numeric fields The contents of Numeric fields are returned as right justified strings padded
out to the full length of the field. If the field has any decimal places, the string
will contain exactly that many decimal places.
On Logical fields When used on Logical fields, these functions simply return a string containing
"Y", "N", "y", "n", "T", "F", "t", or "f" (depending on the value stored on disk
in the Logical field).
22 CodeBase

Retrieving In addition to returning a field's contents as a string, CodeBase can also


Numerical convert its contents to numerical data.
Data
f4double f4double returns the contents of the field as a double value. If f4double is
unable to convert the field's contents, a value of 0 is returned.
f4int and f4long The other two numeric operators, f4int and f4long convert the field's contents
to integer or long values. Any digits to the right of the decimal place (if any
are stored in the field) are truncated.

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

' FIELD4 structure pointers -- Used by NewList Program


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

'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

fname = d4field( db, "F_NAME" ) 'Get field pointers


lname = d4field( db, "L_NAME" )
address = d4field( db, "ADDRESS" )
age = d4field( db, "AGE" )
birthDate = d4field( db, "BIRTH_DA TE" )
married = d4field( db, "MARRIED" )
amount = d4field( db, "AMOUNT" )
comment = d4field( db, "COMMENT" )

If not cbError( ) Then OpenDataFile = True


End Function

Sub PrintRecordsNewList( )

Dim x As Integer
x = 12
DataBase Access 23

If d4top( db ) <> r4success Then Exit Sub

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 )

Form1.Print "Name:"; Tab(x); fnameStr + " " + lnameStr


Form1.Print "Address:"; Tab(x); addressStr
Form1.Print "Age:"; Tab(x); Str$(ageVal); Tab(x + 7); "Married:" +
marriedStr
Form1.Print "Birthdate:"; Tab(x); birthdateStr
Form1.Print "Amount:"; Tab(x); amtVal
Form1.Print "Comment:"; Tab(x); commentStr
Form1.Print

rc = d4skip( db, 1 )
Loop While rc = r4success

End Sub

Sub InitField4( )

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(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_DAT E"


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 AddNewRecord( fnameStr$, lnameStr$, addressStr$, ageVal%, birth$,


marriedVal%, amountVal#, commentStr$)
If d4appendStart( db, 0 ) <> r4success Then Exit Sub

Call f4assign( fname, fnameStr )


Call f4assign( lname,lNameStr )
Call f4assign( address,addressStr )
Call f4assignInt( age,ageVal )
24 CodeBase

Call f4assign( birthdate, birth )

If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If

Call f4assignDouble( amount,amountVal )


Call f4memoAssign( comment,commentSt r )

rc = d4append( db )

End Sub

USER.FRM List1_DblClick( ) Event

Dim save1 As Integer, save2 As Integer

'NewList Sample code

save1 = code4errOpen( cb, 0 ) 'Disable error message


save2 = code4safety( cb, 0 ) 'Disable overwrite safety

If Not OpenDataFile Then Exit Sub 'Open the file

If d4recCount( db ) > 0 Then 'Not a new file


PrintRecordsNewList 'Print records
Else 'Add records then print
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
End If

rc = code4errOpen( cb, save1 ) 'Restore original settings


rc = code4safety( cb, save2 )

rc = code4close( cb ) 'Close all files


DataBase Access 25

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
. . .

db = d4open( cb, fPath + "DATA1" )

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 )

Assigning values to the fields will be discussed in the next section.


The first byte of the record buffer is reserved for the 'deleted' flag.
Note This flag is also brought forward when you call d4appendStart. If
you are not calling d4blank, call d4recall to ensure that the current
record is not also marked as 'deleted'.

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

Code fragment rc = d4append( db )


from NewList

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

Sub PrintDeleteStatus( status%, recno& )

Dim display As String

If status Then
display = Str$( recno ) + " - DELETED"
Else
display = Str$( recno ) + " - NOT DELETED"
End If
Form1.Print "Record # " + display

End Sub

Sub PrintRecordsStatus( )

Dim status$, recno&


30 CodeBase

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

ReDim fieldInfo( 1 to 2 ) As FIELD4INFO 'Two fields

fieldInfo(1).fname = "DUMMY" 'Field data


fieldInfo(1).ftype = r4str
fieldInfo(1).flength = 10
fieldInfo(1).fdecimals = 0

fieldInfo(2).fname = "MEMO"
fieldInfo(2).ftype = r4memo
fieldInfo(2).flength = r4memoLen
fieldInfo(2).fdecimals = 0

save = code4safety( cb, 0 ) 'Overwrite old file


'Create data file only
db = d4createData( cb, "DELETION", fieldInfo() )

If db = 0 Then
rc = code4errorCode( cb, 0 )
CreateDeleteFile = False
Else
CreateDeleteFile = True
End If

rc = code4safety( cb, save)

End Function

USER.FRM List1_DblClick() Event

'Deletion sample code

Dim count As Integer

'Create test file


If Not CreateDeleteFile( ) Then Exit Sub

'Append 5 blank records


For i = 1 To 5
rc = d4appendBlank( db )
Next i

PrintRecordsStatus 'Print status of records

rc = d4go( db, 3 ) 'Go to record 3


Call d4delete( db ) 'Delete the record
rc = d4go( db , 1 )
Call d4delete( db )
rc = d4go( db, 4 )
Call d4delete( db )
PrintRecordsStatus

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

Deletion output Record # 1 - NOT DELETED


part 1 Record # 2 - NOT DELETED
Record # 3 - NOT DELETED
Record # 4 - NOT DELETED
Record # 5 - NOT DELETED

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

When a record is marked for deletion, it is not physically removed


Note from the data file. In fact you can still access the record normally.
The main importance of the deletion flag is that the marked record
is physically removed when the data file is packed. The deletion flag
may be used in tag filter expressions. Refer to the "Indexing"
chapter for details.
32 CodeBase

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

Wasted memo file space is mainly caused by packing records that


Note have memo entries without doing a memo compress. If you are
using Clipper compatibility, wasted memo space can also occur
when memo entries are increased beyond 504 bytes. In this case,
memo compressing reduces disk space usage.

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( )

'DateInfo sample code


Dim dbName As String, fldRef As Long, y As Integer
DataBase Access 33

y = 17

dbName = InputBox$( "Enter Name of File to Examine", "Open File", "DATA1" )

If dbName = "" Then Exit Sub

db = d4open( cb, fPath + dbName )

If cbError( ) Then Exit Sub 'Check for error

Print "Data File:"; Tab(y); dbName 'Print data file info


Print "Alias:"; Tab(y); d4alias( db )
Print "Record Count:"; Tab(y); d4recCount( db )
Print "Record Length:"; Tab(y); d4recWidth( db )
Print "Field Count:"; Tab(y); d4numFields( db )

List2.Visible = True
List2.Clear

For i = 1 To d4numFields( db ) 'Loop through each field


fldRef = d4fieldJ( db, i ) 'Get next field pointer
List2.AddItem "Field " + Str$( i ) 'Add field info
List2.AddItem f4name( fldRef )
List2.AddItem Chr$( f4type( fldRef ) )
List2.AddItem Str$( f4len( fldRef ) )
List2.AddItem Str$( f4decimals( fldRef ) )
List2.AddItem ""
Next i

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( )

'CopyData sample code

Dim fldInfo As Long 'Pointer to 'C' FIELD4INFO array


Dim db2 As Long, fileName As String
DataBase Access 35

fileName = InputBox$( "Enter the f ile name to copy", "CopyData", "DATA1" )

If fileName = "" Then Exit Sub

db = d4open( cb, fPath + fileName ) 'Open original file


If cbError( ) Then Exit Sub

save1 = code4safety( cb, 0 ) 'Overwrite existing file

fldInfo = d4fieldInfo( db ) 'Get 'C' field info

db2 = d4createCB( cb, fPath + "DATA2", ByVal fldInfo, 0& )

If db2 > 0 Then MsgBox "Successful File Copy"

Call u4free( fldInfo ) 'Free field info

rc = code4close( cb )

rc = code4safety( cb, save1 ) 'Restore to initial setting


36 CodeBase
Indexing 37

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:

File Format Compatibility Number of Production Group Index Descending


Tags Indexes Files Filtering Ordering

.MDX dBase IV 1 - 47 yes no yes yes


.CDX FoxPro 2.x No Limit yes no yes yes
FoxPro 3.0
.NTX Clipper 87 1 Only with yes no Special Case
Clipper 5 group files

lipper 5 Figure 3.1 Index File Formats

The difference between indexes and tags are illustrated in Figure 3.2.
38 CodeBase

PRODUCTS.DBF

Data File Record


Number PRODUCT PRO_ID COST

1 Widgit 2001 14.95

2 Giszmo 321T 19.47

3 Dludge 1023 4.57


Records
4 Shimly A323 32.97

5 Dunsel 2145 12.21

6 Foobar 1046 34.00

PRODUCTS.MDX COST.MDX

Index File
Index File PRO_TAG ID_TAG COST_TAG
Dludge 1023 4.57

Dunsel 1046 12.21

Foobar 2001 14.95


Index
Gismo 2145 19.47 Keys
Shimly 321T 32.97

Widgit A323 34.00

Tags

Figure 3.2 Index Files


Indexing 39

Index An index expression is a dBASE expression that is used to determine the


Expressions index key for each record. An index expression must evaluate to a value of
one of the following types: Numeric, Character, or Date types. When you are
using .CDX (FoxPro) indexes, you can also index on Logical expressions.
Refer to "Appendix C: dBASE Expressions" in the CodeBase Reference
Guide for details on dBASE expressions.
The most commonly used index expression is a field name. For example, the
index expression for the PRO_TAG tag in Figure 3.2 is "PRODUCT".
When this expression is evaluated for record number 6, the value of the field
PRODUCT is returned; for record number 6, this value is "Foobar ". To
put it simply, tag PRO_TAG is ordered by the contents in field PRODUCT.
Other common index expressions involve generating tags based on two or
more fields. This is known as a compound index key. For example, if we
assume that field PRO_ID is also a character field, we can base a tag ordering
with the following index expression: "PRODUCT + PRO_ID". This
produces an index key consisting of the PRODUCT field concatenated with
the PRO_ID field. For record number 6, the resulting index key is "Foobar
1046".
In addition, most dBASE functions are also permitted. See "Appendix C:
dBASE Expressions" in the CodeBase Reference Guide for a list of dBASE
functions supported. If we wanted to base a tag on the PRODUCT field, but
remain case insensitive, we can use the dBASE function UPPER() to convert
the index key to upper case. In this case the index expression would be
"UPPER(PRODUCT)".

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

'FIELD4 structure pointers


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

'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( )

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(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( )

ReDim tagInfo( 1 to 4 ) As TAG4INFO

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_T AG"


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 Sub

Function createDataFile( overWrite As Integer )

Dim save As Integer

If overWrite Then save = code4safety( cb, 0 ) 'Ok to overwrite

InitField4
InitTag4

db = d4create( cb, fPath + "DATA1", fieldInfo(), tagInfo() )

If cbError() Or db = 0 Then
CreateDataFile = False
Exit Function
End If

fname = d4field( db, "F_NAME" ) 'Get field pointers


lname = d4field( db, "L_NAME" )
address = d4field( db, "ADDRESS" )
age = d4field( db, "AGE" )
birthDate = d4field( db, "BIRTH_DATE" )
married = d4field( db, "MARRIED" )
mount = d4field( db, "AMOUNT" )
comment = d4field( db, "COMMENT" )
Indexing 41

If Not cbError() Then CreateDataFile = True

rc = code4safety( cb, save ) 'Restore original setting

End Function

Sub AddNewRecord( fnameStr$, lnameStr$, addressStr$, ageVal%, birth$,


marriedVal%, amountVal#, commentStr$)
If d4appendStart( db, 0 ) <> r4success Then Exit Sub

Call f4assign( fname, fnameStr )


Call f4assign( lName,lName_str )
Call f4assign( address,addressStr )
Call f4assignInt( age,ageVal )
Call f4assign( birthdate, birth )

If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If

Call f4assignDouble( amount,amountVal )


Call f4memoAssign( comment,commentStr )

rc = d4append( d b )

End Sub

Sub PrintRecordsNewList( )

Dim x As Integer
x = 12

If d4top( db ) <> r4success Then Exit Sub

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 )

Form1.Print "Name:"; Tab(x); fnameStr + " " + lnameStr


Form1.Print "Address:"; Tab(x); addressStr
Form1.Pri nt "Age:"; Tab(x); Str$(ageVal); Tab(x + 7); "Married:" + marriedStr
Form1.Print "Birthdate:"; Tab(x); birthdateStr
Form1.Print "Amount:"; Tab(x); amtVal
Form1.Print "Comment:"; Tab(x); commentStr
Form1.Print

rc = d4skip( db, 1 )
Loop While rc = r4success

End Sub

USER.FRM List1_DblClick( ) Event

'NewList2 Sample code

Dim overWrite As Integer


overWrite = True

'Create the data and index file

If Not OpenDataFile( overWrite ) Then Exit Sub

'Get TAG4 pointers


nameTag = d4tag( db, "NAME_TAG" )
ageTag = d4tag( db, "AGE_TAG" )
amtTag = d4tag( db, "AMT_TAG" )

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, "" )

Print "Natural Order:"


42 CodeBase

PrintRecordsNewList
Print

Print "Sorted by Name:"


Call d4tagSelect( db, nameTag )
PrintRecordsNewList

'And so on...

rc = code4close( cb ) 'Close files

Production A production index is an index that is opened automatically when its


Indexes associated data file is opened. A data file can have only one production index
and this index has the same name as the data file, but with a different
extension.
Non- All other index files are non-production indexes. A data file can have an
Production unlimited number of non-production indexes. To use these indexes, they must
Indexes explicitly be opened after the data file has been opened.

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

...

db = d4createData( cb, fPath + "DATA1", fldInfo() )


ind = i4create( db, fPath + "DATAINDEX", tagInfo() )

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

rc = code4accessMode( cb, OPEN4DENY_RW )

db = d4createData( cb, fPath + "DATA1", fldInfo() ) ;

ind = i4create( db, fPath + "", tagInfo() ) ;

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 )

'the changes are flushed to disk. If "NAME" is a


'key field, the index tag entry is automatically
'updated. A seek for "ROBERT WILKHAM" would succeed

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")

ind = i4open( db, fPath + "INDEXFILE")

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

Dim cb&, db&, ind1&, ind2&


Dim rc%

cb = code4init( )
rc = code4autoOpen( cb, 0 )

db = d4open( cb, "DATAFILE" )

ind1 = i4open( db, "INDEXFILE" )


ind2 = i4open( db, "DATAFILE" )

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( )

Dim fldPtr As Long

rc = d4top( db )

Do While rc = r4success 'Loop through each record


For j = 1 To d4numFields( db ) 'Loop through each field
fldPtr = d4fieldJ( db, j ) 'Get Field pointer
Form1.Print f4name( fldPtr ); Tab( 15 ); f4memoPtr( fldPtr )
Next j
Form1.Print 'Blank line between recs.
rc = d4skip( db, 1 ) 'Go to next record
Loop

MsgBox "Click to Continue"


Form1.Cls
End Sub

USER.FRM List1_DblClick( )

'ShowData2 sample code

Dim tagPtr As Long, dbfName As String

dbfName = InputBox$( "Enter File Name", "ShowData2", "DATA1" )

If Rtrim$( dbfName ) = "" Then Exit Sub

db = d4open( cb, fPath + dbfName ) 'Open file

If db = 0 Then
rc = code4errorCode( cb, 0 ) 'Reset error code
Exit Sub
End If

Print "Natural Order"


Print
GenericPrint 'Natural order

tagPtr = d4tagNext( db, 0& ) 'Get first tag, if any

Do Until tagPtr = 0

Call d4tagSelect( db, tagPtr )


Print "Datafile sort by tag: "; t4alias( d4tagSelected( db ) )
Print
GenericPrint
tagPtr = d4tagNext( db, tagPtr ) 'Look for next tag
Loop

rc = d4clos e( db ) 'Close file


46 CodeBase

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

db = d4open( cb, "DATAFILE" )


tag1 = d4tagDefault( db )

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.

Code fragment Print "Sorted by Name:"


Call d4tagSelect( db, nameTag )
from NewList2 PrintRecordsNewList

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

'FIELD4 structure pointers


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

Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function

Sub InitField4( )

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(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

save = code4errOpen( cb, 0 )

db = d4open( cb, fPath + "DATA1" )

If db = 0 Then
InitField4
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError() Then
OpenDataFile = False
Exit Function
End If
End If

fname = d4field( db, "F_NAME" ) 'Get field pointers


lname = d4field( db, "L_NAME" )
address = d4field( db, "ADDRESS" )
age = d4field( db, "AGE" )
birthDate = d4field( db, "BIRTH_DATE" )
married = d4field( db, "MARRIED" )
mount = d4field( db, "AMOUNT" )
comment = d4field( db, "COMMENT" )

rc = code4errOpen( cb, save )

If Not cbError() Then OpenDataFile = True

End Function

Sub AddNewRecord( fnameStr$, lnameStr$, addressStr$, ageVal%, birth$,


marriedVal%, amountVal#, commentStr$)
If d4appendStart( db, 0 ) <> r4success Then Exit Sub

Call f4assign( fname, fnameStr )


Call f4assign( lName,lNameStr )
Call f4assign( address,addressStr )
Call f4assignInt( age,ageVal )
Call f4assign( birthdate, birth )

If marriedVal Then
Call f4assign( married,"T" )
Else
Call f4assign( married,"F" )
End If

Call f4assignDouble( amount,amountVal )


Call f4memoAssign( comment,commentStr )

rc = d4append( db )

End Sub

Function CreateIndexFile%( )

ReDim tagInfo( 1 to 4) As TAG4INFO


Dim indPtr As Long, save As Integer

save = code4safety( cb, 0 )

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 = ""

indPtr = i4create( db, "", tagInfo( ) )

If Not cbError() And indPtr > 0 Then CreateIndexFile = True

rc = code4safety( cb, save )

End Function

USER.FRM List1_DblClick( )

Dim save1 As Integer, save2 As Integer, save3 As Integer


'ShowData3 sample code

save1 = code4autoOpen( cb, 0 )


save2 = code4errOpen( cb, 0 )
save3 = code4accessMode( cb, OPEN4DENY_RW ) 'Open exclusivel y so production
'index file can be created with
'i4create

If Not OpenDataFile( ) Then Exit Sub

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

If Not CreateIndexFile( ) Then Exit Sub

tagPtr = d4tagN ext( db, 0& ) 'Get first tag if any


Do Until tagPtr = 0
Call d4tagSelect( db, tagPtr ) 'Select Ordering
Print "Datafile sorted by tag: "; t4alias( d4tagSelected( db ) )
Print "With filter: "; t4filter( d4tagSelected( db ) )
Print
GenericPrint
tagPtr = d4tagNext( db, tagPtr ) 'Look for next tag
Loop

rc = code4autoOpen( cb, save1 ) 'Restore initial settings


rc = code4errOpen( cb, save2 )
rc = code4accessMode( cb, save3 )

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

5 Dunsel 2145 12.21

6 Foobar 1046 34.00

Figure 3.3 Filter Expressions

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

Figure 3.4 Unique Keys

Here is an example TAG4INFO array that uses unique keys:


tagInfo(1).name = "NAME_TAG"
tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETED()"
tagInfo(1).unique = r4unique
52 CodeBase

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.

t4uniqueSet can only be used to change the setting of a unique tag.


Note A CodeBase error is generated when an attempt is made to set a
unique tag to a non-unique tag or a non-unique tag to a unique tag.

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

Sub AddNewRecord( fnameStr$, lnameStr$, addressStr$, ageVal%, birth$,


marriedVal%, amountVal#, comme ntStr$)
If d4appendStart( db, 0 ) <> r4success Then Exit Sub

Call f4assign( fname, fnameStr )


Call f4assign( lName,lName_str )
Call f4assign( address,addressStr )
Call f4assignInt( age,ageVal )
Call f4assign( birthdate, birth )

If marriedVal Then
Call f4assign( married,"T" )
Indexing 53

Else
Call f4assign( married,"F" )
End If

Call f4assignDouble( amount,amountVal )


Call f4memoAssign( comment,commentStr )

rc = d4append( db )

End Sub

Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function

Function CreateDataFile( overWrite As Integer )

Dim save As Integer

If overWrite Then save = code4safety( cb, 0 ) 'Ok to overwrite

InitField4
InitTag4

db = d4create( cb, fPath + "DATA1", fieldInfo(), tagInfo() )

If cbError() Or db = 0 Then
CreateDataFile = False
Exit Function
End If

fname = d4field( db, "F_NAME" ) 'Get field pointers


lname = d4field( db, "L_NAME" )
address = d4field( db, " ADDRESS" )
age = d4field( db, "AGE" )
birthDate = d4field( db, "BIRTH_DATE" )
married = d4field( db, "MARRIED" )
mount = d4field( db, "AMOUNT" )
comment = d4field( db, "COMMENT" )

If Not cbError() Then CreateDataFile = True

rc = code4safety( cb, save ) 'Restore original setting

End Function

Sub InitField4( )

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(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 = 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( )

ReDim tagInfo( 1 to 4) As TAG4INFO

tagInfo(1).name = "NAME_TAG" 'NAME_TAG is NOT a unique tag


tagInfo(1).expression = "F_NAME + L_NAME"
tagInfo(1).filter = ".NOT. DELETE D()"
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

Sub PrintRecordSeek( rc As Integer )


Dim status As string, y As Integer
y = 15

Call SeekStatus( rc, status )


'Copy contents of field
lnameStr = f4str( lname )
fnameStr = f4str( fname )
addressStr = f4str( add ress )
ageVal = f4int( age )
birthdateStr = f4str( birthdate )
marriedStr = f4str( married )
amtVal = f4double( amount )
commentStr = f4memoStr( comment )

Form1.Print "Seek Status:"; Tab(y); status


Form1.Print String( 40, "-" )
Form1.Print "Name:"; Tab(y); fnameStr + " " + lnameStr
Form1.Print "Address:"; Tab(y); addressStr
Form1.Print "Age:"; Tab(y); ageVal
Form1.Print "Birth:"; Tab(y); birthdateStr
Form1.Print "Married:"; Tab(y); marriedStr
Form1.Print "Comment:"; Tab(y); commentStr
Form1.P rint "Amount Pchsed:"; Tab(y); amtVal

MsgBox "Click to Continue"


Form1.Cls
End Sub

Sub SeekStatus( rc As Integer, status As String )


Select Case rc
Case r4success
status = "r4success"
Case r4eof
status = "r4eof"
Case r4after
status = "r4after"
Case Else
status = "other"
End Select
End Sub

USER.FRM List1_DblClick( )

'TAG4 pointers
Indexing 55

Dim nameTag As Long, ageTag As Long, amtTag As Long, birthTag As Long

'Seeker sample code

If Not CreateDataFile( True ) Then Exit Su b

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" )

Call d4tagSelect( db, nameTag )

' 12345678901234567890 Full Key, 20 Characters


rc = d4seek( db , "Sarah Webber " )
PrintRecordSeek( rc )

rc = d4seek( db, "Sar" ) 'Partial key


PrintRecordSeek( rc )

rc = d4seek( db, "Sar " ) 'Exact match


PrintRecordSeek( rc )

Call d4tagSelect( db, birthTag )


rc = d4seek( db, "19381212" )
PrintRecordSeek( rc )

Call d4tagSelect( db, amtTag )


rc = d4seekDouble( db, 35.75 )
PrintRecordSeek( rc )

rc = d4seek( db, "250.75" )


PrintRecordSeek( rc )

'The following code finds all the occurrences of John Albridge in the tag

Call d 4tagSelect( db, nameTag )


rc = d4seekNext( db, "John Albridge " )

Do Until rc <> r4success


PrintRecordSeek( rc )
rc = d4seekNext( db, "John Albridge " )

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";

Call d4tagSelect(db, binaryTag);


rc = d4seekN(db, key, Len(key));
Indexing 57

d4seekNext, The d4seekNext, d4seekNextDouble and d4seekNextN functions only


d4seekNextDouble
and d4seekNextN
differ from their d4seek counterparts in that the searches begin at the current
position in the tag. This provides the capability of performing an incremental
search through the data base. Consequently, all index keys matching the
search key can be found by repeated calls to d4seekNext functions. On the
other hand, d4seek functions always begin their searches from the first
logical record as determined by the selected tag and therefore, they only locate
the first index key in the tag that matches the search key.
All three d4seekNext functions work in the following manner. If the index
key at the current position in the tag does not match the search key,
d4seekNext calls d4seek to find the first occurance of the search key.
Conversely, if the index key at the current position does match the search key,
d4seekNext tries to find the next occurance of the search key. Similarly,
d4seekNextDouble and d4seekNextN call d4seekDouble and
d4seekNextN respectively to find the first occurrance of the search key.
d4tagSelect( db,nameTag)
rc = d4seekNext( db, "John Albridge " )
PrintRecordSeek( rc )

Do Until rc <> r4success


rc = d4seekNext( db, "John Albridge " )
PrintRecordSeek( rc )
Loop

d4seek, d4seekDouble, d4seekN, d4seekNext, d4seekNextDouble and


d4seekNextN return an integer value indicating the success or failure in
locating the key value in the selected tag.
Exact CodeBase gives you the ability to perform both exact and partial matches on
Matches tags. An exact match occurs when the index key in the tag is the same as the
search key. When searching using a character tag, the criteria that all d4seek
and d4seekNext functions use for determining an exact match is as follows:
• The characters in the search string must be the same as corresponding
characters in the index key.
• The comparison is case sensitive. Therefore a search key of "aaaa" does
not match an index key of "AAAA".
To do a case insensitive search, one must change the indexing strategy,
not the search method. Create a tag with the index expression
"UPPER(fieldName)" and then a search for "AAAA" will be case
insensitive. Therefore, both "Aaaa" and "aAaA" will be found.
• The strings are only compared up to end of the shortest value. Therefore
a search key of "abcd" exactly matches an index key of "abcdefg" because
only four characters are compared.
You can force CodeBase to compare additional characters by
Note padding the search key out to the full length of the index key.
Therefore a search key of "abcd " does not exactly match
"abcdefg"
58 CodeBase

When the d4seek or d4seekNext functions find an exact match, a value of


r4success is returned.

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 )

rc = d4seek( db, "Sar" )


PrintRecordSeek( rc )

rc = d4seek( db, "Sar " )


PrintRecordSeek( rc )

Assuming that there is a record in the data file containing "Sarah


Webber " in the compound tag NAME_TAG: The first time d4seek is
called, it returns r4success because its search key exactly matched and was
the same length as the index key. The second call will also return r4success
because it only compares the first three characters. The third call however,
returns r4after because it could not find an exact match. All three, however,
result in the "Sarah Webber " record being loaded into the record buffer.
possible returns When d4seekNext is called and the index key at the current position in the
from d4seekNext tag does not match that of the search key, d4seek is called to find the first
functions
instance of the search key. Should this seek fail, r4after is returned and tag is
positioned as discussed above.
On the other hand, if d4seekNext is called and index key at the current
position does match the search key, d4seekNext searches for the next
occurrance of the search key. If this seek fails, r4entry is returned and the
tag is positioned in the same manner as r4after.
d4seekNextDouble and d4seekNextN function in the same manner as
d4seekNext.

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) ;

rc = d4seek( db, "Sarah Webber " )


PrintRecordSeek( rc )
Indexing 59

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" ) )

search = Space$( size1 + size2 )

Mid$( search, 1, 5 ) = "Sarah"


Mid$( search, 11, 6 ) = "Webber"
.
.
.
rc = d4seek( db, search )

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

1 Widgit 2001 14.95

2 Giszmo 321 19.47


Group Files
3 Dludge 1023 4.57

4 Shimly 323 32.97


PRODUCTS.CGP
5 Dunsel 2145 12.21
PRO_TAG.NTX
6 Foobar 1046 34.00
P_NUM_TAG.NTX

PRO_TAG.NTX P_NUM_TAG.NTX COST_TAG.NTX

PRO_TAG P_NUM_TAG COST_TAG


4.57
Dludge 321
12.21
Dunsel 323

Foobar 1023 14.95

Gismo 1046 19.47

Shimly 2001 32.97

Widgit 2145 34.00

Figure 3.5 Group Files

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( )

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(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
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( )

ReDim tagInfo( 1 to 4) As TAG4INFO

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%( )

db = d4open( cb, fPath + "DATA1" )

If db = 0 Then
InitField4
db = d4createData( cb, "DATA1", fieldInfo( ) )
If cbError() Then
OpenDataFile = False
Exit Function
End If
End If

fname = d4field( db, "F_NAME" ) 'Get field pointers


lname = d4field( db, "L_NAME" )
address = d4field( db, "ADDRESS" )
age = d4field( db, "AGE" )
birthDate = d4field( db, "BIRTH_DATE" )
married = d4field( db, "MARRIED" )
mount = d4field( db, "AMOUNT" )
comment = d4field( db, "COMMENT" )

If Not cbError() Then OpenDataFile = True

End Function

USER.FRM List1_DblClick( )

Dim save1 As Integer, save2 As Integer

'NoGroup sample code

rc = MsgBox( "Are you using a Clipper Library?",


MB_YESNO + MB_ICONQUESTION, "NoGr oup" )

save1 = code4autoOpen( cb, 0 )


save2 = code4safety( cb, 0 )

If Not OpenDataFile( ) Then Exit Sub

InitTag4

ind1 = i4create( db, "", tagInfo( ) )

If ind1 > 0 then


If rc = IDYES Then
MsgBox "Index created with no Group file"
Else
MsgBox "Index created"
End If
End If

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

Dim save1 As Integer, save2 As Integer

'NoGroup2 sample code

rc = MsgBox( "Are you using a Clipper Library?",


MB_YESNO + MB_ICONQUESTION, "NoGroup" )

If rc <> IDYES Then


MsgBox "Cannot continue this example"
Exit Sub
End If

save1 = code4autoOpen( cb, 0 )


save2 = code4safety( cb, 0 )

If Not OpenDataFile( ) Then Exit Sub

InitTag4

nameTag = t4open( db, "NAME" )


addrTag = t4open( db, "ADDRESS" )
ageTag = t4open( db, "AGE" )

If cbError( ) Then Exit Sub

Call d4tagSelect( db, nameTag )


GenericPrint

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

db = d4open( cb, fPath + "TEST" )


ind = d4index( db, "INDEX" )

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( )

If code4errorCode( cb, 0 ) < 0 Then


cbError = True
Else
cbError = False
End If
End Function

USER.FRM List1_DblClick( )

Dim save1 As Integer, save2 As Integer


Dim fileName As String, db2 As Long

'CopyData2 sample code


save1 = code4autoOpen( cb, 0 )
save2 = code4safety( cb, 0 )

fileName = InputBox$( "Enter file name to copy", "CopyData", "DATA1" )

If fileName = "" Then Exit Sub

db = d4open( cb, fPath + fileName ) 'Open source data file


ind1 = i4open( db, "" ) 'Open index

If cbError( ) Then
rc = code4close( cb )
Exit Sub 'Check for error
End If

fldArray = d4fieldInfo( db ) 'Get 'C' field/tag info


tagArray = i4tagInfo( ind1 )

'NOTE: Pass the 'C' info ByVal only!


db2 = d4createCB( cb, fPath + "DATA2", ByVal fldArray, ByVal tagArray )

If db2 > 0 Then MsgBox "Successful file copy"

Call u4free( fldArray ) 'Free field info


Call u4free( tagArray ) 'Free tag info

rc = code4close( cb )
rc = code4autoOpen( cb, save1 ) 'Reset to initial status
rc = code4safety( cb, save2 )
Queries and Relations 67

4 Queries And Relations


One of the most powerful features of CodeBase is its relation and query
capabilities. By using relations, you can turn a collection of separate data
files into a fully relational database. Relations are used for two tasks: lookups
and queries.
Lookups are used to automatically locate records between related data files.
Essentially, this makes several related data files act like a single large data
file.
Queries retrieve groupings of records from the database that meet conditions
that you can specify at run-time. Queries use CodeBase's Query Optimization
which greatly reduces the time it takes to perform queries.

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

Master Data File Slave Data File


STUDENT.DBF ENROLL.DBF
ID F_NAME L_NAME AGE STU_ID C_CODE
654321 Ken Hirshfeld 30
DATAFILE 157932 ECON102
123345 Sandra Donaghey 32 234533 CMPT389
873454 Barry Webber 22 423232 MATH114
423232 Harvey Tyler 43 423232 CMPT411
463722 James Miller 34 234533 MATH114
234533 David Krammer 25 125753 CMPT411
ID STU_ID_TAG
534452 Bernie McFarland 22 423232 MATH115
835543 Douglas Samoil 39 873454 CMPT201
153543 Ron Watson 22 765343 MATH114
858343 George Dean 43 125753 ECON102
157932 Albert Miller 34 876097 CMPT411
876097 Scott Greig 23 534452 CMPT201
345742 Brian Perron 24
RELATION 234533 CMPT411
336544 Allan Racine 29

865422 Cameron Calvert 30

125753 Reginald Page 24


STU_ID_TAG
874632 Eric Lane 41 TAG is based on the
765343 Upali Shivji 32 “STU_ID” field

Figure 4.1 Relation on two data files

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

Figure 4.2 A Composite Record

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

123345 Sandra Donaghey 32


ENROLL.DBF
423232 CMPT411
873454 Barry Webber 22
234533 CMPT389
423232 Harvey Tyler 43
423232 MATH114
463722 James Miller 34
157932 ECON102
234533 David Krammer 25
ID STU_ID_TAG 234533 MATH114
534452 Bernie McFarland 22
125753 CMPT411
835543 Douglas Samoil 39
423232 MATH115
153543 Ron Watson 22
873454 CMPT201
858343 George Dean 43
765343 MATH114
157932 Albert Miller 34
125753 ECON102
876097 Scott Greig 23
876097 CMPT411
345742 Brian Perron 24
534452 CMPT201
336544 Allan Racine 29
234533 CMPT411
865422 Cameron Calvert 30

125753 Reginald Page 24

874632 Eric Lane 41

765343 Upali Shivji 32

654321 Ken Hirshfeld 30


123345 Sandra Donaghey 32
873454 Barry Webber 22 873454 ECON102
423232 Harvey Tyler 43 423232 CMPT411
423232 Harvey Tyler 43 423232 MATH114
423232 Harvey Tyler 43 423232 MATH115
463722 James Miller 34
234533 David Krammer 25 234533 CMPT389
234533 David Krammer 25 234533 MATH114
234533 David Krammer 25 234533 CMPT411
534452 Bernie McFarland 22 534452 CMPT201
835543 Douglas Samoil 39
153543 Ron Watson 22
858343 George Dean 43
157932 Albert Miller 34 157932 ECON102
876097 Scott Greig 23 876097 CMPT411
345742 Brian Perron 24
336544 Allan Racine 29
865422 Cameron Calvert 30
125753 Reginald Page 24 125753 CMPT411
125753 Reginald Page 24 125753 ECON102
874632 Eric Lane 41
765343 Upali Shivji 32 765343 MATH114

COMPOSITE DATA FILE


Figure 4.3 The Composite Data File

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

Composite Data File Record # 3

873454 Barry Webber 22 87345 CMPT201

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

654321 Ken Hirshfield 30

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 ...

CMPT411 Computer Graphics


234533 MATH114
MATH114 Intro to Calculus I
125753 CMPT411
ECON101 Intro to Micro Econ ...
423232 MATH115
MATH521 Intro to Chaos
873454 CMPT201
MATH115 Intro to Calculus II
765343 MATH114
ECON102 Intro to Macro Econ ...
125753 ECON102 CODE_TAG MATH221 Intro to Linear Alge ...
876097 CMPT411 is based on the
CMPT201 Computer Languages
534452 CMPT201 “CODE” field
234533 CMPT411

STUDENT.DBF TOP MASTER


654321 Ken Hirshfeld 30
123345 Sandra Donaghey 32
873454 Barry Webber 22
423232 Harvey Tyler 43
463722 James Miller 34
234533 David Krammer 25
534452 Bernie McFarland 22
835543 Douglas Samoil 39
153543 Ron Watson 22
858343 George Dean 43
157932 Albert Miller 34
ENROLL.DBF Slave of STUDENTS
876097 Scott Greig 23
345742 Brian Perron 24
423232

234533
CMPT411

CMPT389
Master of COURSE
336544 Allan Racine 29
423232 MATH114
865422 Cameron Calvert 30
125753 Reginald Page 24 157932 ECON102

874632 Eric Lane 41 234533 MATH114


765343 Upali Shivji 32 125753 CMPT411
423232 MATH115
873454 CMPT201
765343 MATH114
125753 ECON102
876097 CMPT411
COURSE.DBF
534452 CMPT201
CMPT401 Software Engineering Slave of
234533 CMPT411
CMPT389 Intro to Databases ... ENROLL
CMPT272 Programming Con ...
CMPT411 Computer Graphics

MATH114 Intro to Calculus I


ECON101 Intro to Micro Econ ...
MATH521 Intro to Chaos
MATH115 Intro to Calculus II

Slave Family ECON102 Intro to Macro Econ ...


MATH221 Intro to Linear Alge ...
of Student CMPT201 Computer Languages

Relation Set

Figure 4.4 A Relation Set


Queries and Relations 73

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:

Master Data File Slave Data File


STUDENT.DBF
ID F_NAME L_NAME AGE
ENROLL.DBF
STU_ID C_CODE
654321 Ken Hirshfeld 30
157932 ECON102
123345 Sandra Donaghey 32
234533 CMPT389
873454 Barry Webber 22
423232 MATH114
423232 Harvey Tyler 43
463722 James Miller 34 423232 CMPT411
234533 MATH114
125753 CMPT411
423232 MATH115
873454 CMPT201

Composite Data File

873454 Barry Webber 22 873454 CMPT201


Only One Entry In
423232 Harvey Tyler 43 423232 MATH114
463722 James Miller 34
Composite Data
234533 David Krammer 25 234533 CMPT389
File

Figure 4.5 An Exact Match Relation

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.

Master Data File Slave Data File


STUDENT.DBF
ID F_NAME L_NAME AGE
ENROLL.DBF
STU_ID C_CODE
654321 Ken Hirshfeld 30
157932 ECON102
123345 Sandra Donaghey 32
234533 CMPT389
873454 Barry Webber 22
423232 MATH114
423232 Harvey Tyler 43
463722 James Miller 34 423232 CMPT411
234533 MATH114
125753 CMPT411
423232 MATH115
873454 CMPT201

Composite Data File

873454 Barry Webber 22 873454 CMPT201


423232 Harvey Tyler 43 423232 MATH114
Three Entries In
423232 Harvey Tyler 43 423232 MATH441
423232 Harvey Tyler 43 423232 MATH115
Composite Data
463722 James Miller 34 File
234533 David Krammer 25 234533 CMPT389

Figure 4.6 A Scan Relation

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

Figure 4.7 Approximate Match Relations

An approximate match relation is shown in Figure 4.7. In this case, the


employees' retirement benefits are determined by the number of years that they
put into the company. Instead of making an entry for each possible year
served, the BENEFIT.DBF only lists the upper limit for each pay out level.
The first pay out level is from zero to five years, the second is six to ten, et
cetera until the maximum entry of twenty-one years and above pays out
100000.

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

Dim student As Long, enrollment As Long 'DATA4


Global master As Long, slave As Long 'RELATE4
Dim nameTag As Long, idTag As Long 'TAG4

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" )

nameTag = d4tag( student, "NAME" )


idTag = d4tag( enrollment, "STU_ID_TAG" )

If Not cbError( ) Then OpenFileRelate1 = True


End Function

Function SetRelation1( )
master = relate4init( student )
76 CodeBase

If master = 0 Then
SetRelation1 = False
Exit Function
End If

slave = relate4createSlave( master, enrollment, "ID", idTag )


rc = relate4type( slave, relate4scan )
rc = relate4top( master )
If rc = r4success And Not cbError( ) Then SetRelation1 = True
End Function

Sub PrintRecRelate1( )
Dim relation As Long, dbf As Long
Static recNo As Long

relation = master

Form1.List2.AddItem "Composite Rec. #" + Str$( recNo + 1 )


Form1.List2.AddItem String$( 30, "-" )

Do While relation <> 0


dbf = relate4data( relation )
For j = 1 To d4numFields( dbf )
Form1.List2.AddItem f4memoStr( d4fieldJ( dbf, j ) )
Next
rc = relate4next( relation )
Loop

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

If Not SetRelation1( ) 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

Code fragment master = relate4init(student)


from RELATE1

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& )

Since the master expression is "RECNO()", it returns the record number of


master data file's current record. As a result, record n in the master must
match record n in the slave.
If the master expression contains a reference to a non existent record number
( <= 0 or > d4recCount ), CodeBase generates a -70 error ("Reading File",
see Appendix A: Error Codes in the Reference Guide).

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

relate4type always takes a pointer to the slave in the relationship,


Note never the master.
Queries and Relations 79

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

relate4bottom or relate4skipEnable to initialize the backwards skipping


abilities.
An example of this is given in the following code segment:
Sub ListRecs( )
rc = relate4bottom( master )
Do While rc = r4success
PrintRecRelate1
rc = relate4skip( master, -1 )
Loop
End Sub

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

Sub PrintRecRelate2( dbf As Long )


Static recNo As Long

Form1.List2.AddItem "Query Rec. #" + Str$(recNo + 1 )


Form1.List2.AddItem String$( 30, "-" )

For j = 1 To d4numFields( dbf )


Form1.List2.AddItem f4memoStr( d4fieldJ( dbf, j ) )
Next

Form1.List2.AddItem ""
recNo = recNo + 1
End Sub

Sub Query( dbf As Long, expr As String, order As String )


Dim relation As Long 'RELATE4 pointer

relation = relate4init( dbf ) 'Initial ize the relation/query


If relation = 0 Then Exit Sub

'Set query condition and sort order

rc = relate4querySet( relation, expr )


rc = relate4sortSet( relation, order )

rc = relate4top( relation ) 'First record

Form1.List2.AddItem "Query: " + expr


Form1.List2.AddItem String$( 35, "*" )
Form1.List2.AddItem ""
Queries and Relations 81

Do While rc = r4success 'Print each record in set


PrintRecRelate2( dbf )
rc = relate4skip( relation, 1 )
Loop

Form1.List2.AddItem ""
rc = relate4free(relation, 0 )
End Sub

USER.FRM List1_DblClick( )
'Relation2 sample code

db = d4open( cb, fPath + "STUDENT" )


If cbError() Then Exit Sub

List2.Visible = True
List2.Clear

Call Query( db, "AGE > 30", "" )


Call Query( db, "UPPER(L_NAME) = 'MILLER'", "L_NAME + F_NAME" )

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'") ;

These queries compare a field called NAME in FATHER.DBF with the


string "Ben Nyland". The file name FATHER does not need to be specified
in the query because the FATHER.DBF file is a master.
In order to check for the composite record that has Ben Nyland and his son
Eric Nyland, the query can be specified by either of the following:
relate4querySet( relation, "FATHER->NAME = 'Ben Nyland'
.AND. SON->NAME = 'Eric Nyland'");
relate4querySet( relation, "NAME = 'Ben Nyland' .AND. SON->NAME = 'Eric Nyland'");

The query expression can be changed at any time, although relate4top or


relate4bottom must then be called before relate4skip can be called.
82 CodeBase

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.

865422 Cameron Calvert 30


123345 Sandra Donaghey 32
Query :”AGE >= 30 654321 Ken Hirshfeld 30
.AND.AGE <= 39”
157932 Albert Miller 34 157932 ECON102
Sort : “L_NAME + F_NAME”
463722 James Miller 34
835543 Douglas Samoil 39
765343 Upali Shivji 32 765343 MATH114

Query Set

Figure 4.8 Query Example One

835543 Douglas Samoil 39


Query :”L_NAME >= ‘S’ 765343 Upali Shivji 32 765343 MATH114
.AND.L_NAME < ‘U’” 423232 Harvey Tyler 43 423232 CMPT411
Sort : “F_NAME + L_NAME 423232 Harvey Tyler 43 423232 MATH114
+ ENROLL ->C_CODE”
423232 Harvey Tyler 43 423232 MATH115

Query Set

Figure 4.9 Query Example Two


Queries and Relations 83

Accessing The query set is accessed by relate4top , relate4bottom and relate4skip.


When a query has been specified, these three functions then access a query
The Query
set instead of the entire composite data file.
Set
Generating The query set is initially formed by the first call to either relate4top or
The Query relate4bottom. These functions analyze the query expression for the most
Set efficient way of performing the query. They then locate all of records that
are in the query set. If a sort expression has been specified, the composite
records are sorted. And finally, lookups are performed on the data files in
the relation to locate the first or last composite record in the query set.
When complex relations and sort expressions are used on a
relation set consisting of large data files, relate4top and
WARNING relate4bottom can take considerable time to execute.

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

Dim studentId As Long, firstName As Long 'FIELD4


Dim lastName As Long, studentAge As Long
Dim classCode As Long, classTitle As Long

Global classRel As Long, enrollRel As Long 'RELATE4


Dim studentRel As Long

Dim idTag As Long, codeTag As Long 'TAG4

Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function

Function OpenFileRelate3( )
84 CodeBase

student = d4open( cb, fPath + "STUDENT " )


enrollment = d4open( cb, fPath + "ENROLL" )
classes = d4open( cb, fPath + "CLASSES" )

If cbError() Then Exit Function

studentId = d4field( student, "ID" )


firstName = d4field( student, "F_NAME" )
lastName = d4field( student, "L_NAME" )
studentAge = d4field( student, "AGE" )
classCode = d4field( classes, "CODE" )
classTitle = d4field( classes, "TITLE" )

idTag = d4tag( student, "ID_TAG" )


codeTag = d4tag( enrollment, "C_CODE_TAG" )

If cbError( ) Then Exit Function

OpenFileRelate3 = True
End Function

Function SetRelation3( )
ClassRel = relate4init( classes )
enrollRel = relate4createSlave( classRel, enrollment, "CODE", codeTag )
studentRel = relate4createSlave( enrollRel, student, "STU_ID_TAG", idTag )

If Not cbError( ) Then SetRelation3 = True


End Function

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

Sub ListRecsRelate3( expr As String, direction As Long )


Static querySet As Integer 'Query count

rc = relate4querySet( classRel, expr )


rc = relate4sortSet( classRel, "STUDENT->L_NAME + STUDENT->F_NAME" )
rc = relate4type( enrollRel, relate4scan )

If cbError( ) Then Exit Sub

If direction > 0 Then


rc = relate4top( classRel )
endVal = r4eof
Else
rc = relate4bottom( classRel )
endVal = r4bof
End If

Form1.List2.AddItem "Relational Query # " + Str$( querySet + 1 )


Form1.List2.AddItem expr
Form1.Lis t2.AddItem String$( 30, "*" )
Form1.List2.AddItem ""

Do While rc <> endVal


PrintRecRelate3
rc = relate4skip( classRel, direction )
Loop

Form1.List2.AddItem ""
querySet = querySet + 1
End Sub

USER.FRM List1_DblClick( )
'Relate3 sample code

List2.Clear

If Not OpenFileRelate3( ) Then Exit Sub

If Not SetRelation3( ) Then Exit Sub

Call ListRecsRelate3( "CODE = 'MATH114 '", 1 )


Call ListRecsRelate3( "CODE = 'CMPT411 '", -1 )

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

Dim studentId As Long, firstName As Long 'FIELD4


Dim lastName As Long, studentAge As Long
Dim classCode As Long

Global classRel As Long, enrollRel As Long 'RELATE4


Dim studentRel As Long
Global master As Long, slave As Long

Dim nameTag As Long, idTag As Long 'TAG4

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" )

If cbError() Then Exit Function

studentId = d4field( student, "ID" )


firstName = d4field( student, "F_NAME" )
lastName = d4field( student, "L_NAME" )
studentAge = d4field( student, "AGE" )
classCode = d4field( enrollment, "C_CODE_TAG" )

nameTag = d4tag( student, "NAME" )


idTag = d4tag( enrollment, "STU_ID_TAG" )

Call d4tagSelect( student, nameTag )

If cbError( ) Then Exit Function

OpenFileRelate4 = True
End Function

Function SetRelation4( )
master = relate4init( student )
slave = relate4createSlave( master, enrollment, "ID", idTag )

If Not cbError( ) Then SetRelation4 = True


End Function

Sub SeekRelate4( key As String )


rc = d4seek( student, key )
rc = relate4doAll( master )
End Sub
86 CodeBase

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

If Not OpenFileRelate4( ) Then Exit Sub

If Not SetRelation4( ) Then Exit Sub

' <-----15------><-------15---->
Call SeekRelate4( "Tyler Harvey " )

Print "Seek #1"


PrintRecRelate4
Print

Print "Seek #2"


Call SeekRelate4( "Miller Albert " )
PrintR ecRelate4

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

relate4doOne A similar function to relate4doAll is relate4doOne. This function accepts


the RELATE4 pointer to a slave and performs a lookup on that slave.
Any query and sort expressions are ignored by functions
relate4doAll and relate4doOne. Consequently, these functions
WARNING provide somewhat independent functionality. Using them in
conjunction with relate functions such as relate4top, relate4bottom,
and relate4skip is not particularly useful.

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 "Compo site Rec. #" + Str$( recNo + 1 )


Form1.List2.AddItem String$( 30, "-" )

Do While relation <> 0


dbf = relate4data( relation )
For j = 1 To d4numFields( dbf )
Form1.List2.AddItem f4memoStr( d4fieldJ( dbf, j ) )
Next
rc = relate4next( relation )
Loop

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

Global fieldInfo() As FIELD4INFO


Global tagInfo() As TAG4INFO

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

Query optimization is automatically used in the following situations.

Query Expression Explanation


L_NAME = "SMITH" L_NAME is a tag expression which is compared to a constant.
L_NAME >= "S" .AND. Each sub expression compares a tag expression against a
L_NAME <= "T" constant, and so the entire query can use Query Optimization.
L_NAME = "SMITH" .AND. Only the first sub expression can use Query Optimization because
F_NAME = "JOE" there is no tag based on F_NAME.
UPPER(COMPANY) = "IBM" Again, this is a tag expression compared to a constant. Even
though the expression contains a function (UPPER), Query
Optimization is still used.
UPPER(COMPANY) = "IBM" Both parts of the expression use Query Optimization, since both
.AND. L_NAME="SMITH" sides compare a tag expression to a constant value.
AGE > 50 Even though the tag is Numeric, the expression can still be
optimized.
AGE > 25*2 25*2 is still a constant even though it is a complicated constant.
DTOS(DT) >= "19930101" This would return all records where the date value in the DT field is
greater than or equal to January 1,1993. If the tag expression was
DT instead of DTOS(DT), then a query expression such as
DT>=CTOD("01/01/93") would also use Query Optimization. ( Be
careful when using CTOD because the format of its parameter
depends on the data picture set in code4dateFormat.)
COMMENT = "SALE" In this case the tag was set using r4descending. Query
Optimization can be used because CodeBase is indifferent to the
ordering of the tag.
PHONE = "555 6031" Query Optimization can utilize unique tags as long the tag has not
been specified with r4uniqueContinue. r4unique prevents
duplicate records from being added to the data base. Therefore,
r4unique does not act as a filter for the tag, unlike
r4uniqueContinue.
Note that when the index is opened, the unique code is
automatically set to the default value of
CODE4.errDefaultUnique, which is r4uniqueContinue. Use
t4uniqueSet to set unique code to r4unique before making a
query. See the "Unique Keys" section of the "Indexing" chapter of
this guide for more information.

The following expressions cannot use the Query Optimization. Queries on


these expressions are not optimized and take a longer time to execute.
Query Optimization 93

Query Expression Explanation


F_NAME = "JOE" There is no tag based on F_NAME.
COMPANY = "IBM" There is no tag based on COMPANY. The tag is
UPPER(COMPANY). Again, the tag expression must match
the expression being compared to the constant exactly.
L_NAME = F_NAME This expression cannot be optimized because F_NAME is not
a constant.
AMOUNT = 1000.00 Query Optimization will not be used because the tag contains
a filter.
ADDRESS = "104 ELM ST" Query Optimization will not be used because the tag contains
a filter, even though the filter is useless. Tags with filters are
useless, regardless of the filter result.
PRODUCT ="GIZMO" Query Optimization will not be used because the tag specifies
r4uniqueContinue. r4uniqueContinue acts like a filter,
allowing duplicate records in the database but not in the index.
PRODUCT + ID ="GIZMO 13445" Query Optimization will not be used because the tag consists
of two fields.

How To Use Query Optimization is automatically enabled whenever a relation


Query operation occurs. CodeBase uses query optimization on the top
Optimization master data file transparently when retrieving records from the
composite data file.
Query optimization, as described above, operates only on the top
master data file and only when a tag sort ordering corresponds to a
portion of the query expression. All you need to do is ensure that the
top master data file has an appropriate index file open.
How to tell if It is possible to determine whether Query Optimization can be used
Query by calling the function relate4optimizeable. This function returns
Optimization true (non-zero) when the relation is able to use the Query
can be used Optimization and it returns false (zero) when it can not. If there is
insufficient memory, the Query Optimization will not be invoked,
even if relate4optimizeable returns true (non-zero).
Call relate4optimizeable before functions that may use query
optimization. If false (zero) is returned, it may be possible to create a
new index file for those queries that can not use Query Optimization.
In the above scenario, the expression "F_NAME = 'JOE' ", is not able
to use query optimization. If a new index were built on "F_NAME",
the execution time of the query would be improved.
94 CodeBase

Memory The activation of the Query Optimization also depends on the


Requirements compiler.
of Query When a 16 bit compiler is used, the Query Optimization will be able
Optimization to handle 500,000 records. If the data base has more than half a
million records, the query will be made without using query
optimization.
It is recommended that 32 bit compiler be used when using data bases
that have more than 500,000 records. Query Optimization will be
able to handle 32 billion records when a 32 bit compiler is used.
Date Functions 95

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

Function ValidDate( dateS As String )

Dim rcl As Long

rcl = date4long( dateS )


If rcl > 0 Then ValidDate = True
End Function

Sub HowLongUntil( aMonth As Integer, aDay As Integer, title As String )


Dim todayStandard$, today$, theDate$, dow$
Dim julianToday&, julianDate&, days&
Dim theYear%

Call date4today( todayStandard )


Call date4format( todayStandard, today, "MMM DD/CCYY" )

Form1.Print "Today's date is " + today


96 CodeBase

theYear = date4year( todayStandard )


theDate = Format$( theYear ) + Format$( aMonth, "00" ) + Format$( aDay, "00" )

'Convert dates to julian days


julianToday = date4long( todayStandard )
julianDate = date4long( theDate )

' 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

'Calculate the number of days to the 'theDate'


days = julianDate - julianToday

'Get day of week


days = date4cdow( theDate )

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

Dim request As String, standard As String


Dim birth As String
request = "Please enter your birthday" + lf
request = request + "in MMM DD/CCYY fo rmat"
request = request + lf + "eg Dec 23/1993"

Call HowLongUntil( 12, 25, "Christmas" )

Do
birth = InputBox$( request, "Date", "Jan 01" )
If birth = "" Then Exit Sub
Call date4init( standard, birth, "MMM DD\CCYY" )
Loop While Not ValidDate( standard )

Call HowLongUntil( date4month(standard), date4day(standard), "your next birthday" )

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

Figure 6.1 Date Conversions

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 standard date string to another format, the date4format


function is used. This function uses a provided date picture to format the
date string.
code fragment from Call date4format( todayStandard, today, "MMM DD/CCYY" )
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

rcl = date4long( dateS )


If rcl > 0 Then ValidDate = True
End Function

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

Miscellaneous CodeBase also provides the following miscellaneous date functions:


Functions • date4isLeap Returns true (non-zero) if the date falls within a leap year
and false (zero) otherwise.
• date4today Initializes a date string with today's date.
Memory Optimizations 99

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

• OPT4ALL Same as OPT4EXCLUSIVE except that shared files that are


locked are also write optimized.
If read optimization is disabled, then write optimization is also
Note disabled.

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( )

rc = code4optimize( cb, OPT4ALL )


rc = code4optimizeWrite( cb, OPT4OFF )
db1 = d4open( cb, "DATA1" )

rc = code4optimizeWrite( cb, OPT4ALL )


db2 = d4open( cb, "DATA2" )

rc = code4optimize( cb, OPT4OFF )


rc = code4optimizeWrite( cb, OPT4OFF )
db3 = d4open( cb, "DATA3" )

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( )

db1 = d4open( cb, "DATA1" );


rc = d4o ptimize( db1, OPT4ALL );
rc = d4optimizeWrite( db1, OPT4OFF );

db2 = d4open( cb, "DATA2" );


rc = d4optimize( db2, OPT4ALL);
rc = d4optimizeWrite( db2, OPT4ALL );

db3 = d4open( cb, "DATA3" );


rc = d4optimize( db3, OPT4OFF );
rc = d4optimizeWrite( db3,OPT4OFF );

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

Using memory read optimization in a network environment means that


previously read database information is buffered in memory. The next time
the information needs to be read, it can be quickly fetched from local
memory. The disadvantage of this is that if the information has been
changed by another application, the most recent piece of disk information
may not be retrieved. At worst, one half of a read record could be an up-to-
date version and the other half an older version. If read optimization is being
used on memo files being updated by another user, it is theoretically possible
to be returned an old or garbled memo entry. If the file is locked, using read
optimization is completely safe because it cannot be updated by another user.
Consequently, it is necessary to be careful to only use read optimization
under appropriate circumstances.
Using memory write-optimization on shared files is potentially even more
hazardous than using read-optimization. This is because information is
written to disk only when the memory buffers are full. Consequently, from
the perspective of any user reading the changed information, the information
can appear as corrupt. If other applications are using index files which
appear corrupt, they can generate errors. It is not quite as bad as it might
sound because CodeBase is programmed with extensive error checking and
reacts appropriately to most apparent corruption.
Allowing one application to write optimize an index file, while
another application may use the same index file, can lead to
WARNING unpredictable results in the application reading the index file.

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.

For more examples of memory optimization, refer to the "Optimization"


section of the "Multi-User Applications" chapter.
Multi-User Applications 103

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.

Locking At the heart of multi-user applications is locking. Locking is a way in which


multi-user database applications communicate with each other. When an
application locks some data, it is telling other applications "you cannot
modify this data". When data is locked, other applications can still read the
data. However, no other application can lock or modify the data.
Operations that Locking is automatically performed by CodeBase before data is written to
require locking
disk. It is necessary to lock data before it is written to keep two applications
from updating the same data at the same time. This avoids the corruption
due to several applications updating the same index or memo file at the same
time.
When a field is to be modified, the record should be locked to prevent
multiple users from changing the same record at the same time. This
principle is enforced when CODE4.lockEnforce is true (non-zero) and the
field is modified with a field function or d4blank, d4changed, d4delete or
d4recall.
Sometimes it is appropriate for an application to lock data before reading it.
This is done to keep other applications from updating the data while the lock
is present.
104 CodeBase

Supported CodeBase supports a variety of locking protocols that allow applications to


Locking be multi-user compatible with applications built using other products. When
Protocols you use a CodeBase library that uses a specific type of index file
compatibility, you also automatically get the multi-user locking compatibility
for the DBMS that uses those types of files. The supported locking
protocols are as follows:
• FoxPro (This is the default locking protocol). This locking protocol is
employed when using a FoxPro compatible CodeBase library. This
provides multi-user compatibility with FoxPro 2.x and FoxPro 3.0.
• dBASE IV The locking protocol is compatible with dBASE IV when a
dBASE IV compatible CodeBase library is used.
• Clipper Clipper 5.x locking compatibility is provided when using one
of the Clipper compatible CodeBase libraries.
The product versions listed above were the versions which were multi-user
compatible with CodeBase when this document was written. CodeBase may
be updated to support additional product versions. Check the
'README.TXT' file on your CodeBase disk to determine exactly which
versions are currently supported.
Types Of CodeBase performs several types of locking, although the only locks that you
Locking need to be concerned with are record and file locks. The types of locks are
listed as follows:
• Record Locking When a record is locked, that data file record cannot
be updated by other applications. This is the lowest level of locking (ie.
you cannot lock fields).
• Data File Locking When a data file is locked, no records in the data file
may be updated by other applications. In addition, a data file lock
means that no other application may append records while the data file
lock is in place.
• Index and Memo Locking CodeBase often locks and unlocks index
and memo files when they are updated. However, you do not need to be
concerned about this since it is automatic.

Creating Creating well behaved multi-user applications, that is applications that do


Multi-User not lock portions of files for long lengths of time, is relatively easy using
Applications CodeBase. If you use the default CODE4 flag settings, there are only a few
things you must consider when writing multi-user applications.
Multi-User Applications 105

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

Sub AddRecord( dbf As Long, field As Long )


Dim buf As String

buf = InputBox( "Enter new record", "AddRecord" )

rc = d4appendStart( dbf, 0 )
Call f4assign( field, buf )
rc = d4append( dbf )

rc = d4unlock( dbf )
End Sub

Sub FindRecord( dbf As Long )


Dim buf As String

buf = InputBox( "Enter Name to Find", "FindRecord" )

rc = d4seek( db, buf )

If rc = r4success Then
MsgBox "Record Found"
Else
MsgBox "Not Found. Return code = " + S tr$( rc )
End If
End Sub

Sub ModifyRecord( dbf As Long, field As Long )


Dim buf As String
Dim oldLockAttempts As Integer
Multi-User Applications 107

'Save current value of CODE4.lockAttempts and set to one attempt only


oldLockAttempts = code4lockAttempts( cb, 1 )

rc = d4lock( dbf, d4recNo( dbf ) )

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

rc = code4lockAttempts( cb, oldLockAttempts )


End Sub

Sub ListData( dbf As Long, field As Long )


Dim file As String, oldOpt As Long

file = d4alias( dbf )

Form1.List2.Clear

'Reduce optimization buffers when using debugging library


oldOpt = code4memStartMax( cb, 65535 )

rc = code4optStart( cb )
rc = d4optimize( db, OPT4ALL )

rc = d4top( db )

If cbError( ) Then Exit Sub

Form1.List2.AddItem "File Name: " + file$


Form1.List2.AddItem String$( 35, "-" )
Form1.List2.AddItem ""

Do While rc = r4success
Form1.List2.AddItem "Rec. #: " + Str$( d4recNo( db ) )
Form1.List2.AddItem f4str( field )
Form1.List2.AddItem ""
rc = d4skip( dbf, 1 )
Loop

rc = d4optimize( dbf, OPT4OFF )


rc = code4optSuspend( cb )
oldOpt = code4memStartMax( cb, oldOpt )

End Sub

USER.FRM List1 _DblClick( )


'Multi sample code

Dim opt As String, choice As String

save1 = code4accessMode( cb, OPEN4DENY_NONE ) 'Open in shared mode


save2 = code4readOnly( cb, 0 ) 'Read an d Write
save3 = code4readLock( cb, 0 ) 'Don't lock when reading
save4 = code4lockAttempts( cb, WAIT4EVER ) 'Try forever on locks
save5 = code4lockEnforce( cb, 1 ) 'lock when modifying record

db = d4open( cb, fPath + "NAMES" )

If code4errorCode( cb, 0 ) < 0 Then Exit Sub

fName = d4field( db, "NAME" )


nameTag = d4tag( db, "NAME" )

Call d4tagSelect( db, nameTag )

opt$ = "Enter a Command: " + lf


opt$ = opt$ + "'a', 'f', 'l', 'm', or 'x'"

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" )

Select Case choice$


Case "a", "A"
Call AddRecord( db, fName )
Case "f", "F"
Call FindRecord( db )
Case "l", "L"
Call ListData( db, fName )
List2.Visible = True
Exit Do
Case "m", "M"
Call ModifyRecord( db, fName )
Case "x", "X", ""
rc = code4close( cb )
Cls
Exit Do
Case Else
MsgBox "Invalid Option", MB_OK, "Error"
End Select
Loop
rc = c ode4accessMode(cb, save1)
rc = code4readOnly(cb, save2)
rc = code4readLock(cb, save3)
rc = code4lockAttempts(cb, save4)
rc = code4lockEnforce(cb, save5)

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" )

Select Case choice$


Case "a", "A"
Call AddRecord( db, fName )
Case "f", "F"
Call FindRecord( db )
Case "l", "L"
Call ListData( db, fName )
List2.Visible = True
Exit Do
Case "m", "M"
rc = code4close( cb )
Cls
Exit Do
Case Else
MsgBox "Invalid Option", MB_OK, "Error"
End Select
Loop
Multi-User Applications 109

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

buf = InputBox( "Enter new record", "AddRecord" )

rc = d4appendStart( dbf, 0 )
Call f4assign( field, buf )
rc = d4a ppend( dbf )

rc = d4unlock( dbf )
End Sub

Finding The findRecord function is significant, from a multi-user perspective, more


Records for what it does not do rather than what it does. Specifically, there is no
multi-user logic necessary in this function. When CODE4.readLock is false
(zero), the data file functions do not perform any automatic locking as the
data file is being read. Accordingly, you can search using index files and
read data file records without having any extra multi-user logic present.
Code fragment Sub FindRecord( dbf As Long )
Dim buf As String
from Multi
buf = InputBox( "Enter Name to Find", "FindRecord" )

rc = d4seek( db, buf )

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

'Save current value of CODE4.lockAttempts and set to one 'attempt


only

oldLockAttempts = code4lockAttempts( cb, 1 )

rc = d4lock( dbf, d4recNo( dbf ) )


110 CodeBase

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

rc = code4lockAttempts( cb, oldLockAttempts )


End Sub

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

'Reduce optimization buffers when using debugging library


oldOpt = code4memStartMax( cb, 65535 )

rc = code4optStart( cb )
rc = d4optimize( db, OPT4ALL )

rc = d4top( db )

If cbError( ) Then Exit Sub


Multi-User Applications 111

Form1.List2.AddItem "File Name: " + file$


Form1.List2.AddItem String$( 35, "-" )
Form1.List2.AddItem ""

Do While rc = r4success
Form1.List2.AddItem "Rec. #: " + Str$( d4recNo( db ) )
Form1.List2.AddItem f4str( field )
Form1.List2.AddItem ""
rc = d4skip( dbf, 1 )
Loop

rc = d4optimize( dbf, OPT4OFF )


rc = code4optSuspend( cb )
oldOpt = code4memStartMax( cb, oldOpt )

End Sub

Repeated The next example multi-user application demonstrates how to append


Appending records from one data file to another.
PROGRAM The program Append demonstrates how to append records from one data file
Append to another. It assumes that data files "TO_DBF.DBF" and
"FROM_DBF.DBF" are both present and that they both have a field named
"INFO".
USER.BAS

Function cbError( )
If code4errorCode( cb, 0 ) < 0 Then
cbError = True
Else
cbError = False
End If
End Function

USER.FRM List1_DblClick( )
'Append sample code

Dim dbTo As Long, dbFrom As Long


Dim in foTo As Long, infoFrom As Long
Dim rc1 As Integer, rc2 As Integer, rcl As Long

rc = code4optimize( cb, OPT4ALL )


rc = code4optimizeWrite( cb, OPT4ALL )
rcl = code4memStartMax( cb, 262140 )

dbTo = d4open( cb, "TO_DBF" )


dbFrom = d4open( cb, "FROM_DBF" )

Print "Record count TO_DBF: "; d4recCount( dbTo )

If cbError( ) Then Exit Sub

rc = code4optStart( cb )

infoTo = d4field( dbTo, "INFO" )


infoFrom = d4field( dbFrom, "INFO" )

rc = code4lockAttempts( cb, 1 )
rc1 = d4lockFile( dbTo )
rc2 = d4lockFile( dbFrom )

If rc1 Or rc2 Then


MsgBox "Locking Failed"
Exit Sub
End If

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 )

Print "Record count TO_DBF: "; d4recCount( dbTo )

rc = code4close( cb )

In this application, both memory read optimization and memory write


optimization are used. In order to do this, the CODE4.optimize and
CODE4.optimizeWrite defaults are changed before the files are opened.
Please refer to the "Memory Optimizations" chapter for details.
Code fragment rc = code4optimize( cb, OPT4ALL )
rc = code4optimizeWrite( cb, OPT4ALL )
from Append
dbTo = d4open( cb, "TO_DBF" )
dbFrom = d4open( cb, "FROM_DBF" )

Print "Record count TO_DBF: "; d4recCount( dbTo )

If cbError( ) Then E xit Sub

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

Code fragment rc = d4top( dbFrom )


from Append Do While rc = r4success
rc = d4appendStart( dbTo, 0 )
Call f4assignField( infoTo, infoFrom )
rc = d4skip ( dbFrom, 1 )
Loop

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 )

If rc1 Or rc2 Then


MsgBox "Locking Failed"
Exit Sub
End If

Another way to avoid deadlock is to be careful to always lock data files in


the same order. If one application locks data file "FROM_DBF" before
"TO_DBF" using unlimited retries, then all applications using unlimited
retries should do the same.
If applications always lock data files in the same order, deadlock is not
possible. This is because once the first application succeeds in locking the
first data file, other applications will wait for the first application to finish its
locking, do what it needs to do and then unlock all of the data files. The best
way to ensure you are always locking data files in the same order is to lock
and unlock all data files together.
114 CodeBase

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 )

rc = code4accessMode( cb, OPEN4DENY_NONE )


db = d4open( cb, "DATAFILE" ) 'Open in shared mode
Multi-User Applications 115

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

If CODE4.lockAttempts is set to any value greater than one, CodeBase


keeps trying the lock at approximately one second intervals until either a lock
is established or the number of attempts equals CODE4.lockAttempts. If
the lock fails, a value of r4locked is returned from the function attempting
the lock.
If you wish to perform a special operation if the lock fails, such as displaying
a message to the user, you should set CODE4.lockAttempts to one and test
for return values. Valid settings for CODE4.lockAttempts is WAIT4EVER
(-1) or any value greater than or equal to 1. Any other value is undefined.
116 CodeBase

Automatic When CODE4.readLock is true, several CodeBase functions automatically


lock records before reading them. This ensures that no other application can
Record
modify a record that your application has in its record buffer.
Locking
Unfortunately, locking data file records when they are only being read can
create unnecessary locking contention. Simply put, this would increase the
chance of a record being locked when an end user just wants to look at it.
Consequently, the default of no automatic read locking is appropriate for
most multi-user applications.
Code fragment rc = code4readLock( cb, 0 )
from Multi

The following list of functions perform record locking when the


CODE4.readLock flag is set to true: d4bottom, d4go, d4position,
d4seek, d4seekDouble, d4seekN, d4seekNext, d4seekNextDouble,
d4seekNextN, d4skip, and d4top.

Enforced Locking a record before it is modified is very important in the multi-user


configuration, since it ensures that only one application can edit a record at a
Locking
time. The CODE4 member variable CODE4.lockEnforce can be used to
ensure that an application has explicitly locked a record before it is modified
with a field funcition or the following data file functions: d4blank,
d4changed, d4delete or d4recall. When CODE4.lockEnforce is true
(non-zero), an e4lock error is generated when a attempt is made to modify
an unlocked record.
An alternative method of ensuring that only one appliction can modify a
record at a time is to deny all other applications write access to a data file.
Write access can be denied to other applications by passing
OPEN4DENY_WRITE or OPEN4DENY_RW to code4accessMode before
opening the file. Thus, it is unnecessary to explicitly lock the record before
editing it, even when CODE4.lockEnforce is true (non-zero), since no other
application can write to the data file. This method suffers from the same
disadvantage as discussed in "Exclusive Access" section of this chapter. The
restricted access to the files prevents the creation of practical multi-user
applications.
Performance Tips 117

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

Another important CODE4 variable that can affect performance in a network


environment is CODE4.lockDelay, which determines how long to wait
between lock attempts. Consider this variable carefully because if the value
is too small, it can cause an increase in network traffic and possibly a
decrease network performance.

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 OpenLogFile( ) 'Opening or creating a log file is only


'required in Stand-Alone mode

'Zero length string is passed as the log file name


'so the default "C4.LOG" is used as the log file name
122 CodeBase

rc = code4logOpen( cb, fPath + "C4.LOG", "user1" )


If rc = r4noOpen Then
rc = code4logCreate( cb, fPath + "C4.LOG", "user1" )
End If
If Not cbError( ) Then
OpenLogFile = True
Else
OpenLogFile = False
End If
End Function

Function OpenDataFileTransfer( )

db = d4open( cb, fPath + "BANK" )

acctNo = d4field( db, "ACCT_NO" )


balance = d4field( db, "BALANCE" )

acctTag = d4tag( db, "ACCT _TAG" )


balTag = d4tag( db, "BAL_TAG" )

If Not cbError( ) Then


OpenDataFileTransfer = True
Else
OpenDataFileTransfer = False
End If
End Function

Function Credit( toAcct As Long, amt As Double )


Dim newBal As Double

Call d4tagSelect( db, acctTag )


rc = d4seekDouble( db, toAcct )

If rc <> r4success Then


Credit = rc
Exit Function
End If

newBal = f4double( balance ) + amt

Call f4assignDouble( balance, newBal )

If Not cbError( ) Then


Credit = r4success
Else
Credit = -1
End Function

Function Debit( fromAcct As Long, amt As Double )


Debit = Credit( fromAcct, -amt )
End Function

Sub Transfer( fromAcct As Long, toAcct As Long, amt As Double )


Dim rc1 As Integer, rc2 As Integer

rc = code4tranStart( cb )

rc1 = Debit( fromAcct, amt )


rc2 = Credit( toAcct, amt )

If rc1 = r4success And rc2 = r4success Then


rc = code4tranCommit( cb )
Else
rc = code4tranRollback( cb )
End If

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 = code4errOpen( cb, 0 ) 'Suppress errors when opening files


rc = code4safety( cb, 0 )
rc = code4lockAttempts( cb , 5 )
If Not OpenLogFile() Then Exit Sub
If Not OpenDataFileTransfer() Then Exit Sub
PrintRecords
'The account number 56789 d oesn't exist in the data file
'the transfer is aborted and the data file is not changed
Call Transfer( 12345, 56789, 200# )
PrintRecords

'Both accounts exist so the transfer is completed and


'the data file is updated
Call Transfer( 12345, 55555, 150.50 )
PrintRecords

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 )

'... code to be treated as a unit

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

How a CodeBase has implemented transactions in the following manner. When a


Transaction transaction is initiated with code4tranStart, all the changes to the database
Works are recorded in a log file. The log file stores both the old value and the new
value, as well as who made the change. At every step during a transaction,
each change is recorded in the log file and then the database is modified.
After the transaction has been committed using code4tranCommit, the
changes to the database are permanent. During a rollback,
code4tranRollback uses the original values from the log file to reverse all
the changes made to the database.

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

rc = code4log( cb, LOG4ON )

datafile = d4open( cb, "DATA.DBF" )


'logging is turned off for this
'particular data file and the previous logging
'status is returned
rc = d4log( datafile, 0 )
'the current logging status is returned
rc = d4log( datafile, -1 )

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 )

rc = code4logOpen( cb, "", "user1" )

If rc = r4noOpen Then
rc = code4logCreate( cb, "", "user1" )
End If
If Not cbError( ) Then OpenLogFile = True
End Function

If code4logOpen or code4logCreate have not been explicitly called,


d4open, d4create and code4tranStart automatically try to open a log file
by calling code4logOpen. code4logOpenOff can be used to instruct
d4open, d4create and code4tranStart not to automatically open the log
file. When code4logOpenOff is used, no transactions can start unless the
log file is opened explicitly by the application.
126 CodeBase

the log file is not rc = code4logOpenOff( cb )


datafile = d4open( cb, "DATA.DBF" )
automatically
opened

Logging in In the client-server configuration, the automatic logging and transaction


logging are handled by the server. Calling the functions code4logCreate,
the Client-
code4logOpen and code4logOpenOff in a client application will have no
Server case effect, since the log file is controlled by the server. These functions will
always return r4success when called by a client application and will have
no effect on the log file.

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

When an application is running in a multi-user environment, it is strongly


recommended that records be locked before they are modified to prevent
more than one user access to the record at the same time. In this case, it is
prudent to set CODE4.lockEnforce to true ( non-zero) to ensure that a
record is explicitly locked before it is modified. If it is known before hand
that many changes will be made, use d4lockAll, or use d4lockAddAll
followed by code4lock to explicitly lock the files.
Transaction Processing 127

If the file is explicitly locked, it must be explicitly unlocked by calling


code4unlock or d4unlock after the transaction is committed or rolled back.
If an attempt is made to unlock files during a transaction, an error is
generated. **
128 CodeBase
Index 129

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

—F— buffering. See Memory Optimizations


locking. See Locking
Field Attributes opening exclusively, 105, 114, 116
decimal, 8, 25 overwriting, 26
length, 8, 25 Filters
name, 8, 25 queries. See Queries
type, 8, 25 tag filters. See Tags
Field Functions FoxPro
f4assign, 28, 96 descending order tags, 42
f4assignDouble, 29 index files, 37
f4assignInt, 29 index filters, 47
f4assingLong, 29 locking protocol, 104
f4decimals, 34
f4double, 22
f4int, 22 —G—
f4len, 34
Group Files, 60
f4long, 22
bypassing, 60
f4memoAssign, 28
creating, 60
f4memoStr, 21
f4name, 34
f4str, 21, 96 —I—
f4type, 34
Field Types Index Files, 37
character. See Character Fields .CDX, 10, 37, 39, 43, 60
Date. See Date Fields .MDX, 10, 37, 42, 43, 60
determining, 34 .NTX, 10, 37, 42, 43, 60, 61, 63, 64
Floating Point. See Numeric Fields buffering. See Memory Optimizations
Logical. See Logical Fields creating, 39, 43
Memo. See Memo Fields descending order. See Tags
Numeric. See Numeric Fields exclusive access. See Exclusive Access
FIELD4, 14, 20, 34 filter expression, 49, 50
FIELD4INFO index expression, 39
dec member, 25 index key, 39
len member, 25 locking. See Locking
name member, 25 non-production indexes, 39, 42
obtaining a copy of, 34 opening, 44, 63
type member, 25 production indexes, 10, 39, 42, 43, 44
Fields reindexing, 65
accessing, 19 reindexing a single file, 65
assigning contents, 28 reindexing all files, 65
character fields. See Character Fields search key. See Seeking
creating, 25 seeking. See Seeking
date fields. See Date Fields tags. See Tags
decimal, 34 unique keys, 42, 50
field attributes. See Field Attributes INDEX4, 43, 65
field number, 20 Index4 Functions
field type, 21 i4create, 43, 60, 65
floating point fields. See Numeric Fields i4open, 44, 63, 65
generic access, 20, 21 i4reindex, 105
length, 34 i4tagInfo, 65
name, 34 Indexes. See Index Files
numeric fields. See Numeric Fields
numeric values, retrieving, 22
—J—
qualifying, 33, 69, 82
referencing, 19 Julian Day Format. See Date Operations
referencing by field number, 20
referencing by name, 20
retrieving contents, 21
Files
132 CodeBase

—K— Memory Optimizations, 99


activating, 100
Keys buffering, 99, 117
index key. See Index Files memory requirements, 101, 117
lookup key. See Relations multi-user mode, 101, 110
search key. See Seeking read optimizations, 99, 102
refreshing the buffers, 101
single user mode, 101
—L— specifying files, 99, 100
suspending, 100
Locking
using, 99
automatic data file locking, 105
write optimizations, 99, 102
automatic record locking, 105, 116
Multi-User
automatic record unlocking, 106, 126
appending records, 109, 111
Clipper locking protocol, 104
applications, 103
data file locking, 104
applications, creating, 104
dBASE IV locking protocol, 104
common tasks, 106
efficient locking, 117
deadlock, 113
enforced, 103, 105, 110, 116
exclusive access. See Exclusive Access
explicit, 28, 116, 126
finding records, 109
FoxPro locking protocol, 104
listing records, 110
group locks, 114
lock attempts. See Locking
index and memo file locking, 104
locking protocols. See Locking
lock attempts, 115, 118
memory optimizations. See Memory Optimizations
protocols, 104
modifying records, 109
record locking, 104
opening files, 108
unlocking, 106, 126
optimizations, 110
Locking Protocols. See Locking
read only mode. See Read Only Mode
Log Files, 124, 125
LOG4ALWAYS, 124
LOG4ON, 124 —N—
LOG4TRANS, 124
Logging Natural Order
automatic, 124 selecting, 46
changing the status, 29, 124 Numeric Fields
Client-Server configuration, 124 assigning values, 28, 29
current status, 114, 115 creating, 25
Stand-Alone Configuration, 126 retrieving contents, 22
transaction, 121, 124
Logical Fields
assigning values to, 29
—O—
creating, 25
Optimizations. See Memory Optimizations
retrieving contents, 21
using Query Optimizations, 94
Lookups. See Relations

—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

Production Indexes. See Index Files, production undeleting, 32


indexes width, 33
Reindexing. See Index Files
RELATE4, 76
—Q— relate4approx, 78
relate4blank, 79
Queries, 67, 80
relate4exact, 78
creating, 81
relate4scan, 78
generating, 81, 83
Relate4set Functions
query expression, 81
lockAdd, 114
query set, 80, 81, 83
relate4skipRec, 79
sort expression, 82
relate4terminate, 79
sorting, 82
Relation Functions
using Query Optimization, 89
relate4bottom, 79
Query Expression. See Queries
relate4changed, 83
Query Optimization, 89
relate4createSlave, 77
how to use, 93
relate4doAll, 86
queries, 90
relate4doOne, 86
relate4optimizeable, 93
relate4errorAction, 79
relations, 89
relate4init, 76
requirements, 90
relate4lockAdd, 114
Query Set. See Queries
relate4skip, 79
relate4skipEnable, 79, 80
—R— relate4sortSet, 81, 82
relate4top, 79
r4after, 58 relate4type, 78
r4bin, 25 Relation Set. See Relations
r4date, 25 Relation Types. See Relations
r4descending, 42 Relations, 67
r4eof, 58 approximate match relation, 74
r4float, 25 complex, 71
r4gen, 25 composite data file, 69
r4locked, 115 composite record, 68
r4log, 25 creating, 75
r4logOff, 124, 125 error action, 79
r4logOn, 124 exact match relation, 73
r4memo, 25 lookup key, 67
r4num, 25 lookups, 85, 86
r4str, 25 many to many relation, 74
r4success, 12, 19, 58 many to one relation, 73
r4unique, 51 master, 67, 77
r4uniqueContinue, 51 master expression, 67, 77
Read Only Mode multi-layered, 71
opening files in, 105, 114, 115 one to many relation, 74
Read Optimizations. See Memory Optimizations one to one relation, 73
Record Buffer. See Data Files relation set, 71, 77
Record Buffering. See Memory Optimizations scan relation, 74
Record Count, 16 skipping, 79
Record Number, 8 skipping, backwards, 79
Records, 8, 79 slave tag, 67, 77
adding, 27 slaves, adding, 77
appending, 27 slaves, creating without tags, 78
buffering. See Memory Optimizations top master, 71, 76
deleting, 29, 31 top master, specifying, 76
deletion status, 31 types, 73
listing records, 75 using Query Optimization, 89
physically removing, 32 Removing
recalling, 32 removing records. See Records
removing, 31 Rereading, 101
134 CodeBase

—S— unique member, 42


Tags
Seeking creating, 42
character tags, 55 currently selected, 46
compound keys, 58 default tag, 46
date tags, 56 deleting, 44
exact matches, 57 descending order, 42
incremental, 57 effects of, 47
no matches, 58 filters, 42, 47
numeric tags, 56 filters, creating, 49
partial matches, 58 index expression, 39, 42
performing seeks, 55 index expression, compound, 39
search key, 52 index key, 39, 57
soft seek. See Seeking, partial matches name, 42
Single User natural order. See Natural Order
memory optimizations, 101 referencing, 45
Slave Data Files. See Relations referencing by name, 46
Soft Seek. See Seeking, partial matches search key. See Seeking
Sorting seeking. See Seeking
queries. See Queries selecting, 46
records. See Indexes slave tag. See Relations
Standard Format. See Date Operations Top Master. See Relations
Strings Transactions, 121
copying to fields, 28 client-server configuration, 124
retrieving from fields, 21 committing, 121, 123
Structures implementation, 124
CodeBase structures, 12 log files, 125
logging status, 124
rollback, 121, 123
—T— stand-alone configuration, 126
starting, 121, 123
Tables, 7
Tuples, 7
Tag Functions
t4open, 64
t4uniqueSet, 92 —U—
TAG4, 19, 41, 45, 46, 54, 55, 64, 75, 77, 78, 83, 85
TAG4INFO, 42 u4free, 65
arrays, 43 Unlocking. See Locking
descending member, 42
expression member, 42
filter member, 42
—W—
name member, 42 Write Optimizations. See Memory Optimizations

You might also like