Wiki Home

UDFFix Rec Count

Namespace: VFP
Ever need to fix the record count in a DBF because foxpro wasn't shut down properly?
This situation can manifest itself by the Not ATable error (see Not ATable for another header fixing UDF).

FUNCTION FixRecCount( pcFile, plActual, plFix )
* Adapted From code by Slighthaze on Tek-Tips May 16, 2003
* pcFile   : the file to find the record count of
* plActual : if .T., find the actual number of records
*              based on FileSize vs Record Size
* plFix    : if .T., and the Header # records doesn't match the actual
*              number, then fix the header.
LOCAL lnHandle, lcRecCnt, laJnk[5], lcRecSize, lcFirstRec, ;
      lnRecCnt, lnRecSize, lnFirstRec, lnRes, lnFileSize, ;
nErr  = 0
lnRes = 0
if adir(laJnk,pcFile)=0
lnFileSize = laJnk[2]
lnHandle = FOPEN(pcFile)   && Open file
IF lnHandle > 0
  oErr = On("ERROR")
  ON ERROR nErr=Error()
  FSEEK(lnHandle,4,0) && Record Count is bytes 4-7 in dbf header (0 offset)
  lcRecCnt   = FREAD(lnHandle,4)
  FSEEK(lnHandle,8,0) && First Record Pos is bytes 8-9 in dbf header (0 offset)
  lcFirstRec = FREAD(lnHandle,2)
  FSEEK(lnHandle,10,0) && Record Size is bytes 10-11 in dbf header (0 offset)
  lcRecSize  = FREAD(lnHandle,2)
  ON ERROR &oErr
  if nErr=0
    lnRecCnt   = BinStrToInt( lcRecCnt   )
    lnFirstRec = BinStrToInt( lcFirstRec )
    lnRecSize  = BinStrToInt( lcRecSize  )
    lnActRecCnt = (lnFileSize-lnFirstRec-1)/lnRecSize
    IF plActual
    	lnRes = lnActRecCnt
    	lnRes = lnRecCnt
    IF plFix
      if lnActRecCnt<>lnRecCnt
        lnHandle = FOPEN(pcFile,12)   && Open file R/W, Unbuffered
        FSEEK(lnHandle,4,0) && Record Count is bytes 4-7 in dbf header (0 offset)
        FWRITE( lnHandle, Num2DWord(lnActRecCnt) )
    lnRes = -3
  lnRes = iif(lnHandle=0,-1,lnHandle)

FUNCTION BinStrToInt( pcStr )
LOCAL lnTot, lnI
lnTot = 0
for lnI = 1 to len(pcStr)
  lnTot = lnTot + ASC(substr(pcStr,lnI)) * 256^(lnI-1)

*!*	    lnTot = ASC(substr(lcRecCnt,1))
*!*	    lnTot = ASC(substr(lcRecCnt,2)) * 256   + lnTot
*!*	    lnTot = ASC(substr(lcRecCnt,3)) * 256^2 + lnTot
*!*	    lnTot = ASC(substr(lcRecCnt,4)) * 256^3 + lnTot

FUNCTION num2dword( lnValue )
#DEFINE m0       256
#DEFINE m1     65536
#DEFINE m2  16777216
  LOCAL b0, b1, b2, b3
  b3 = Int(lnValue/m2)
  b2 = Int((lnValue - b3*m2)/m1)
  b1 = Int((lnValue - b3*m2 - b2*m1)/m0)
  b0 = Mod(lnValue, m0)
  RETURN Chr(b0)+Chr(b1)+Chr(b2)+Chr(b3)

This assumes that the the length of the table always reflects an integral number of records. Typically that is the case, and it's only off by one record (once it errors, no records are added, nor was the error reported prior to VFP8). However, I have seen several instances where the last records is short by a byte or two, and the table would continue to error. In such a case, the table needs to either be truncated by removing the partial record, or perhaps better still, bytes added to pad the last record.

It is also likely that most memo file and index file errors follow the same pattern. The fix for memos would also be useful, but a rebuild is no doubt the best fix for an index file.

The smartest way to fix these problems is to use the VFP copy commands.
I think it's a given that data up to the header count will be safe, and this is where the copy commands stop. And given that VFP can now report the error after the attempt has failed, we need not have any data loss.
- gp
I'm not so sure about that last statement.
Reports are sparse regarding occurrence of this error, but I have seen at least one where the loss of a record(s?) was observed after a REPLACE and the documenttion for VFP8 mentions only APPEND/INSERT as where it is checked.
Also, simple testing has shown that running with SET TABLEVALIDATE TO 2, the count is checked after the write. When the count is off by 1 then there is no error reported because now the count is correct. I don't know if this situation can actually arise, but if it can then there is a loss of the (old) last record in such a situation.
Use of the VFP COPY command may be considered the smartest way to fix these, but that would seem to guarantee that the last (lost) record(s) will remain lost for sure.
But the real message I have is: SET TABLEVALIDATE is not a "fix" to the problem but only a "trap" to detect it and prevent lost data. So, whenever you encounter the situation it will be most helpful to the community if you report the occurrence on as many forums as possible along with all the conditions that seem relevant. With luck perhaps the important pattern to its occurrence will be discovered. -- Jim Nelson
Contributors: wgcs
Category Code Samples Category Data Corruption Category UDF
( Topic last updated: 2007.02.15 08:27:56 PM )