home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
blt2_214.zip
/
src
/
compact.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-11-06
|
17KB
|
592 lines
/* compact.c - 5-Nov-1996 Cornel Huth
* This module is an -example- technique, showing one method to compact a database (DBF and DBT).
* Any index file(s) for this data file must be reindexed after this compaction is run. This
* is for a DBF with DBT memo file attached. For just DBFs, use PACK_RECORDS_XB, instead.
*
* Input consists of two filenames: dbfFilenamePtr is the current DBF filename
* (must not be open, though could be if this source were modified), and
* dbfNewFilenamePtr the name to give the compacted data file (.DBF). Output
* is the dbfNewFilenamePtr files (both DBF and DBT), compacted by removing both
* deleted data records along with the memo records that belonged to those
* deleted data records. It's not necessary to compact a memo file since any
* deleted space is made available for re-use, but this technique may come in
* handy anyway.
*
* #define TEST_COMPACT to compile test routine at end of this module
*
*/
// platform.h again used to simplify construction for various platforms
// FOR_WINDOWS should not be used since this example uses printf() for output
#define TEST_COMPACT // define if main() test wanted (at end of this source)
#include "platform.h" // defines platform, brings in includes
LONG compactDB(CHAR *dbfFilenamePtr, CHAR *dbfNewFilenamePtr) {
#pragma pack(1)
ACCESSPACK APo; // for old files
ACCESSPACK APn; // for new files
COPYPACK CP;
DESCRIPTORPACK DP;
HANDLEPACK HP;
MEMODATAPACK MDPo;
MEMODATAPACK MDPn;
OPENPACK OP;
STATDATAPACK SDP;
#pragma pack()
int i,j;
LONG rez;
CHAR tmpStr[128];
ULONG deletedRecs=0;
ULONG recs=0;
ULONG dbfHandleOld=0;
ULONG dbfHandleNew=0;
CHAR *dbfBufferPtr=NULL;
#define MAX_LIKELY_MEMO_SIZE 96 // max likely memo data size, in KB, for memoBufferPtr
#define MAX_MEMO_FIELDS 128 // max memo fields per data record (1 or 2 is typical)
int memoMaxSize = MAX_LIKELY_MEMO_SIZE * 1024;
int memoFields=0; // number of memo fields in data record
ULONG memoNo; // memo number, binary (for scanf)
ULONG memoFO[MAX_MEMO_FIELDS]={0}; // offset start of each memo field in record
VOID *memoBufferPtr=NULL; // memo I/O buffer
// open DBF/DBT files
OP.func = OPEN_DATA_XB;
OP.filenamePtr = dbfFilenamePtr;
OP.asMode = READWRITE | DENYREADWRITE;
rez = BULLET(&OP);
if (rez) {
printf("DBF open failed, rez: %d\n",rez);
return 0;
}
dbfHandleOld = OP.handle;
// get stats on data file handle
// goto used to simplify flow structure on error exits
SDP.func = STAT_DATA_XB;
SDP.handle = dbfHandleOld;
rez = BULLET(&SDP);
if (rez==0) {
if (SDP.records == 0) {
printf("DBF has no records\n");
goto ExitNow;
}
if (SDP.memoHandle == 0) {
printf("DBF has no DBT memo file attached\n");
// may choose to do a PACK_RECORDS_XB here, then exit
goto ExitNow;
}
// determine memo field locations in DBF
memoFields = 0;
DP.func = GET_DESCRIPTOR_XB;
DP.handle = dbfHandleOld;
for (i=1; i <= SDP.fields; i++) {
DP.fieldNumber = i;
rez = BULLET(&DP);
if (rez) {
printf("GET_DESCRIPTOR_XB failed, rez: %d\n",rez);
goto ExitNow;
}
if (DP.FD.fieldType == 'M') {
if (memoFields <= MAX_MEMO_FIELDS) {
memoFO[memoFields] = DP.fieldOffset;
memoFields++;
}
else {
printf("Too many memo fields defined for program\n");
goto ExitNow;
}
}
}
if (memoFields==0) {
printf("No memo fields in DBF record\n");
goto ExitNow;
}
// make new files
CP.func = COPY_DATA_HEADER_XB;
CP.handle = dbfHandleOld;
CP.filenamePtr = dbfNewFilenamePtr;
rez = BULLET(&CP);
if (rez) {
printf("Failed COPY_DATA_HEADER, rez: %d\n",rez);
goto ExitNow;
}
// open new DBF/DBT files
OP.func = OPEN_DATA_XB;
OP.filenamePtr = dbfNewFilenamePtr;
OP.asMode = READWRITE | DENYREADWRITE;
rez = BULLET(&OP);
if (rez) {
printf("DBF open failed, rez: %d\n",rez);
return 0;
}
dbfHandleNew = OP.handle;
// allocate DBF record I/O buffer
dbfBufferPtr = malloc(SDP.recordLength);
if (dbfBufferPtr==NULL) {
printf("Cannot malloc() dbfBuffer space\n");
goto ExitNow;
}
// allocate reusable memo I/O buffer -- size enlarged if necessary
// for truly large memo data records you may want to modify the program
// to do memo I/O in chunks
memoMaxSize = MAX_LIKELY_MEMO_SIZE * 1024;
memoBufferPtr = malloc(memoMaxSize);
if (memoBufferPtr==NULL) {
printf("Cannot malloc() memoBuffer space\n");
goto ExitNow;
}
recs = SDP.records;
// process:
// read each data record
// if deleted skip it and any memo records belonging to it
// otherwise copy it to new DBF and its memo record(s) to new DBT
// (PACK_RECORDS_XB is not used in this technique, but could be)
// since it is not necessary to reload all pack members for each
// operation, often news BULLET calls can be made by updating only
// one or two of the pack members -- for clarity, this module does
// setup -all- pack members, even if already setup correctly from
// previous calls, though may have // commenting them out
MDPo.memoPtr = memoBufferPtr;
MDPn.memoPtr = memoBufferPtr;
for (i=1; i <= SDP.records; i++) {
// get an old data record
APo.func = GET_RECORD_XB;
APo.handle = dbfHandleOld;
APo.recNo = i;
APo.recPtr = dbfBufferPtr;
rez = BULLET(&APo);
if (rez==0) {
// check if not deleted
if (*dbfBufferPtr != '*') {
// for each memo field in old record, get memo copy to new DBT
for (j=0; j < memoFields; j++) {
sscanf( (CHAR*)((ULONG)dbfBufferPtr + (ULONG)memoFO[j]), "%10u",&memoNo);
if (memoNo) {
// non-zero memo number means a memo record is out there
MDPo.func = GET_MEMO_SIZE_XB;
MDPo.dbfHandle = dbfHandleOld;
MDPo.memoNo = memoNo;
rez = BULLET(&MDPo);
if (rez==0) {
// check if current memo buffer needs to be expanded
if (MDPo.memoBytes > memoMaxSize) {
free(memoBufferPtr);
memoBufferPtr = malloc(MDPo.memoBytes);
if (memoBufferPtr==NULL) {
printf("Cannot expand memo buffer to %u bytes\n",MDPo.memoBytes);
goto ExitNow;
}
memoMaxSize = MDPo.memoBytes;
MDPo.memoPtr = memoBufferPtr;
MDPn.memoPtr = memoBufferPtr;
}
// <- offset to left means the structure member is already set correctly
// get old memo data, all of it in one read
MDPo.func = GET_MEMO_XB;
MDPo.dbfHandle = dbfHandleOld;
// MDPo.memoNo = memoNo;
// MDPo.memoPtr = memoBufferPtr;
MDPo.memoOffset = 0;
// MDPo.memoBytes set in previous call
rez = BULLET(&MDPo);
if (rez==0) {
// may want to verify MDPo.memoBytes OUT same as requested (IN)
// not done here since it should be the same
// copy old memo data to new memo file
MDPn.func = ADD_MEMO_XB;
MDPn.dbfHandle = dbfHandleNew;
// MDPn.memoPtr = memoBufferPtr;
MDPn.memoBytes = MDPo.memoBytes;
rez = BULLET(&MDPn);
if (rez==0) {
// copy was successful, update DBF record's memo field
// with the memo number returned from the add above, in ASCII digits
// sprintf() to a temp buffer to avoid \0 being added in DBF record
sprintf(tmpStr,"%10.10u",MDPn.memoNo);
//strncpy(dbfBufferPtr + *(memoFO[j]),tmpStr,10); // put memo number in
strncpy( (CHAR*)((ULONG)dbfBufferPtr + (ULONG)memoFO[j]),tmpStr,10); // put memo number in
}
else {
printf("ADD_MEMO_XB failed, rez: %d\n",rez);
goto ExitNow;
}
}
else {
printf("GET_MEMO_XB failed, rez: %d\n",rez);
goto ExitNow;
}
}
else {
printf("GET_MEMO_SIZE_XB failed, rez: %d\n",rez);
goto ExitNow;
}
} // memoNo was zero for this memo field, continue with...
} // next memo field in record, if any more
// write out old record to new data file (with new memo field numbers)
APn.func = ADD_RECORD_XB;
APn.handle = dbfHandleNew;
APn.recPtr = dbfBufferPtr;
rez = BULLET(&APn);
if (rez) {
printf("Failed ADD_RECORD_XB, rez: %d\n",rez);
goto ExitNow;
}
}
else {
// track records deleted
deletedRecs++;
}
}
else {
printf("GET_RECORD_XB failed, rez: %d\n",rez);
goto ExitNow;
}
} // next i (old records)
} // SDP rez
ExitNow:
HP.func = CLOSE_DATA_XB;
if (dbfHandleOld) {
HP.handle = dbfHandleOld;
rez = BULLET(&HP);
}
if (dbfHandleNew) {
HP.handle = dbfHandleNew;
rez = BULLET(&HP);
}
if (dbfBufferPtr) free(dbfBufferPtr);
if (memoBufferPtr) free(memoBufferPtr);
return 0;
}
#ifdef TEST_COMPACT
////////////////////////////////////////////////
/////////////////////////////////// test routine
////////////////////////////////////////////////
#define TEST_RECS_TO_ADD 1000
// each test rec uses about 40 bytes in the DBF and about 1KB in the memo
// (since each memo is 512 bytes, and two memoes are written for each DBF record)
int main(void) {
void BuildFieldListTest(FIELDDESCTYPE fieldList[]);
typedef struct _TestRecType {
CHAR tag;
CHAR ID[9];
CHAR notes1[10];
CHAR nada1[6];
CHAR notes2[10];
CHAR nada2[4];
} TestRecType; // 40 bytes
#pragma pack(1)
ACCESSPACK AP;
DOSFILEPACK DFP;
CREATEDATAPACK CDP;
EXITPACK EP;
HANDLEPACK HP;
INITPACK IP;
MEMODATAPACK MDP;
OPENPACK OP;
STATDATAPACK SDP;
FIELDDESCTYPE testFieldList[5];
TestRecType testRec;
#pragma pack()
ULONG dataID=0;
CHAR dataFilename[] = ".\\$compact.dbf";
CHAR memoFilename[] = ".\\$compact.dbt";
CHAR dataFilenameNew[] = ".\\$compnew.dbf";
CHAR memoFilenameNew[] = ".\\$compnew.dbt";
// ### filled in with DBF record number at time memo data created
// just for later viewing/checking to tell them apart
CHAR memoTestData[]= "######### any memo data, just testing (ONE)";
CHAR memoTestData2[]="######### any memo data, just testing (TWO)";
CHAR tmpStr[128];
int i;
LONG rez;
ULONG deletedRecs=0;
IP.func = INIT_XB;
IP.JFTsize = 20;
rez = BULLET(&IP);
if (rez) {
printf("INIT_XB failed, rez: %d\n",rez);
goto Abend;
}
memset(testFieldList,0,sizeof(testFieldList));
BuildFieldListTest(testFieldList);
testRec.tag = ' ';
strncpy(testRec.ID,"123456789",9);
strncpy(testRec.notes1," ",10);
strncpy(testRec.nada1,"nada1",6);
strncpy(testRec.notes2," ",10);
strncpy(testRec.nada2,"end",4);
// Delete previous files from any previous run (disregard any error return)
DFP.func = DELETE_FILE_DOS;
DFP.filenamePtr = dataFilename;
rez = BULLET(&DFP);
DFP.filenamePtr = memoFilename;
rez = BULLET(&DFP);
DFP.filenamePtr = dataFilenameNew;
rez = BULLET(&DFP);
DFP.filenamePtr = memoFilenameNew;
rez = BULLET(&DFP);
// Create the data files
CDP.func = CREATE_DATA_XB;
CDP.filenamePtr = dataFilename;
CDP.noFields = 5;
CDP.fieldListPtr = testFieldList;
CDP.fileID = 0x8B; // bit7&3=1 then also create memo file
rez = BULLET(&CDP);
if (rez) {
printf("Failed TEST data file create. Err: %d\n",rez);
goto Abend;
}
// Open the data file
OP.func = OPEN_DATA_XB;
OP.filenamePtr = dataFilename;
OP.asMode = READWRITE | DENYNONE;
rez = BULLET(&OP);
if (rez) {
printf("Failed EMP data file open. Err: %d\n",rez);
goto Abend;
}
dataID = OP.handle;
// generate memo data and data records
AP.func = ADD_RECORD_XB;
AP.handle = dataID;
AP.recPtr = &testRec;
AP.nextPtr = NULL;
MDP.func = ADD_MEMO_XB;
MDP.dbfHandle = dataID;
for (i=0;i < TEST_RECS_TO_ADD;i++) {
sprintf(tmpStr,"%9.9u",i);
strncpy(memoTestData,tmpStr,9); // overwrite ### with original DBF record number
strncpy(memoTestData2,tmpStr,9); // just for later viewing (so not all are the same)
MDP.memoPtr = memoTestData;
MDP.memoBytes = strlen(memoTestData)+1; // +1 so it stores \0, too
rez = BULLET(&MDP);
if (rez!=0) {
printf("ADD_MEMO_XB #%d failed, err: %d\n",i,MDP.stat);
goto Abend;
}
sprintf(tmpStr,"%10.10u",MDP.memoNo); // "0000000001" is first memo...
strncpy(testRec.notes1,tmpStr,10); // put memo number in
MDP.memoPtr = memoTestData2;
MDP.memoBytes = strlen(memoTestData2)+1; // +1 so it stores \0, too
rez = BULLET(&MDP);
if (rez!=0) {
printf("ADD_MEMO_XB #%d failed, err: %d\n",i,MDP.stat);
goto Abend;
}
sprintf(tmpStr,"%10.10u",MDP.memoNo);
strncpy(testRec.notes2,tmpStr,10);
// AP. members already setup
rez = BULLET(&AP);
if (rez) {
printf("ADD_RECORD_XB failed, rez: %d\n",rez);
goto Abend;
}
}
//////////////////////////////// test by deleting every other DBF record
SDP.func = STAT_DATA_XB;
SDP.handle = dataID;
rez = BULLET(&SDP);
if (rez) {
printf("STAT_DATA_XB failed, rez: %d\n",rez);
goto Abend;
}
printf("There are %u records (each had two memo records)\n",SDP.records);
deletedRecs = 0;
AP.func = DELETE_RECORD_XB; // -mark- odd records as deleted
AP.handle = dataID;
for (i=1; i <= SDP.records; i++) {
if (i & 1) { // if odd, delete
AP.recNo = i;
rez = BULLET(&AP);
if (rez) {
printf("DELETE_RECORD_XB failed, rez: %d\n",rez);
goto Abend;
}
deletedRecs++;
}
}
printf("%u records were marked as deleted (all odd record numbes)\n",deletedRecs);
printf("Calling compactDB()...\n");
// file must be closed since compactDB opens it by filename
HP.func = CLOSE_DATA_XB;
HP.handle = dataID;
rez = BULLET(&HP);
if (rez) {
printf("Failed CLOSE_DATA_XB, rez: %d\n",rez);
goto Abend;
}
rez = compactDB(dataFilename, dataFilenameNew);
printf("rez: %d\n",rez);
// Fatal errors above come straight to here
Abend:
EP.func = EXIT_XB;
BULLET(&EP);
printf("\nPress ENTER to end...");
gets(tmpStr);
return rez;
}
//---------------------------------------------
// Init field list items for test data file
void BuildFieldListTest(FIELDDESCTYPE fieldList[]) {
strcpy(fieldList[0].fieldName, "ID");
fieldList[0].fieldType = 'C';
fieldList[0].fieldLen = 9;
fieldList[0].fieldDC = 0;
strcpy(fieldList[1].fieldName, "NOTES1");
fieldList[1].fieldType = 'M';
fieldList[1].fieldLen = 10;
fieldList[1].fieldDC = 0;
strcpy(fieldList[2].fieldName, "NADA1");
fieldList[2].fieldType = 'C';
fieldList[2].fieldLen = 6;
fieldList[2].fieldDC = 0;
strcpy(fieldList[3].fieldName, "NOTES2");
fieldList[3].fieldType = 'M';
fieldList[3].fieldLen = 10;
fieldList[3].fieldDC = 0;
strcpy(fieldList[4].fieldName, "NADA2");
fieldList[4].fieldType = 'C';
fieldList[4].fieldLen = 4;
fieldList[4].fieldDC = 0;
return;
}
#endif // end TEST_COMPACT