Preface
CachéRDD is developed on top of USRRDD. Thanks Przemek for this superb tool.
CachéRDD talks to the database directly. You never need to create a DSN or to install Caché ODBC Driver for CachéRDD linked applications. Caché ODBC driver may be needed to connect to the database from outside of the CachéRDD compiled applications, i.e., widely available SQL tools.
Scope
CachéRDD extends the concept of data sharing between (x)Harbour applications and other web-based components. It means that (x)Harbour application can co-exist with VB or DELPHI application using ODBC or other connection protocol to connect to Caché Database manipulating same tables.
This design has been implemented intentionally and not by mistake.
Table Names
Click here to know how table names are resolved and stored into database and explore yet another extension of CachéRDD - the SCHEMAS.
Indexes
Click here to know all about how indexes are implemented in CachéRDD.
Deleted Records
Click here to have a detailed description of how deleted records are being managed in CachéRDD.
USEing a Table
Using a table in EXCLUSIVE/SHARED mode is purely a RDD level functionality. Other processes touching that table knows nothing about EXCLUSIVE USE. In RDBMS every table has a SHARED status by design. Processes using CachéRDD will honor EXCLUSIVE/SHARED USE exactly the same way as is applicable in Clipper / (x)Harbour.
File Lock FLOCK() is maintained on RDD as well as other processes level. This means, if (x)Harbour application has FLOCK()ed a table, any external process cannot update it. Similarly, (x)Harbour application may not be able to update that table if external process would been able to obtain a file-wide lock on the table.
Record Locks RLOCK() is implemented on RDD as well as external proccesses level in lines with FLOCK() as above.
Within the CachéRDD, USE EXCLUSIVE confirms to the standard Clipper behavior. That is, if a table is FLOCK()ed by any other process, it can not be USEd EXCLUSIVEly. The same is true for SHARED USE. Once table is used shared/exclusive, external processes will honor the file lock or record lock standards.
Record Numbers
Record numbers are maintained by Caché itself and so have some special significance to mension. Records are given a numbic id as soon as these are inserted. Once a record is given a number, that number never changes anytime even if a record prior to it is deleted any other time. You can think of record numbers in Caché as a serialized unique field in a table. In contrary, DBFCDX or other DBF oriented RDD's calculates the record number from its offsets only. Record numbers are not stored in the database itself. Because this behavior is peculiar to Caché Database and hence is implemented by CachéRDD. If your application does not rely on record numbers, except during execution of the application, then you need not to worry anything. Luckily majority of applications do not store/rectrieve record numbers of a table in another table and use it next time for some other operations. Mere use of RECNO() function is during the execution of the application like:
nRecNo := RECNO()
SEEK 'Something'
if !( FOUND() )
GO nRecNo
endif
The code above is a valid code in the context of CachéRDD also. My emphasis is on the issue that record number for a saved record never changes in the lifetime of a table unless the table is rewritten from scratch.
LastRec()
The function LastRec() returns the number of records inserted till the time it is called. It may or may nor represent the actual records in the table. Deleted records are never taken into account for purposes of LastRec() calculations. In fact, in SQL databases it is never possible.
Phantom Record
The phantom record in a table can be reached as:
DbGoBottom()
DbSkip( 1 )
There is no way to reach it via record id as:
GO LastRec()+1
You need to change those parts of your code where phantom record is reached as - GO LastRec()+1 and change those to GO BOTTOM ; SKIP construct. Typically phantom record is reached when 1) blank field values are to be loaded in memory variables, 2) function called for SkipBlock of a TBrowse object. There could be few more situations but I can not recall any other.
Alternatively you can use CachéGotoPhantom() function.