PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 1/9 TITLE : Custom Records with Paradox Engine ================================================================= Custom Records ================================================================= INTRODUCTION: One of the new features provided by version 3.0 of the Paradox Engine and Database Frameworks is the concept of Custom Records. This document describes how to create and use Custom Records, including advanced features such as data validation and field mapping. Custom Records can only be used with the C++ or Pascal Database Frameworks. At the most basic level, Custom Records map the fields of a table to variables within the Custom Record class. This simplifies the housekeeping required to access data in a Paradox table. These variables can be directly accessed without using the getField or putField methods ( the field number of the variable is not required ). Reading records from a table will automatically load the variables into the Custom Record, while writing data to the table will automatically get the data from the variables. The generate.exe utility will automatically create a custom record from a table, generating a .h file and a .cpp file. The .h file contains the class definition and the .cpp file contains the method definitions. SIMPLE EXAMPLE: Create a table called 'people' in either Paradox or using the maketbl.cpp example ( which is supplied at the end of this document ) with the following table structure: - First Name ( A20 ) - Last Name ( A20 ) - Age ( N ) - Date ( D ) - Note ( M40 ) Use the generate utility to create the Custom Record: generate /c people PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 2/9 TITLE : Custom Records with Paradox Engine View the people.h file. Notice that the 'people' class contains the following data members: char First_Name[21]; char Last_Name[21]; double Age; BDate Date; BLOBHANDLE Note; char nullVec[1]; The nullVec member is used as a flag to marking if a field is NULL. The generate utility will create a nullVec member large enough to contain one bit per member of the Custom Record class. The nullVec member is described later in this section. The First_Name, Last_Name, Age, Date, and Note members correspond to the fields of the same name in the table. All non-BLOb ( Binary Large OBject ) fields ( Numeric, Currency, Short, and Date ) can be directly modified: data written to the variables will automatically be written to the table when the Custom Record is saved. BLOb fields are not directly accessible in the same manner. Custom records allow a lower level of control over BLOb fields than Generic Records by providing access to the BLOBHANDLE. The BLOBHANDLE is used with the standard C functions to access a BLOb field. The getFieldNumber() method can also be used to get the FIELDNUMBER for use with the BRecord methods for accessing BLOb's. The file memfld.zip, available on the local BBS at ( 408 ) 439-9096, is an example of using BLOb fields from within the Database Framework. Custom Records make use of the nullVec variable to determine whether a field is NULL or not. The actual data member is not modified when a NULL field is read from the disk: only the corresponding bit in the nullVec variable is set. Failing to respect the nullVec member can lead to two sorts of problems: Displaying a NULL field, and updating a field. The variable corresponding to the NULL field will contain whatever data it contained before the record was read from the table, resulting in invalid data being displayed: use the isNull() PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 3/9 TITLE : Custom Records with Paradox Engine method to test the field before displaying it. Modifying a NULL field and saving the record will not work because the Database Framework will not write NULL fields to disk, resulting in no changes being made to that field: Use the clearNull() method to mark the field as being used ( not NULL ). Custom Records also provide the setNull() ( mark the field as NULL ) and the clear() ( Mark all fields as NULL ) methods for modifying the nullVec variable. The following example shows how a non-BLOb field is modified, taking into consideration the nullVec variable: BEngine myEngine( pxLocal ); BDatabase myDataBase( &myEngine ) ; BCursor myCursor( &myDataBase, "people" ) ; people table( &myCursor ) ; myCursor.gotoBegin() ; // Before first record myCursor.gotoNext() ; // First record in table // Which loads the members of table // We have to test if the table is NULL myCursor.getRecord( &table ) ; if( !table.isNull( "First_Name" ) { cout << table.First_Name << endl ; } else { // Mark field as not being NULL table.clearNull( "First_Name" ) ; } // Modify the first field strcpy( table.First_Name, "Testing" ) ; // Post the changes to disk myCursor.updateRec( &table ) ; A test was added to determine if the retrieved data member is NULL because the data members of a Custom Record are not modified if the field is marked as NULL. If the 'First Name' field is NULL on disk, the value in First_Name will be the same as it was before the call to myCursor.getRecord(). This also means that any changes made to the First_Name member will not be stored on disk as long as the field is marked as NULL. We call the clearNull() to mark the field as used so that our change gets saved to the table. PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 4/9 TITLE : Custom Records with Paradox Engine Note that the nullVec variable is used in all facets of custom records, not just reading and writing data. For example, the nullVec variable is used when using the searchIndex() function to determine which values get written to the record buffer that is used in the search. ================================================================= Deriving Classes ================================================================= The 'people' class currently does not take advantage of the full potential of Custom Records. Functionality is added by deriving a new class and overriding certain methods of the 'people' class. First, the housekeeping. At a minimum, the derived class will need a constructor and a new nameOf method: class mypeople : public people { public: mypeople( BCursor *cursor ) : people( cursor ) {}; char *nameOf() const { return( "mypeople" ) } ; } ; The nameOf() method is used internally by the Database Framework to, for example, determine if the current class is a Custom Record or a Generic Record. It is recommended to add a method, set(), which marks all fields as being used. This allows one method to be called when all variables in the custom record are being used. DATA VALIDATION: Functionality is added by overriding the preprocess() and postprocess() methods. The preprocess() method is called whenever the record is written to the table using the BCursor methods updateRec(), insertRec(), and appendRec(). preprocess() is called internally before the data is written. The postprocess() method is called whenever the record is retrieved from the disk using the BCursor method getRecord(). postprocess() is called after the data is read from the table. This is where a Custom Record would handle any data validation. PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 5/9 TITLE : Custom Records with Paradox Engine It would be nice for the 'mypeople' Custom Record to automatically handle NULL fields. This can be done by modifying the postprocess method: Retcode people::postprocess() { if (isNull( "First_name" ) { strcpy( First_Name, "" ) ; clearNull( "First_Name" ) ; } return(lastError = PXSUCCESS); } Now the 'First_Name' field is set to a defined value even if the field is NULL on disk. The return value will not prevent the data members from being set, but it will return the error value to the calling method ( one of the BCursor methods ) for the programmer to detect and handle ). Similar tests can be performed in the preprocess() method: Retcode people::preprocess() { // test if the 'First_Name' field contains "" if ( strcmp ( First_Name, "" ) ) { cerr << endl ; cerr << "Must give a valid First Name" ; cerr << endl ; return ( lastError = PXERR_OUTOFRANGE ) ; // Any error message can be used.... } return(lastError = PXSUCCESS); } This will ensure that the 'First_Name' field contains a value before the record is written to disk. Returning an error from the preprocess method will prevent the record from being written to the table. These methods can also be used to determine if the value in the field is a specific value or within a certain range of values. PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 6/9 TITLE : Custom Records with Paradox Engine ================================================================= Advanced Features: ================================================================= MAPPING FIELDS: The generate utility can also use a "map" file, which allows the explicit mapping of a field in the table to a field in the Custom Record. The data type of the variable within the Custom Record can be different from the data type of the field, the variable can have a name different from the field name, and "derived" fields can be created which do not correspond to any fields in the table. This section describes how to use these features. The map file contains the name of the Custom Record, the name of the table, the data type for each member of the Custom Record, the variable name for each member, and the name of the field in the table. The format of the mapfile is: from ( > : , > : ) Recordname and Variablename can be any identifier that is not a C or C++ keyword. Tablename needs to be the name of a valid Paradox table in the current directory. The Datatype can be any valid C, C++, Paradox Engine, or user defined type. Note that the generate utility will give a warning for types that it does not understand ( such as a user defined type ), but the Custom Record will still be created. It is the programmers responsibility to make certain that the unknown types are declared in the Custom Record class ( i.e., include the correct header files ). The Database Framework will automatically make certain conversions, but it is the programmer's responsibility to make certain that the field given by Fieldname can be converted to the specified Datatype ( the DBF uses the convertFld function in BRecord.cpp ): To: | Char | Short | Long | Double | Date | -------|------|-------|------|--------|------- From: Char | X | X* | X* | X* | X | PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 7/9 TITLE : Custom Records with Paradox Engine Short | X | X | X | X | | Long | X | X* | X | X | | Double | X | X* | X* | X | | Date | X | x* | | | X | * Only if argument in Range. Fieldname can be either a valid fieldname in the given table, or it can be "", for a 'derived' field, to indicate that the member of the Custom Record does not directly map to a field in the table. The mapfile for the above example would look like this: people from people ( char First_Name[21] : "First Name", char Last_Name[21] : "Last Name", double Age : "Age", BDate Date : "Date", BLOBHANDLE Note : "Note" ) The /f switch is provided with the generate utility to read a map file: generate /c /f people.map The map file can be changed to incorporate different fields and data types. First off, Age is currently stored as a double, which is slightly overkill. This can be changed to an INT16 using a mapfile ( INT16 is defined in envdef.h ). There are also cases when it is desirable to have two fields in the table combined into one field in the Custom Record. For example, a single member which contains both the first and the last name. This can be done by adding a new member to the Custom Record class, "Name", which does not map directly to any field in the table ( using "" as it's field type ). Lastly, the name to the "Note" field will be changed to "description". This is a new Custom Record, so it deserves a new name, 'newCust': newCust from people ( char First_Name[21] : "First Name", char Last_name[21] : "Last Name", PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 8/9 TITLE : Custom Records with Paradox Engine char Name[42] : "", INT16 Age : "Age", BDate Date : "Date", BLOBHANDLE Description : "Note" ) 'generate /c /f newcust.map' creates a newcust.h and a newcust.cpp from the 'people' table containing the custom record newCust. The postprocess() method has to be modified to load the 'Name' variable. For this example 'First_Name' needs to be copied to 'Name', a blank needs to be inserted into 'Name', and then 'Last_Name' needs to be concatenated to the end of 'Name': RetCode newCust::postprocess() { strcpy( Name, First_Name ) ; strcat( Name, " " ) ; strcat( Name, Last_Name ) ; return 0 ; } This same general method is used to create other types of derived fields, such as structures and unions. ================================================================= Example Files: ================================================================= //********************* MAKETBL.CPP ************************ // Used to create the table. // Create a project file containing: maketbl.cpp, // pxmsg.cpp, pxengtcl.lib, and dbfeng.lib. // //********************************************************** #include #include #include #include #include #include PRODUCT : Paradox Engine NUMBER : 1372 VERSION : 3.0 OS : DOS DATE : October 25, 1993 PAGE : 9/9 TITLE : Custom Records with Paradox Engine #define TABLENAME "people" const int numFields = 5 ; FieldDesc desc[numFields] = { // Define the fields { 1, "First Name", fldChar, fldstNone, 20 }, { 2, "Last Name", fldChar, fldstNone, 20 }, { 3, "Age", fldDouble, fldstNone, 0 }, { 4, "Date", fldDate, fldstNone, 0 }, { 5, "Note", fldBlob, fldstMemo, 40 } } ; int main( void ) { BEngine myEngine( pxLocal ) ; if ( !myEngine.lastError ) { BDatabase myDataBase( &myEngine ) ; if ( !myEngine.lastError ) { myDataBase.createTable( TABLENAME, numFields, desc ) ; } } return 0 ; } //**************** end maketbl.cpp *********************** DISCLAIMER: You have the right to use this technical information subject to the terms of the No-Nonsense License Statement that you received with the Borland product to which this information pertains.