Lazarus Tdbf Tutorial

From Free Pascal wiki
Revision as of 21:46, 23 October 2012 by Chronos (talk | contribs) (notes)
Jump to navigationJump to search

Deutsch (de) English (en) español (es) français (fr) português (pt) русский (ru) 中文(中国大陆)‎ (zh_CN)

Overview

This tutorial is about basic database development using the TDbf component (by Micha Nelissen) with Lazarus. Additional documentation for the TDbf component is available. In Lazarus, the DbfLaz component makes use of the TDbf component.

This page was created by Tony Maro but other contributions are welcome!

For a TDbf documentation PDF go to the SourceForge site. It may be useful to keep that pdf alongside this document while reading.

Not maintained

Light bulb  Note: at least since somewhere in 2011, the TDbf component maintainer (Micha Nelissen) has indicated he no longer maintains TDbf. It is also unmaintained by FreePascal and Lazarus developers.

Though the component works well, there are some bugs in the bugtracker. Unless somebody supplies good patches for those issue, these bugs will not be fixed.

Alternatives for embedded database use could be Firebird embedded (advantage: easily scalable to client/server), SQLite (advantage: easy deployment), or ZMSQL (using CSV files; advantage: only Pascal code required).

No need for installation

The DbfLaz package is now (Lazarus version 0.9.13 or superior) installed by default. The DBFLaz package uses the TDbf and associated units in the FreePascal Free Component Library. In other words, no need to install anything if you have a fairly recent version of Lazarus.

What the TDbf package provides

The TDbf package provides access to dBase and FoxPro database tables for Lazarus (and others). It allows for reading, writing and creation of dBase III+, dBase IV, Visual dBase VII and FoxPro tables. It does all of this without the need for additional libraries or database engines. Simply drop the TDbf on your form and you have instant access to a cross-platform database environment. The TDbf works in both Windows and Linux using Lazarus.

What to put in your uses section

If you use the visual DBF components in Lazarus, Lazarus will put Dbf in your uses clauses. If you use FPC (console mode) only, you can put it there yourself. Handy other units are db for DataSet support and Dbf_Common for things such as field type definitions.

So you'd get:

uses Dbf, db, Dbf_Common;

How to create a new database table

There is a "Database Desktop" application for Lazarus (open the project in your Lazarus directory/tools/LazDataDesktop). You could create a new data dictionary, then create code to set up DBF files based on the dictionary. As of now (July 2011), this process is still a bit convoluted.

Another option is to use OpenOffice or LibreOffice Calc and save your spreadsheet as a .dbf file.

You may also (on windows - works with wine too) try this application: http://www.dirfile.com/cdbf_explorer.htm

In any event, we shall illustrate creating a new database in code below.

Setting the path

It's a good idea to give your application's database its own directory. This simplifies making backups of the data. There are two ways to set the path. You can set the full path using the FilePathFull property, or you can set a path relative to the current application path with FilePath. For instance, setting "FilePath" at runtime to "data/" would use a data subdirectory just below the executable file. Setting the "FilePathFull" property to "/var/data/" would place everthing in that exact folder, ignoring the application's location.

Choosing a TableLevel

By default, the TDbf components will create dBase IV tables. While this is very compatible, there are features you may wish to use that are not supported. To support auto-incrementing fields, you must use something newer. The table types are:

  • Level 3 - dBase III+;
  • Level 4 - dBase IV;
  • Level 7 - Visual dBase VII;
  • Level 25 - FoxPro.

You choose a table type by setting the TableLevel property appropriately.

TableLevel and field lengths

Each TableLevel has different field length limitations (from bug [1]): As best as I could figure out: the following field size limits are present for the various DBase formats: 3: DBase III: 10 characters [2]

4: DBase IV: 10 characters [3]

7: DBase VII: 32 characters [4]

8: FoxPro: 10 characters [5] [6] Apparently Visual FoxPro 8 has 27 characters in a DBC (Database container) file, but only 10 in a DBF file.

Adding fields

Creating fields for your new table at runtime pretty much follows the old Delphi standard. Once you have set your FilePath, TableLevel, and TableName properties, manipulate the FieldDefs property to set up the structure. For example:

MyDbf.FilePathFull := '/location/to/my/data'; //Directory where all .dbf files will be stored
MyDbf.TableLevel := 7; //Visual dBase VII
MyDbf.TableName := 'customers.dbf'; // note: is the .dbf really required?
With MyDbf.FieldDefs do begin
  Add('Id', ftAutoInc, 0, True); //Autoincrement field called Id
  Add('Name', ftString, 80, True); //80 character string field called Name
End;

