Read Only DataBase (for Delphi 1)
---------------------------------

By Gilberto Ferreira da Silva
Rio de Janeiro - Brasil

Version 1.0
November 1996

>>>>>>>>>>>>>>>>>>>> Use Delphi Editor to read this document <<<<<<<<<<<<<<<<<<<

Summary
-------

0. Version History

1. Introduction

2. Installation

3. Use

4. RDONLY Unit Interface

--------------------------------------------------------------------------------

0. Version History
   ---------------

October 1996: Beta 1 Version
First Version, only distributed to colleagues.

October 1996: Beta 2 Version
Some memory bugs allocation fixed.
Preliminary tests still running.

November 1996: Version 1.0
All property and method names changed.
First published Version.

--------------------------------------------------------------------------------

1. Introduction
   ------------

The Read Only DataBase (shortly RODB) is a group of four non-visual Delphi 1
components which allows the data recording (once) and later data reading (as
many times as necessary), just like any Data Base, with access to the first
record, to the next one, to the previous one or to the last one, with direct
access to a given physical position record (without an index).

All these capabilities are available, except the updating ability. That means
that after recording, the data can only be read.

This can be interesting for those who need to distribute a catalog of products,
or a subscribers roll, for instance. Any data which once saved will not be
further altered, may be stored in the Read Only Data Base.

Internally, RODB is a Delphi Unit, compiled as RDONLY.DCU, which must be used
to install the four components in the Data Access palette. Then, the components
may be included in any Forms and used as described here.

This document provides an outline of the Read Only DataBase, with emphasis in
the Installation and Use procedures. Technical implementation details are
not included.

Section 2, below, describes the components installation process.

Section 3 shows the available properties and methods in each component.

Section 4 is part of the RDONLY unit interface section, with the declarations
needed to interact with your programs. The components declarations themselves
are not included.

Give a look also the demo programs, one of them to create a data base
(ESRODBP.PAS) and the other one to browse it (LERODBP.PAS). Please note that the
demo programs do not finish out all use possibilities, as well as they don't
intend to establish a model of good use of RODB. Specifically, the reader
program (LERODB.PAS) does not use the ability of direct access to the data
(without an index).

Warning!
--------

RODB is supplied to you "as is", without any implicit or explicit warranties, in
what concerns correctness, performance or accordance to your needs. Use it at
your own risk. The Autor does not accept any responsibility for damages or
injuries directly or indirectly caused by RODB. In particular, RODB was created
for Delphi 1; it is not assured to work under Delphi 2.

RODB is Shareware. The non-registered version may be freely distributed,
provided that you do not do not modify the original RODB.ZIP file and that you
do not earn any direct advantage on the process (you may, of course, charge on
Shipping/Handling and Media).

There is no limitation of use in what concerns the evaluation period, nor there
are "nagging" messages asking you to register. However, if you intend to run any
RODB-based application out of Delphi development environment, you should
register. The non-registered RODB will not work out of Delphi development
environment, of course.

When you register, you receive:
-------------------------------
The source of the Unit (RDONLY.PAS).
The right of installing the registered version in ONE computer. Please e-mail
    for site licenses.
The right of distributing your RODB-based application, without additional fees.
Support directly from the Author, via e-mail. Non registered user do not have
    any support.

However, even registering, you should not:
------------------------------------------
Redistribute the compiled registered unit (RDONLY.DCU).
Redistribute the source of the unit (RDONLY.PAS).
Distribute DLL's or other units which export one or more RODB components,
    properties or methods, directly or not.

To register, send the equivalent to US$ 25.00 to the Author at the postal
address below, by check drawn on a U.S or Brazilian bank (never send cash by
mail), enclosed to a clear indication of your Name and your e-mail. As soon as
possible, the registered version (RODB_R.ZIP) will be sent to you by e-mail. By
now, RODB is distributed only by Internet.


Acknowledgements
----------------

Sergio Werner, for drawing the components icons and for testing exhaustively
    the Beta versions.
Oscar Daudt, for suggesting changes in naming the properties and methods.
Maria Teresa De Len L. Pinheiro, for translating the Manual into English.

