Example Explained

  Previous topic Next topic  

Example Code Analyzed

 

Establishing a connection to the database

/* Force CachéRDD methods to be linked with the applications */
REQUEST CachéRDD

/* Put Caché connection parameters on the RDD stack */
CachéSetServerParams( cServerIP, val( cPort ), cUser, cPassword, val( cSecs ) )

/* Request a connection to the desired NameSpace */
nConxn := CachéAddConnection( cNameSpace )

/* If FAILURE then quit the application gracefully! */
if ( nConxn == 0 )
  Alert( 'Connection to the Server not established!' )
  Return nil
endif

cRDD := 'CACHERDD'

Explain...

CachéAddConnection( cNameSpace ) obtains a connection handle to the namespace supplying ServerIP address, port, user and password information. Connection handle is stored in a persistent object inside the application and becomes the reference point for all subsequent RDD functions. More than one connection can be opened simultaneously for different servers and/or different namespaces on the same server. Tables can be distributed on different servers and/or namespaces as per demand. If connection is established successfully, RDD returns a value greater than 0. A value of 0 represents failure. If establishing a connection fails application should be exited.

The code above is peculiar to CachéRDD only. None of the above functions relates to RDD layer. But for CachéRDD to take charge of subsequent operations this is the starting point.

 

Creating a Table

/*
Definition of data table structure.
FieldName FieldType FieldLength DecimalPlaces
*/
Aadd( aStr, { 'Code'  , 'C',   8, 0 } )
Aadd( aStr, { 'Name'  , 'C',  25, 0 } )
Aadd( aStr, { 'Salary', 'N',  10, 2 } )
Aadd( aStr, { 'Dob'   , 'D',   8, 0 } )
Aadd( aStr, { 'Mrd'   , 'L',   1, 0 } )
Aadd( aStr, { 'Text'  , 'C',1000, 0 } )

/* Request RDD to create the above table */
DbCreate( cDbfFile, aStr /*, cRDD */)

Explain...

<aStr> array holds the information about the structure of table which along with table name and schema is passed to CachéRDD. Please refer to "Tables & Schemas" to know how table names and schemas are handled in CachéRDD. CachéRDD parses the supplied parameters to Caché equivalents, creates a new class object with ##class(%ictionary.ClassDefinition).%New(), inserts properties to match <aStr>, defines two CALLBACK methods and two TRIGGERs, saves and compiles the class. Click here to review how the class code looks after DbCreate() finishes.

 

Opening a Table:

/*
Attempt to open the table in SHARED mode
assigning it an ALIAS and in a NEW work area
*/
USE ( cDbfFile ) NEW SHARED ALIAS 'MYTABLE'
/*
And check if the table could been opened successfully.
If for any reason the attempt to open a table fails,
NetErr() function returns TRUE. In this case alert the user
to this effect and return gracefully.
*/
if NetErr()
  Alert( cDbfFile + ' : could not been opened!' )
  Return nil
endif

Explain...

RDD provides two different modes to open a table, viz., SHARED and EXCLUSIVE. Exclusive mode implies that if one process has opened the table under it, no other process can open the table. In RDBMS there is no concept of, first opening/closing, second SHARED/EXCLUSIVE. So I had to simulate this behavior. I took advantage of Caché's superb locking mechanism. I achieved it for only RDD oriented processes but could not force this mechanism to be recognized by non-RDD processes. The reason is obivous - in RDBMS world every table has a SHARED status by design.

If there are no non-RDD processes active on such tables under use by RDD processes, the behavior is exactly the same what Clipper RDD documents. On the contrary, it is the responsibility of the developer to design non-RDD processes to respect to EXCLUSIVE use.

The code snippet above opens the table in SHARED mode. CachéRDD initializes a few public variables to hold various info about its status, viz., storage globals - data, index and streams, alias, work area, index definitions and so forth. CachéRDD returns the success or failure of such exercise and post it to NetErr() stack short for "NetWork Error" which is later checked by the application. If NetErr() returns TRUE, application need to take corrective measures.

 

Creating Indexes

/*
Create indexes. Subsequently we will refer them with numbers in the order these are created.

1st Index <CODE>   is a normal char index on field Code.
2nd Index <NAME>   is a normal char index on field Name.
3rd Index <SALARY> is a numeric index on field Salary.
4th Index <DOB>    is a date index on field Dob with a FOR condition. fields
*/
INDEX ON Code   TAG 'CODE'   TO ( cIdxFile )
INDEX ON Name   TAG 'NAME'   TO ( cIdxFile )
INDEX ON Salary TAG 'SALARY' TO ( cIdxFile )
INDEX ON Dob    TAG 'DOB'    TO ( cIdxFile ) FOR Left( Name,1 ) == "P"

Explain...

Most demanding aspect of Clipper RDD has been the architecture of index management. Besides column based plain indexes, Clipper RDD features compound indexes, that is, index based on two or more columns, conditional indexes, unique indexes. In all type of indexes columns can be formatted via function calls, both compiler-defined or user-defined. Any number of nested function calls are supported to format a field. This all is not possible in any RDBMS as is, or correct me if something I may be missing. And because rest of the RDD functionality is based heavily on such indexes, it was a MUST to have this in CachéRDD. Single column based plain indexes are the only type similar to what SQL offers. And it took major chunk of development time.

 