Field types are defined as:

  • ftUnknown
  • ftString
  • ftSmallInt
  • ftInteger
  • ftWord
  • ftBoolean
  • ftFloat
  • ftCurrency (TableLevel 25)
  • ftBCD (TableLevel 25)
  • ftDate
  • ftTime
  • ftDateTime
  • ftBytes (TableLevel 25)
  • ftVarBytes
  • ftAutoInc (TableLevel 7 or 25)
  • ftBlob
  • ftMemo
  • ftGraphic
  • ftFmtMemo
  • ftParadoxOle
  • ftDBaseOle
  • ftTypedBinary
  • ftCursor
  • ftFixedChar
  • ftWideString
  • ftLargeInt
  • ftADT
  • ftArray
  • ftReference
  • ftDataSet
  • ftOraBlob
  • ftOraClob
  • ftVariant
  • ftInterface
  • ftIDispatch
  • ftGuid
  • ftTimeStamp
  • ftFMTBcd

Bold types are currently supported by the DBF code. See Database_field_type for a explanation of field types.

Go ahead and create it!

Once you have defined the fields you wish to use in your new table, you can go ahead and create it with:

MyDbf.CreateTable;

How to add indexes to a table

If your database is larger than a few records, chances are you will want to have indexes defined to make searching faster. To change the index structure of a table, we need to have exclusive access to the table - which we would have while creating it anyway.

MyDbf.Exclusive := True;
MyDbf.Open;

Now, we just have to add the index - or two, as in this example:

MyDbf.AddIndex('custid', 'Id', [ixPrimary, ixUnique]);
MyDbf.AddIndex('custname','Name', [ixCaseInsensitive]);
MyDbf.Close;

Put it all together and you get...

The following sample creates a new table "customers" in code. This of course only needs done once, and after that you just OPEN the table, don't create it. ;-)

Light bulb  Note: When using Windows (at least it was so with XP Professional) you must create the relative data directory before trying to run this example, or

else this program will fail.
($MODE OBJFPC}
Program DatabaseTest;
{ We will require the following units be in the USES clause: }
uses Dbf, db, Dbf_Common, SysUtils;  
                                 
{ The Dbf is put there e.g. when you drop a TDbf component on a form...   }
{ but you will need db for the DataSet object and Dbf_Common              }
{ for things such as the field type definitions                           }
{ Finally, use SysUtils for ForceDirectories.                             }
var
  MyDbf: TDbf;
begin
  MyDbf := TDbf.Create(nil);
  try
    { make sure the data directory exists: }
    ForceDirectories('data');
    { use relative path to "data" directory }
    MyDbf.FilePath := 'data' + DirectorySeparator; 
    { we want to use Visual dBase VII compatible tables }
    // Note: November 2011: tablelevel 25 does not seem to work...
    MyDbf.TableLevel := 7;
    MyDbf.Exclusive := True;
    MyDbf.TableName := 'customers.dbf';
    With MyDbf.FieldDefs do begin
      Add('Id', ftAutoInc, 0, True);
      Add('Name', ftString, 80, True);
    End;
    MyDbf.CreateTable;
    MyDbf.Open;
    MyDbf.AddIndex('custid', 'Id', [ixPrimary, ixUnique]);
    { add a secondary index }
    MyDbf.AddIndex('custname','Name', [ixCaseInsensitive]);
    MyDbf.Close;
  finally
    MyDbf.Free;
  end;
end;

External Index Files

The TDbf component also supports storing secondary indexes in a separate file. This might be helpful if the database is expected to be very large. Secondary index files are created almost identically to normal indexes, but with the addition of the '.ndx' file extension:

MyDbf.AddIndex('custname.ndx','Name', [ixCaseInsensitive]);


Each time the TDbf is opened, the index file must be loaded:

MyDbf.OpenIndexFile('custname.ndx');


And indexes must be referenced including the extension:

MyDbf.IndexName := 'custname.ndx';


Index files are packed (i.e. entries pointing to deleted data rows are removed) separately using:

MyDbf.CompactIndexFile('custname.ndx');

How to link the TDbf component to data-aware components

The above examples show how to create a new database table in code. Using that table is even more simple.

Data aware components in Lazarus (such as the TDbEdit control) link to a TDataSource component using their "DataSource" and "DataField" properties. The TDataSource component handles communication between the database engine and the data aware components. A TDataSource then links to the TDbf component using it's "DataSet" property. The connection looks like this:

TDbEdit-------
             |
TDbEdit------|-->TDataSource-->TDbf
             |
TDbNavigator--


Be sure to set the FilePath (or FilePathFulll), TableLevel, and TableName properties of your TDbf component before calling

TDbf.Active := True;


