Article Title

How can I copy a Paradox table with all its family members from one place to another on my system?

When I discovered the TBatchMove component, it was a real god-send because I now had a means to copy and append data from one Paradox table to another, which was my desktop database of choice. But the one thing that irked me about it was that if I wanted to perform a physical copy of a table with all its indexes and other family members, TBatchMove wouldn't copy them. It would only copy the table itself.

An obvious workaround to this dilemma would be to do an operating system level copy of all files in the directory that have the same name as the table. But the problem with this is that a system-level copy is indescriminant of the file types that are being copied. What does this mean? Well, one thing is that if a subdirectory residing in the source directory happens to have the same name as the table, its entire contents will be copied to the destination. Also, the potential for copying other stray files with the same name but having no association with the table exists. So what happens is that you have to write a lot of logic just to deal with those two situations.

So, what do you do when there doesn't seem to be anything available in the VCL that will let you copy a table and all its associates at once. Even the TTable's BatchMove method won't do it. Well, when all else fails, go to the BDE itself.

When I was programming in Paradox and Paradox for Windows, I took it for granted that I could issue a Paradox copy command and all my tables and their family members would be copied all together. All that changed in Delphi, but that doesn't mean it's not available. It's just a matter of doing some programming. And surprisingly enough, it's not that hard to do.

Copying the File

Doing BDE stuff is rarely a one step operation. Usually, you have to instantiate or initialize a few things before you actually make the call you want to make. The BDE call that we want to perform the copy, DBICopyTable, is no exception. But thankfully, it only requires a single prerequisite object to be created before making the call. That object is simply a TDatabase object. DBICopyTable uses the TDatabase object's Handle property internally to get information about the file being copied. Specifically, once it has the handle to the object, it uses the Locale property information get the language driver information it needs so it knows what files to copy. Once we create the database object, we're ready to make the call. Let's look at it in detail.

DBICopyTable takes four (5) parameters. They're explained below:

Parameter Name Type Description
hDB hDBIDb In English, this is read Database Handle, the Handle property of a TDatabase object.
bOverwrite Boolean True = Overwrite the destination file if it exists
False = Don't overwrite. If the file exists, an exception will occur. This is trapped by the Check function which will also pop up a message describing the error.
pszSrcTableName PChar The fully qualified (path and file name) name of the source table to copy
pszSrcDriverType PChar The driver type of the source table. If you supply the file extension, you can set this to nil. Otherwise, you have to specify a valid driver type (i.e. 'STANDARD', SQLServer, etc.).
pszDestName PChar The fully qualified name of the destination table.

Here's some code that encapsulates the call (we'll discuss it below):

 This will copy a Paradox or dBase table from one directory to another.
 Note that this does not use BDE aliases. It would be possible to do that
 by declaring parameters for the source and destination databases,
procedure CopyPdoxTable(SrcTbl, DstTbl: String; Overwrite: Boolean);
  DB      : TDatabase;
  DTbl    : String;
  {Since we're using path names and not BDE aliases, we have to do
   some checking of the paths to see if they're blank; that is, the
   user passed just the file name to the procedure, and not the
   FULLY QUALIFIED file name. In that case, we merely set the source
   and destination to the application's EXEName directory}
  if (ExtractFilePath(SrcTbl) = '') then
    STbl := ExtractFilePath(Application.EXEName) + SrcTbl
    STbl := SrcTbl;

  if (ExtractFilePath(DstTbl) = '') then
    DTbl := ExtractFilePath(Application.EXEName) + DstTbl
    DTbl := DstTbl;

  {First, check to see if the source file actually exists. If it does 
  create a TDatabase that points to the source file's directory.
  This can actually point anywhere using the method we're using because
  we're specifying fully qualified file names as entries as opposed to. The important thing though, is to
  set it to a valid directory}
  if FileExists(STbl) then 
      DB := TDatabase.Create(nil);
      with DB do begin
        Connected := False;
        DatabaseName := ExtractFilePath(SrcTbl);
        DriverName := 'STANDARD';
        Connected := True;

    {Do the table copy from source to dest. Notice the PChar typecast of STbl
     and DTbl. The BDE function actually calls for a DBITBLNAME type. But this
     is just a null-terminated string - a PChar - so we can save ourselves a
     lot of time by just typecasting the strings.}
      Check(DBICopyTable(DB.Handle, Overwrite, PChar(STbl), nil, PChar(DTbl)));

      //Get rid of the database component.
    ShowMessage('Could not copy the table. It is not in the location specified.');


Note that the boldface type is the code you actually write. I did it this way because I added a lot of comments and they got in the way of the code.

So, what's going on in the code?

Well, the first thing that happens is a little sanity check. If only the file name of the table is passed to the procedure, it assumes that the file resides in the same directory as the application. Then the copying operation is enclosed in a conditional statement and only executes depending upon the existence of the file itself.

Once that's done, we create a TDatabase object in memory, set its DatabaseName property to the directory location of the file by calling ExtractFilePath, specify the STANDARD driver (Paradox and dBase files) as our table language driver, then connect. Pretty simple.

Then, it's a simple matter of calling DbiCopyTable, inputting the parameters described above. Notice that I enclose the call in the Check function. This is a special BDE call which checks the error constant returned from a BDE call. While the BDE is fairly complex, it's error tracking is fairly robust. In the old days of the BDE, you had to trap all the constants yourself, then display error messages depending upon the value returned. Check handles all that for you.

Well, that's it. Try it out and see how it works for you.