RC21
Relational Database Class Library
Release 8.1
Vermont
Database Corporation
400 Upper Hollow Hill Road
Stowe VT 05672
802-253-4437
RC21 Release 7.6
DISCLAIMER OF WARRANTY
This manual and associated software are sold "as is" and without
warranties as to performance or merchantability. The seller's salespersons may have made
statements about this software. Any such
statements do not constitute warranties.
This program is sold without any express or implied warranties
whatsoever. No warranty of fitness for a
particular purpose is offered. The user
is advised to test the program thoroughly before relying on it. The user assumes the entire risk of using the
program. Any liability of seller of
manufacturer will be limited exclusively to replacement of diskettes defective
in materials or workmanship.
Vermont Database Corporation
400 Upper Hollow Hill Road
Stowe, VT 05672
802-253-4437
http://www.vermontdatabase.com
RC21 Programmer's Guide and Reference Manual
(c) Copyright 1997 Vermont Database Corporation
All Rights Reserved. No part of this
document may be photocopied, reproduced, or translated by any means without the
prior written consent of Vermont Database Corporation.
RC21
(c) Copyright 1998 Vermont Database Corporation
All Rights Reserved.
This manual printed 12/01/05.
RC21 and Pinnacle Relational Engine are trademarks of Vermont Database Corporation. Other brand and product names are trademarks or registered trademarks of their respective holders.
RC21 is a Relational
Database Management System (RDBMS) for C and C++ programmers. It is presented as a number of surfaces -
primarily as a C++ class library, a C function library, and an ODBC function
library.
RC21 started out as a
C function library called Pinnacle Relational Engine (PRE). When C++ was more standardized and C++
compilers became the rule, the functionality was implemented as a class
library. Actually, PRE had a class
wrapper that closely resembles RC21, but now the kernel code is written in
C++. This has improved the robustness
and maintainability of the code.
An RC21 database is a
single file in the file system of your operating system. The database contains tables. Each table has a name, a description, and
other attributes that may be added by the programmer. A table contains zero or more columns
(actually there is always at least ONE (hidden) column). Each column also has a name, a description,
optionally a data type, a printf format code, optionally a reference column
that contains a list of valid values for the column. The set of tables and their columns and all
their attributes are contained in two user-accessible tables named “_tables”
and “_columns”. Thus, all the relevant
information about the structure of the database is contained in the database
itself and is available to the programmer.
This enables complete manipulation of the database - including
dictionary operations - without the necessity of invoking special
vendor-provided utility routines.
The RC21 database file
is physically divided into self-describing “pages” of a size that can be
specified at the time of creation of the database. The programmer may find it optimal to specify
a size that is equivalent to the natural page size of the file system. This may improve disk access. Since each page is self-describing, sharing
databases in a heterogeneous network environment is possible.
All operations on the
database are cached in RAM or in temporary disk storage until a commit
operation is invoked by the programmer.
Commits are guaranteed to be atomic - that is - they are either all or
nothing. If a commit operation is interrupted
by a power outage or some other catastrophe, it will be completed, if possible,
automatically when the database is accessed the next time. If the commit cannot be completed, the
database reverts to its state upon completion of the last previous commit.
The following is an edited version of the header file for RC21. Implementational details have been omitted.
// Special Types:
typedef CHAR FAR *DBSTRING;
typedef const CHAR FAR * CDBSTRING;
typedef void FAR *DBVOID;
typedef const void FAR * CDBVOID;
typedef UCHAR FAR *DBNBYTES;
typedef DBNBYTES DBBLOB;
typedef DBNBYTES DBUSER;
typedef const void FAR * CDBBLOB;
typedef const void FAR * CDBUSER;
typedef long DBHANDLE;
typedef long FILEADDR;
typedef enum DBERROR;
class Database *DB;
class Table *DBTAB;
class Column *DBCOL;
class SearchObject *DBSEARCH;
// Column Attribute Word
typedef unsigned long COLATTRIBS;
const COLATTRIBS
TypeMask=0x0000ffff,
Integer=0x00000001, Real=0x00000002, String=0x00000004,
DBString=0x00000004,
NBytes=0x00000008, Blob=0x00000008,
User=0x00000010,
Null=0x00000020, Key=0x00000040,
STRING=0x00000080, USTRING=0x00000080, Eblob=0x00000100,
IndexMask=0x03000000,
Indexed=0x01000000,
NonIndexed=0x02000000L,
NoNulls=0x04000000,
Nulls=0x08000000,
NullsMask=0x0c000000,
Unique=0x10000000,
NonUnique=0x20000000,
UniqueMask=0x30000000,
NonShared=0x40000000,
Shared=0x80000000,
SharedMask=0xc0000000,
NOATTRIBS=0
;
#define KeyLen(q) ((COLATTRIBS)q<<16)
#define KeyLenMask KeyLen(0xffL)
#define GetKeyLen(q) ((unsigned
int)(((q)&KeyLenMask)>>16))
// dictionary and table iterator macros
#define ForAllRows(_x) for (ResetRow(_x);NextRow(_x,1);)
#define ForAllRowsBackwards(_x) for
(ResetRow(_x);NextRow(_x,-1);)
#define ForAllTables(_d) for (FirstTable(_d);NextTable(_d);)
#define ForAllColumns(_t) for
(FirstColumn(_t);NextColumn(_t);)
class DBVALUE // a
self-describing database value
{
public:
DBSIZE
size(void) {return n;}
DBBLOB
ptr(void) {return v.b;}
operator
DBINTEGER();
operator
DBSTRING();
operator
DBREAL();
operator
int();
operator
float();
DBVALUE(void);
~DBVALUE(void);
DBVALUE(DBVALUE&V);
DBVALUE(DBTYPE);
DBVALUE&
operator=(DBVALUE&V);
DBVALUE&
operator=(CDBSTRING);
DBVALUE&
operator=(DBINTEGER);
DBVALUE&
operator=(DBREAL);
};
// create a new database
DBERROR Create (CDBSTRING path, unsigned
pagesize=P_DefaultPagesize,
unsigned
nhash=P_DefaultNhash);
// remove a database
DBERROR Remove (CDBSTRING path);
class Database: public RCObject
{
public:
//
constructor, destructor
Database
(CDBSTRING dbname, CDBSTRING access=(CDBSTRING)"rw",
int
npages=20);
Database
(CDBSTRING dbname, int npages);
~Database(void);
//
conversions
operator DB
(void);
// state of
this Database
int
Changed(void); // returns nonzero if
database has been changed.
int
CachePages(void);
CDBSTRING
Path(void);
CDBSTRING Name(void);
CDBSTRING
Release(void);
unsigned
PageSize(void);
unsigned long
HashSize(void);
// adding,
deleting a table
DBERROR
AddTable(CDBSTRING name,
CDBSTRING
description=(CDBSTRING)"no description",
CDBSTRING
view = (CDBSTRING)"");
DBERROR
DeleteTable(CDBSTRING name);
// commit,
rollback
void
Finish(void); // finish current transaction
// find out
resources required for commit
DBERROR
CommitStatus(unsigned long &dbsize, unsigned long &tempsize);
DBERROR
Commit(void);
int
AutoCommit(int onoff);
int
AutoCommit(void);
DBERROR
RollBack (void);
// traversing
list of tables in database
DBTAB
CurrentTable (void);
void
FirstTable (void);
int NextTable (void);
// copy
contents of database to another database
DBERROR
Copy(DB to);
// OPERATORS
Database&
operator =(Database& fromdb);
class
Table& operator[] (CDBSTRING);
};
// find out the release number of a named database
CDBSTRING Release (CDBSTRING dbname);
class EXPORT Table: public RCObject
{
public:
//
constructors, destructor
Table (DB db,
CDBSTRING TableName);
Table
(CDBSTRING TableName); // table in current database
Table (void);
// a placeholder for a table
Table&
operator=(Table& rtab); // copies a
table
~Table
(void);
//
conversions
operator
DBTAB (void); // converts to pointer to
this
operator DB
(void); // return pointer to Database
this Table belongs to
// erase all
the rows in a table.
DBERROR Erase
(void);
// attributes
CDBSTRING
Name (void); // get the name
DBERROR Name
(CDBSTRING NewName); // change the name
CDBSTRING Description
(void); // get the description
DBERROR
Description (CDBSTRING NewDescription);
// change description
CDBSTRING
View (void); // get the view expression
DBERROR View
(CDBSTRING v); // change the view
expression
// adding,
deleting columns
DBERROR
AddColumn(CDBSTRING Name,
COLATTRIBS
attr=NOATTRIBS,
CDBSTRING
description=(CDBSTRING)"no description",
CDBSTRING
format=(CDBSTRING)"", //
printf format
const
DBCOL reference=NULL // referential integrity column
);
DBERROR
DeleteColumn(CDBSTRING n);
// does a
certain column exist for this table?
int
ColumnExists(CDBSTRING colname);
// add a
composite key to the table
DBERROR
AddKey (CDBSTRING Name, CDBSTRING Keys, COLATTRIBS attr=NOATTRIBS);
// Adding,
Deleting Rows
DBERROR
AddRow (void);
DBERROR
DeleteRow(void);
void operator
++(void); // same as AddRow
// count the columns
or rows in the table (not including internal or
// deleted
DBROWID
CountColumns (void);
DBROWID
CountRows (void);
// copying
operations
DBERROR Copy
(CDBSTRING newtab); // make a copy of a table
DBERROR
CopyRow (DBROWID); // copy row to another row in same table.
DBERROR
CopyRow (DBTAB); // copy to current row of another table;
// values go to
same-named columns
DBERROR
DuplicateRow (void); // add a row and
copy to it.
// emptying a
table or removing it ...
void
Empty(void); // remove all the rows in a
table
DBERROR Zap
(void); // remove the table from the
database
// specify
the order-by column for this table
inline
DBERROR OrderBy (CDBSTRING colname);
DBERROR
Unordered(void);
// select or
filter operations
void
ClearFilters (void); // remove all
filters
DBERROR
Filter (CDBSTRING f ...);
DBERROR
vFilter (CDBSTRING f, va_list ap);
int Find (CDBSTRING
filter ...);
int vFind
(CDBSTRING filter, va_list ap);
int RowExists
(CDBSTRING filter ...);
int
vRowExists (CDBSTRING filter, va_list ap);
int
PassesFilter (CDBSTRING filter ...);
int vPassesFilter
(CDBSTRING filter, va_list ap);
// determine
if current row has been deleted
int
IsaRow(void);
// traversing
a table
DBERROR
ResetRow(void);
DBROWID
CurrentRow(void) {return CurrentStatus.rowid;}
int NextRow
(int inc=1); // skip rows if inc != 1 or
-1
int PrevRow
(void); // go backwards
DBERROR
GotoRow (DBROWID RowID); // go to row
with given Row ID
// GetRow
produces formatted string -- for PERL operations
CDBSTRING
GetRow (CDBSTRING column_list, char sep);
// Traversing
list of columns in a table
void
FirstColumn (void);
int
NextColumn (void);
DBCOL
CurrentColumn (void);
// Complete
operations on the current row
DBERROR
FinishRow (void);
// reference column object with given name
Column&
operator [] (CDBSTRING ColumnName);
};
class EXPORT Column: public RCObject
{
public:
//
constructors
Column (DBTAB
TableObject, CDBSTRING ColumnName);
Column (void);
// place holder
Column (DBCOL
q);
Column&
operator=(DBCOL X);
~Column();
//
conversions
operator
DBCOL (void);
operator
DBTAB (void); // return pointer to Table
object for this column
operator DB
(void); // return pointer to Database
object for this column
DBTAB Tab
(void); // table to which this belongs
// fetching,
changing attributes
CDBSTRING
Name (void);
DBERROR Name
(CDBSTRING NewName);
CDBSTRING
Description (void);
DBERROR Description (CDBSTRING
NewDescription);
CDBSTRING
Format (void);
DBERROR
Format (CDBSTRING NewFormat);
COLATTRIBS
Attributes (void); // fetch attributes
DBERROR
Attributes(COLATTRIBS); // change
attributes
DBCOL Reference
(void); // referential integrity column
// storing a
value in a column
DBERROR
PutNull(void); // store a Null (empty)
value
DBERROR
PutNULL(void);
DBERROR
PutReal(DBREAL x);
DBERROR
PutInteger(DBINTEGER i);
DBERROR
PutString(CDBSTRING s);
DBERROR
PutSTRING(CDBSTRING s);
DBERROR
PutBLOB(CDBBLOB b,DBSIZE s);
DBERROR
PutUser(const DBUSER b,DBSIZE s);
DBERROR
PutUser(DBVALUE *u);
DBERROR
PutEBLOB(FILEADDR f);
DBERROR
Put(DBINTEGER i);
DBERROR
Put(DBREAL x);
DBERROR
Put(CDBSTRING s);
DBERROR
Put(CDBBLOB b, DBSIZE s);
DBERROR
Put(const DBUSER b,DBSIZE s);
DBERROR
Put(const DBVALUE&);
DBINTEGER
operator << (DBINTEGER x); //
store x in the column
int operator
<< (int x);
DBREAL
operator << (DBREAL x);
CDBSTRING
operator << (CDBSTRING x);
// fetching a
value from a column
DBINTEGER
GetInteger(void);
DBREAL
GetReal(void);
CDBSTRING
GetString(void);
DBVALUE&
GetValue(void);
COLATTRIBS
GetType (void);
DBSIZE
GetSize (void); // how much RAM required to store value, when fetched
DBSIZE Size
(void);
DBVALUE Get
(void);
//
conversions that fetch values
operator
int(void);
operator
long(void);
operator
const char*(void);
operator
double(void);
operator
float(void);
// order this
column's table by this column
DBERROR
OrderBy(void);
// value
operations on entire column
void Erase
(void); // erase all the values in a
column
int
HasAnyValues(void); // are there any
non-Nulls anywhere in the column
// copy
contents of a column
DBERROR Copy
(CDBSTRING newcol);
// copy just
one value in current row to another column
DBERROR Copy
(DBCOL);
// low-level
operations tofind things in a table.
int Find
(DBSEARCH so);
DBBOOL
FindNext (DBSEARCH dbsearch, int direction);
DBBOOL KeyExists(DBSEARCH dbsearch);
// increment the value in the column
DBINTEGER Inc(DBINTEGER _i);
DBREAL
Inc(DBREAL _i);
};
// column-get operators
CDBSTRING operator << (CDBSTRING &s,
Column& c);
DBINTEGER operator << (DBINTEGER &i,
Column& c);
DBREAL operator << (DBREAL &x, Column& c);
// iostream operators with columns
ostream& operator << (ostream &o,
Column& c);
istream& operator >> (istream& i,
Column& c);
// SearchObject for limited search operations
class SearchObject: public RCObject
{
SearchObject
(DBCOL c, COLATTRIBS attribs, const void *valptr,
const CHAR
*relation, size_t nbyte_length=0);
~SearchObject();
};
#define E_SEEK_SET
0 /* Seeks from beginning of
Eblob */
#define E_SEEK_CUR
1 /* Seeks from current position
*/
#define E_SEEK_END
2 /* Seeks from end of Eblob */
class eblob
{
public:
eblob (DBCOL
cc); // construct an eblob from eblob
value in the column
~eblob
(void);
int
seek(FILEADDR f, int fromwhere=E_SEEK_SET);
// like filesystem seek
int seek(void)
{return seek (lfCurrent);}
FILEADDR
tell(void);
size_t read
(void* buf, size_t len);
size_t write
(void* buf, size_t len);
FILEADDR
inject (void* buf, FILEADDR len);
FILEADDR
extract (FILEADDR len);
};
You create a database with the Create function which has the prototype
DBERROR Create (CDBSTRING path, unsigned pagesize=P_DefaultPagesize,
unsigned nhash=P_DefaultNhash);
Pagesize and nhash may be omitted, which is the typical case. In fact, you may alter the defined constants for pagesize and nhash in the header file, RC21.H to fit your environment. Here is an example:
Create ("test.db");
To delete a database, use the Remove function.
DBERROR Remove (CDBSTRING path);
Remove is
essentially the same as the remove
function in stdio.h but it is
provided for symmetry with Create
and also to provide safety in a multi-user (client-server) environment.
To access a database that has been Create'd, instantiate an object of the class Database. The constructor has the prototypes:
Database (CDBSTRING dbname, CDBSTRING access=(CDBSTRING)"rw", int npages=20);
Database (CDBSTRING dbname, int npages);
The database object may be allocated with the new operator or may be declared as an object on the stack with a simple declaration. If you allocate an object using new, use the datatype DB which is simply Database* type. The access argument may have the values "r" for read access, "w" for write access, and "rw" or "wr" for read/write database access. The default access is "rw" and is typically omitted. The npages argument specifies the number of pages of RAM will be set aside for database buffering. Usually, the larger number yields better performance. However, be aware that an extremely large number here could lead to a virtual memory buffer which is really swapped in and out of disk. Example:
DB test = new Database("test.db");
or
Database test("test.db");
If the file exists and is an RC21 database, a valid object will be created. Otherwise, an invalid object will be created and an exception will be raised.
To add a table to a database, use the AddTable member function, which has the prototype:
DBERROR AddTable(CDBSTRING name, CDBSTRING description=(CDBSTRING)"no description",
CDBSTRING view = (CDBSTRING)"");
Name is the name of the table, which follows the RC21 naming rules, as is unique within a database. Description is optional and for documentation purposes only. If the table is really a view into another table, supply a view expression which is a string. For a discussion of views, see VIEWS on page 29. Here is an example:
Database db("test.db);
db.AddTable ("new_table", "new table");
To access a table in a database, instantiate an object of class Table. The constructor has the prototype:
Table (DB db, CDBSTRING TableName);
Table (CDBSTRING TableName); // table in current database
The arguments are self-explanatory. Example:
Database db("test.db);
Table test(db, "test");
or
DB db = new Database("test.db");
DBTAB test = new Table (db, "test");
To add a column to a table, use the AddColumn member function. This has the prototype:
DBERROR AddColumn(CDBSTRING Name,
COLATTRIBS attr=NOATTRIBS,
CDBSTRING description=(CDBSTRING)"no description",
CDBSTRING format=(CDBSTRING)"", // printf format
const DBCOL reference=NULL // referential integrity column
);
Name is the name of the column in the table. Within a table, columns must have unique names. Attr is a value of type COLATTRIBS. The value is constructed by or'ing together certain constants to indicate data-type, storage-type, indexing, unique, etc. Here is an example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Indexed | Shared);
T.AddColumn("FirstName", String | Shared);
T.AddColumn("Salary", Integer, "Monthly Salary", "%ld");
A composite key is a virtual combination of all or part of columns that is inserted in an index. Once it has been declared for the table it is maintained automatically by RC21 as its constituent parts are written or modified. It is created using the AddKey member function of the Table class. The prototype is:
DBERROR AddKey (CDBSTRING Name, CDBSTRING Keys, COLATTRIBS attr=NOATTRIBS);
Here is an example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Shared);
T.AddColumn("FirstName", String | Shared);
T.AddKey("LastFirst", "+Last+First", Unique);
For a complete discussion of this topic, see Composite Keys on page 23.
Once a column has been added to a table, it may be accessed by instantiating a Column object. The prototype for the constructor is:
Column (DBTAB TableObject, CDBSTRING ColumnName);
Here is an example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Shared);
Column LastName(DBTAB(T), "LastName");
Add a row to a table by using the AddRow member function of the Table class:
DBERROR AddRow (void);
Once the row has been added, the table cursor is placed at this newly-added row and the values in all the columns in this row have the null value. Here is an example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Shared);
T.AddColumn("FirstName", String | Shared);
T.AddKey("LastFirst", "+Last+First", Unique);
Column LastName(DBTAB(T), "LastName");
Column FirstName(DBTAB(T), "FirstName");
T.AddRow();
After the row has been added in the above example, data must be stored into the columns to avoid a unique violation.
Data is stored in each column of the current row by using the type-specific Column member functions PutInteger, PutReal, PutString, PutBlob, or PutUser. Better yet, use the polymorphic Put function or the operator<<, as in the following example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Shared);
T.AddColumn("FirstName", String | Shared);
T.AddKey("LastFirst", "+Last+First", Unique);
Column LastName(DBTAB(T), "LastName");
Column FirstName(DBTAB(T), "FirstName");
T.AddRow();
LastName << "Smith";
FirstName << "John";
T.AddRow();
LastName << "Jones";
FirstName << "Trent";
Database changes, including changes to the dictionary (new tables, columns) are temporary until the data is commited to the physical database using the Commit member function of class Database:
DBERROR Commit(void);
as in the following example:
Create ("test.db");
Database D("test.db");
Table T(D, "test");
T.AddColumn("LastName", String | Shared);
T.AddColumn("FirstName", String | Shared);
T.AddKey("LastFirst", "+Last+First", Unique);
D.Commit(); // data written to physical database file
To visit every row in a table, use the ResetRow and the NextRow Table member functions or the ForAllRows iterator macro:
DBERROR ResetRow(void);
int NextRow (int inc=1); // skip rows if inc != 1 or -1
int PrevRow (void); // go backwards
#define ForAllRows(_x) for (ResetRow(_x);NextRow(_x,1);)
The ResetRow member function prepares to iterate over the entire table, row-by-row. It doesn't actually position to the first row. This is accomplished by the first NextRow. Each NextRow moves to the next available row (deleted rows are skipped as are filtered-out rows). Thus, the idiom for visiting all rows in a table is:
for (ResetRow(_x);NextRow(_x,1);)
{
.
.
.
}
This idiom is conveniently encapsulated in the ForAllRows macro. Thus we may simply write:
ForAllRows(table)
{
.
.
.
}
A row may be accessed by some kind of key, or a reference to the current row may be obtained by using the CurrentRow member function of the Table class. Each row has a built-in unique (within a table) identifier of type DBROWID. This may be retrieved by using the GetRow member function:
DBROWID CurrentRow(void) {return CurrentStatus.rowid;}
The DBROWID thus retrieved may be later used to return to that row using the GotoRow member function. Note that bookmarks of type DBROWID are only valid while the database is open. The DBROWID for a particular row is not guaranteed to stay the same over time, except while the database is open. The DBROWID for a particular row will probably change if the table is copied. Thus it would be unwise to store data of type DBROWID in the database itself, except for purely temporary use.
To go to a particular row in a table use the GotoRow member function with a previously-assigned DBROWID value:
DBERROR GotoRow (DBROWID RowID);
The result is to set the table's current-row to the given row. If the table is not already positioned at the given row, end-of-row processing will occur for the current-row before moving to the new row.
The intersection of a row and a column (often called a field) may contain a value or may have the special value Null (no value). The Table has a current row. Certain member functions of the Column class can be used to fetch the value in the column at the current row. The prototypes are:
DBINTEGER GetInteger(void);
DBREAL GetReal(void);
CDBSTRING GetString(void);
DBVALUE& GetValue(void);
COLATTRIBS GetType (void);
DBSIZE GetSize (void);
DBSIZE Size (void);
DBVALUE Get (void);
The member functions GetInteger, GetReal, and GetString have obvious meaning. They fetch the data from the field and convert it (if possible) to the return type. Note that when pointers or references are returned, as in GetString, these pointers point to data in a temporary buffer. The pointer is only valid until the next call to an RC21 function.
The member function GetValue returns a reference to a DBVALUE object. DBVALUE is a class designed to hold both the value and its type and size, and so is useful for fetching the value of BLOBs because we need to fetch both the value and its length.
GetType is used to determine the type of a field if it is not known. Note that it is not necessary to assign a type to a column, so the type of a particular field in that column may be unknown.
GetSize returns the number of bytes that would be required to store the returned value.
Get returns a DBVALUE. Any pointers in the DBVALUE object point to data that are owned by the object.
An additional way implicitly to fetch data from a field is to use the ostream operator<< which is prototyped:
ostream& operator << (ostream &o, Column& c);
Here is an example:
#include <rc21.h>
#include <iostream.h>
char *LongSpell (unsigned long num);
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database test("test.db");
test.AddTable ("newtab");
Table newtab(test, "newtab");
newtab.AddColumn ("col1", Integer);
newtab.AddColumn ("col2", String);
Column col1(newtab, "col1"), col2(newtab, "col2");
// fill the table
for (long i=1; i<100; ++i)
{
newtab.AddRow();
col1 << i;
col2 << LongSpell(i);
}
// retrieve the values
ForAllRows (newtab)
{
cout << col1;
CDBSTRING s; s << col2;
cout << ' ' << s << endl;
}
test.Commit();
}
To find a row in a table that fulfills certain criteria, use the Find or vFind member f unctions of the Table class. These have the prototypes:
int Find (CDBSTRING filter ...);
int vFind (CDBSTRING filter, va_list ap);
The filter argument is a string that contains an infix expression that has operators and operands that resemble those in the C language. For a complete discussion of the syntax of filter expressions, see Selecting Rows In a Table - Filters on page 24.
The variable-length argument list in the Find function is to accommodate replaceable parameters that may be present in the filter argument. For example, we may have
table.Find ("LastName == 'Smith' && FirstName == 'John');
or we could have
table.Find ("LastName == %S && FirstName == %S, "Smith", "John");
The second function, vFind, is useful for encapsulating calls to the filter mechanism when an argument list may be passed through several layers of function calls.
The effect of the Find or vFind functions is to position the current-row of the table at the row that matches the criteria specified in the filter argument. If no such row is found, the current-row is not changed.
When it is desired to select more than one row in a table that match certain criteria, use the Filter or vFilter member functions. These have the prototypes:
DBERROR Filter (CDBSTRING f ...);
DBERROR vFilter (CDBSTRING f, va_list ap);
These have the effect of screening out all rows in a table that do not satisfy the criteria described in the filter argument. These functions do not alter the current-row of the table. Once the filter has been applied, simply traverse the table using the ForAllRows iterator macro. To remove the filter constraint, simply call Filter with a NULL argument. Note: filters may be stacked and the Find member function works on the subset of rows that satisfy the filters in effect. Example:
// examp203.cpp
#include <rc21.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database test("test.db");
test.AddTable ("newtab");
Table newtab(test, "newtab");
newtab.AddColumn ("col1", Integer);
for (long i=1; i<100; ++i)
{
newtab.AddRow();
col1 << i;
}
// show all rows:
ForAllRows (newtab)
cout << col1 << endl;
// apply filter and show again (only even rows)
newtab.Filter ("col1 % 2 == 0");
ForAllRows (newtab)
cout << col1 << endl;
test.Commit();
}
The SQL "join" operation is achieved in RC21 procedurally with nested loops containing Filter or Find operations. Please see a complete discusion of this topic on page 30.
To delete the row at the current-row of a table, use the DeleteRow member function:
DBERROR DeleteRow(void);
The current row is deleted. The table cursor, however, remains at the deleted row. A NextRow() will be required to move to the next or previous row. Here is an example based upon the example previously used to illustrate the Filter member function:
// examp204.cpp - deleting rows from a table
#include <rc21.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database test("test.db");
test.AddTable ("newtab");
Table newtab(test, "newtab");
newtab.AddColumn ("col1", Integer);
Column col1(newtab, "col1");
for (long i=1; i<100; ++i)
{
newtab.AddRow();
col1 << i;
}
// show all rows:
ForAllRows (newtab)
cout << col1 << endl;
// apply filter and delete even rows:
newtab.Filter ("col1 % 2 == 0");
ForAllRows (newtab)
newtab.DeleteRow();
// show all rows again:
newtab.Filter(NULL);
ForAllRows (newtab)
cout << col1 << endl;
test.Commit();
}
To delete a column from a table, use the DeleteColumn member function:
DBERROR DeleteColumn(CDBSTRING n);
Note that this is different from the delete operation on a Column object. Rather, it removes the database column from the database table. So, we could have
table.DeleteColumn("col1");
A side-effect of this operation is that any objects that refer to this column are marked invalid.
To delete a table from a database, use the DeleteTable member function:
DBERROR DeleteTable(CDBSTRING name);
Note that this is different from the delete operation on a Table object. Rather, it removes the database table from the database. So, we could have
db.DeleteTable("table_1");
A side-effect of this operation is that any objects that refer to this table are marked invalid.
The value in a field may be erased by using the PutNull function of the column class. The value at the current-row in the column is set to Null (no value).
DBERROR PutNull(void); // store a Null (empty) value
To erase all the values in a column, you may either traverse the entire table, writing Nulls into the column's value with the PutNull function, or you may use the Erase member function:
void Erase (void);
as in
ForAllRows(table)
column.PutNull();
or
column.Erase();
To delete all the rows in a table, use the Erase member function:
void Erase (void);
as in
table.Erase();
The attributes of a database may be fetched by using the following Database member functions:
CDBSTRING Path(void);
CDBSTRING Name(void);
CDBSTRING Release(void);
unsigned PageSize(void);
unsigned long HashSize(void);
int CachePages(void);
int Changed(void);
Both Path and Name return the same - the full path of the database file, to the extent it is available from the operating system. In some cases, full path qualification is not available.
The Release member function returns a string that represents the release number of the RC21 software that was used to create the database file. Please se a discussion of the meaning of "Release Numbers" on page 23.
The PageSize and HashSize return the parameters with which the database was created.
The CachePages function returns the parameters with which the database object was instantiated. In some cases this number is different from the Database constructor argument, for example, if the requested number of pages is not available.
The above functions are provided mainly for symmetry.
The Changed member function returns a nonzero value if the database has been changed since the last Commit.
Since a given database file must be accessed with a compatible release of the RC21 software it must be possible to determine the release number of the software without actually instantiating a Database object. This is accomplished by using the Version nonmember function:
CDBSTRING Version (CDBSTRING dbname);
Example:
// examp205.cpp - fetching database attributes
#include <rc21.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db", 2048, 4096);
Database test("test.db");
cout << "Changed: " << test.Changed() << endl;
cout << "Pages: " << test.CachePages() << endl;
cout << "Path: " << test.Path() << endl;
cout << "Name: " << test.Name() << endl;
cout << "Release: " << test.Release() << endl;
cout << "Page Size: " << test.PageSize() << endl;
cout << "Hash Table Size: " << test.HashSize() << endl;
}
The name, description, and view-expression (if any) of a table may be fetched by using the following Table member functions:
CDBSTRING Name (void);
CDBSTRING Description (void);
CDBSTRING View (void);
The name and the description of a table may be changed by using the member functions Name and Description with arguments:
DBERROR Name (CDBSTRING NewName);
DBERROR Description (CDBSTRING NewDescription);
DBERROR View (CDBSTRING NewView);
All the attributes of a column that were established when the column was created with the AddColumn function may be retrieved using the following Column member functions:
CDBSTRING Name (void);
CDBSTRING Description (void);
CDBSTRING Format (void);
COLATTRIBS Attributes (void);
DBCOL Reference (void);
Name(), Description(), and Format() return the name, description, and printf format specifier for the column.
Attributes() returns a COLATTRIBS containing information about datatype, indexing and so forth. See COLATTRIBS - Column Attributes on page 24.
DBCOL() returns a DBCOL pointer to a Column object for the reference column for this column (see Referential Integrity on page 24).
The name, description, and format of a column may be changed using the following:
DBERROR Name (CDBSTRING NewName);
DBERROR Description (CDBSTRING NewDescription);
DBERROR Format (CDBSTRING NewFormat);
The COLATTRIBS attribute of a column contains information about indexing. This is the only attribute that may be changed. The bits in the NewAttributes argument will be masked for the indexing bits and replace the corresponding bits in the attributes for the column. In this way, a column may be indexed after it is created. Then, if desired, it may be unindexed, saving the overhead of maintaining an index.
DBERROR Attributes(COLATTRIBS NewAttributes);
The FirstTable, NextTable, Database member functions can be used to walk through all the tables in a database much like ResetRow and NextRow are used to walk through all the rows in a table:
void FirstTable (void);
int NextTable (void);
DBTAB CurrentTable (void);
#define ForAllTables(_d) for (FirstTable(_d);NextTable(_d);)
FirstTable gets ready to walk through all the tables in the database. NextTable moves to the next table. CurrentTable creates a new Table object for the current table and returns a DBTAB pointer to it. So, we could have
Database test("test.db");
for (test.FirstTable(); test.NextTable();)
{
DBTAB t = test.CurrentTable();
delete t;
}
or we could use the provided macro ...
Database test("test.db");
ForAllTables (test)
{
DBTAB t = test.CurrentTable();
delete t;
}
Here is an example:
// examp206.cpp - traversing all the tables
#include <rc21.h>
#include <stdio.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database test("test.db");
// add some tables to the database
for (int i=0; i<10; ++i)
{
char tablename[20];
sprintf (tablename, "table_%2.2d", i);
test.AddTable(tablename);
}
// now, traverse the database tables
ForAllTables(test)
{
DBTAB t;
t = test.CurrentTable();
cout << t->Name() << endl;
delete t;
}
}
Note that the CurrentTable member function returns all tables, even the special internal tables that RC21 uses to store the data dictionary. These special tables are easily recognized because their names start with underscore (_).
These member functions and the corresponding Table member functions for traversing all the columns in a table can be used to write generalized utilities that work on a database of unknown contents.
The Table member functions for traversing all the columns in the given table are:
#define ForAllColumns(_t) for (FirstColumn(_t);NextColumn(_t);)
void FirstColumn (void);
int NextColumn (void);
DBCOL CurrentColumn (void);
Here is an example that combines ForAllTables and ForAllColumns:
// examp207.cpp - traversing all the tables
#include <rc21.h>
#include <stdio.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database test("test.db");
// add some tables to the database
for (int i=0; i<10; ++i)
{
char tablename[20];
sprintf (tablename, "table_%2.2d", i);
test.AddTable(tablename);
DBTAB t = new Table (test, tablename);
// for each table, add some columns
for (int j=0; j<10; ++j)
{
char columnname[20];
sprintf (columnname, "column_%2.2d", j);
t->AddColumn(columnname);
}
delete t;
}
// now, traverse the database tables
ForAllTables(test)
{
DBTAB t;
t = test.CurrentTable();
cout << t->Name() << endl;
// traverse all the columns in the table
ForAllColumns(t)
{
DBCOL c = t->CurrentColumn();
cout << " " << c->Name() << endl;
delete c;
}
delete t;
}
}
BLOBs are Binary Large Objects. BLOBs are a single, atomic entity that cannot be addressed in parts. EBLOBs (Extended Binary Large Objects), on the other hand, can be fetched and stored in parts. Both BLOBs and EBLOBs are values of a single field (cell, if you like) in a database table.
EBLOBs may be very large. They may be fetched and retrieved without requiring the allocation of memory to store the entire value. In fact, EBLOBs can be thought of as embedded files within a database.
EBLOBs support analogs to the standard Unix functions for manipulating files, lseek, read, and write. In addition, EBLOBs support the inject, and extract functions. This means you can insert a series bytes into an EBLOB stream or delete a series of bytes without having to reconstruct the entire EBLOB.
The field where an EBLOB resides within a table may be thought of as the handle of a file. To place an EBLOB in a field, use the form:
eblob *e = new eblob(f);
where f is a reference to a column or a pointer to a column. The field in the named column (at the current row) will be used as the handle for an EBLOB. If the current value of the field is Null, the eblob e will be a new, empty eblob.
To write data into an eblob, use
e->write (buffer, bytecount);
To read data at the current position, use
e->read(buffer, bytecount);
To seek to a specific location within the eblob, use
e->seek(address);
This may also be written
e->seek(address, seekfrom);
where seekfrom is one of the manifest constants, E_SEEK_SET, E_SEEK_CUR, and E_SEEK_END.
To set or reset a bookmark at the current position, use
e->mark("label");
To remove the bookmark, use
e->unmark("label");
To seek using a bookmark, use
e->seek(BOOKMARK("label"));
BOOKMARK is a helper class that resolves argument ambiguity.
NOTE: Bookmarks continue to point to original position after inject and extract operations.
To insert a sequence of bytes into the EBLOB at the current position, use
e->inject (buffer, bytecount);
To remove a sequence of bytes, use
e->extract (bytecount);
Like any other database operation, EBLOB operations are in suspense until a Commit operation is performed on the database.
RC21 provides a general error-handling mechanism that includes the ability to notify you if an error occurs. This is a sensible alternative to checking error codes after every RC21 operation. The capability is provided in the form of the SetErrorFunc function:
ERRORFUNC SetErrorFunc(ERRORFUNC arg);
Here is an example of its use:
#include <rc21.h>
void errorfunc (void)
{
PrintError (NULL);
}
void main (void)
{
SetErrorFunc (errorfunc);
// other operations ...
}
For a more complete discussion of error-handling including structured exception handling, see the discussion later in this document.
An RC21 database can be viewed as a set of 2-dimensional tables.
Each table has a set of rows and columns.
The intersection of a row and a column is a field. Each field has a datatype and a value. A value can be an integer, a real, a string, or a counted-byte array (sometimes called a BLOB [Binary Large OBject]). A variation of BLOB is USER data, which have their own comparison rules. An additional type, EBLOB, can be thought of as an embedded file type with the usual file operations - read, write, seek - and some additional operations - inject, and extract.
Tables and columns have a number of attributes including name and description. An RC21 database may also have certain index structures that help it to find fields with certain values or rows with combinations of values. These index structures are maintained automatically by RC21.
RC21 stores variable-length data like strings and BLOBs according to two different schemes, Shared or Nonshared (the default scheme).
Shared data is stored in a value pool. All occurrences of a given string or BLOB are stored only once - in the value pool, and are pointed to by all the fields that have that value. To facilitate this scheme, a hash table is present in a RC21 database. The hash table allows RC21 to discover quickly whether a given string is already present in the value pool. Both the value pool and the associated hash table are inaccessible by ordinary means. That is, they are for the internal use of RC21. However, you need to be aware that they exist, for RC21 performance depends upon the appropriate matching of hash-table size and database size. This hash-table size is under the control of the user at the time he/she creates a new database.
Nonshared data is stored in a heap. There is a one-to-one correspondence between field-values and heap entries, such that, when a field is deleted, the heap entry is deleted - freeing space for reuse by RC21.
The schema for a database, that is, the names of all the tables and columns and their attributes are stored in the Data Dictionary. The Data Dictionary is really just a set of two tables named "_tables", and "_columns". _tables stores data about the tables in the database. _columns stores data about the columns in the database. These two tables can be manipulated just like any other tables. However, special functions are provided to make routine dictionary operations like adding tables and columns simple.
Each software release of RC21 has associated a release number. The release number is of the form xx.yy.zz where xx is the Major Release, yy is the Minor Release and zz is the Sub Release. A Major Release is associated with a database file format. A Minor Release is associated with database operational features. A Sub Release is used to release bug fixes.
The release number of the software with which a given database file was created is stored in the file itself. A given release of software may only access database files with the same Major Release.
The release number of the software can be accessed with the Release function.
The release number of a database file can be accessed with the Release(char* dbname) function.
There are six fundamental types of data that you may store using RC21. These are as follows:
DBINTEGER - 32-bit signed integer. If indexed, these are ordered by numerical value.
DBREAL - double-precision floating point. If indexed, these are ordered by numerical value.
DBSTRING - a null-terminated character string. There are two subtypes of DBSTRING: String and USTRING. If indexed, String data is ordered according to strcmp return values. USTRING data are ordered according to stricmp return values (case insensitive compare).
DBBLOB - an unstructured binary object containing one or more bytes; each byte may contain any value, including the 0 value; DBBYTES may be used to store almost any kind of data - C structures, video or audio images, pictures. DBBLOB data , if indexed, will be ordered by results of memcmp function return values (byte-by-byte compare).
DBUSER - this is like DBBLOB except it will be treated differently for the purpose of comparing with other DBUSER values and inserting in an index; the user may provide a compare routine that evaluates the order of any two DBUSER values and returns +1, 0, or -1.
DBEBLOB - an embedded file type.
When adding a new column to the database, you may specify one of the preceeding types using the keywords Integer, Real, String, Blob, User, or Eblob. When you do this, only the specified type of data may be stored in a column. If you do not specify a data type for a column, any type of data may be stored in that column. We can think of columns as being typed or untyped. Regardless of whether a column is typed, each individual field in that column has a type. The type for a field is simply the type of data that is stored in that field.
We store data in a database using any of a family of type-specific Put functions: PutInteger, PutReal, PutString, PutBlob, and PutUser. The EBLOB type is associated with a field by instantiating an eblob object associated with the given field. Seek and read operations are performed with the eblob object.
Likewise, we retrieve data from a field using any of a family of type-specific Get functions: GetInteger, GetReal, GetString, GetBlob, and GetUser. If the type of data stored in a field does not match the Get type, it will be converted if possible. For example, if we store a number using PutInteger, we may retrieve it using GetReal, or even GetString (a character-string representation of the integer value will be returned). However, it's more efficient and more accurate to Put and Get using symmetric routines. EBLOB objects have a read function.
When we retrieve data that cannot be returned on the stack - String, Blob, and User data, RC21 will return a pointer to that data. This pointer is valid until your next call to a RC21 function. Subsequent calls to RC21 may change the contents of memory at that location.
Get and put functions are somewhat simplified by overloading the << and >> operators. Thus, we may have something like:
column << “This is a test\n”;
or
column << 15;
Every table and column in a database has a name. Names for tables and columns are strings that contain one or more characters. Thus, a name may contain as many characters as a string. In DOS, this means you could have a name that was thousands of characters long. However, this would probably not be convenient.
In RC21, we restrict the characters that may appear in a name. RC21 names may contain any of the upper or lower-case letters (a..z, A..Z), the digits (0..9), and the underscore (_). Names may not contain embedded spaces. Names should not begin with an underscore. Names that do begin with an underscore are reserved for the use of RC21. Names also may not begin with a digit. You may recognize these rules as the same as the rules for the C language. This was intentional.
The following are valid names Name, Address, Social_Security_Number, SocialSecurityNumber, SSNO, ssno (different from SSNO), AbCdEf123, etc.
The following are not valid names: _ItemNumber (starts with an underscore), 1BC (starts with a digit), ABC.def (contains a period, which is not a legal name character), qwert$yuiop (contains a $, which is not a legal name character).
All tables in a given table must have unique names. That is, no two tables may have the same name.
Columns within a table must have unique names. However, two different tables may have columns that share a name. For example, it is allowed to have a table named "Products" thas has a column called "Name" and another table called "Plants" with a table also called "Name".
A table name and a column name may be combined to refer to a specific column in a specific table by concatenating the table name and column name with a dot. So, we could have a column referred to by the name "Products.Name". Another column could be referred to by the name "Plants.Name". Such names are called qualified column names.
Expressions of the form
database[tablename]
are interpreted as references to table objects referring to the database table with the given name. Similarly, expressions of the form
table[columnname]
refer to an underlying column, and ...
database[tablename][columnname]
would also refer to a column. These are called manifest objects. Here is an example:
// examp101.cpp
#include <rc21.h>
#include <iostream.h>
void main(void)
{
Remove ("test.db");
Create ("test.db");
Database db ("test.db");
db.AddTable ("newtab");
cout << db["newtab"].Name() << endl; // outputs "newtab"
db["newtab"].AddColumn("newcol");
cout << db["newtab"]["newcol"].Name() << endl; // outputs "newcol"
}
One might be tempted to use something like db[“tablename.columnname”] to represent a reference to a column. This won’t work simply because the operator [] for a database always yields a reference to a table.
In addition to its name, a column has a description, a C format code, a reference column and other attributes called COLATTRIBS.
The description is simply descriptive information for a field. For example, we may have a column called SSNO that has the description "Social Security Number". The description field is used only for documentation.
The C format code is stored along with the column so that we may have an easy way to write a program that displays the contents of a database without knowing exactly what's in the database.
The reference column is a reference to another column, probably in another table, that contains valid field values for this column. You need not specify a reference column for a column, but if you do, RC21 will enforce the reference rule. That is, you may only store values that appear in the reference column. As an example, consider a table of employees that has a column called JobCode. There is another table in the database called Skills. This table has columns called Code, Description, BasePay, and so forth. The Code column contains all the valid job codes. An employee must have a job code that appears in this table (or possibly no code at all). Now, when we want to add an employee to our Employees table, we want to enter his or her JobCode. RC21 will automatically cross check in the Code column of the Skills table to make sure that a valid code is being entered. This is an aspect of what is called referential integrity.
COLATTRIBS is an aggregate of other information about the column. It tells us whether the column has an index, whether to store it as Shared or Nonshared, whether each value in the column must be unique, its data type, and whether we will allow null values in the field. For a thorough discussion of COLATTRIBS see the reference section of this manual.
The database may be created entirely using C program statements. There is also a command utility, pinnacle.exe, that you may use to create your database. More about that later.
The operations required to define a database are:
· Create an empty database.
· Add tables to the database.
· Add columns to the tables.
To illustrate this we will create a simple database that contains only one table.
//--------------------------- FONEMAKE.CPP -------------------------------
#include "rc21.h"
void errorfunc(void)
{
PrintError("WHOOPS");
exit (-1);
}
void main (void)
{
SetErrorFunc(errorfunc);
Remove ("fonelist.db");
Create ("fonelist.db");
Database db ("fonelist.db");
db.AddTable ("PhoneList");
Table fonelist (db, "PhoneList");
fonelist.AddColumn ("Last", String);
fonelist.AddColumn ("First", String);
fonelist.AddColumn ("Address", String);
fonelist.AddColumn ("Phone", String);
fonelist.AddKey ("LastFirst", "+Last+First", Unique);
db.Commit();
}
A table may be added to a database by using the AddTable member function of class database. This function specifies the name and description of the table and the view expression if this new table is a view rather than a base table (See VIEWS for more about this). An ordinary, or base table does not have a view expression. The normal form for AddTable is:
DBERROR AddTable(CDBSTRING name,
CDBSTRING description=(CDBSTRING)"no description",
CDBSTRING view = (CDBSTRING)"");
So, a table has a name and a description. Sometimes it is a view into another table, in which case it has a view expression. Once added, a table may be deleted. Thus, it is possible to have temporary tables in a database.
A column may be added to a table by using the AddColumn member function of the table class. This function specifies the name of the new column, the description, the C format code, a referential integrity column, and a list of other attributes like data type that are contained in one 32-bit word.
DBERROR AddColumn(CDBSTRING name,
COLATTRIBS attr=NOATTRIBS,
CDBSTRING description=(CDBSTRING)"no description",
CDBSTRING format=(CDBSTRING)"",
const DBCOL reference=NULL);
Description is for
documentation purposes.
Format need not be supplied. You may simply use "". However, supplying a C format code will make it possible to use certain utilities like QUICKTAB.
Reference is the name of another column or a column object that refers to another column that has valid values for this column. Reference may be supplied as NULL. If you supply the name of another column or a DBCOL object, each time data is written into this new column, the validity of the value will be checked against the values in the referential integrity column.
The final parameter, attributes, is a collection of other attributes of the column. The attributes are specified as an integer expression of keywords or'd together to specify the various attributes. The attributes are grouped together into classes. You may only specify one value per class. The classes of attributes you may specify are depicted in the following table:
|
Defaults to untyped - any type of value may be stored in any row |
|
|
Integer |
A 32-bit signed integer |
|
Real |
A double-precision floating point number |
|
String |
A zero-terminated sequence of characters |
|
USTRING |
A string where upper-case and lower-case characters are treated the same for the purpose of indexing, although the original case is preserved |
|
BLOB |
A counted array of bytes that may contain zeros |
|
User |
A special type of BLOB with user-defined sorting (ordering) rules |
|
|
|
|
Indexing |
Defaults to NonIndexed |
|
Indexed |
An index will be automatically maintained for this column. |
|
NonIndexed |
No index will be maintained (default). |
|
|
|
|
Nulls |
Tells whether missing values (nulls)
are permitted. Defaults to
nulls-permitted. |
|
Nulls |
This column may have null (missing) values (default). |
|
NoNulls |
This column must have a value in every row. |
|
|
|
|
Unique/NonUnique |
Tells whether each value in the column
must be different from every other value.
Defaults to uniqueness not required. |
|
Unique |
This column must have unique values for every row. |
|
NonUnique |
Values in the column need not be unique (default). |
|
|
|
|
Sharing |
Tells whether values for this
column will be stored in the heap,
or the value pool. Defaults to storing values in the heap |
|
Shared |
Values in this column will be stored in a shared value pool. |
|
NonShared |
Values in this column will be stored in a value heap (default). |
|
|
|
|
Embedded
Key Length |
Used for index performance;
defaults to 0. |
|
KeyLen(key length) |
If there is an index for this column the first key length bytes of the value will be embedded in the index; default key length is zero. This affects performance only - not the size of a key value. |
|
|
|
Here are some examples:
t.AddColumn (“Column0”); // no type, description specified
t.AddColumn ("Column1", Integer, "A New Column", "%ld");
t.AddColumn ("Column2", String, "A New Column", "%-20s");
t.AddColumn ("Column3", USTRING | Indexed | KeyLen(4), "A New Column", "%-20s");
t.AddColumn ("Column4", Integer, "A New Column", "%ld", "Tab1.Codes");
t.AddColumn ("Column5", String | NoNulls, "A New Column", "%-20s");
t.AddColumn ("Column6", Blob | Indexed, "A New Column", "");
t.AddColumn ("Column7", User | Indexed, "A New Column", "%s");
Once added to a table a column may be deleted. Thus, you may have temporary columns in a table.
Now that we have created a database, we may write data into it. To write data, simply add a row to a table with the AddRow function. The row just added now has no values in it. To store data in each of the columns in the row, use the put functions or use the << operator.
//--------------------------- FONEADD.CPP ---------------------------------
#include <iostream.h>
#include <string.h>
#include "rc21.h"
void errorfunc(void)
{
PrintError("WHOOPS");
exit (-1);
}
void main (void)
{
SetErrorFunc(errorfunc);
Database db ("fonelist.db");
Table Fonelist (db, "PhoneList");
Column Last(Fonelist, "Last"), First(Fonelist, "First"),
Address(Fonelist, "Address"), Phone(Fonelist, "Phone");
for (;;) // exit on ctl-c
{
Fonelist.AddRow();
cout << "\nLast Name: ";
cin >> Last;
cout << "First Name: ";
cin >> First;
cout << "Address: ";
cin >> Address;
cout << "Phone: ";
cin >> Phone;
db.Commit ();
}
}
Here is a simple program to store the numbers from 1 to 50 and their square roots:
// examp901.cpp
#include <rc21.h>
#include <math.h>
void main (void)
{
Create ("numbers.db");
Database db ("numbers.db");
db.AddTable ("Numbers");
Table t (db, "Numbers");
t.AddColumn ("Number", Real);
t.AddColumn ("SQRT", Real);
Column c1 (t, "Number");
Column c2 (t, "SQRT");
DBREAL i;
for (i=0; i<50; ++i)
{
t.AddRow();
c1 << i;
c2 << sqrt(i);
}
db.Commit();
}
So, in general, to write data in the database, we add a row to a table then use one of the Put member functions - PutInteger, PutReal, PutString, PutBlob, or PutUser. We may modify the contents of a field by positioning ourselves at a row, and using one of the Put functions to replace the value. Use the polymorphic Put function to simplify things. For the absolute maximum in simplicity, use the form column << value.
We put data into a database so we can get it back, right? We read data from an RC21 database by positioning ourselves at a row in a table using a sequence of ResetRow, NextRow function calls. Table.ResetRow positions us before the first row of a table. Table.NextRow moves to the next row, returning TRUE if there is a next row, and a FALSE if we have reached the end of the table. The table.NextRow function takes a +1 or -1 argument to indicate whether to move forward (+1) or backward (-1) through the table.
Data are fetched from named columns using the Get functions. These functions are GetInteger, GetReal, GetString, and GetBlob. As a simplification, you may simply convert the column to the appropriate type. If the type of data stored is not the same as the requested Get type, a conversion will be performed if possible. If a conversion is not possible, an error will be set in the global error code, DB_Error.
Here is a program that reads and prints the data from the list of numbers and square roots:
#include <rc21.h>
#include <math.h>
void main (void)
{
Database db ("numbers.db");
Table t (db, "Numbers");
Column c1 (t, "Number");
Column c2 (t, "SQRT");
ForAllRows(t)
cout << DBREAL(c1) << ' ' << DBREAL(c2) << ' ' << endl;
}
We find data in the database by specifying a filter expression and applying it to a table using the Filter member function. A filter expression is a string that resembles a C expression but with the names of columns instead of the names of variables. We then go through all the rows in a table and the rows that pass through the filter are "found".
Consider the table we've been working with. Let's say we want to find all the even numbers in the first column. Here's the program:
// examp906.cpp
#include <rc21.h>
#include <math.h>
void main (void)
{
Database db ("numbers.db");
Table t (db, "Numbers");
Column c1 (t, "Number");
Column c2 (t, "SQRT");
t.Filter("Number%2==0");
ForAllRows(t)
cout << DBREAL(c1) << ' ' << DBREAL(c2) << ' ' << endl;
}
ForAllRows is a macro that substitutes for the ResetRow, NextRow sequence.
If there is only one row that would satisfy the search criteria, the Find member function may be used. Find locates any one row that satisfies the filter expression. Let's say we want to find the row where the value in Numbers is 25:
// examp903.cpp
#include <rc21.h>
void main (void)
{
Database db ("numbers.db");
Table t (db, "Numbers");
Column c1 (t, "Number");
Column c2 (t, "SQRT");
t.Find("Numbers == 25");
cout << DBREAL(c1) << ' ' << DBREAL(c2) << ' ' << endl;
}
Modifying a field is just like writing a field the first time. We simply use one of the Put functions or operators to put a new value. To illustrate, let's take our table of numbers and change all the odd numbers.
#include <rc21.h>
#include <math.h>
void main (void)
{
Database db ("numbers.db");
Table t (db, "Numbers");
Column c1 (t, "Number");
Column c2 (t, "SQRT");
t.Filter("Number%2==1");
ForAllRows(t)
{
c1 << DBREAL(c1)*2;
c2 << sqrt(DBREAL(c1));
}
t.Filter(NULL);
ForAllRows(t)
cout << DBREAL(c1) << ' ' << DBREAL(c2) << endl;
}
We can add rows to a table. We can delete rows. Let's delete all the odd rows from our table.
// examp905.cpp
#include <rc21.h>
#include <math.h>
void main (void)
{
Database db ("numbers.db");
Table t (db, "Numbers");
Column c1 (t, "Number");
Column c2 (t, "SQRT");
t.Filter("Number%2==1");
ForAllRows(t)
t.DeleteRow();
t.Filter(NULL);
ForAllRows(t)
cout << DBREAL(c1) << ' ' << DBREAL(c2) << endl;
}
The Delete member function deletes the current row in the table.
When a column is created it may be declared to be indexed by specifying the keyword Indexed in the attributes argument of the call to table.AddColumn member function. The effect is that a btree index will be created and automatically maintained for all the values in the column. Later, the table.OrderBy function may be called to indicate that access to the rows in the table will be in the order of the values in that column.
Here's a program that creates a column of pseudo-random numbers, then prints them out in ascending order, then in descending order.
// examp907.cpp
#include <iostream.h>
#include <rc21.h>
void main (void)
{
Remove ("test.db");
Create ("test.db");
Database db("test.db", "rw", 20);
db.AddTable ("table");
Table t(db, "table");
t.AddColumn("column", Integer | Indexed);
Column c(t, "column");
// add 100 rows to the table and put in random values.
DBINTEGER i;
for (i = 0; i < 100; ++i)
{
t.AddRow();
c << DBINTEGER(rand());
}
// now, print out the values in natural order
cout << "NATURAL:" << endl;
ForAllRows (t)
cout << DBINTEGER(c) << ' ';
// now, print out the values in ascending order
cout << "ASCENDING:" << endl;
c.OrderBy();
ForAllRows (t)
cout << DBINTEGER(c) << ' ';
// now, print out the values in descending order
cout << "DESCENDING:" << endl;
ForAllRowsBackwards (t)
cout << DBINTEGER(c) << ' ';
}
There are several things to note here. First, note that the Table ordering is controlled by a Column member function. The semantics is that the Table associated with the Column is now ordered by the Column. This may also be accomplished by using Table.OrderBy("column_name");
Next, note that the OrderBy remains in effect until it is replaced by another OrderBy or a Table.Unordered().
Finally, note the introduction of the ForAllRowsBackward macro. This is just a variation of the ForAllRows macro. The definition for the macro is in the file.
Composite keys are virtual columns that allow you to access data in the order of the values of more than one column. An obvious example is the phone book. The phone book table contains four columns: LastName, FirstName, Address, and PhoneNumber. We want to list our phone number by FirstName within LastName. We do this by creating a composite key called LastFirst that is made up of LastName and FirstName. Here is a program that creates the database:
//--------------------------- FONEMAKE.CPP -------------------------------
#include "rc21.h"
void main (void)
{
Remove ("fonelist.db");
Create ("fonelist.db");
Database db ("fonelist.db");
db.AddTable ("PhoneList");
Table fonelist (db, "PhoneList");
fonelist.AddColumn ("Last", String);
fonelist.AddColumn ("First", String);
fonelist.AddColumn ("Address", String);
fonelist.AddColumn ("Phone", String);
fonelist.AddKey ("LastFirst", "+Last+First", Unique);
db.Commit();
}
The arguments for the AddKey member function are key-name, key-list, and attributes. The key-list argument is a list of column names each preceeded by either a + sign or a - sign. The plus indicates ascending order. The minus indicates descending order. In the example above, we will have an index in ascending order of LastName and FirstName. An index may contain any combination of other column names with any combination of signs, but the columns must be real columns and not composite keys themselves. The attribute argument may contain the keyword unique to indicate that there must be no other key entries with this value. That is, to take our phone list as an example, that there must be no other identical combination of LastName and FirstName. If you attempt to store a value that would be non-unique, an error condition will be created, although the data will be stored anyway. This implies that you should check the global error flag, DB_Error , after you have complete this operation.
Note that composite keys, like all other indexes are maintained automatically by RC21. You do not have to build a key entry or anything like that. Simply by storing the data with PutString in the columns LastName and FirstName, the composite key will be maintained. This raises a question: when does the key get built? Does the key get built when we store LastName or when we store FirstName? Or does it get rebuilt as we store each value? The answer is that the composite key gets built when we attempt to move to another row. We move to another row with the next AddRow, Find, ResetRow, or NextRow as well as with several other function calls. The question, then, is how do we know when to check the DB_Error flag for a unique violation? The answer is - we can force the composite key to be built and stored by using the Table.FinishRow() function. FinishRow will do all the composite key building and unique checking associated with operations on a given row. So, we can check for errors before we move away from the row. Thus, we will have a chance to correct our mistakes if necessary. Note that it not necessary for you to call FinishRow to cause the composite key to be built. Its only use is to force this operation before you move to another row, possibly complicating error correction.
A derivation of composite keys is substring keys. Substring keys, like composite keys, comprise a series of strings. In substring keys, you specify a subset of the characters in each component of the key. For a substring key, you specify the beginning character (first character is designated "0") and the number of characters that are to be considered in each component. Here is an example:
t.AddKey ("key", "+field1:0:5+field2:3:4");
The first five characters of field1 and the fourth through the seventh characters of field2 are the parts of the above key.
The common objects in RC21 are Table and Column.
There are three ways to refer to a Table or Column object:
Direct Reference - define the object:
Table a(db, "Table1");
Pointer Referral - use the builtin pointer-type (or handle) DBTAB, or DBCOL and the new operator:
DBTAB t = new Table(db, "Table1");
Manifest Referral - refer to the table or column using the special syntax for manifest objects.
db["Table1"]
to refer to a table, or
db["Table1"]["Column1"]
to refer to a column
It is possible to have several objects that refer to the same underlying database table. For example:
DBTAB t = new Table(db, "Table1"), t2 = new Table(db, "Table1");
Table t1(db, "Table1");
t->Find ("Column1 == 1");
t1.Find ("Column1 == 2");
t2->Find ("Column1 == 3");
db["Table1"].Find("Column1 == 4");
All the objects are positioned at different rows within the (underlying) table. Therefore, the different objects are not interchangeable.
Databases, Tables and columns aquire names, descriptions, and other attributes when we add them to the database. These properties may be retrieved by using a set of RC21 nember functions. Here are the functions:
|
int Database.Changed(void); |
returns nonzero if database has been changed |
|
int Database.CachePages(void); |
returns the number of cache pages allocated at open |
|
CDBSTRING Database.Path(void); |
returns the path of the database file |
|
CDBSTRING Database.Name(void); |
same as path() |
|
CDBSTRING Database.Release(void); |
returns the release number in the form xx.yy.zz |
|
Unsigned Database.PageSize(void); |
returns the size of one database page established a Create |
|
Unsigned long Database.HashSize(void); |
returns the number of elements in the hash table |
|
|
|
|
CDBSTRING Table.Name(void); |
returns the name of a table |
|
CDBSTRING Table.Description(void); |
returns the description of a table |
|
CDBSTRING Table.View(void); |
returns the view expression of a table that is a view |
|
|
|
|
CDBSTRING Column.Name(void); |
returns the name of a column |
|
CDBSTRING Column.Description(void); |
returns the description of a column |
|
CDBSTRING Column.Format(void); |
returns the printf format stored with a column |
|
DBCOL Column.Reference(void); |
returns handle to associated reference column for this column |
|
COLATTRIBS Column.Attributes(void); |
returns the other attributes stored in a COLATTRIBS word |
The following program illustrates the use of some of these
functions:
// examp906.cpp
#include <rc21.h>
DEFT_ERROR
void main (void)
{
SET_DEFT_ERROR;
Database db("fonelist.db", "rw", 25);
Table tab(db, "PhoneList");
cout << db.Path() << " - Created by version "
<< db.Release() << " of RC21." << endl;
cout << " Hash table size: " << db.HashSize() << endl;
cout << " Page size: " << db.PageSize() << endl;
cout << " Database opened with " << db.CachePages() << " pages."
<< endl;
cout << "Table: " << tab.Name() << " - " << tab.Description() << endl;
ForAllColumns(tab)
{
cout << "Column: " << tab.CurrentColumn()->Name();
cout << " - " << tab.CurrentColumn()->Description();
cout << " Format: " << tab.CurrentColumn()->Format();
cout << endl;
}
}
This program will produce the following text on stdout:
fonelist.db - Created by version 7.1 of RC21.
Hash table size: 512
Page size: 512
Database opened with 25 pages.
Table: PhoneList - PhoneList
Column: _status - status of row in table Format: %1d
Column: Last - no description Format:
Column: First - no description Format:
Column: Address - no description Format:
Column: Phone - no description Format:
Column: LastFirst - +Last+First Format:
Once a table or column has been set up you may have a need to change some of its attributes. For example, you may wish to change the name or description of a table or a column. The following functions help you to do this:
|
DBERROR Table.Name(CDBSTRING NewName); |
changes the name of a table |
|
DBERROR Table.Description(CDBSTRING NewDescription); |
changes the description of a table |
|
DBERROR Table.View(CDBSTRING NewView); |
changes the view expression of a table that is a view |
|
|
|
|
DBERROR Column.Name(CDBSTRING NewName); |
changes the name of a column |
|
DBERROR Column.Description(CDBSTRING NewDescription); |
changes the description of a column |
|
DBERROR Column.Format(CDBSTRING NewFormat); |
changes the printf format stored with a column |
|
DBERROR Column.Attributes(COLATTRIBS NewAttributes); |
changes the other attributes stored in a COLATTRIBS word |
From time to time it may be necessary to access the rows in a table in the order of the contents of some column. This is normally achieved by declaring that column to be Indexed and then doing an OrderBy of that column. Then, the rows in the table will be accessed in ascending or descending order by that column. However, let's say that the column in question is not indexed. This is easily remedied by changing the attributes of the column so that it is indexed. Then you may perform whatever operations on the table are desired. Afterwards, you may change the column back to NonIndexed. This sequence of operations is achieved by using the Attributes function with the Indexed attribute set. Later, you may revert to non indexed by setting the NonIndexed attribute for the column. Alternatively, you may simply use the Index or Uninex Column member functions.
If you find yourself doing this frequently for a given column you should ask yourself if you should just leave the column indexed. After all, each time you change this attribute to Indexed, RC21 must build a complete index. However, if you need the index only once, it is better to remove the index so as to avoid the overhead of maintaining an unnecessary index.
COLATTRIBS is a typedef for a word that contains attributes for a column. A COLATTRIBS word contains information about the following attributes:
Data type - the type column may be any of the following: Integer, Real, String, USTRING, Blob, User, or Eblob. It may also be unspecified, in which case any type of data may be stored in that column. If a data type is specified, only data of that type may be stored in the column. A reserved type, Key, is used by RC21 to identify composite key columns. The Key attribute may not be specified by the user.
Sharing - data in a column will be stored in a value pool or in a heap depending upon the value of the Sharing attribute. The values are Shared or NonShared. Shared data will be stored in a value pool. Only one copy of a data value will be found in the pool. All fields that share this value will reference it. When a Shared value is written to the database, it must first be located, if possible, in the value pool. This is accomplished by hashing-and-chaining. When a Shared value is deleted, the corresponding entry in the value pool is not deleted. This is because another field may be referencing this value. NonShared data is stored in a heap. There is a one-to-one relationship between heap values and NonShared fields. When the value of a NonShared field is deleted or changed, the corresponding entry in the heap is freed. When an entire page of heaped data is freed, the page is returned to the PINNACLE filespace manager and may be reused. Thus, the advantages of NonShared data are twofold: no chaining necessary to store data; deleted data frees up space. You should choose the Shared or NonShared attributes depending upon your understanding of the nature of the data in the column. If neither Shared or NonShared is specified, Shared is the default attribute.
Nulls - the two values for this attribute are Nulls and NoNulls. Nulls implies that null values are permitted. NoNulls implies that null values are not permitted. If a column has the attribute NoNulls, any attempt to store a null value in a field by the PutNull member function or by failure to give a value to a field in a new row after the AddRow function will produce an error. Nulls is the default attribute.
Unique - this attribute may have the values Unique and NonUnique. Columns with the Unique attribute will never have two different rows with the same value. An attempt to store a value that would violate this assertion will result in an error.
Indexing - this attribute may have the values Indexed and NonIndexed. Indexed columns will have an automatically-maintained index so that rows in the table may be retrieved in ascending or descending order depending on the value of this column. This is accomplished with the OrderBy function. Also, fast access to a rows with the Find function or to a set of rows with the Filter function is possible. NonIndexed columns have no index. Finds and Filters will require linear searching of the column. OrderBy will result in chronological access.
Keywords are defined in RC21.H for each of these attributes. In addition, mask keywords are defined so that you can retrieve the value of an attribute for a specific class.
Attributes may be combined in a COLATTRIBS word by or'ing together the attribute keywords. The following, for example, is a valid COLATTRIBS expression:
String | Indexed | NoNulls | Unique | NonShared
Attributes within a TYPE may not be or'd together. Thus, the following is not a valid COLATTRIBS expression:
String | Integer | NoNulls | Unique | NonShared
Attributes may be and'd with the class masks to yield the attribute value for a given class. For example, the expression
(attributes & IndexMask) == Indexed
would yield the value TRUE if attributes contained the Indexed attribute. Or, you could write
(attributes & Indexed)
which would yield a non-zero value if attributes contained the Indexed attribute.
A COLATTRIBS expression is used as an argument to the AddColumn function when you are adding a column to a table. If you desire not to specify an attributes, and just allow them to default, the value 0 may be used. This has been #define'd to the keyword NOATTRIBS.
Attributes of a column may be subsequently retrieved by the Attributes member function. These attributes may be displayed using the DB_DecodeAttributes function. The inverse of the the DB_DecodeAttributes function is the DB_EncodeAttributes function, which encodes a character string into a COLATTRIBS word
Some of these attributes may be changed by the Attributes function with a non-void argument. Some attributes may not be changed. An attempt to change non-changeable attributes will result in an error. The only attribute that is changeable is the Indexing attribute.
Consider the following problem: you have a Vendor table, and a Purchases table. Each row in the Vendor table has a vendor number, name, address, phone number, and so forth. In the Purchases table each row has date, vendor number, item, quantity, price, and so forth. It is essential that the vendor code in the Purchases table corresponds to some valid vendor number in the Vendor table. Guaranteeing that this is the case is an example of what is called "referential integrity".
RC21 supports referential integrity in a simple way. When you add a column to a table you may define a referential-integrity column that is to be associated with that column. To use the example in the previous paragraph, we would create the column in the Purchases table as follows:
Table t1(db, “Purchases”);
table.AddColumn("Vendor", Integer, "Vendor Code", "%4ld", "Vendor.Number");
The last argument, “Vendor.Number” specifies the table and column for referential integrity.
When a referential-integrity column exists for a column, RC21 will attempt to ensure that only permitted values will be stored. An error condition will be raised if your program attempts to violate the referential-integrity constraint, and the value will not be stored. However, the row where you attempted to store the data will continue to exist. You must decide whether to delete the row, store another value in the column, or whatever is appropriate.
A filter is an expression that evaluates to a TRUE or FALSE value (nonzero or zero respectively) when applied to a row in a table. In other words, it is a predicate. It is similar in syntax to a C language expression. There are operators, which may be relational or arithmetic operators, and parentheses, and operands which may be constants, or column-names.
A filter may be applied to a Table object by invoking theFilter member function as in the following example:
Database db("employee.db");
Table tab(db,"EMPLOYEES");
tab->Filter("JOBCODE = 5 || JOBCODE == 7");
tab->OrderBy ("LASTFIRST");
cout << "All employees with jobcode 5 or 7:\n" << endl;
ForAllRows (tab)
cout << CDBSTRING(tab["FirstName"]) << CDBSTRING(tab["LastName"]) << endl;
The example above shows how a filter causes only certain rows for which the filter expression is TRUE to be retrievable. In this case, the filter examined the column named "JOBCODE" and tested for certain values.
The operators permitted in a filter expressions include the following:
Arithmetic operators: +, -,
*, /, %
Relational operators: ==, !=, >, <, >=, <=
Wildcard match operators: ?, ~= (these are equivalent), and ?*
(case-insensitive).
Wordsearch operator: ??
Substring operator: ?>
Parentheses: (, )
The operands that may be used in a filter expression are:
Column name - no quotes
Strings surrounded by single quotes; strings may include escaped characters preceeded by backslash (\)
Numbers - all numbers start with a digit; if the first digit is 0 and this is followed by a 'B' or a 'b', the number is a binary number and may contain only 0's or 1's; if the first digit is 0 and this is followed by an 'X' or an 'x', the number is a hexadecimal number and may contain only the hexadecimal digits 0-9, a-f and A-F; if the first digit is zero and this is not a binary or a hexedecimal number, it is taken to be an octal number and may contain only the octal digits 0-7; sign extension is not performed. All other numbers are either integers or reals. If only digits are present in the number, it is an integer. If other characters acceptable to scanf are present, it is a real number.
Replaceable Parameters - %R, %r, %I, %i, %S, %s, %B, %b - which are replaced with the actual values of the corresponding arguments in the argument list of a call to any of the functions that have filter-expression as an argument
A filter expression, when applied to a row in a table, produces a TRUE or FALSE value. Here are the semantic rules:
There are binary expressions and unary expressions; binary expressions have the form term1 operator term2 Unary expressions have the form operator term1
Parentheses cause the evaluation of the expression inside parentheses as a term
term1 + term2: add term1 to term2
term1 - term2: subtract term2 from term1
term1 * term2: multiply term1 by term2
term1 / term2: multiply term1 by term2
term1 % term2: moduls of term1, term2
term1 > term2: 1 if term1 is greater than term2, otherwise 0
term1 < term2: 1 if term1 is less than term2, otherwise 0
term1 == term2: 1 if term1 is equal to term2, otherwise 0
term1 >= term2: 1 if term1 is greater than or equal to term2, otherwise 0
term1 <= term2: 1 if term1 is lexx than or equal to term2, otherwise 0
term1 != term2: 1 if term1 is not equal to term2, otherwise 0
term1 ? term2: 1 if term1 matches the wildcard expression term2 (see below), otherwise 0
term1 ?* term2: 1 if term1 matches the wildcard expression term2 (see below), otherwise 0; matching is case-insensitive
term1 ?? term2: 1 if term1 satisfies the wordsearch string,term2 (see below), otherwise 0
term1 ?> term2: 1 if term1 contains the substring term2, otherwise 0; substring tests are case-insensitive
!term1: 1 if term1 is zero; 0 if term1 is nonzero
-term1: the
negative value of term1
after evaluation of the filter expression, a non-zero value is considered TRUE, a zero value is considered FALSE
"LastName == 'Smith'"
"LastName == 'Smith' && FirstName == 'John'"
"Age > IQ"
"Salary > 4*Mortgage && Salary > 25000"
"LastName ? 'Mc*' || LastName ? 'O\'*'"
A wildcard pattern comprises a sequence of wildcard phrases. A wildcard phrase is one of:f
• Any non-null character except asterisk (*), question mark (?), left brace ([), left bracket ({), or circumflex (^). These ordinary characters are matched literally.
• An asterisk to match an arbitrary string.
• A question mark to match any signal character.
• A list of wildcard patterns enclosed within braces and separated by commas. This matches any of those wildcard patterns.
• A list of characters or ranges of characters (like "a-z" for "a" to "z") enclosed within square brackets and with no separators except a hyphen between the two characters of a range. The list may be preceeded with a circumflex (^) to match any character BUT the specified ones.
|
"*" |
matches anything. |
|
"a*z" |
matches "a" folowed by 0 or more characters followed by "z". |
|
"a?c" |
matches "aac", "abc", "acc", "adc", and so forth. |
|
"{if, and, but}" |
matches any of "if", "and", or "but" |
|
"[a-f,z]*" |
matches any string that starts with "a", "b", "c", "d", "e", "f", or "z". |
|
"[^x]*" |
matches any string that doesn't start with an "x". |
|
"\[*]" |
matches anything within square brackets. The \ means the next character is quoted. Use the \ just like you would in C. |
The operator "??" indicates a wordsearch operation. The wordsearch operation permits the scanning of text fields for the presence or absence of words. The wordsearch operator is much more convenient than the wildcard operator for this sort of application.
A word is defined as a sequence of characters comprised entirely of digits, upper or lower case letters, underscores, apostrophes, and dashes. Words do not include whitespace or punctuation.
A phrase is defined as a sequence of words.
Wordsearch Operators comprise the following: && (and), || (or), ! (not), () (parentheses), [] (phrase brackets].
The wordsearch operator takes as an operand a string that contains a wordsearch expression. The wordsearch expression consists of words and operators.
'Kentucky && racing'
'Kentucky || racing'
'!Kentucky && racing'
'Senate && (welfare || education)'
'[Abraham Lincoln] && abolition'
'O\'Bryan || O\'Malley'
Precedence from highest to lowest: [], (), !, &&, ||.
Now, consider that we have a table like this:
|
TABLE: |
LibraryOfCongress |
|
|
COLUMN: |
CatalogNumber |
string |
|
COLUMN: |
Title |
string |
|
COLUMN: |
Author |
string |
|
COLUMN: |
Text |
string |
|
COLUMN: |
Illustrations |
blob |
To find all books in the LC about Abraham Lincoln and abolition, you could use something like:
Table tab(db, "LibraryOfCongress");
tab.Filter (db["LibraryOfCongress"], "Text ?? '[Abraham Lincoln] && abolition'");
ForAllRows (tab)
{
cout << CDBSTRING(tab["CatalogNumber"]);
cout << ' ' << CDBSTRING(tab["Title"]);
cout << ' ' << CDBSTRING(tab["Author"]);
}
The Lex function may be used to determine if a string matches a lexical pattern. See the reference section for further information.
A filter is applied to a table by use of the Filter member function. A filter stays with the table until it is removed by used of the Filter function with a NULL argument. If a filter is applied to a table for which there is already a filter in effect, the new filter is stacked on top of the previous filter. The logical effect of stacked filters is as though they were and'd together with &&. So, the following sequence:
table.Filter ("JOBCODE = 5 || JOBCODE == 7");
table.Filter ("SEX == 'M'");
would produce an effect as though we had written:
table.Filter("(JOBCODE = 5 || JOBCODE == 7) && SEX == 'M'");
When a filter has been applied to a table, only the rows for which the filter expression is TRUE are accessible.
A filter stays with a table for the duration of the program or until it is removed.
The filter is removed from a table by applying a filter value of NULL or a zero-length string as in the following examples:
table.Filter ("");
or
table.Filter (NULL);
So, to continue the previous example:
table.Filter ("JOBCODE = 5 || JOBCODE == 7");
table.Filter ("SEX == 'M'");
// now, we have JOBCODE 5 or 7 and SEX == 'M'
table.Filter (NULL);
// now, we have JOBCODE 5 or 7
table.Filter (NULL);
// now, we have no filter
table.Filter (NULL);
// the last DB_Filter produced an error because there is
// to filter to remove.
Filter expressions may be used with the Find member function to simplify searching. The Find function accepts one or more arguments - the filter expression and zero or more replaceable arguments (more about that later).
Employees.Find ("First == 'John' && Last == 'Smith'");
Filter expressions may contain replaceable elements resembling those in printf arguments. Both Find and Filter Table member functions accept variable-length argument lists. The filter expression itself may contain substrings that start with the percent sign (%) and followed by one of the following letters: R, I, S, B. The meaning of these is as follows:
|
%R |
replace with a DBREAL argument from the argument list |
|
%I |
replace with a DBINTEGER argument from the argument list |
|
%S |
replace with a DBSTRING argument from the argument list |
|
%B |
replace with a DBBLOB argument from the argument list; a DBBLOB argument really has two values - a pointer to the blob value followed by a DBSIZE that has the size of the blob |
Thus, the following:
char *lastname = "Smith", *firstname = "John";
Employees.Find ("Employees", "LastName == %S && FirstName == %S", lastname, firstname);
would have the same effect as
Employees.Find ("Employees", "FirstName == 'John' && LastName == 'Smith'");
This feature makes the filter mechanism easier to use than it would be if you had to use the sprintf function to embed your arguments in the filter expression. Also, it is more efficient to use this form in a loop because the filter expression is parsed and partially optimized only the first time it is seen by the filter functions in RC21.
Filter expressions may be arbitrarily complex. You may combine the relational operators, <, >, <= , >=, ==, and !=. You may use the wildcard match operator ? and the lexical operator ??. You may use the logical || && and !. You may use the arithmetic operators +, -, *, /, and %, and you may use the grouping operators ( and ). Thus, if you wanted to find all the employees whose first name was equal to their last name and whose salary in thousands was equal to their age, you might write something like:
Employees.Filter ("LastName == FirstName"
"&& Salary / 1000 == (90 - DOB)");
Note that although the parentheses are shown in this example, they are not necessary because the C rules of precedence apply.
When possible, RC21 will use existing index structures to reduce disk access and processing. Currently, RC21 does not use composite key indexes that have substring segments. Also, expressions that have ORs at the highest level in the parse tree produce inefficient search strategies. Improvements to search optimization strategy remain high on our list of priorities.
When the RC21 observes a recurrence of a filter expression, it uses the parse tree and optimization strategy it produced the last time it saw that filter expression. Thus, it is more efficient to use a filter expression that has replaceable parameters rather than to produce a new filter expression by generating a string using sprintf or something like that.
Along with filters comes the capability of views. Views are simply filters that are permanently associated with a table. The table-filter combination is called a view.
Let's say we have a table called employees that was created like this:
db.AddTable ("Employees");
Table Employees(db, "Employees");
Employees.AddColumn ("FirstName", String);
Employees.AddColumn ("LastName", String);
Employees.AddColumn ("Salary", Integer);
Now, we could create a VIEW into this table by selecting only those employees with salary < 15000 as follows:
db.AddTable ("Group1", "Group 1 employees",
"Employees: Salary < 15000");
Observe that the view expression is formed by concatenating the base table name with a colon and the filter expression. Now, when we access the table called "Group1", we will automatically select only those employees with salary < 15000.
Anything you may do with a table, you may do with a view. Therefore, you can base a view on another view:
Employees.AddTable ("Group1 Females", "Group 1 employees",
"Group1: Sex == 'F'");
.. which would be the same as if you had written:
Employees.AddTable ("Employees", "Group1 Females", "Group 1 employees",
"Employees: Salary < 15000 && Sex == 'F'");
Note the the binding occurs at the time the table is constructed, not when it is created with AddTable. Thus, you may create a view that fails to open properly (with appropriate error handling) when you try to use it if you were to, say, delete the base table.
As mentioned previously, anything you may do with a table you may do with a view. So, you may do an AddRow, DeleteRow, PutString, PutInteger, etc.
You may apply a filter to a view just as you may apply to the base table. The result is as though you had and'd the selection conditions. You may also do a Find. Then, it's as though you had concatenated all three selection expressions with &&.
A VIEW expression may be supplied in place of a TABLE name expression in the Table function. Here is an example:
DBTAB tab = new Table (db, "Employees : %I - YearOfHire > 10",
(DBINTEGER) getyear());
In case it's not obvious, the relational join is performed explicitly by nesting Filter and ForAllRows or using a Find. For example, consider a table called "people" that has columns "name", and "zip", "zip" being the zipcode of the place where the person lives. There is also a table called "cities" that has columns "name" and "zip". Now, suppose we want to find all the people who live in "Burlington". In SQL, we would use something like this:
SELECT people.name FROM people, cities
WHERE people.zip = cities.zip AND cities.name = 'Burlington'
The code that follows builds a database and performs the above query using the ForAllRows macro and the Find function.
// EXAMP063 - Join
#include <iostream.h>
#include <rc21.h>
#include <string.h>
struct {
char *cityname;
long zipcode;
} citylist[9] =
{
{"Burlington", 5401},
{"Bennington", 5201},
{"Barre", 5641},
{"Middlebury", 5753},
{"Stowe", 5672},
{"Morrisville", 5661},
{"Waterbury", 5676},
{"Waterbury Center", 5677},
{"Moscow", 5662},
};
struct {
char *personname;
long zipcode;
} person[20] =
{
{"John Elkins", 5672},{"Ethan Allan", 5401},{"Joe Smith", 5201},
{"Zeke", 5641},{"Aaron", 5753},{"Geoffrey", 5672},
{"Thomas Jefferson", 5661},{"Miles Davis", 5676},{"Mark Spitz", 5677},
{"Bill Koch", 5662},{"Lola Falana", 5401},{"Ross Perot", 5201},
{"George Bush", 5641},{"Bill Clinton", 5753},{"Rush Limbaugh", 5672},
{"John McLaughlin", 5672},{"Madonna", 5401},{"Frank Zappa", 5201},
{"Bill Frizzle", 5641},{"Anita Hill", 5753},
};
void errorfunc(void)
{
PrintError("OH NO");
}
void main(void)
{
int i;
cout << "\nBegin examp063." << endl;
SetErrorFunc(errorfunc);
Remove ("test.db");
Create("test.db");
Database db("test.db");
db.AddTable ("cities");
Table cities(db, "cities");
cout << cities.IsTable() << endl;
DBTAB t;
ForAllTableObjects (&db,t)
{
cout << t->ID();
cout << ' ' << t->Name() << endl;
}
cities.AddColumn ("name", String, "name of a city");
Column cityname(cities, "name");
cities.AddColumn ("zip", Integer, "zip code of this city");
Column cityzip (cities, "zip");
for (i=0; i<9; ++i) /* build the city table */
{
cities.AddRow ();
cityname << citylist[i].cityname;
cityzip << citylist[i].zipcode;
}
cout << "\nCities:" << endl;
ForAllRows (cities)
{
cout << cityname << ' ';
cout << cityzip << endl;
}
db.AddTable ("people");
Table people (db, "people");
people.AddColumn ("name", String, "name of a person");
Column personname (people, "name");
people.AddColumn ("zip", Integer, "zip code of reference", "",
cityzip);
Column personzip (people, "zip");
for (i=0; i<20; ++i) /* build the person table */
{
people.AddRow ();
personname << person[i].personname;
personzip << person[i].zipcode;
}
cout << "\nPeople:" << endl;
ForAllRows (people)
{
cout << personname << ' ';
cout << personzip << endl;
}
/*
now, join the list of people with the list of cities;
the outer loop will select all people; the inner loop will
select city based on zip code.
*/
cout << "\nWhere people live:" << endl;
ForAllRows (people)
{
DBINTEGER zip = personzip.GetInteger ();
cities.Find ("zip == %I", zip);
cout << personname << " lives in " << cityname << endl;
}
db.Commit ();
}
Fundamentally, the C code from the program above that is equivalent to the SQL code is
ForAllRows (people)
{
DBINTEGER zip = personzip.GetInteger ();
cities.Find ("zip == %I", zip);
cout << personname << " lives in " << cityname << endl;
}
When traversing a table, the order of retrieval of rows in the table may depend upon a number of factors. If there is an OrderBy, the order will depend strictly upon the values in the OrderBy column. When there is a filter and there is no OrderBy, the order will be determined by RC21 based upon some optimization strategy.
WARNING: When there is an OrderBy and the program changes a value in the OrderBy column, the current row of the table is at the new position. This technique could lead to unexpected results and is therefore not recommended However, if you must change the value in the OrderBy column, try something like the following:
DBROWID count; DBTAB table; DBCOL column;
int i;
...
count = Table->CountRows();
DBROWID rowid_array[count];
// first, get all the pre-change rowid's
column->OrderBy ();
ForAllRows (table)
{
rowid_array[i] = table->CurrentRow();
}
/* now, go back and change the values */
for (i=0; i<count; ++i)
{
table->GotoRow (rowid_array[i]);
/* now, change the value */
column->Put...
}
Class BookMark:public RCObject
The BookMark class is used to remember a row in a table and go to it. This replaces the function of Table.CurrentRow and Table.GotoRow (which may not function properly on uncommited rows in client-server operation).
Public Members
Construction/Destruction
|
BookMark |
Constructs a new BookMark object
and sets it to the current row in a given table. |
|
~Database |
Destroys the BookMark object. |
Operations
|
Operator= |
Assigns the bookmark to a row in
a table. |
|
Go |
Goes to the table and row last
assigned. |
|
|
|
|
Syntax |
BookMark(); BookMark(DBTAB table); |
|
Parameters |
table |
|
Remarks |
Creates a new bookmark object, optionally set to a row in a table. |
|
Return Value |
None. |
|
Example |
DBTAB t = new Table(db, “table”); BookMark x; BookMark y(t); |
|
|
|
|
Syntax |
DBERROR Go(); |
|
Parameters |
None |
|
Remarks |
Goes to a row in a table. |
|
Return Value |
A reference to the bookmark. |
|
Example |
DBTAB t = new Table(db, “table”); BookMark x; . . . t.Find(“name == %s”, name); x = t; . . . x.Go(); // something like t.GotoRow(... |
|
|
|
|
Syntax |
BookMark& operator=(Table& table); |
|
Parameters |
table |
|
Remarks |
Assigns to the bookmark the indicated table and its current row. |
|
Return Value |
A reference to the bookmark. |
|
Example |
DBTAB t = new Table(db, “table”); BookMark x; . . . t.Find(“name == %s”, name); X = t; |
class Database:public RCObject
The Database class encapsulates all database operations.
Public Members
Construction/Destruction
|
Database |
Constructs a new database object
given a pathname to a database file. |
|
~Database |
Destroys the database object and
any subsidiary objects like Table and Column objects. |
Conversion
|
Operator DB |
Converts a reference to the
object to a pointer to the object |
Operations
|
Changed |
Tells whether the database has
been changed by any operation since the last Commit operation. |
|
CachePages |
Tells the number of cache pages associated
with the Database object. Cache pages
are essentially an LRU buffer for reading and writing data from/to the
permanent storage device (the disk). |
|
Name |
Returns the name of the Database
file. |
|
Release |
Tells the release number of the
release of RC21 with which the associated database file was created. |
|
PageSize |
Tells the size of a database
page. This is a property of the
database file specificed when it is Created. |
|
HashSize |
Tells the number of entries in the
database’s hash table. This value was
specified or defaulted when the database was Created. |
|
CountTables |
Tells how many tables there are
in the database. |
|
AddTable |
Adds a new table to the database. |
|
DeleteTable |
Deletes an existing table from
the database. |
|
TableExists |
Tells whether a table with a
given name exists within the database. |
|
Commit |
Commits all changes to the
database to the permanent, public database file. In multi-user operations, makes all changes
visible to other users of the databae. |
|
AutoCommit |
In single-user operations
specifies whether updates shall be automatically written to the database
file. Also, tells whether the Database
object is in AutoCommit mode. |
|
RollBack |
Rolls back uncommited changes. |
|
FirstTable |
Prepares to loop through a list
of tables for the database. Used with NextTable. |
|
NextTable |
With FirstTable, iterates through all the tables in a database. |
|
CurrentTable |
With FirstTable, NextTable returns
a pointer to the current table during iteration.. |
|
Copy |
Makes a logical copy of a
database in a new file. |
|
LockSchema |
In multi-user operations,
attempts to get write control of the database schema. |
|
CommitStatus |
In single-user operations, tells
how much resources will be needed successfully to comple a Commit operation. |
|
operator = |
Copies contents of one database
into another database. |
|
operator [] |
A cute way to reference a table. |
|
|
|
|
Syntax |
DBERROR AddTable(CDBSTRING name, |
|
Parameters |
name description view |
|
Remarks |
Adds a new table or view to the database. |
|
Return Value |
Returns a DBERROR value. |
|
Example |
Create (“test.db”); Database x(“test.db”); x.AddTable(“Employees”); x.AddTable(“Males”, “Male Employees”, “Employees: Sex == ‘M’”); x.AddTable(“Females”, “Female Employees”, “Employees: Sex == ‘F’”); |
|
|
|
|
|
|
|
Syntax |
int AutoCommit(int onoff); int AutoCommit(void); |
|
Parameters |
onoff |
|
Remarks |
In single-user operations it is possible to have changes to the database be commited to the disk as necessary, given the size of the cache. Naturally, this could result in the loss of database integrity should it turn out that the database is not properly closed. The AutoCommit operation with an argument reset the variable. Without an argument, the operation is used simply to find the current value of the variable. |
|
Return Value |
The value of the AutoCommit state prior to this operation. |
|
Example |
Database x(“test.db”); x.AutoCommit(1); // no Commit operations necessary . . . cout << x.AutoCommit(0); // outputs “1” |
|
|
|
|
Syntax |
int CachePages(void); |
|
Remarks |
Returns the number of cache pages allocated when the Database was constructed. |
|
Return Value |
The number of cache pages associated with this Database object. |
|
Example |
Database x(“test.db”, 500); cout << x.CachePages() << endl; // outputs 500 |
|
|
|
|
Syntax |
int Changed(void); |
|
Remarks |
Returns TRUE if the database has changed since the last Commit, otherwise False. |
|
Return Value |
True if the database has changed since the last Commit operation, otherwise, FALSE. |
|
Example |
Database x(“test.db”); x.AddTable(“newtab”, “new table”); cout << x.Changed() << endl; // outputs 1 x.Commit(); cout << x.Changed() << endl; // outputs 0 |
|
|
|
|
Syntax |
DBROWID CountTables(void); |
|
Remarks |
Returns the number of tables in the database. This number includes the two “internal” tables, which are used to store the database schema. Note: DBROWID is a long. |
|
Return Value |
Returns a DBROWID (long) value. |
|
Example |
Create (“x.db”); Database x(“test.db”); cout << x.CountTables() << endl; // outputs “2” x.AddTable(“newtab”); cout << x.CountTables() << endl; // outputs “3” |
|
|
|
|
Syntax |
DBERROR Commit(void); |
|
Remarks |
Commits all database changes to the database file. There are several steps to this process: 1. Copy all changes from buffers to a secondary file. 2. Flush secondary file to the disk. 3. Mark the database file with the name of the secondary file. 4. Copy all changes from the secondary file to the primary database file. 5. Flush the primary database file to the disk. 6. Unmark the database file with the name of the secondary file. This way, if some unexpected interuption occurs, such as the machine losing power, it should be possible to restore the integrity of the database file. Such a restoration operation would occur automatically if the state of the database file necessitates it. The database would be restored to its state before or after the Commit, depending on how much of the operation had been completed. |
|
Example |
Database x(“test.db”); . . . x.Commit(); |
|
|
|
|
Syntax |
DBERROR CommitStatus(unsigned long &dbsize, unsigned long &tempsize); |
|
Parameters |
dbsize tempsize |
|
|
|
|
Remarks |
In single-user operations use of this function will enable you to take action to ensure the successful completion of a Commit operation. You may compare the values returned with available memory and disk space. If a problem looms, you may be able to free resources so that the Commit will complete successfully. |
|
Return Value |
A DBERROR code. |
|
Example |
Database x(“test.db”); . . unsigned long msize, tempsize; x.CommitStatus(msize, dsize); . // make sure there’s enough memory and disk space . x.Commit(); |
|
|
|
|
Syntax |
DBERROR Copy(DB to); |
|
Remarks |
Copies the contents of a database into another database. |
|
Example |
Database x(“test.db”); Create (“newtest.db”); Database y(“newtest.db”); x.Copy(y); // contest of test.db copied to newtest.db |
|
|
|
|
Syntax |
DBTAB CurrentTable (void); |
|
Remarks |
With FirstTable, NextTable, selects the current table in an iteration through all the tables in a database. |
|
Return Value |
Returns a DBTAB pointer to a Table object. This object was created with the new operator, so the Table must be deleted when it is no longer needed. |
|
See Also |
FirstTable, CurrentTable, ForAllTables (macro) |
|
|
|
|
|
|
|
Syntax |
Database (CDBSTRING dbname, CDBSTRING access= "rw", Database (CDBSTRING dbname, int npages=20); |
|
Parameters |
dbname access Npages |
|
Remarks |
Constructs a new database object and associates it with the named database file. If access is not specified, “rw” is assumed. |
|
Example |
DB x = new Database(“x.db”); Database y(“x.db”, “rw”, 1000); Database z(“x.db”, 1000); |
|
|
|
|
Syntax |
~Database(void); |
|
Remarks |
Destroys the database object. Any Table objects associated with the database through the Table constructor are destroyed as well. Any Column objects associated are also destroyed. |
|
|
|
|
Syntax |
DBERROR DeleteTable(CDBSTRING name); |
|
Parameters |
name |
|
Remarks |
Deletes the named table from the database. All objects associated with this table are deleted or marked invalid |
|
Return Value |
DBERROR. |
|
Example |
Database x(“test.db”); x.DeleteTable(“newtab”); |
|
|
|
|
Syntax |
void FirstTable (void); |
|
Remarks |
With NextTable, CurrentTable, prepares to iterate through all the tables in a database. |
|
See Also |
NextTable, CurrentTable, ForAllTables (macro) |
|
|
|
|
|
|
|
Syntax |
ForAllTables (DBTAB db) … |
|
Remarks |
This is a macro that combines the Database member functions FirstTable, NextTable to iterate through all the rows in a table. |
|
See Also |
FirstTable, CurrentTable, ForAllTables (macro) |
|
Example |
Database x(“test.db”); ForAllTables(x) // object x converted to &x automatically { DBTAB y = x.CurrentTable(); cout << y.Name() << endl; delete y; } |
|
|
output: _tables _columns . . any other table names . . |
|
|
|
|
Syntax |
unsigned long HashSize(void); |
|
Remarks |
Returns the number of entries in the hash table of the database. This is the number specified when the database was Created, or the default value if it was not specified. |
|
Return Value |
Returns the number of entries in the hash table. |
|
Example |
Create (“test.db”); Database x(“test.db”); cout << x.HashSize(); // outputs “1024” Create (“test2.db”, 2048, 1536); Database y(“test2.db”); cout << y.HashSize(); // outputs “1536” |
|
|
|
|
Syntax |
IsUsable (void); |
|
Remarks |
Returns FALSE if the object was not properly constructed. Typically, would return FALSE if the database name were improperly specified. |
|
|
|
|
Syntax |
LockReturn LockSchema (LockMode m); |
|
Parameters |
m |
|
Remarks |
For multi-user operations, locks the schema, enabling changes to the schema. Lock persists until Commit. |
|
Example |
Database x(“test.db”); x.LockSchema(LockWait); x.AddTable(“newtab”, “new table”); x[“newtab”].AddColumn(“newcol”, Integer); x.Commit(); |
|
|
|
|
Syntax |
CDBSTRING Name(void); |
|
Remarks |
Returns the filename of the database. |
|
Return Value |
Pointer to a string containing the filename of the database. |
|
Example |
Database x(“test.db”); cout << x.Name(); // outputs “test.db” |
|
|
|
|
Syntax |
int NextTable (void); |
|
Remarks |
With FirstTable, CurrentTable, moves to the next table in a database. This is used to iterate through all the tables in a database. Typically, the ForAllTables macro is used. |
|
Return Value |
Returns TRUE if there are any more tables to iterate, otherwise, returns FALSE. |
|
See Also |
FirstTable, CurrentTable, ForAllTables (macro) |
|
|
|
|
|
|
|
Syntax |
operator DB(void); |
|
Remarks |
Converts a Database object to a pointer to the object (returns this). This is provided so that the compiler will be able to perform automatic conversions between types. |
|
|
|
|
Syntax |
Database& operator =(Database& fromdb); |
|
Parameters |
fromdb |
|
Remarks |
Copies the contents of the right-hand Database operand to the left-hand Database operand. |
|
Return Value |
A reference to the left-hand Database. |
|
Example |
Database x(“test1.db”); Create (“test2.db”); Database y(“test2.db”); y = x; // copies contents of x into y |
|
|
|
|
Syntax |
class Table& operator[] (CDBSTRING tablename); |
|
Parameters |
tablename |
|
Remarks |
Basically, this is syntactic fluff - a cool way to reference a table without constructing a Table object. Fundamentally, a reference is returned to a preconstructed object. All references to the preconstructed object have the same effect on the database as if they were references to an object constructed by you. |
|
Example |
Database x(“test.db”); x.AddTable(“newtab”, “new table”); cout << x[“newtab”].Name() << endl; // outputs “newtab” |
|
|
|
|
Syntax |
unsigned PageSize(void); |
|
Remarks |
Returns the size in bytes of one logical page in the database. This is the number specified when the database was Created, or the default value if it was not specified. |
|
Return Value |
Returns the logical pagesize for the database. |
|
Example |
Create (“test.db”); Database x(“test.db”); cout << x.PageSize(); // outputs “1024” Create (“test2.db”, 2048); Database y(“test2.db”); cout << y.PageSize(); // outputs “2048” |
|
|
|
|
Syntax |
CDBSTRING Release(void); |
|
Remarks |
Returns a string that contains the release number of the release of RC21 with which the database was originally Created. |
|
Example |
Create (“test.db”); Database x(“test.db”); cout << x.Release() << endl; // outputs “7.1.B1” |
|
|
|
|
Syntax |
DBERROR RollBack (void); |
|
Remarks |
Rolls back all the changes to the database since the last Commit. In other words, all changes are discarded. |
|
Return Value |
A DBERROR code. |
|
Example |
Database x(“test.db”); x.AddTable(“newtab”, “new table”); cout << x.Changed() << endl; // outputs 1 x.RollBack(); cout << x.Changed() << endl; // outputs 0 |
|
|
|
|
Syntax |
int TableExists(CDBSTRING name); |
|
Parameters |
Name |
|
Remarks |
Looks in the list of tables for this database to determine if a table exists. |
|
Return Value |
Returns TRUE if the named table is a table in this database, otherwise false. |
|
Example |
Create (“test.db”); Database x(“test.db”); x.AddTable(“newtab”); cout << x.TableExists(“newtab”) << endl; // outputs 1 cout << x.TableExists(“anothertab”) << endl; // outputs 0 |
The Table class encapsulates all operations on a table.
Public Members
Construction/Destruction
|
Table |
Constructs a new Table object
given a Database object and the name of a table. |
|
~Table |
Destroys the Table object and any
subsidiary objects like Column objects. |
Conversion
|
Operator DB |
Converts a reference to the Table object to a pointer to its Database object. |
|
Operator DBTAB |
Converts a reference to the Table object to a pointer to itself. |
Classifiers
|
IsTable |
Returns TRUE for a Table object. Returns FALSE for any other kind of object. This is useful for determining the type of a generic RCObject. Truth be told, RTTI could be used, but this is easier. |
Properties
|
Name |
Returns the name of a table or
changes the name of a table. |
|
Description |
Returns the description of the
table or changes the description. |
|
View |
Returns the view expression for a
table or changes the view expression. |
|
ID |
Returns the unique ID for this
table in the database. |
|
CountColumns |
Returns the number of columns in
the table. |
|
CountRows |
Returns the number of rows in the
table. |
Operations
|
AddColumn |
Adds a new column to the table. |
|
AddKey |
Adds a new key to the table. A key is a combination of the contents of
one or more columns with potentially ascending and descending segments |
|
DeleteColumn |
Deletes a column or a key. |
|
Copy |
Copies the table to another
table. |
|
AddRow |
Add a new, empty row to the
table. |
|
DeleteRow |
Delete the current row. |
|
CopyRow |
Copy the contents of a row to
another (existing) row. |
|
DuplicateRow |
Add a row and copy in the
contents of the current row. |
|
IsaRow |
Returns TRUE if the Table cursor
is positioned at a valid row. |
|
FinishRow |
Checks constraints and returns
errors if the current row is not “complete and correct”. |
|
ResetRow |
Positions the Table cursor prior
to iterating through the rows of a table. |
|
NextRow |
With ResetRow, i |