Author's Name and Addresses
---------------------------

Gilberto Ferreira da Silva
e-mail: gilberto.silva@poboxes.com
Postal Address:
    Caixa Postal 9103
    22.272-970 Rio de Janeiro - RJ
    Brasil

--------------------------------------------------------------------------------

2. Installation
   ------------

The following procedure may be followed to install any components at Delphi 1.

a. Create a directory (C:\READONLY, for example) and unzip the distribution file
   (RODB.ZIP or RODB_R.ZIP). This will create the following files:

   esrodbp.dpr         Project file for the first demo program (which creates a
                       demo data base).

   esrodbu1.dfm        Unit files for the first demo program.
   esrodbu1.pas

   file_id.diz         Short description, for BBS's.

   lerodbp.dpr         Project file for the second demo program (which reads the
                       demo data base).

   lerodbu1.dfm        Unit files for the second demo program.
   lerodbu1.pas

   manual_e.txt        This document, manual in English.

   manual_p.txt        This manual in Portuguese.

   rdonly.dcr          Icons file for rdonly.dcu unit. Only at the registered
                       version.

   rdonly.dcu          Compiled rdonly unit.

   rdonly.pas          Source for rdonly unit. Only at the registered version.

   u_ordem.pas         Source for a unit used by the two demo programs.
                       Basically, this unit contains comparison functions
                       examples.

b. Start Delphi.

c. On the Menu Bar, choose Options and click over Install Components...

d. In the Install Components Dialog, click over the Add... button.

e. In the Add Module dialog, click over the Browse... button.

f. In the following dialog, modify the Files of Type edit box to allow
   *.DCU files. Find the rdonly.dcu file and then click over the OK button
   to go on.

g. Back to the Install Components dialog, click on OK to go on. At this
   moment, Delphi will re-compile the COMPLIB.DCL library (this will take
   some minutes...) and will include the four components on the Data Access
   palette. To see the components, you must activate the Data Access tab in the
   component palette, and stop the mouse pointer over the icons for a while.
   The Hint then will show you the name of the following components:

   1. TCReadOnlyDB (on Hint, CReadOnlyDB)
      Data base component, for the Data base creation step.

   2. TCReadOnlyIndex (on Hint, CReadOnlyIndex)
      Index component, for the Data base creation step.

   3. TRReadOnlyDB (on Hint, RReadOnlyDB)
      Data base component, for the Data base reading step.

   4. TRReadOnlyIndex (on Hint, RReadOnlyIndex)
      Index component, for the Data base reading step.

To use the demo programs, you just need to compile it and to run it. Be free
to examine and modify both programs at will.

--------------------------------------------------------------------------------

3. Use
   ---

RODB is a Data base without Tables! That means that you don't have to define
nor write at Tables. You just need to define your records and call a Write
method.

How does RODB work:
-------------------

Each record it writes, rdonly Unit returns a longint with the physical storage
position. Then, you add an entry to the suitable index (or, indexes), giving the
position and the associated  key as parameter.

RODB doesn't care about what's contained in the record. It receives a memory
area which size must be defined by you, and writes that area to the next
available position on disk. There is a maximum limit to the record's length of
65,528 characters.

On the reading phase, you provide a key and the Unit finds the actual position
and fetches the record.

