Databank system with methods for efficiently storing non uniforms data records5809497Abstract System and methods are described for efficient storage and processing of non-uniform data records. An exemplary embodiment includes a Databank system having a Database Engine, a Database Engine API (Application Program Interface), a Databank Engine, a Databank Engine Class Interface, and a Databank (storage). The Databank storage itself comprises a Descriptor Table (Form Definition) and a Data Repository. The Descriptor Table comprises a plurality of field descriptors for characterizing user information stored in the Databank. The Data Repository, on the other hand, stores the actual data from the non-uniform data records. It comprises "static" fields and a "dynamic" field. The static fields store core fields necessary for characterizing each data record (irrespective of what type of information a given data record stores). User data are stored in a structured, pre-defined manner using logical fields (or "subfields") of the dynamic field. The system correctly interprets the dynamic contents based on the information stored in the descriptors. Methods are described for storing and retrieving information from the Databank in a manner which is transparent to clients, thus allowing the Databank subsystem to easily replace existing storage subsystems. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
______________________________________
Name Type Length Comment
______________________________________
NAME Char 40 Name of the owner of the record.
TITLE Char 40 Title of the record. This title is
defined in the form and can be either
a constant for example "Dental
History" or calculated from
other fields. The title determines
the text in a pick list for this
record.
CLASS Char 20 This field determines what group of
forms the record belongs to, for
example "Dental Records". It is only
used in the albums (Family, Vehicle,
and the like).
FORM Char 20 This is the name of the form that
defines the dynamic part of the
record, for example "Dental
History".
ALBUM Char 1 This single character field determines
to which module of the system the
record belongs. This can be either
one of the albums or address, multi-
day events etc.
System-wide constants are defined in
the module HBMAIN:
cFamily = `F`; {Family album}
cVehicle = `V`; {Vehicle album}
cFinance = `$`; {Finance album}
cAssets = `A`; {Home album}
cAddress = `@`; {Address book}
cToDo = `T`; {ToDo list}
cMultiDay = `M`; {Multidate list}
cSetup = `S`; {Setup info}
cAll = ` `; {Generic}
CATEGORY Char 4 This field is used to allow efficient
filter mechanisms within a module
and its meaning is determined by the
specific module. It also is used to
determine the primary order of the
records (together with title}.
CREATED Char 8 Date when record was created
DBDATA Memo 10 This memo field contains the
dynamic part of the record. Its
contents are determined by the form.
The data has the structure
<Dynamic Fieldname> = <String>
For example:
Lastname = Flintstone
Firstname = Fred
Birthday = 09/12/1959
Only fields whose value doesn't
match the default will be saved here,
the complete structure of the record is
in the forms definition.
Unlike regular fields these field can
contain any characters except for "="
and are case sensitive.
NOTES Memo 10 This field contains user notes
attached to the record.
______________________________________
Accordingly, the static fields may be constructed as follows (shown as an array of TField in Object Pascal):
______________________________________
DBankFldList: array ›0..DBankFldCount! of TField =
((name: `NAME`;
atype: dtStr;
len: 40; dec:0),
(name:. `TITLE`;
atype: dtStr;
len: 40; dec:0),
(name: `CLASS`;
atype: dtStr;
len: 20; dec:0),
(name: `FORM`;
atype: dtStr;
len: 20; dec:0),
(name: `ALBUM`;
atype: dtStr;
len: 1; dec:0),
(name: `DONE`;
atype: dtStr;
len: 1; dec:0),
(name: `CROSSREF`;
atype: dtNum;
len: 6; dec:0),
(name: `DATETIME`;
atype: dtStr;
len: 12; dec:0),
(name: `TIME`;
atype: dtStr;
len: 4; dec:0),
(name: `CATEGORY`;
atype: dtStr;
len: 4; dec:0),
(name: `CREATED`;
atype: dtDate;
len: 8; dec:0),
(name: `CHANGED`;
atype: dtDate;
len: 8; dec:0),
(name: `DBANKDAT`;
atype: dtMemo;
len: 10; dec:0),
(name: `NOTES`;
atype: dtMemo;
len: 10; dec:0),
(name: nil; atype: dtNull;
len: 0; dec:0));
______________________________________
Each will be described in turn. Name is the name of the "owner" of the record. In the exemplary embodiment for tracking home information, for instance, Name stores the person (e.g., Fred Flintstone) who the record belongs to. Title is the particular title defined by the form where the data appears. In an Address Form, for instance, Title is Last Name plus First Name, as this is how the title is defined for the form; this "title" is, in turn, stored in the Title field as a string. During system operation, the title is employed in pick lists, for allowing the user to easily select a new form (based on the data contained therein). Accordingly, the system can provide the user with easy navigation among forms, all without the user having to look up particular form data. Class stores information allowing for subcategories, defined within certain areas in the system. For example, a class of a business address is a "Doctor." Form is the name of the form. It defines the context of the information. Album defines broader categories of information, such as vehicle, address, to do, calendar, and the like. Thus, Albums represent overall categories in the system. Done is a housekeeping field, used in To Do management. Crossref stores any cross-reference to a parent or related record, as previously described. Datetime and Time store date/time and time stamps, respectively. Storing these in the static fields is particularly useful for To Do and calendar entries, which generally will always store date and time information. As an optimization, the system stores this information in static fields so that it is readily accessible (without having to retrieve it from a dynamic logical field). Category stores a sequence of characters for specifying an index order for a given data record. Generally, the main index is by Title. Category allows a form in the system to change the ordering of displayed records to a new ordering (as specified by the category). In an Address Manager, for instance, the user may request that all records first be ordered by Title (e.g., Doctor) and then by Name. In this manner, Category allows the system to easily track additional sort criteria specified by the user (i.e., in addition to the default sort order by title). Created stores the date the record is created; it is used for internal housekeeping purposes. This is followed by Formdata which contains the dynamic field storing the actual user data. This is followed by a Memo field, which stores a memo or note which the user has specified during system operation (i.e., a note which the user may attach to a particular data record). The data files use compressed FoxPro indexes in a single file, with the following index tags defined:
______________________________________
Tag Name Expression
______________________________________
NAME NAME
TITLE CATEGORY + TITLE
FORM FORM
CREATED CREATED
CHANGED CHANGED
______________________________________
This may be specified via an array of TTag (shown in Object Pascal):
______________________________________
DBankTagList: array ›0..DBankTagCount! of TTag =
((name: `NAME`;
expression:
`NAME`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `TITLE`;
expression:
`CATEGORY + TITLE`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `FORM`;
expression:
`FORM`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `CREATED`;
expression:
`CREATED`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `CHANGED`;
expression:
`CHANGED`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `ALBUM`;
expression:
`ALBUM`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `CROSSREF`;
expression:
`CROSSREF`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: `DATETIME`;
expression:
`DATETIME`;
filter: nil;
unique: dtNotUnique;
descending:
dtAscending),
(name: nil;
expression:
nil;
filter: nil;
unique: dtNull;
descending:
dtNull));
______________________________________
The Descriptor Table (Form Definitions) contains the definitions for all forms and form lists used by the system. It may be implemented as a simple table, such as by using the standard Windows .INI file format (fully described in the Microsoft Windows Software Development Kit, available from Microsoft of Redmond, WA). In an exemplary embodiment, the format for lists (such as ClassList, FormList, and the like) is
______________________________________
›<List name>!
001 = <1st list entry>
002 = <2nd list entry>
003 = <3rd list entry>
...
00n = <nth list entry>
______________________________________
For example, an exemplary list of "Family Album" may be constructed as:
______________________________________
›Family!
001 = Dental Care
002 = Family History
003 = Home Services
004 = Important Contacts
005 = Medical Care
006 = Medical Providers
007 = Personal Information
008 = Personal Papers
009 = Pet Information
010 = Professional Advisors
011 = Vision Care
______________________________________
The name of the list entry also is used to reference other lists or forms; for example "Family History" points to another list of forms relating to that subject:
______________________________________
›Family History!
001 = Ancestors
002 = Deceased family
003 = Extended family
004 = Immediate family
______________________________________
An exemplary format for forms (shown in Windows'.INI form) may be constructed as:
______________________________________
›<Form name>!
001 = <lst field description>
002 = <2nd field description>
003 = <3rd field description>
...
00n = <nth field description>
______________________________________
Here, a <field description> is a string of comma separated fields:
______________________________________
Index
Name Comment
______________________________________
1. Field name ›FName!
Name of the field as it appears in the
form. In addition there are several pre-
defined "Field names that have special
meaning for the system:
$T: Definition of Title for this form.
A title can be a list of fields and con-
stants seperated by a "/" character.
For example:
001 = $T, Lastname/Firstname
$P: Insert a new page in form. This is
followed by an optional page name:
005 = $P, My New Page
$L: Start the next field at the left
margin of the form ("NewLine")
$S: Start the next field in a new
section
$F: Call a subform and after inserting
the fields of the subform return to the
next line. Also works in lists.
006 = $F, SubForm
1. Field type ›FType!
Data type of field. Currently defined
are:
C: Generic character
CL: Char list
CR: Char radiobutton
CC: Char combo box
1. Minimum Width ›FMin!
Minimum width in pixel
1. Maximum Width ›FMax!
Maximum width in pixel
1. Character Width ›FChr!
Character width for text entry
1. Picture ›FPict!
Template for editing
1. Default ›FDefault!
Default data value
1. List ›FList! Pick list for radio buttons, combo boxes
etc. separated by a "/" character.
1. Alias name ›FAlias!
Name for field to allow shorter field
names and avoid potential conflicts with
predefined fields in the databank
______________________________________
Except for "Field name," all entries are optional. For example the form "ToDo" might look like this:
______________________________________
›ToDo!
001 = $P,Description
002 = Description,C,100,150,40,,
003 = $L
004 = Priority,CL,100,200,20,,Normal,Urgent/Medium/Normal
005 = Due date,C,50,100,10,,
006 = $P, Options
007 = Mark as done on due date,CR,80,80,3,,No,Yes/No
008 = Archive when done,CR,80,80,3,,No,Yes/No
009 = $L
010 = Recurring,CR,100,200,20,,none,none/Daily/Weekly/Monthly
______________________________________
In a preferred embodiment, a conventional database engine is configured as described above for accommodating the storage methodology of the present invention. For instance, CodeBase.TM., a database engine (available from Sequiter Software, Inc., Edmonton, Alberta, Canada), may be employed as the Database Engine of the system 600. Preferably, the Database Engine 610 is CodeBase configured to use FoxPro's implementation of the dBASE file format with FoxPro indexes. FoxPro indexes are preferred since they are generally more compact (e.g., than dBASE indexes). Use of compressed indexes is particularly advantageous for maintenance of "dynamic" data fields of the present invention. Use of a dBASE-compatible file format, on the other hand, allows one to apply a variety of standard aforementioned database tools to the data. FoxPro's implementation is preferred as it provides more control over freeform or "memo" fields, such as the ability to alter block size (i.e., granularity) of the field. The latter aspect allows for smaller block sizes, thus reducing the storage requirement for implementing a dynamic field. The structure, layout, and operation of dBASE-compatible data files (.dbf) and FoxPro index files (.cdx) are well documented in the above-mentioned technical, trade, and patent literature. Other advantages of the approach include reduced cost of implementation and increased data processing flexibility. Cost of implementing the system is reduced since "off the shelf" database engines may be employed for constructing the system. Besides costs, use of a traditional database engine has other advantages. In particular, a traditional database engine provides all the power of data maintenance and integrity. A relational database engine, such as CodeBase, already includes built-in support for searches, indexes, ad hoc queries, referential integrity, and the like. B. Database Engine Interface The Database Engine Interface 620 is the Application Programming Interface surfaced by the particular vendor of the Database Engine. It comprises a set of named functions which are the entry points by which functionality of the Database Engine 610 may be invoked. The Application Programming Interface for CodeBase is documented by its respective vendor. Alternatively, the Database Engine 610 may comprise Borland Database Engine, available from Borland International of Scotts Valley, Calif. With the Borland Database Engine as the Database Engine 610, the Application Programming Interface 620 comprises IDAPI--the API for the Borland Database Engine. IDAPI is documented in the programming guide accompanying the Borland Database Engine. As a further alternative, the Application Programming Interface 620 may comprises Microsoft's ODBC--Open Database Connectivity API. Database Engine 610 may comprise an engine supporting ODBC, such as Microsoft's Jet Engine, available from Microsoft Corporation of Redmond, Wash. The Database Class Interface layer 640 isolates the Databank engine from the API (Application Programming Interface) of the conventional Database Engine employed. In this fashion, a different conventional Database Engine may be employed, yet the only change needed in the system is modification to the Database Interface layer. In other words, the database interface layer encapsulates any vendor-specific or other proprietary considerations of the conventional Database Engine's API. Although not necessary for operation of the Databank system, the "de-coupling" of the Database Engine API from the rest of the system allows for greater flexibility and is, therefore, the preferred embodiment. This interface will now be described in greater detail. C. Databank Interface The Databank layer functions to translate data processing tasks from the conventional database paradigm to the Databank paradigm. Consider, for example, a request received for "last name." A system using the conventional database paradigm would simply go to the relevant table and perform a lookup on a Last Name field (if any). In the system of the present invention, since such information would be stored in the dynamic field (as a logical field thereof), a simple lookup on last name using the database paradigm would return "not found." Adding the Databank paradigm, however, the system would then proceed to perform a lookup in the field descriptors (in the static fields) for locating a descriptor of Last Name. The system may then proceed to satisfy the request by retrieving the desired information from the dynamic field. In an exemplary embodiment, the Database Class Interface 640 is constructed as an Object Pascal interface which maps the Database Engine API 620 into an object-oriented database interface (as opposed to the non-object oriented interface typically provided by conventional Database Engines). The Database Class Interface 640 may be implemented as an Object Pascal class, TTable, as follows:
______________________________________
TTable = class(TObject)
private
d4: DATA4;
dbFields: pTField;
dbTags: pTTag;
dbFlags: TDBFlags;
dbEditRecNo: longint;
dbEditRec: pChar;
dbEditNew: boolean;
destructor dbDestruct;
procedure dbFreeMem(ptr: PtrRef);
function dbPrepare: TDBError;
function dbPrepChange: TDBError;
public
dbFieldFound: boolean;
function dbOpen(name: Pchar; var fields: array of TField;
var tags: array of TTAG): TDBError;
function dbCreate(name: PChar; var fields: array of TField;
var tags: array of TTAG): TDBError;
function dbClose: TDBError;
function dbGetMemo(fldname: pChar; data: pChar; len: word):
word;
function dbPutMemo(fldname: pChar; data: pChar; len: word):
TDBError;
function dbGetData(fldname: pChar): pChar;
function dbPutData(fldname: pChar; data: pChar): TDBError;
function dbPutBlank(fldname: pChar): TDBError;
function dbBlankRecord: TDBError;
function dbSetOrder(value: pChar): pChar;
function dbSeek(value: pChar): integer;
function dbSeekLast(value: pChar): integer;
function dbSkip(value: LongInt): TDBError;
function dbIsEOF: boolean;
function dbGoTop: TDBError;
function dbGoBottom: TDBError;
procedure dbGoEOF; virtual;
procedure dbGoRec(rec: longint); virtual;
function dbRecNo: longint;
function dbIsDeleted: boolean;
function dbAppendBlank: TDBError;
function dbCommit: TDBError; virtual;
function dbAbandon: TDBError;
function dbNavigate(Button: TdbPos): boolean; virtual;
function dbCheckRecord: TDBError; virtual;
procedure dbNewPosition; virtual;
function dbOptimize(b: boolean) :boolean;
procedure dbBrowseInit;
procedure dbBrowseExit;
end;
______________________________________
The class methods for the TTable class provide routine database functionality, such as open table (dbOpen), create table (dbCreate), and close table (dbClose). Operations at the record level include get data (dbGetData), put data (dbPutData), put blank (dbPutBlank), and blank record (dbBlankRecord). Other class methods correspond to analogous dBASE commands, such as Set Order, Seek, Skip, Go Top, Go Bottom, and the like. As will be seen below, the TTable class serves as a base class--its methods and data members are inherited by other classes in the system. In this manner, other classes inherit the database functionality provided by the TTable class (which is an encapsulation of the database functionality provided by the Database Engine 610). In particular, the TTable class is inherited by the class for the Databank Engine, which will now be described. D. Databank Engine The Databank Engine 650 is implemented as an Object Pascal class, TDBank, as follows:
______________________________________
TDBank = class(TTable)
DBankData: pChar;
DBankRecNo: longint;
DBankLen: word;
DBankOffset: word;
DBankChanged: boolean;
LastChar: char;
function dbCommit: TDBError; override;
function GetBuffer(r: longInt): TDBError;
function GetChar: char;
function GetLine: char;
function GetFld(nme: PChar): boolean;
function GetDBankData(recno: LongInt; FldName: pChar;
Default: PChar; ReturnedString: PChar;
Size: Integer): Boolean;
function PutDBankData(recno: LongInt;
FldName, Data: pChar): Boolean;
function GetCrossRef(recno: LongInt): LongInt;
function PutCrossRef(recno, reference: LongInt): TDBError;
function DelCrossRef(recno: LongInt): TDBError;
function RunQuery(tag, name, form, album, date: pChar;
reference: longint;
decending: boolean;
QueryEvent: TQueryEvent): TDBError;
function NewRecord: TDBError;
function DeleteRecord: TDBError;
function DeleteRecordEx: TDbError;
{Only deletes dynamic part of record}
end;
______________________________________
As shown, the TDBank class inherits from the TTable class and, thus, inherits all of the database functionality provided by TTable (as described above). The data members of the TDBank class are as follows. DBankData is a pointer to type character (char) and is set to point to the buffer for the dynamic data. DBankRecNo (Databank record number) is a long (32-bit) integer storing the record number to which this buffer (pointed to by DBankData) belongs. DBankLen (Databank length) describes the length for the data stored in the buffer. DBankOffset stores a particular offset into the buffer (e.g., useful for searching within the dynamic buffer). DBankChanged is a boolean or flag for indicating that the buffer has changed (i.e., one or more of its contents has changed). LastChar is a character data member and stores the is last character encountered during searching (e.g., for determining whether additional characters follow). Next, the TDBank class definition defines methods or functions for processing (e.g., searching) the buffer: GetBuffer, GetChar, GetLine, and GetKey. Recall that the dynamic field is a freeform or memo field storing dynamic logical fields. These methods, therefore, provide the functionality for efficiently processing the logical fields of the dynamic field. GetBuffer functions to get the dynamic buffer for a passed in record number (r). GetChar returns the next character from the buffer. GetLine gets the next line (e.g., of text) from the buffer. GetKey determines whether a particular key or field (e.g., Last Name) is present in the dynamic buffer. Collectively, these routines manage the buffer. They find the appropriate data for particular search criteria, retrieve the data, and replace the data with something longer or shorter in size. GetDBankData provides the core functionality for retrieving information from the dynamic field. In an exemplary embodiment, the method or function may be implemented as follows:
______________________________________
function TDBank.GetDBankData(recno: LongInt; FldName: pChar;
Default: PChar; ReturnedString: PChar;
Size: Integer): Boolean;
var c: char;
i: integer;
ref: Longint;
begin
GetBuffer(recno);
StrCopy(ReturnedString, dbGetData (FldName));
result := dbFieldFound;
if not result then
begin
result := GetFld(FldName);
if (size < > 0) and (ReturnedString < > nil) then
begin
if result then
begin
if LastChar = `=` then
begin
repeat
c := GetChar;
until not IsWhiteSpace(c);
i := 0;
while (i < (size - 1)) and (not (c in ›chr(0), chr(13)!)) do
begin
ReturnedString›i! := c;
inc(i);
c := GetChar;
end;
while (i < > 0) and IsWhiteSpace(ReturnedString›i - 1)) do
dec(i);
ReturnedString›i! := chr(0);
end
else
ReturnedString›0! := chr(0);
end
else
begin
ref := GetCrossRef(recno);
if (ref < > 0) and (ref < > recno) then
begin
result := GetDBankData(ref, FldName, Default,
ReturnedString, Size);
dbGoRec(Recno);
end
else
StrLCopy(ReturnedString, Default, Size);
end;
end;
end;
end;
______________________________________
As shown, the method is invoked with a record number (Recno), a key or field name (FldName), a default value (Default), a pointer where to return the data (ReturnedString), and a size of the data returned (Size). FIG. 7 summarizes the steps of the GetDBankData method 700. At step 701, the method verifies that a valid buffer exists for the passed in record number; this is done by calling GetBuffer with the passed-in record number. Next, at step 702, the method calls dbGetData with the passed-in field name in an attempt to find the field as a "regular" database field. If the field is found, at step 703, then a "result" variable is set to "found" (dbFieldFound) and the field name is copied to a local string, at step 715. The field will be found if it is one of the conventional fields (i.e., static fields). If the field name is not found, on the other hand, then the method must proceed to determine whether the field is one of the dynamic logical fields. This is illustrated by step 704 where the method tests whether the field is a conventional (static) field. If the field is a static field, yet not found, then the method returns an empty string at step 716. At step 705, the method traverses the buffer in search for a dynamic logical field satisfying the request (i.e., matching the field name passed in as a parameter to the method). In an exemplary embodiment, the equal sign character (i.e., "=") is employed as a delimiter--that is, separating a dynamic field name from its data. Those skilled in the art will appreciate that other delimiter characters may be employed, as desired. The delimiter character may be employed to determine the field apart from its data. Thus as step 705, the method proceeds to get the dynamic logical field which is desired (i.e., the one which matches the passed-in field name). Then, at step 706, the method finds the beginning of the data for that dynamic logical field; the beginning starts immediately after the delimiter character (i.e., immediately after the equal sign). If the beginning of the data is not empty (e.g., not NULL string), then at step 707 the method proceeds to build up a return string, for returning the found data. At step 708, the method "trims" (removes) any trailing white space characters and then NULL terminates the data (i.e., adding ASCII 0 to the end), so that it may be returned as a NULL-terminated data string. The default string is employed in the instance where neither a static field nor a dynamic logical field matches the passed in field name. The default string is not used, however, in the instance where a dynamic logical field is found but stores an empty string (i.e., is "blank"). In such an instance, the method returns an empty string. At step 709, the method checks for cross references. A cross reference points to another, related record stored in the Databank. Consider, for instance, the task of tracking "To Do" items. In an exemplary embodiment, a recurring "To Do" is tracked by storing a master "To Do" record which defines the recurring "To Do" (e.g., daily, weekly, monthly, and the like). Additionally, the system stores instances of that "To Do" item as separate, additional records. Separate instance records are required because the user may in fact change the information for a particular "To Do" instance (e.g., changing the description, changing the due date, checking it off as completed, or the like). By defining a cross reference between instance records and their master, information common to all instances may be stored with the master, thus eliminating the need for storing redundant information (i.e., with each instance). In a preferred embodiment, each instance record only stores information which is different from the master record (i.e., only the "delta" information). Since the Databank is implemented on top of a conventional Database Engine, all the features of the conventional engine may be employed to further process the data. For instance, links may be defined from one Databank dynamic logical field to another simply by defining an index on the logical fields desired to be linked. In this manner, the user may create links between database dynamic logical fields and other fields (dynamic or otherwise) simply by using the built-in indexing and linking features already provided by the conventional Database Engine. If a cross reference is located at step 709, then the method invokes itself recursively to get the Databank data for the parent record (i.e., the cross referenced record) of the current record. Otherwise, if the cross reference is not invoked (tested at step 710), then the method returns as the Databank data the return string which has been built up. PutDBankData essentially performs the opposite function of GetDBankData: it puts or stores particular data in a given logical field of the dynamic field. In an exemplary embodiment, PutDBankData may be constructed as follows:
______________________________________
function TDBank.PutDBankData(recno: LongInt;
FldName, Data: pChar): Boolean;
var c: char;
i, pos, delta, lenFld, lenData: word;
procedure Copy(s: pChar; ofs: word);
var i: integer;
begin
i := 0;
while s›i! < > chr(0) do
begin
DBankData›ofs! := s›i!;
inc(ofs);
inc(i);
end;
end;
begin
GetBuffer(recno);
dbPutData(FldName, data);
result := dbFieldFound;
if not result then
begin
if DBankLen + 512 <= maxDBankData then
begin
if Data = nil then
lenData := 0
else
lenData := StrLen(Data);
lenFld := StrLen(FldName);
result := GetFld(FldName);
if not result then
begin
inc(DBankLen, lenFld);
Copy(FldName, DBankOffset);
inc(DBankOffset, lenFld);
end;
if LastChar < > `=` then
begin
inc(DBankLen);
if LastChar < > chr(0) then
begin
move(DBankData›DBankOffset - 1!,
DBankData›DBankOffset!,
DBankLen - DBankOffset);
dec(DBankOffset);
end;
DBankData›DBankOffset! := `=`;
inc(DBankOffset);
end;
pos := DBankOffset;
if LastChar = `=` then
begin
repeat
c := GetChar;
until c in ›chr(0), chr(13)!;
if c = chr(13) then dec(DBankOffset);
end;
i := lenData - (DBankOffset - pos);
if (i < > 0) and (lastChar < > chr(0)) then
move(DBankdata›DBankOffset!,
DBankdata›DBankOffset + i!,
DBankLen - DBankOffset + 1);
if lenData < > 0 then Copy(Data, pos);
DBankLen := DBankLen + i;
if lastChar = chr(0) then
begin
DBankdata›DBankLen! := chr(13);
inc(DBankLen);
DBankdata›DBankLen! := chr(10);
inc(DBankLen);
end;
DBankData›DBankLen! := chr(0);
DBankChanged := true;
end;
end;
end;
______________________________________
In addition to storing data, the PutDBankData method must manage the buffer, including expanding and shrinking the buffer, and moving items around within the buffer (e.g., compaction). As shown, the method includes a nested procedure, Copy. The Copy nested procedure copies data from a source memory location to a target memory location, where the target is generally an offset into the Databank buffer. The steps of the PutDBankData method 800 proper, illustrated in FIGS. 8A-B, may be summarized as follows. At step 801, the method validates the buffer. Again, this is done by calling the GetBuffer routine with the current record number. Next, at step 802, the method attempts to write to a physical (i.e., static) field, by calling dbPutData with the passed-in field name. If the attempt fails (i.e., the field name is not found among the existing static fields), then the method proceeds to determine whether the field sought (as specified by the passed-in field name) exists as a dynamic logical field. At step 803, therefore, if the field sought is not among the static fields, then the method proceeds to look for the field among the dynamic fields. If it is a static field, however, then the information contained therein is simply returned from the field at step 820 and the method concludes. At step 804, the method performs a safety check, making sure that the stored Databank data (DBankData buffer) will not exceed the maximum amount accommodated by the system. At step 805, the system examines the length (i.e., size) of the data to be stored and sets a local variable, lenData, accordingly. Similarly, the length of the field name is stored in a local variable, lenFld. At step 806, the method looks for a dynamic logical field matching the passed-in field name. If one is not found however (step 807), then the method proceeds to step 808 to add this field to the buffer as a new dynamic logical field. Specifically, this is done by growing the buffer, copying the field name for the new dynamic logical field into the buffer at its current offset, and then incrementing the current offset of the buffer to a position just beyond the copied-in field name. At step 809, if the last character (LastChar) is not the delimiter, then at step 810 the method appends the delimiter to the buffer, moving Databank data as necessary; otherwise, step 810 is skipped. Upon finding the delimiter (i.e., equal sign char), the method next positions the buffer offset to the end of this dynamic field, at step 811, by looking for a carriage return (ASCII character 13) or a NULL terminator (ASCII character 0). At step 812, the method again moves data around in the buffer, if necessary, to accommodate the new data which is about to be stored (and whose length is already known from the local variable, lenData). At step 813, the data stored is now actually copied into the Databank buffer; the length of the Databank buffer, which is tracked in a class variable, DBankLen, is updated accordingly. At step 814, the newly-stored data is now terminated by a line feed/carriage return delimiter if one has not been previously added (i.e. when the logical field was created by a prior iteration of the method); the end of the buffer itself (i.e., after all dynamic logical fields) is NULL terminated, by storing a NULL character. Finally, the class variable DBankChanged is set to true, at step 815, thereby indicating that the Databank buffer for this record has changed. The scenarios of writing data to the Databank buffer may be summarized as follows. First, if a field does not exist, then it must be created (i.e., storing the field name), followed by the delimiter (i.e., storing the equal sign), and followed by the data (i.e., copy operation). Data are moved (including shrinking or growing the buffer) as necessary to accommodate the data to be stored. The second case is a simpler case in which the dynamic logical field already exists and the data to be stored are equal to the data present. In such a case, the data may simply be copied into the buffer, overriding the previously-stored data. A third case exists in which the dynamic logical field is present but the data to be stored are greater than or less than in size than the previously-stored data. In such a case, other dynamic logical field data are shifted, as necessary, to accommodate the newly-stored data. Advantages The static/dynamic field combination is particularly advantageous for storing non-uniform information. Since all non-uniform information is normalized for storage in a single table, standard database methodology may be employed for adding and deleting records (i.e., adding and deleting from the Databank). Further, since generic indexes (e.g., using conditional indexes available from dBASE or FoxPro) are employed, indexes for the information can be easily calculated. At the same time, the non-uniform data itself can be stored as a single block in an interpreted field. This affords very rapid lookups. Moreover, the field descriptors allow for storage optimization of data records in the data file: the data file need only store information which is different from the default (as described by the descriptor). If a boolean field defaults to a logical value of "true," then the data file need only store instances which are false (i.e., different from the default). While the invention is described in some detail with specific reference to a single preferred embodiment and certain alternatives, there is no intent to limit the invention to that particular embodiment or those specific alternatives. For instance, those skilled in the art will appreciate that the present invention may be implemented with a dedicated database engine, in addition to the implementation described using a conventional database engine. Further, the preferred embodiment's implementation with a traditional database engine is but one implementation for generic indexes. A dedicated database engine could be built, for instance, which allowed generic indexing on any field. In a preferred embodiment, however, a traditional database engine is employed for reduced cost and for increased availability of standard database tools. Thus, the true scope of the present invention is not limited to any one of the foregoing exemplary embodiments but is instead defined by the appended claims.
|
Same subclass Same class Consider this |
||||||||||