There is much more that can be said about programming with databases in Lazarus, and I would recommend a good Delphi database programming book or two as the underlying concepts are the same. I constantly refer to my copy of "Delphi 2 Unleashed" because the concepts and basic code haven't changed much in 8 years.

Packing and rebuilding the tables

When a record is deleted, it's not truly removed from the physical table. Periodically you must "pack" a table to recover that lost space. This should be done with exclusive mode set. See above for remarks on packing external index files.

MyDbf.Exclusive := True;
MyDbf.Open;
MyDbf.PackTable;
// let's also rebuild all the indexes
MyDbf.RegenerateIndexes;
MyDbf.Close;
MyDbf.Exclusive := False;

Master table relations

Real power in database programming begins when you have multiple tables that reference each other. While TDbf does not yet support referential integrity, it does support a master / detail relationship between TDbf's.

When there are two tables related, for instance:

[customers]
Id       <----|
Name          |
Phone         |
Address       |
              |  The CustID in invoices references a customer primary  field
[invoices]    |
Id            |
Amount        |
CustID   -----|  * This field indexed as "idxcustid"


If you wanted to display all invoices for a given customer, the detail table (invoices) can stay in sync with the master table (customers) automatically.

On the invoices TDbf component set the following:

InvDbf.IndexName := 'idxcustid'; // our field that will match the customers table ID
InvDbf.MasterSource := dsCustomers; // datasource that is linked to the customers TDbf
InvDbf.MasterFields := 'Id'; // field on the customers table we are matching against our index


How to Filter Date in Blanks

You can show only date fields that have no contents (are blank) e.g. in Lazreport.

The easiest way is using the OnFilterRecord event handler with code like the following:

if not YourDBFComponentName.FieldByName('YOURDATEFIELD').IsNull then Accept:=False;
Light bulb  Note: the following code would seem to be similar, but shorter:
Accept:=YourDBFComponentName.FieldByName('YOURDATEFIELD').IsNull;

When we put the value of the Accept False all records that do not meet that particular condition are disregarded, i.e, are not displayed in the report.

It is also necessary to put the Filtered property database to true, but outside of that event.

How to Filter a Value with TDBF

Imagine you have a database that identifies the products by a code, for example, B019, and you need to filter only those products that are of this code (B019, in this example so that a report is generated with LazReport. Component is TDbf called the data here. field that stores the value called "product_no"

We have two ways to filter. The first would be to use the following code:

dados.filter: = 'product_no' = "B019" '

It turns out that this works very well with several components, but does not work with LazReport.

To solve the problem with LazReport you can use the OnFilterRecord event handler and use the following code:

Accept: = Copy (Trim (dadosNot.FieldByName ('product_no'). AsString), 1,4) = 'B019';

It is necessary to colcoar dados.filtered to true, out of that event.

As said before, when the value is false Accept the registers are disregarded and this way you can show off all products with that code in the report LazReport.


Encryption

Apparently at least DBase IV and 5 support some kind of encryption (there is an encryption flag in the file structure), but it may well be that the password/encryption key is in the DBF itself (see [7]), making it insecure.

There are other options if you want to encrypt your data. See here discussion on DBase encryption

Operating system level encryption

You can use an encrypted filesystem such as TrueCrypt (containers) to encrypt the directory where the database files get stored.

Encrypt the DBF file

You can use streams (e.g. the Blowfish encyprtion stream code supplied with Lazarus/FPC) to encrypt the data (all tables etc) by storing the unencrypted file e.g. in memory, then writing that to disk:

  1. Open your file in a TFileStream.
  2. Decrypt TFileStream to a new stream, YourDecryptedStream
  3. Tdbf.UserStream:= YourDecryptedStream;
  4. Tdbf.Storage:=stoMemory;
  5. Tdbf.Open;

When finished

  1. Tdbf.Close
  2. Encrypt YourDecryptedStream to TFileStream
  3. free stream(s)

Detailed code can be found in the forum thread mentioned above. People implementing this are welcome to adapt this article with working code.

Encrypt field data

You could also choose to encrypt field data by implementing the OnGetText and OnSetText events to encrypt/decrypt the field contents.

Sample application - DB Browser

I've written a simple application that will use the TDbf to open and display database tables using the dbGrid control. The Linux executable along with project sources which should compile fine in Windows is available from: tony.maro.net

Looks like the link is broken, please update !

Third party software

  • MyDbf Studio is an open source admin tool to manage your dbase table entirely written under lazarus IDE and based on TDbf.

Things you need to be aware of

- Currently there is no support for referential integrity, or internally encrypted .dbf files.

- indexing a DBase file with [ixCaseInsensitive] currently does not work.