Populate Table

/* Insert a record in the table */
APPEND BLANK

/* In case insertion of a new record succeeds */
If !( NetErr() )
  /* Populate fields with some values */

  REPLACE MyTable->Code   WITH "JOHNY"
  REPLACE MyTable->Name   WITH "Johny Walker"
  REPLACE MyTable->Salary WITH 7500.00
  REPLACE MyTable->Dob    WITH CtoD( '11/11/1956' )
  REPLACE MyTable->Mrd    WITH .T.
  REPLACE MyTable->Text   WITH 'This is a large text.'

  /* Tell RDD to force a physically commit the record buffer */
  DbCommit()

  /* New appended record is always locked, so release it */
  DbUnlock()
Endif

Explain...

In contrast to SQL which inserts and populate fields in one go, XBASE inserts a blank record before populating the fields. When an insertion is made that record is locked so that no other process could update it. APPEND BLACK requests CachéRDD to insert a blank record, obtain a lock on it, and return its %ID. RDD layer checks for if %ID > 0 and acts accordingly. Once the insertion is successful fields are populated with REPLACE command. After relevant CachéRDD field buffers are filled a request to commit the record is sent with DbCommit() command. Finally DbUnlock() is issued to let the record avaible to other processes to manipulate.

Because a record insert operation is done twice Caché update the data and index globals twice. This is the overhead I could not eliminate, not because Caché doesn't support but because it is RDD's mandatory behavior.

 

Navigation and Data Retrieval

/* Set table to naviagte rows in 1st Index <CODE> */
DbSetOrder( 1 )

/* Search for a record matching 'LINEN' with current index */
If DbSeek( 'LINEN' )

  /* Record is found, attempt to lock it */
  If RLock()
     /* Record locked, update fields */
     REPLACE MyTable->Mrd WITH .F.

     /* Unlock, otherwise no other process will be able to update it */
     DbUnlock()

  Endif
Endif

/* Set the index order to 2 which is <DOB> */
DbSetOrder( 2 )

/* Execute a command instead of function to search a record */
SEEK CtoD( '11/11/1956' )

/* Check if the record is there matching the search criteria */
If Found()
  alert( MyTable->Name + ' : is recorded in the database!' )
Endif

/*
Move to the top of the file.
This top position is based on the current controlling index.
*/
DbGoTop()

/* Check if there are any records are there in the table */
If !( Bof() )
  /* Display contents of few fields on the screen */
  ? MyTable->Code, MyTable->Name, MyTable->Salary

Endif

/* Skip to the next record. Again under current index order */
DbSkip()

/* Check if we have moved to the end of table */
If .NOT. Eof()
  ? MyTable->Code, MyTable->Name, MyTable->Salary

Else
  /* If already at the end of table, move to the last record */
  DbGoBottom()

Endif

/* Set table to obey natural order */
SET ORDER TO 0

/* Goto the top of the table ID = 1 */
DbGoTop()

/* Navigate the whole table and display rows */
Do While !( EoF() )
  ? MyTable->Code, MyTable->Name, MyTable->Salary
  DbSkip( 1 )

Enddo

Explain...

A table can be navigated in natural order or in the order of current index. Clipper RDD provides powerful commands to address those issues. Once an order is set - natural or index, all subsequent navigation commands respect it.

These are the commands to set the controlling index order:

DbSetOrder( 0 ) - Zero Order or Natural Order in which the records are inserted.
DbSetOrder( n ) - n = ordinal position of the index when tags were created.

These are the navigation commands in Clipper RDD:

DbGotop() - Positions the record pointer at the first record in controlling index.
DbGobottom() - Positions the record pointer at the last record in controlling index.
DbSkip( nRecords ) - Skips nRecords in the direction indicated by the sign of nRecords and positions the record pointer there. Skipping is performed respecting controlling index.

These are the commands to check if above movements succeeded or failed:

Eof() - End-of-File. If a forward record movement went past the last record in controlling index order, Eof() returns TRUE.
Bof() - Beginning-of-File. If a backward record movement went up the first record in controlling index order, Bof() returns TRUE.

These are the commands to search a particular record:

DbSeek( xValue ) - Seeks or searches a record matching xValue in the controlling index. This method is only operational when table is set for an index greater than zero. If table were been in natural order, the method raises a r/t error. If search succeeds the function returns TRUE and record pointer is positioned there. If search fails FALSE is returned and record pointer stays at phantom record and Eof() returns TRUE. DbSeek() accepts a few more parameters to control the search behavior. DbSeek() also respects some global settings provided by the RDD. But all this discussion is not for this article.

The above sets of commands comprise the backbone of navigation in XBASE dialects. This approach of table navigation is where XBASE dialects differ from SQL dialects.

Retrieval of data as : MyTable->Code, MyTable->Name, MyTable->Salary is a fairly simple process so need not be detailed.

 

Close Work-Area:

/* Close the current work area */
DbCloseArea()

Explain...

When job is done table is closed via DbCloseArea() method. It frees up the memory occupied by the RDD layer as well as CachéRDD. In CachéRDD context this command kills the node in public variable which was holding information about this table, nay, about this work area. In RDD concept one table can be opened in as many work areas as needed. Once closed the work-area slot on RDD layer is freed and made available to the next table to be opened subsequently.