""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
   POINTERS--WHAT THEY ARE & HOW TO FIX THEM IF THEY BREAK
   """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
   PRODUCT   :  R:BASE                  VERSION      :  3.1
   CATEGORY  :  POINTERS                SUBCATEGORY  :  INTEGRITY
   """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
   Here's a thorough explanation of broken pointers - what they are, how
   to know if your database has them, how to fix them, and how to prepare
   for the possibility of broken pointers, so you can easily recover from
   them. Broken pointers can cause trouble unless you find them early and
   fix them right away.
 
 
   How R:BASE Stores & Tracks Data
   """""""""""""""""""""""""""""""
   Each R:BASE database is always made up of three files regardless of
   the number of tables. File 1 (CONCOMP1.RBF) holds the database
   structure and statistics about the database, file 2 (CONCOMP2.RBF)
   holds data, and file 3 (CONCOMP3.RBF) holds indexes. All three files
   must be thought of as a unit, a single database. When you back up a
   database, you must back up all three files, or the backup will be
   useless.
 
   R:BASE finds a row of data in a database by using an address, just as
   you use an address to find a house. Each row resides at a specific
   address in database file 2.
 
   A pointer is a row address. Each row stored in file 2 has two pointers
   - the "next row pointer" (zero if it's the last row) and the "previous
   row pointer" (zero if it's the first row).
 
   A next row pointer is the address of the next row - it points to the
   next row. A previous row pointer is the address of the previous row -
   it points to the previous row. R:BASE follows the chain of next row
   pointers to find all the rows in a table.
 
 
   Broken Pointers Kink the Chain
   """"""""""""""""""""""""""""""
   When something happens to damage a database, the damage is usually not
   as bad as it looks. Maybe only a single row is damaged (broken), but
   even one broken pointer causes R:BASE to lose its place in the chain
   that links all the rows in the table. R:BASE doesn't know where to
   look for the next row because the next row pointer is broken.
 
   However, if the table has an indexed column, you may be able to use a
   procedure outlined later in this article to jump over the damage in
   the chain and find the good rows on the other side. This is because
   R:BASE stores row addresses in database file 3 with index values in
   addition to storing row addresses as pointers in database file 2.
 
 
   Indexes Store Values & Addresses
   """"""""""""""""""""""""""""""""
   For each row that contains an indexed column, R:BASE stores the index
   value and row address in database file 3. An index on a column works
   like an index in a book. R:BASE looks in the index (database file 3)
   to get the correct row address. Then armed with the address, R:BASE
   goes directly to the correct row in database file 2 instead of reading
   through all the rows.
 
   Whether or not a row has an indexed column is up to you. Usually
   database designers will index one or more of the columns in a table
   for fast access to selected rows in that table. Typically, you will
 
   want to index columns that identify rows in the table or that link
   that table to other tables.
 
   An indexed four-byte column (INTEGER, TEXT 4, REAL, DATE, or TIME)
   helps to protect the database because you may be able to use it to
   recover from a broken pointer. Therefore, you might want to have an
   indexed four-byte column in every table. To add an index to a column,
   use the CREATE INDEX command or define the column as an indexed
   column. R:BASE maintains the index for you.
 
 
   Row Address Locations
   """""""""""""""""""""
   In summary, R:BASE stores each row's address in multiple locations:
 
     o  In the next row pointer of the immediately preceding row.
     o  In the previous row pointer of the next row.
     o  In database file 3 if there is an indexed column in the row.
 
 
   An Example Situation
   """"""""""""""""""""
   Say you have a hundred rows of data in your CUSTOMER table, and each
   customer has a unique CUSTID number ranging from 1 to 100. On a normal
   (non-indexed) search of your data, R:BASE consults file 1 to get the
   address (pointer) for the first row of data. Then it goes to file 2 to
   look for the data. Once it finds the first row, it uses the first
   row's next row pointer (address), to go find the next row, then the
   next, and so on.
 
   But say R:BASE gets stuck at CUSTID 75 because the next row address is
   somehow corrupted so that it has a value of zero or some astronomical
   number. In such a case, R:BASE does not know where to find the next
   row. CUSTID 76 is most likely still out there, but because CUSTID 75's
   next row pointer is broken, R:BASE can't find the remaining 25 rows of
   data.
 
   R:BASE can use either of two methods to locate rows of data in
   CUSTOMER: the forward pointing method or the direct indexing method.
 
 
   Forward Pointing Method
   """""""""""""""""""""""
   Database file 1 points at (holds the address of) the first row of data
   in each table. That first row stores the address for the next row,
   which in turn stores the address for the next, and so on.
 
   Database file 2 holds all the data - including not only the data that
   you loaded, but also the definitions for rules, forms, reports,
   labels, and views.
 
   Many people think of their data as being in nice, neat, separate files
   or tables in file 2. But actually, R:BASE adds data to file 2 as you
   enter it. Therefore, the rows of data for any given table are not
   always contiguous (right next to each other). One row may point to a
   next row that is in fact separated by a lot of data from all the other
   tables. Therefore, the addresses for any one table can often leap-frog
   data from other tables.
 
   Each row points to the next row in sequence in a table, so it's called
   a "forward pointing address method."
 
 
   Direct Indexing Method
   """"""""""""""""""""""
   In addition to the forward pointing address method, R:BASE also
   locates rows by using the "direct indexing method." As mentioned
   earlier, there's index information in database file 3 for every row in
   file 2 that has an indexed column. File 3 holds the row's index value
   and file 2 address. R:BASE uses the address to go directly to the row
   in database file 2.
 
   Going back to the example, say you had an index on the CUSTID column
   in CUSTOMER. When you initially created that index, R:BASE went down
   through all the current data in file 2 collecting the addresses from
   the next row pointers in file 2 and recording the addresses in file 3.
 
   When you add a new row to CUSTOMER, R:BASE inserts the new row's
   address in file 2 as the previous row's next row pointer. If there's
   an indexed column in the row, R:BASE also inserts the new row's
   address in file 3.
 
 
   Using Indexes to Recover Data
   """""""""""""""""""""""""""""
   R:BASE can't find the missing 25 rows in CUSTOMER because of the
   broken pointer. But file 3 still has the address information stored
   with each CUSTID index value, so you can recover the data by using the
   direct indexing method.
 
   For example, in R:BASE 3.1, use the following code to recover
   CUSTOMER:
 
     RENAME TABLE customer TO xx NOCHECK
     PROJECT customer FROM xx USING +
       ALL WHERE custid BETWEEN 0 AND 101
     INSERT INTO customer SELECT * +
       FROM xx WHERE custid IS NULL
     DROP TABLE  xx
     CREATE INDEX ON customer custid
 
   The RENAME...NOCHECK command preserves all your form and report
   locations. The PROJECT command uses the addresses stored in file 3
   with each CUSTID index value to find all the rows because its WHERE
   clause uses indexes.
 
   The INSERT command adds any rows where the indexed column is null. You
   need it because null values don't fall into the range specified in the
   PROJECT WHERE clause. The DROP TABLE command erases the broken table
   (XX). Then the CREATE INDEX command reindexes the CUSTID column in the
   new CUSTOMER table.
 
   If you're going to use an indexed column to recover data, it must be a
   four-byte column; that is it must be defined as an indexed INTEGER,
   REAL, DATE, TIME, or TEXT 4 column. Indexes on larger TEXT columns
   like COMPANY or LASTNAME are useful for speed reasons, but they won't
   recover data.
 
 
   Using R:SCOPE to Recover Tables
   """""""""""""""""""""""""""""""
   Microrim's R:SCOPE is an easy-to-use tool that may make it possible
   for you to recover a table that was heretofore unrecoverable. R:SCOPE
   lets you see the raw data and pointers stored in file 2 and helps you
   make sense of it so that you may be able to find and jump over the
   break in the prointer chain.
 
   Programs such as The Norton Utilities or PC Tools can also see raw
   R:BASE data, but they have no mechanism for making sense of it, so
   they won't help you fix broken pointers.
 
 
   R:SCOPE's AUTOFIX feature makes recovery easy. AUTOFIX uses a
   sophisticated recovery method, a technique that only a professional
   programmer would know how to do manually. In addition, you can use
   R:SCOPE to fix other problems. Use R:SCOPE regularly to examine your
   database and correct problems while they're small.
 
   Victor Andrews, an R:BASE application developer, told us of a client
   who had a badly corrupted database. Using R:SCOPE, Victor was able to
   fix more than  200 broken pointers. After fixing the pointers, his
   client lost only three rows out of 50,000. R:SCOPE had saved months of
   work.
 
   You can purchase R:SCOPE by calling Microrim at 800-248-2001. The cost
   of purchasing R:SCOPE to fix a table will often be less than the cost
   of reentering the data.
 
 
   Causes for Broken Pointers
   """"""""""""""""""""""""""
   Broken pointers are primarily caused by an interruption of some kind
   while R:BASE writes or modifies data in database file 2. The source of
   the interruption could be a power fluctuation, network connection
   problems, interference from memory-resident programs, turning the
   computer off while in R:BASE, or some electromagnetic interference
   such as a sudden static discharge or radio frequencies coming from
   speakers placed too close to your PC. Often it's impossible to know
   exactly what caused the broken pointers.
 
 
   Checking Database Health
   """"""""""""""""""""""""
   Use one of the following methods regularly to check your database for
   broken pointers.
 
 
   METHOD 1
   """"""""
   Enter this command at the R> prompt:
 
     COMPUTE ALL (1) FROM tblname
 
   Replace TBLNAME with the name of the table you're checking. The first
   two numbers (rows and count) should be the same. If they're different,
   you probably have a broken pointer. This technique works because the
   COUNT function counts all the rows in database file 2 and the ROWS
   function looks in file 1 for the row count.
 
 
   METHOD 2
   """"""""
   Run the short R:BASE 3.1B program listed below to check tables for
   broken pointers. First, connect the database. Then run TBLCHECK.CMD.
   The program lists all the tables inside a check box menu. Check off
   the tables you want to verify as correct, press [F2], and R:BASE will
   automatically check each one.
 
     *( TBLCHECK.CMD--Check table integrity.)
     CLS
     SET MESSAGES OFF
     SET ERROR MESSAGES OFF
     DEBUG SET MESSAGES ON
     DEBUG SET ERROR MESSAGES ON
     SET VAR vcnt INTEGER = 0, vtables TEXT
     WRITE 'Press [Enter] to choose the tables +
     you want to check. Press [F2] when finished.'
     CHOOSE vtables FROM #TABLES CHKBOX
     IF vtables IN ('[Esc]','HELP') +
       OR vtables IS NULL THEN
       WRITE 'You did not choose a table.'
       QUIT
     ENDIF
     WHILE #PI IS NOT NULL THEN
       SET VAR vcnt = (.vcnt + 1)
       SET VAR vtblpick = (SSUB(.vtables,.vcnt))
       IF vtblpick IS NULL THEN; BREAK; ENDIF
       COMPUTE v1 AS COUNT (1) +
         FROM &vtblpick
       COMPUTE v2 AS ROWS FROM &vtblpick
       IF v1 <> .v2 THEN
         WRITE .vtblpick 'may have some errors.'
         WRITE '  Database file 2 counts' +
           .v1 'rows,'
         WRITE '  but file 1 says there are' +
           .v2 'rows.'
       ELSE
         WRITE .vtblpick 'looks good.'
       ENDIF
     ENDWHILE
     WRITE 'Finished checking tables.'
     CLEAR VAR v1, v2, vcnt, vtblpick, vtables
     RETURN
 
   You need R:BASE 3.1B because it allows check box menus with the
   dynamic table (#TABLES) option in a CHOOSE command.
 
 
   METHOD 3
   """"""""
   Use RBCHECK, a program that comes with Personal R:BASE and R:BASE 3.x.
   To use it, exit to the DOS prompt, move to the directory that contains
   your three database files, and enter this command:
 
     RBCHECK dbname > rbcheck.dat
 
   Replace DBNAME with the name of your database exclusive of the 1, 2,
   or 3.RBF extensions. For example, use CONCOMP, not CONCOMP1.RBF.
 
   The output will be captured in a file (RBCHECK.DAT). Print it out or
   call it up in an editor when RBCHECK finishes. If you experience any
   trouble or get error messages associated with file 2, such as "Id=
   ..., ROW COUNT ...," or "IDEND ...," you may have a broken pointer.
   For more information on RBCHECK, see "Blunder Insurance" in this
   issue.
 
 
   METHOD 4
   """"""""
   Use AUTOCHK, a program that comes with R:SCOPE and R:BASE 3.1B, to
   check the integrity of your database. Here's the syntax:
 
     AUTOCHK dbname /n
 
   Replace DBNAME with the name of the database.
 
   AUTOCHK opens the database and performs the same database integrity
   checking routine that R:SCOPE performs when you choose the Database
   Info, Structure, and Data options from the Check menu. AUTOCHK doesn't
   check indexes.
 
   If AUTOCHK finds an error, it stops checking and displays one error
   message.  If AUTOCHK finds no errors, it displays "No errors found."
   If you press a key while AUTOCHK is checking the database, the program
   stops and displays "User abort." To suppress messages include the /n
   option."
 
   AUTOCHK automatically sets the DOS exit code (error variable) to the
   number corresponding to the message returned. For example, if AUTOCHK
   displays this error: "Unable to open database file number 2," it sets
   the DOS error variable to 52 - if you specified the /n option.
 
   Use the DOS error variable to alert a user to database errors. Below
   are two examples showing you how. The first demonstrates how to run
   AUTOCHK from inside R:BASE by using the ZIP command, and the second
   shows how to use it in a DOS batch file.
 
 
   AUTOCHK from Inside R:BASE
   """"""""""""""""""""""""""
   If you have R:BASE 3.1B, you can run AUTOCHK from inside R:BASE
   because the ZIP command passes the DOS error variable back to R:BASE.
 
   Set an error variable before executing ZIP. Then on return to R:BASE,
   the error variable will hold the DOS error variable value. CHECKDB.CMD
   (listed below) checks database integrity.
 
     *( CHECKDB.CMD--Use AUTOCHK to)
     *( check database integrity.)
     SET MESSAGES OFF
     SET ERROR MESSAGES OFF
     DEBUG SET MESSAGES ON
     DEBUG SET ERROR MESSAGES ON
     CLS
     WHILE #PI IS NOT NULL THEN
       WRITE 'Choose a database or press [Esc].'
       CHOOSE vdb FROM #DATABASE +
         CLEAR FOOTING
       IF vdb IN ('[Esc]','HELP') +
         OR vdb IS NULL THEN
         BREAK
       ENDIF
       WRITE 'Checking database for errors...'
       DISCONNECT
       SET ERROR VAR evar
       ZIP AUTOCHK &vdb /n
       SET VAR e1 = .evar
       IF e1 > 53 AND e1 <> 456 THEN
         WRITE 'AUTOCHK found errors in' .vdb
         BEEP
       ENDIF
       IF e1 > 0 AND e1 < 54 OR e1 = 456 THEN
         WRITE 'AUTOCHK did not run.'
         BEEP
       ENDIF
       IF e1 = 0 THEN
         WRITE 'No errors found in' .vdb
       ENDIF
     ENDWHILE
     CLEAR VAR e1, evar, vdb
     RETURN
 
   If you don't have R:BASE 3.1B, run AUTOCHK from a DOS batch file.
 
 
   AUTOCHK from a DOS Batch File
   """""""""""""""""""""""""""""
   When executing AUTOCHK from a DOS batch file, use the DOS IF command
   to check the DOS error variable. Below is an example batch file that
   uses AUTOCHK to check the database and, depending on the outcome,
   starts R:BASE or displays a message. It assumes DBNAME is the database
   name and DBFILES is the directory holding the database.
 
     @ECHO OFF
     CD \dbfiles
     ECHO Checking database for errors...
     AUTOCHK dbname /n
 
     IF errorlevel 50 GOTO errors
     IF errorlevel 20 GOTO abort
     :rbase
     ECHO No errors found  - Starting R:BASE.
     RBASE -r
     PAUSE
     GOTO end
     :errors
     ECHO Errors found in the database.
     ECHO Correct before running R:BASE.
     GOTO end
     :abort
     ECHO AUTOCHK not run - User aborted.
     ECHO You must check the database before
     ECHO starting R:BASE.
     :end
 
 
   AUTOCHK Errors
   """"""""""""""
   After examining a database, AUTOCHK may return a zero, meaning no
   errors were found, or it may return any one of 37 error messages, each
   with its own number. All 37 are listed in the R:BASE 3.1B
   documentation.
 
   Once AUTOCHK determines there is an error, you'll want to use R:SCOPE
   to see complete information.
 
 
   AUTOCHK in Multi-user
   """""""""""""""""""""
   If you use AUTOCHK on a database that's currently open with MULTI set
   ON, AUTOCHK opens the database and places a database lock on it. The
   database stays locked until AUTOCHK finishes. The other users must
   wait to make any changes.
 
   If you use AUTOCHK on a database that's currently open with MULTI set
   OFF, AUTOCHK won't be able to open the database. It says: "Unable to
   open the database file 1."
 
   If you try to open a database currently being checked by AUTOCHK and
   nobody else is in the database, you'll see this message: "The database
   is currently open in a mode that makes it unavailable." If others have
   the database open with MULTI set ON and the database is currently
   being checked, you'll see the "Waiting in lock queue" message.
 
 
   AUTOCHK & Passwords
   """""""""""""""""""
   If you run AUTOCHK on a database that has an owner password, AUTOCHK
   won't prompt you for the password but will continue the integrity
   check. AUTOCHK doesn't display data or structure information, so
   security isn't compromised.
 
 
   An Indexed INTEGER in Each Table
   """"""""""""""""""""""""""""""""
   As explained above, you can use an indexed INTEGER column containing
   unique values to quickly restore a table that has broken pointers.
   Therefore, as a precaution, you might want to make sure every table
   has one.
 
   First, use one of the methods outlined above to ensure database
   integrity. Then add an indexed INTEGER autonumber column to those
   tables that don't already have an indexed four-byte column.
 
   By making it an autonumber column, you'll ensure unique values. Give
   each autonumber column a unique name, so it doesn't become a common
   (linking) column. Then create an index for each autonumber column.
 
   R:BASE will manage the indexes and automatically number the autonumber
   columns for you, but the overhead involved will slow down data entry.
   You have to decide if the extra protection is worth the cost in speed.
 
 
   Back Up, Back Up, Back Up
   """""""""""""""""""""""""
   Back up your database regularly. First, check database integrity. Then
   back it up by using the R:BASE BACKUP command, the DOS BACKUP command,
   or a third-party backup utility. The few minutes it takes to make a
   backup may save you hours, even months, of work.
 
   Rotate your set of backup disks so you never overwrite your most
   recent backup. That is, back up Wednesday's database on top of
   Monday's. Then if Wednesday's database is bad, you'll still have
   Tuesday's database.