Besides the indexes created by you, RODB also creates a virtual Index, called
IndexIndex, which you may use to know what Indexes are there. If you want to use
IndexIndex on reading pass, define a record exactly with the following fields
(the names don't need necessarily be the same, of course):

var
    IxIxRec = record
        IndexName: Str10;
        KeyLength: word;
        Population: longint;
        FirstOffset: longint;
        end;

where:
    IndexName stands for Index name; Str10 is defined as string [10].
    KeyLength says the length of the Index key, in bytes.
    Population is the count of Index entries.
    FirstOffset is the physical position of the first entry of the Index on the
        Data Base file.


Recording program skeleton
--------------------------

...
{ Records you plan to write }
type
    OneRecord = record
        Field1: ...
        Filed2: ...
        ...  { Any fields you want }
        end;

var
    DataBase: TCReadOnlyDB;
    Index1: TCReadOnlyIndex;
    Index2: TCReadOnlyIndex;
    ...   { Any indexes you want, too. Please note that these declarations
            aren't inserted here by you; rather, Delphi insert them based on
            your drawing of the Form. }

{$F+} { Only if you want to define a comparison or index sort watching
                                                                    functions. }

{ See demo programs. Specially, see unit u_ordem. }

function MyComparison (var Key1, Key2): CompareResult;
begin
...
end; { MyComparison }

{$F-}

procedure DataBaseCreate;
begin
DataBase.CreateFiles;
    { Creates Data Base file and two temporary files.  }
DataBase.AddIndex (Index1, @MyComparison, KL1);
    { Here, you are using your comparison function.
      KL1 is the fixed length of the Index1 key. }
DataBase.AddIndex (index2, nil, KL2);
    { Here, you are using the RODB default comparing function. }

... { AddIndex for all Indexes defined ...}

while not EndOfData do
begin
Position := DataBase.DBWrite (OneRecord, SizeOf (OneRecord));
Index1.AddEntry (Key1, SizeOf (OneRecord), Position);
    { Key1 is the field key for Index1. Note that each stored record can have a
      distinct length. Same is not true for the keys of an Index. }
Index2.AddEntry (Key2, SizeOf (OneRecord), Position);
...
end;

DataBase.DBClose (SortWatch);
    { Terminates the Data Base recording: closes the Data Base file and removes
      the temporary files. This method can waste a lot of time, depending on
      the amount of Indexes and records stored. SortWatch is a pointer to a
      procedure, written by you, which allows you to watch the Indexes sort
      progress. Specifically, in your procedure you should call
      Application.ProcessMessages to allow Windows to share the processor time
      with other processes concurrently. SortWatch may also be nil. }
...
end; { DataBaseCreate }

... { Other procedures and functions. }



Reading Program Skeleton
------------------------

...
{ Records you plan to read }
type
    OneRecord = record
        Field1: ...
        Field2: ...
        ... { The same fields you defined and stored before }
        end;

var
    DataBase: TRReadOnlyDB;
    Index1: TRReadOnlyIndex;
    Index2: TRReadOnlyIndex;
    ... { Here, besides the indexes you created, you may also define an index to
          gain access to the Index "table". To achieve that, you must set
          property IndexName (in run-time or at the Object Inspector) to
          "IndexIndex". Please note that these declarations aren't inserted here
          by you; rather, Delphi insert them based on your drawing of the Form }

{$F+} { Here, If applicable, you must define exactly the same comparing
        functions you defined in the recording program. }

{ See demo programs; Specially, see unit u_ordem. }

function MyComparison (var Key1, Key2): CompareResult;
begin
...
end; { MyComparison }

{$F-}

procedure DataBaseOpen;
begin
DataBase.DBOpen;
    { Opens Data Base file and verifies its integrity. }
DataBase.OpenIndex (Index1, @MyComparison);
    { You must use THE SAME comparing function you did at recording program. }
DataBase.OpenIndex (Index2, nil);

... { More OpenIndex calls, if applicable ... }
end;

procedure DataBaseClose;
begin
Index2.IndexClose;
Index1.IndexClose;
    { You should close each index open to save memory }
DataBase.DBClose;
end;

begin
DataBaseOpen;

{ Here, you do call the DataBase access methods, described below. }

DataBaseClose;
end.


After each operation over the Data Base you may want test property
DataBase.DBErrorCode, which reveals whether any error or exception ocurred or
not. Also, after each operation over an Index, you may test property
Index.IndexErrorCode, which reveals whether any error or exception ocurred or
not.

The zero value stands for the No Error condition for both DataBase.DBErrorCode
and Index.IndexErrorcode. Section 4 contains a complete error table. See a
textual translation function for the error table in section 4.


Methods and Properties for Recording
------------------------------------

procedure TCReadOnlyDB.CreateFiles
Creates the Data Base file and two temporary files. The two temporary files are
removed when you call DBClose method.

procedure TCReadOnlyIndex.AddIndex (var Ix: TCReadOnlyIndex;
                                                 PComp: PCompFunction; KL: word)
Adds an Index definition to the Data Base. The Data Base must be open. The Index
must already have had a valid name assigned. Valid name is a valid Pascal
variable name, except 'IndexIndex' which is reserved to the Index to Indexes.
Parameters:
    IX: An existing index.
    PComp: Pointer to a comparing function of type CompareFunction. You may also
        pass "nil" to tell RODB to activate the default comparison function,
        which is equivalent to ascending ASCII order. The function pointed by
        PComp must be declared in far mode.
        PCompFunction is declared as ^CompareFunction;
        CompareFunction is declared as function (var Key1, Key2): CompareResult;
        Key1 and Key2 are the two keys to be compared;
        CompareResult is declared as -1 .. + 1 (that is, a sub-range), where
            -1 stands for "Key1 precedes Key2",
             0 stands for "Key1 equals Key2", and
            +1 stands for "Key1 succeeds Key2".
        The demo programs use the just described mechanism.
    KL: Key length, in bytes.

function TCReadOnlyDB.DBWrite (var Buff; Len: word): longint
Stores a record on Data Base.
Parameters:
    Buff: Record to be stored.
    Len: Record length, in bytes.
    Return value: Absolute position to which the record was written.

procedure TCReadOnlyDB.DBClose (SW: PSortWatchProc)
Does all final housekeeping, closes Data Base file and removes the two
temporary files. This method can waste a lot of time, depending on the amount
of Indexes and records stored.
*** Note: Clearly, this is a point on RODB needing enhancement. The wasted time
in index closing is excessive, although it doesn't compromise the reading
performance. This point will be reviewed on the next version. By now, you are
emphatically suggested to use the sort watch mechanism (see SW parameter).
Parameter:
    SW: Pointer to a procedure, written by you, which may be used to watch the
        Indexes sort progress. Specifically, in your procedure you should call
        Application.ProcessMessages to allow Windows to share the processor time
        with other processes concurrently. SW may also be nil.
        PSortWatchProc is defined as ^SortWatchProc.
        SortWatchProc is defined as procedure (IxName: string; Phase: SortPass;
                                                                RecNo: longint).
        IxName is the name of the Index which is being sorted;
        Phase is the current step;
        SortPass is defined as (Start, OneMore, Ending), that is, an
        enumeration, where Start indicates the beginning of sort, no records
        read yet; OneMore stands for "one more record will be read"; and Ending
        indicates the end of sort, all records already ordered.
        RecNo is valid only if SortPass equals OneMore, and informs the number
        of the record which will be read.
        The procedure pointed by SW must be declared in "far" mode.
        The recording demo program uses the mechanism just described.

property TCReadOnlyDB.DBErrorCode: integer (read only, run time)
Error code after each operation over the Data base. Is reset when tested.
See the error table, with codes and descriptions, in section 4.

property TCReadOnlyDB.DBName: Str80 (read/write, design/run time)
Data base physical file title. Str80 is defined as string [80].

procedure TCReadOnlyIndex.AddEntry (var Key; BufLen: word; Position: longint)
Adds an entry to the Index.
Parameters:
    Key: Entry key.
    BufLen: Length of record pointed to by this key.
    Position: Absolute position where the record was stored.

property TCReadOnlyIndex.IndexErrorCode: integer (read only, run time)
Error code after each operation over the Index. Is reset when tested.
See the error table, with codes and dscriptions, in section 4.

property TCReadOnlyIndex.DBName: Str80 (read only, run time)
Data base (which contains the Index) physical file title. Str80 is defined as
string [80].

property TCReadOnlyIndex.KeyLength: word (read only, run time)
Index key length, in bytes.

property TCReadOnlyIndex.IndexName: Str10 (read/write, design/run time)
Index name to the Data base. This name must be a valid Pascal identifier, except
for 'IndexIndex', which is reserved to the Indexes Index. As in Pascal,
upper-case or lower-case make no difference.
Str10 is defined as string[10].


Methods and properties for reading
----------------------------------

procedure TRReadOnlyDB.DBOpen
Opens the Data base.

procedure TRReadOnlyDB.DBOpenIndex (var Ix: TRReadOnlyIndex;
                                                           PComp: PCompFunction)
Opens an Index. The function pointed to by PComp must be exactly the same as in
the recording pass. Otherwise, the search algorithm will not work.
Parameters:
    IX: An Index included on the form.
    PComp: Pointer to a comparing function of type CompareFunction.
        See comments under method TCReadonlyDB.AddIndex, above, for details.
        The function pointed to by PComp must be essentially the same used to
        create the Data base; otherwise, the search engine will not work
        correctly.

procedure TRReadOnlyDB.DBClose
Closes the Data base. RODB is a read only Data base; so, you won't loose any
data if you don't close the Data base. Anyway, you should close it to free
memory areas previously alocated by DBOpen method.

procedure TRReadOnlyDB.PhysicalRead (CharNo: longint; var Buff; Len: word)
Does a physical reading over the Data base. The start position and the amount of
characters transferred are indicated by the parameters.
Parameters:
    CharNo: Absolute position to start reading. The first character in the file
        is at position zero.
    Buff: Record read buffer.
    Len: Amount of characters to be read.

property TRReadOnlyDB.DBErrorCode: integer (read only)
Error code after any operation over the Data base. Is reset when tested. See
the error table, with codes and descriptions, in section 4.

property TRReadOnlyDB.DBName: Str80 (read/write)
Data base physical file title. Str80 is defined as string [80].

procedure TRReadOnlyIndex.IndexClose
Closes the Index. Comments under TRReadOnlyDB.DBClose are also applicable here:
you should close the Index to free memory.

procedure TRReadOnlyIndex.GetCurrentKey (var Key)
Reads the key corresponding to the current Index entry.
Parameters:
    Key: Key read buffer.

procedure TRReadOnlyIndex.GetCurrent (var Buff; InfBufLen: word;
                                                               var BufLen: word)
Reads the record corresponding to the Index current entry.
Parameters:
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

procedure TRReadOnlyIndex.GoFirstKey (var Key)
Reads the key corresponding to the first Index entry.
Parameter:
    Key: Key read buffer.

procedure TRReadOnlyIndex.GoFirst (var Buff; InfBuffLen: word; var BufLen: word)
Reads the record corresponding to the first Index entry.
Parameters:
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

procedure TRReadOnlyIndex.GoLastKey (var Key)
Reads the key corresponding to the last Index entry.
Parameter:
    Key: Key read buffer.

procedure TRReadOnlyIndex.GoLast (var Buff; InfBuffLen: word; var BufLen: word)
Reads the record corresponding to the last Index entry.
Parameters:
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

procedure TRReadOnlyIndex.GoNextKey (var Key)
Reads the key corresponding to the next Index entry.
Parameter:
    Key: Key read buffer.

procedure TRReadOnlyIndex.GoNext (var Buff; InfBuffLen: word; var BufLen: word)
Reads the record corresponding to the next Index entry.
Parameters:
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

procedure TRReadOnlyIndex.GoPrevKey
Reads the key corresponding to the previous Index entry.
Parameter:
    Key: Key read buffer.

procedure TRReadOnlyIndex.GoPrev (var Buff; InfBuffLen: word; var BufLen: word)
Reads the record corresponding to the previous Index entry.
Parameters:
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

procedure TRReadOnlyIndex.FindKey (var KeyIn, KeyOut)
Fetches the entry key the associated to a given key value. The search gets a
key identical to the given key, if contained in the Index. If not, the key on
the next entry is returned. If the given key is posterior to the last Index
entry, the Search ends and IndexErrorCode is set to esNotFound to indicate that
no record matches the given key.
To decide whether the returned key matches (or not) the given key, you may
compare them or test Match property, below.
Parameters:
    KeyIn: Given key.
    KeyOut: Returned key.

procedure TRReadOnlyIndex.Find (var Key, Buff; InfBuffLen: word;
                                                               var BufLen: word)
Fetches the record associated to a given key value. See comments under
TRReadOnlyIndex.FindKey, above.
Parameters:
    Key: Given key.
    Buff: Record read buffer.
    InfBufLen: Length of Buff, in bytes.
    BufLen: Actual length of record read, in bytes.

property TRReadOnlyIndex.IndexErrorCode: integer (read only, run time)
Error code after each operation over the Index. Is reset when tested. See the
error table, with codes and descriptions, in section 4.

property TRReadOnlyIndex.BOT: boolean (read only, run time)
Indicates that the current is the first entry of the Index.

property TRReadOnlyIndex.EOT: boolean (read only, run time)
Indicates that the current is the last entry of the Index.

property TRReadOnlyIndex.Match: boolean (read only, run time)
Indicates exact match between the given and the retuned key, on a key search
(see methods FindKey and Find, above). True stands for exact match. False stands
for next entry.

property TRReadOnlyIndex.DBName: Str80 (read only, run time)
Data base physical file title. Str80 is defined as string [80].

property TRReadOnlyIndex.KeyLength: word (read only, run time)
Define Index key length, in bytes.

property TRReadOnlyIndex.EntryNumber: longint (read/write, run time)
Entry number of the actual Index entry. For all Indexes, except for IndexIndex,
the first entry is number zero. For IndexIndex, the first entry has the number
minus one. You may set EntryNumber and then call GetCurrent to get the (new)
current record.

property TRReadOnlyIndex.Connected: boolean (read only, run time)
Indicates whether or not the Index is already connected to a Data base. There
are operations which require the Index not connected, and other which
require the Index connected.

property TRReadOnlyIndex.IndexName: Str10 (read/write, design/run time)
Index name to the Data base. The name must be a valid Pascal identifier. The
name 'IndexIndex' may be used to refer to the indexes Index. As in Pascal,
upper-cases and lower-cases make no difference. Str10 is defined as string [10].

--------------------------------------------------------------------------------

4. RDONLY unit Interface section
   -----------------------------

unit Rdonly;

interface

uses
    SysUtils,
    Classes;

type
    Str10           = string [10];
    Str80           = string [80];
    CompareResult   = -1 .. +1;
        { CompareResult (A, B) = -1 if A precedes B; 0 if A  equals  B; and
                                                            +1 if A follows  B }
    CompareFunction = function (var Key1, Key2): CompareResult;
    PCompFunction   = ^CompareFunction;
    SortPass = (Start, OneMore, Ending);
    SortWatchProc   = procedure (IxName: string; Phase: SortPass;
                                                                RecNo: longint);
    PSortWatchProc  = ^SortWatchProc;

const
    { Error code table }
    esNoError            =    0; { No error }
    esDBNotOpen          =   -1; { Data base not open }
    esDBNotClosed        =   -2; { Data base not closed }
    esFileAccessError    =   -3; { Physical file access error }
    esFileOpenError      =   -4; { Physical file open error }
    esCorruptDataFile    =   -5; { Corrupted physical file }
    esNotAssigned        =   -6; { Index without a name }
    esBadKeyValue        =   -7; { Bad value for a key }
    esCreateError        =   -8; { Unexpected error during Data base creation }
    esKeysNotSameSize    =   -9; { Key length differs from definition }
    esBadIndexName       =  -10; { Bad Index name }
    esSameNameIndex      =  -11; { Duplicated Index name }
    esShortBuffer        =  -12; { Warning only: short buffer }
    esNotConnected       =  -13; { Index not connected to Data base }
    esAlreadyConnected   =  -14; { Index already connected to Data base }
    esOutOfMemory        =  -15; { Out of memory }
    esMaxIndexesExceeded =  -16; { Maximum Index count exceeded }
    esInvalidKeyLength   =  -17; { Bad length for the key }
    esNoCurrentRecord    =  -18; { There is no current record }
    esNotFound           =  -19; { The record was not found }
    esInvalidRecLength   =  -20; { Bad length for the record }
    esNonRegistered      =  -21; { Non registered version requires Delphi }

{ interface section end }

--------------------------------------------------------------------------------

MANUAL_E.TXT end.
