Lazarus Databases
Lazarus Databases
Lazarus Databases
This page is an introduction to the topic 'Lazarus and databases'. The following table provides an overview
of supported databases.
Only the database components for which there are client libraries should be installed (if the database needs
any client libaries), otherwise Lazarus could fail to start because of missing files. Then Lazarus must be
reinstalled as uninstalling the component is not possible.
Contents
1 Supported databases
2 The bindings to the database clients
3 Datasets
o
3.1 Using datasets from code
o
3.2 Using the visual (data-aware) controls
o
3.3 Dataset State
o
3.4 Dataset UpdateStatus
o
3.5 Post and Cancel
o
3.6 Inserting a new record
o
3.7 How to quickly jump to 1 particular record in the table
Supported databases
Database
Package
name
Need
client
lib?
Need
server?
Supported
versions
Supported platforms
Advantage
TAdsDataSet
Yes
No
DBase
DBFLaz
No
No
All
In memory
memds
No
No
All
In memory
bufdataset
No
No
All
1 - 2.5
Firebird
SQLdb
Yes
Depends
Pagina 1 din 69
Database
Package
name
Need
client
lib?
Need
server?
Supported
versions
(Visual) FoxPro
DBFLaz
No
No
Interbase
SQLdb
Yes
Yes
4-6
SQLdb
Yes
Yes
6-
MySQL
SQLdb
Yes
Yes
3.0 - 5.5
ODBC
SQLdb
Yes
Depends
3.x 3
Oracle
SQLdb
Yes
Yes
Paradox
TParadoxDataSet No
No
up to Table Level
All
7 (and up ??)
PostgreSQL
SQLdb
Yes
Yes
6.6 - 8
Sybase
Server
(ASE)
Adaptive
Enterprise SQLdb
Yes
Yes
Any
Linux,
OSX,
Win32,
probably *BSD, probably
Solaris2)
SQLite
SQLdb
Yes
No
sqlite3
All
SQLite
SQLite(3)Laz
Yes
No
sqlite2,sqlite3
All
Microsoft
Server
SQL
Supported platforms
Text files
sdf
No
No
All
Note (1): You can use an embedded version of Firebird on Windows and Linux (possibly on Mac OS X too),
or you can connect to a Firebird server running on Windows/Unix/OSX/FreeBSD/other Firebird supported
platforms
Note (2): These connectors use the FreeTDS library as a driver. The FreeTDS documentation indicates it
should build on at least these platforms. Windows versions for x86 and x64 can be downloaded from
e.g. [1] and [2]
Note (3): This version number refers to the ODBC standard, not to the version number of a driver or driver
manager. There are ODBC 3.x drivers for most DBMSs.
Datasets
Database use in Lazarus (or Free Pascal) is fundamentally based on the TDataset class. This represents a
table or query to your application. However, like many other such fundamental classes, you don't use the
TDataset class itself, you use a descendant of it. There are many of these. They provide access to different
Pagina 2 din 69
kinds of databases, such as local dbase or text files, or back-end databases such as PostgreSQL, Firebird,
MySQL and so forth. Some dataset descendants link directly to database tables, while others use additional
components or libraries to perform the link.
Dataset descendants, being non-visual components are (usually) part of the Free Component Library (FCL)
rather than the Lazarus Component Library (LCL).
Datasets can be used both programmatically and with visual controls. A typical Lazarus database
application will often use both methods. In either case, the first step is to create the TDataset descendant,
initialise it to connect to the table or query you want, and open it. This can be done either in code at run time
or by putting a component on your form and setting it's properties at design time. The details of this vary
considerably with different TDataset descendants, so see the various guides under Databases for what has
to be done for your database.
When the dataset is opened, a set of field components are created, one for each field or column of the table
or query you opened. Each field component is a descendant of TField, appropriate to the particular data
type of the field, eg, TStringField.
Use the TDataset descendant to open the table or query, filter the rows you want to see, and to
move from row to row.
Access the specific data values for the current row. (use the As... properties, such as
AsString, AsInteger, etc.)
The FieldByName method, eg FieldByName('AGE') returns the field associated with the
database field called 'AGE'
See Database_field_type for a list of field types.
Add the dataset descendant for the database of your choice, together with any supporting
components, to your form, and open it (Set the ' Active' property to true )
Add a TDataSource component (from the Data Access tab) to the form, and "link" it to the dataset
(set the DataSet property)
Add data-aware controls from the Data Controls tab to the form, and link each one to the
DataSource (not dataset) component
Most controls link to a single field, so you also need to set the Field for each tab.
See #Data Controls below for more details on the controls
Dataset State
Datasets can be in a number of states. While there are quite a few (look up TDataSetState in the source),
the main ones to be aware of initally are
State
Function
dsInactive The dataset is closed
dsBrowse The user can browse through the dataset, looking at values
dsEdit
The user can edit values on the current row.Values are not saved until a post is performed.
A new row has been added, and the user can set the values.The record is not saved until a post is
performed
The other states are fairly transitory, and are usually handled "automatically". They are used internally and
in more complicated code. If your database only views data, and you open the dataset at design time, you
can largely ignore the state, as it will mostly be dsBrowse. However, most applications will want to change
the data at some stage. If you are using data-aware controls, they will handle a lot of this automatically. If
you change the text in a TDBEdit control, for example, it will put the dataset into dsEdit state - unless you
are already in dsEdit or dsInsert. If you "scroll" to a different record while the current state is dsEdit or
dsInsert, that record will be "posted" and the dataset revert to dsBrowse. However, if you are accessing the
dataset from code, you will often have to change the state in code as well. The TDBNavigator control (see
below) allows the user to change the state explicitly.
dsInsert
Dataset UpdateStatus
Pagina 3 din 69
UpdateStatus determines the current state of the record buffer, if updates have not yet been applied to the
database.
Example how to detect if ApplyUpdates will Insert, Update or Delete data:
procedure QueryAfterPost(DataSet: TDataSet);
begin
case DataSet.UpdateStatus of
usUnmodified : ShowMessage('Unmodified');
usModified
: ShowMessage('Modified');
usInserted
: ShowMessage('Inserted');
usDeleted
: ShowMessage('Deleted');
end;
end;
Value Explanation
usInserted: Record does not yet exist in the database, but is locally inserted
Calling the dataset cancel method removes the new record (insert) or reverts the values to their
previous values (edit).
Calling the dataset post method saves the values (edit) or record (insert). In some dataset
descendants, they will be written to the database immediately, while in others they will be stored in a list
of updates until a further call is made to save all changes to the database. Finally, even when they are
written to the database, you may still have to call a "commit" method to make the database write them
permanently. All of this also varies considerably with the dataset descendant, so look up the details for
the one you are using.
Pagina 4 din 69
// Actually, if this is done in a loop, you only need to set the SQL.Text
once,
// and only change the parameter value
MyDataset.SQL.Text
:=
'select
*
from
"GameSession"
WHERE
"SessionID"=:SessionID');
MyDataSet.ParamByName('SessionID').AsLargeInt := ASessionID;
try
MyDataset.Active := True;
except
//...
end;
You can then read information using something like this:
lPlayerId := MyDataset.Fields[4].AsInteger;
Filtering
You can filter your dataset to restrict the records to a subset you want (e.g. all surnames starting with
Smith).
Using .Filter:
TDbf, TBufDataset and descendants (including TSQLQuery) use the TDBF filtering parser;
see Lazarus Tdbf Tutorial#Expressions for details on using this.
Using a callback/event procedure with OnFilter where you can program your own filter function
Locate/lookup
Although more often used in non-SQL datasets (e.g. TParadoxDataSet, TDbf) you can jump between
records using locate/lookup.
Using TSQLQuery
For more information about TSQLQuery see Working With TSQLQuery
Exporting
FPC/Lazarus contains functionality to let you export datasets to various formats; see e.g.
fpXMLXSDExport
fpdbfexport
(see
using
Data Controls
To use any of these controls, add the control to a form and set at least the datasource property. Other key
properties will be noted.
Datasource Control
This control keeps track of which record the linked controls currently are on. The datasource contorl must
be linked to a dataset (e.g. a TSQLQuery).
DBImage control Displays a picture stored in a database as a BLOB. Note: by default, Lazarus
stores a header with the image type before the image data in the database BLOB field. This is different
from Delphi. However, you can make TDBImage Delphi compatible: see Lazarus For Delphi
Users#TDBImage
DBListBox control and DBComboBox Control Allow the user to insert values into a database field
from the list of values in the Items property of the controls
DBLookupListBox control and DBLookupComboBox control, see also dblookupcombobox Allow the
user to insert values into a database field by displaying the contents of a lookup field in another table.
Though these controls store their results in a single field, you need another field for the lookup
values. Note: at lest for DBLookupComboBox, there is a bug with FPC 2.6.0 that requires the listfield to
be present in the datasource as well, you can bypass it by declaring a calculated field with the same
name as the listfield in the datasource's dataset that does nothing.
DBRadioGroup control Displays the items as in a normal radio group, reading/setting the field value
from a matching values list
Pagina 5 din 69
DBGrid control
This control can show a number of fields in a row/column layout - in fact by default it shows them all.
However, you can put entries into the columns collection to restrict it to specific fields and to set the widths
and titles of each column.
Apart from the mentioned documentation, some more detail can be found here: Grids Reference
Page#TCustomDBGrid
Navigator Control
This control gives the user some direct control over the dataset. It allows the user to:
Move to the next or previous record, or to the start or end of the records
Delete a record
VisibleButtons: Lets you control what the user can do. For example, if deletes are not allowed, hide
the delete button. If you have a DBGrid attached to the same dataset, you may decide you do not need
the next and prior buttons.
Width: If you do not show all buttons, you may want to set the width to
(height*number_of_visible_buttons)
Interbase/Firebird
MySQL
Pagina 6 din 69
SQLdb Package
dbflaz.lpk
This package provides access to dBase and FoxPro databases. You can get more information in
the Lazarus Tdbf Tutorial. The TDbf component is on the Data Access tab in the component palette.
sqlitelaz.lpk
This package provides access to SQLite databases. You can get more information in the Lazarus Database
Overview.
sdflaz.lpk
The component TSdfDataSet can be found on the Data Access tab in the component palette.
lazreport.lpk
The homepage of the report generator is http://lazreport.sourceforge.net/. More informationen (et al. an
additional link) can be found here. LazReport depends on the Printer4Lazarus package. With revision 11950
LazReport was included in the Lazarus SVN repository.
lazdbexport.lpk
See lazdbexport.
pgsql-8.1
oracle
like prepared statements, binding, and stored procedures are supported by database API called Pascal
Data Objects, which is inspired by PHP Data Objects. All the code and documentation necessary to use this
new API is available on Sourceforge:
http://pdo.sourceforge.net
TPSQL
These components provide access via TCP/IP to PostgreSQL databases. You can find more information
on this page.
FIBL
These components provide access
is http://sourceforge.net/projects/fibl.
to
Interbase
and
Firebird
databases.
The
homepage
IBX
IBX For Lazarus are components to access Firebird databases: see IBX
Multiplatform [Win32,Gnu/Linux,FreeBSD)
Pagina 7 din 69
LGPL License agreement
Extract Metadata
Events Alerter
You can download documentation on FBLib's website.
Unified Interbase
UIB provides access to Interbase, Firebird and YAFFIL databases. The homepage is www.progdigy.com. A
svn repository is available under https://uib.svn.sourceforge.net/svnroot/uib .
Multi-Platform (Clients supported on Windows and Linux, Server supported on Windows, Linux,
and NetWare)
Online Backup
Master/detail filtering
Referential integrity
Parameterized queries
The download contains the source code, some demo applications illustrating the features of the component
as well as a readme.
Pagina 8 din 69
1 Overview
2 Lazarus and Interbase / Firebird
3 Lazarus and MySQL
4 Lazarus and MSSQL/Sybase
5 Lazarus and ODBC
5.1 Microsoft Access
6 Lazarus and Oracle
7 Lazarus and PostgreSQL
8 Lazarus and SQLite
9 Lazarus and Firebird/Interbase
10 Lazarus and dBase
11 Lazarus and Paradox
12 TSdfDataset and TFixedDataset
13 Lazarus and Advantage Database Server
Overview
This article is an overview of which databases can work with Lazarus.
Lazarus supports several databases out of the box (using e.g. the SQLDB framework), however the
developer must install the required packages (client libraries) for each one.
You can access the database through code or by dropping components on a form. The data-aware
components represent fields and are connected by setting the DataSource property to point to
a TDataSource. The Datasource represents a table and is connected to the database components
(examples: TPSQLDatabase, TSQLiteDataSet) by setting the DataSet property. The data-aware
components are located on the Data Controls tab. The Datasource and the database controls are located
on the "Data Access" tab.
See the tutorials for Lazarus/FPC built in database access, suitable for Firebird, MySQL, SQLite,
PostgreSQL etc:
SQLdb Tutorial0
SQLdb Tutorial1
SQLdb Tutorial2
SQLdb Tutorial3
SQLdb Tutorial4
Firebird is very well supported out of the box by FPC/Lazarus (using SQLDB); please
see Firebird for details.
Other Firebird libraries has a list of alternative access libraries (e.g. PDO, Zeos, FBlib)
1.
2.
3.
4.
Please see mysql for details on various access methods, which include:
Built-in SQLdb support
PDO
Zeos
MySQL data access Lazarus components
Pagina 9 din 69
protocol ado for MSSQL 2005 or later. Using protocols mssql or ado generates code not
platform independient.
2. On Linux the only way is with FreeTDS protocols and libraries (you should
use libsybdb.so).
4. ODBC (MSSQL
and
Sybase
ASE)
with
SQLdb TODBCConnection (consider
using TMSSQLConnection and TSybaseConnection instead)
1. See also [1]
2. On Windows it uses native ODBC Microsoft libraries (like sqlsrv32.dll for MSSQL 2000)
3. On Linux it uses unixODBC + FreeTDS (packages unixodbc or iodbc, and tdsodbc). Since
2012 there is also a Microsoft SQL Server ODBC Driver 1.0 for Linux which is a binary
product (no open source) and provides native connectivity, but was released only for 64
bits and only for RedHat.
Microsoft Access
You can use the ODBC driver on Windows as well as Linux to access Access databases; see MS Access
Pagina 10 din 69
It doesn't require any sort of runtime database engine. However it's not the best option for large database
applications.
See the TDbf Tutorial page for the tutorial as well as documentation.
You can use e.g. OpenOffice/LibreOffice Base to visually create/edit dbf files, or create DBFs in code
using TDbf.
TParadox: Install package "lazparadox 0.0" included in the standard distribution. When you install
this package, you will see a new component labeled "PDX" in the "Data Access" palette. This
component is not standalone, it uses a "native" library, namely the pdxlib library which is available for
Linux and Windows. For example, to install in Debian, you could get pxlib1 from package manager. In
Windows you need the pxlib.dll file.
TPdx: Paradox DataSet for Lazarus and Delphi from this site. This component is standalone (pure
object pascal), not requiring any external library, but it can only read (not write) Paradox files. The
package to install is "paradoxlaz.lpk" and the component should appear in the "Data Access" palette
with PDX label (but orange colour).
TParadoxDataSet: is a TDataSet that can only read Paradox Files up to Version 7. See this wiki
page. The approach is similar to the TPdx component, the package to install is "lazparadox.lpk" and the
component should also appear in the "Data Access" palette.
Linux
If installing the Advantage TDataSet on Linux, you will need to complete a two-part installation. First,
download and install the Advantage Client Engine for Linux (version 10.1). Second, download and extract
the Linux TDataSet source tarball. Both downloads are available at the Advantage Developer Zone
at http://devzone.advantagedatabase.com. (The Advantage Client Engine download is located in the
"Product Download" section of the site, and the Linux TDataSet source is available in the Delphi
Applications section of the CodeCentral page of the Advantage Developer Zone.)
Pagina 11 din 69
2. In the Object Inspector, click the drop-down for the AliasName property, and select
"ADTDemoData".
Alternately, you can select the ConnectPath property and enter (or browse to) the path to
the Help\ADS_Data directory beneath your TDataSet (or acesdk on Linux) install directory.
3. Expand the AdsServerTypes property, and change the "stADS_LOCAL" property to True.
If you have an Advantage Database server configured and running, you may set
"stADS_REMOTE" to true instead.
4. Drop a TAdsTable object on the form. (The TAdsTable object is immediately to the right of the
TAdsConnection object on the tab, and it looks like a red table.)
5. Set the AdsConnection Property for AdsTable1 to AdsConnection1 (the connection object you just
dropped in step 1.)
6. Scroll down to the "TableName" property, and use the drop-down box to select the biolife.adt table.
7. Drop a TDataSource component on the form, and set its DataSet property to "AdsTable1".
8. Drop a TDBGrid component on the form, and set its "DataSource" property to "Datasource1".
9. At this point, you should be able to select the AdsTable1 object and set its "Active" property to
"True".
10. Finally, run the project. (Note that you may need to save the project before running it.)
Since the Advantage components descend from the TDataSet, you can use these components with any
data-aware component that supports the TDataSet. This extremely simple example should demonstrate
how simple it is to get started with Advantage.
Pagina 12 din 69
SqlDBHowto
This text is setup as a 'how-to'. I want to answer a number of questions one by one, and explain how you
can use the various classes. All those questions are put one after the other and form a sort of tutorial.
I will try to word it in such a way that the text can be used for Lazarus as well as FreePascal. However, the
examples are for FreePascal (i.e. they are console applications.)
Contents
1 Where can I find official documentation?
2 How to connect to a database server?
3 How to execute direct queries/make a table?
4 How to read data from a table?
o
4.1 Example: reading data from a table
o
4.2 Why does TSQLQuery.RecordCount always return 10?
o
4.3 Lazarus
5 How to change data in a table?
6 How does SqlDB send the changes to the database server?
7 How to execute a query using TSQLQuery?
8 How to use parameters in a query?
o
8.1 Select query
o
8.2 Example
9 Troubleshooting: TSQLConnection logging
o
9.1 FPC (or: the manual way)
o
9.2 Lazarus (or: the quick way)
Firebird/Interbase: TIBConnection
Sybase ASE: TSybaseConnection (available since FPC 2.6.1, see Lazarus and MSSQL/Sybase)
Note for MySQL - There are many differences between the client versions to the extent that the clients and
connections cannot be interchanged. If a MySQL client library version 4.1 is installed, you have to use a
TMySQL41Connection. This is not related to the MySQL server; using the MySQL 4.1 client library you can
probably connect to a MySQL 5.0 server (see MySQL documentation regarding what combinations are
supported).
Although details differ for the various databases, in general you need to set four properties to connect to a
database server:
the username
the password
Pagina 13 din 69
When these properties are set, you can create a connection with the 'open' method. If the connection fails,
a EDatabaseError exception is thrown. Use the property 'connected' to test if a connection has been made
with the database server. Use the 'close' method to end the connection with the server.
Program ConnectDB;
function CreateConnection: TIBConnection;
begin
result := TIBConnection.Create(nil);
result.Hostname := 'localhost';
result.DatabaseName := '/opt/firebird/examples/employee.fdb';
result.UserName := 'sysdba';
result.Password := 'masterkey';
end;
var
AConnection : TIBConnection;
begin
AConnection := CreateConnection;
AConnection.Open;
if Aconnection.Connected then
writeln('Successful connect!')
else
writeln('This is not possible, because if the connection failed, ' +
'an exception should be raised, so this code would not ' +
'be executed');
AConnection.Close;
AConnection.Free;
end.
If an exception is thrown, read the error message carefully. It may be that the database server is not
running, the user name or password are incorrect or the database name or IP address are typed incorrectly.
If the error message states that the client library cannot be found, then check if the client is installed
correctly. Often the error message states literally the name of the file looked for.
SQL statements that return information (a dataset). For this, you have to use the TSQLQuery
component; see #How to read data from a table?.
statements that do not return information but do something else, e.g. update data. For this, you
may also use the 'ExecuteDirect' method of a TSQLConnection. (You can also use this if you get a
dataset back but are not interested in the results, e.g. in a selectable stored procedure).
Most database system execute SQL statements within a transaction. If you want changes made within a
transaction available in other transactions, or have those changes available even after closing the
transaction(!), then you have to 'commit' the transaction.
To support transactions Sqldb contains the TSQLTransaction component. A SQL statement that is executed
by Sqldb must always be executed within a transaction, even if the database system does not support
transactions. Also, there are database systems that do support transaction for which TSQLConnection does
not (yet) support transaction. Even then, you must use the TSQLTransaction component.
To use TSQLConnection.ExecuteDirect to execute a SQL statement you must specify which
'Transaction' must be used. In turn, to use TSQLTransaction you must specify which TSQLConnection
component must be used.
The following example creates a table 'TBLNAMES' with fields 'NAME' and 'ID' and inserts two records. The
used SQL statements are not explained. For more information about the SQL statements, their use and
syntax, please refer to the database system documentation. The procedure 'CreateConnection' is defined in
the code example in #How to connect to a database server? above.
program CreateTable;
function CreateTransaction(pDB: TIBConnection): TSQLTransaction;
begin
result := TSQLTransaction.Create(pDB);
Pagina 14 din 69
result.Database := pDB;
end;
var
AConnection : TSQLConnection;
ATransaction : TSQLTransaction;
begin
AConnection := CreateConnection;
ATransaction := CreateTransaction(AConnection);
AConnection.Transaction := ATransaction;
AConnection.Open;
ATransaction.StartTransaction;
AConnection.ExecuteDirect('create
table
TBLNAMES
varchar(40));');
(ID
integer,
NAME
// Some database-server types need a commit before you can use a newly
created table. (Firebird)
// With .Commit you also close the transaction
ATransaction.Commit;
ATransaction.StartTransaction;
AConnection.ExecuteDirect('insert
(1,'Name1');');
AConnection.ExecuteDirect('insert
(2,'Name2');');
ATransaction.Commit;
AConnection.Close;
AConnection.Free;
ATransaction.Free;
end.
into
TBLNAMES
(ID,NAME)
values
into
TBLNAMES
(ID,NAME)
values
TIBConnection;
Pagina 15 din 69
pTransaction:
var
AConnection : TSQLConnection;
ATransaction : TSQLTransaction;
Query
: TSQLQuery;
begin
AConnection := CreateConnection;
ATransaction := CreateTransaction(AConnection);
Query := CreateQuery(AConnection, ATransaction);
Query.SQL.Text := 'select * from tblNames';
AConnection.Open;
Query.Open;
while not Query.Eof do
begin
Writeln('ID: ', Query.FieldByName('ID').AsInteger, 'Name: ' +
Query.FieldByName('Name').AsString);
Query.Next;
end;
Query.Close;
AConnection.Close;
Query.Free;
ATransaction.Free;
AConnection.Free;
end.
(The code above of course is not quite finished, it misses 'try...finally' blocks. However, the above code
intends to show the database code and thus the finishing touches are left out.) Please note that
'TSQLTransaction.StartTransaction' is not used. This is not necessary. When TSQLQuery is opened, the
SQL statement is executed and if no transaction is available then a transaction is automatically started. The
programmer does not need to start the transaction explicitly. The same applies for the connection
maintained by TSQLConnection. The connection is opened as needed, the line 'Aconnection.Open' is not
really required. If a TSQLTransaction is destroyed, an automatic 'rollback' will be executed. Possible
changes to data contained in the transaction will be lost.
Lazarus
Lazarus has various components to show data from a TDataset on a form. Instead of a While-loop and
Writeln statements as used above, you can use the components to show the data in a table. Place the right
TSQLConnection, TSQLTransaction and TSQLQuery components on a form, then connect them and set
them properly. In addition you will need a TDatasource; set the 'TDatasource.Dataset' property to the
TSQLQuery component you used. (Note do not set the 'TSQLQuery.Datasource' property to the
TDatasource compnent you used. The 'TSQLQuery.Datasource' property is used only in master-detail
tables - see MasterDetail) Subsequently you may put a TDBGrid onto the form and set the 'Datasource'
property of the grid to the TDatasource component you added before.
To see if it all works, set the 'Connected' property of the TSQLConnection to 'True' in the Lazarus IDE. The
IDE will try to connect to the database server immediately. If this works you can set the 'TSQLQuery.Active'
property to 'True'. If everything is right, you will see - within the IDE - all data from the table immediately on
the screen.
Pagina 16 din 69
record at the end of the table. In edit mode you can change field values through the 'Fields' property. Use
'Post' to validate the new data, if the data is valid then the edit mode is left. If you move to another record for example by using '.Next' - and the dataset is in edit mode, then first '.Post' is called. Use '.Cancel' to
discard all changes you made since the last '.Post' call and leave the edit mode.
Query.Edit;
Query.FieldByName('NAME').AsString := 'Edited name';
Query.Post;
The above is not the complete story yet. TSQLQuery is derived from TBufDataset which makes use of
buffered updates. Buffered update means that after you called 'Post' the changes in the dataset are visible
immediately, but they are not sent to the database server. What does happen is that the changes are
maintained in a change log. When the '.ApplyUpdates' method is called, then all changes in the change log
are sent to the database. Only then will database server know of the changes. The changes are sent to the
server within a transaction of TSQLTransaction. Make sure to properly set the transaction before
'ApplyUpdates'. After applying the updates, a commit must be executed to save the changes on the
database server.
The below is an example of changing the data in a table, sending the changes to the server and comitting
the transaction.
Program EditData;
var
AConnection : TSQLConnection;
ATransaction : TSQLTransaction;
Query : TSQLQuery;
begin
AConnection := CreateConnection;
ATransaction := CreateTransaction(AConnection);
AConnection.Transaction := ATransaction;
Query := CreateQuery(AConnection, ATransaction);
Query.SQL.Text := 'select * from tblNames';
Query.Open;
Query.Edit;
Query.FieldByName('NAME').AsString := 'Edited name';
Query.Post;
Query.UpdateMode := upWhereAll;
Query.ApplyUpdates;
ATransaction.Commit;
Query.Free;
ATransaction.Free;
AConnection.Free;
end.
For a discussion of 'UpdateMode' continue reading.
to
generate
one.
(No
fields
for
To understand what went wrong, you must understand how changes are sent to the database server. The
only way to get data in a SQL server is by executing SQL queries. SQL has three types of queries for three
different ways of manupulating a record. To create a new record, change or delete a record insert, update
and delete statements are executed respectively. An update statement may be as follows:
UPDATE TBLNAMES SET NAME='Edited name' WHERE ID=1;
To send a change to the database server, Sqldb must assemble an update query. To assemble the query,
three things are needed:
The name of the table
The table name is retrieved from parsing the select query, although this doesn't always work.
UPDATE or INSERT clause
These contain the fields that must be changed.
Pagina 17 din 69
WHERE clause
This contains the fields that determine which records should be changed.
Every field (each TField in Fields) has a ProviderFlags property. Only fields
with pfInUpdate in ProviderFlags will be used in the update or insert cluase of a query. By
default all fields have pfInUpdate set in their ProviderFlags property.
Which fields are used in the WHERE clause depends on the UpdateMode property of the query
and the ProviderFlags property of the fields. Fields with pfInkey in their ProviderFlags are
always used in the WHERE clause. A field will have the pfInKey flag set automatically if the field
is part of the primary key of the table and 'TSQLQuery.UsePrimaryKeyAsKey' returns 'True'.
The default value for UpdateMode of the query is upWhereKeyOnly. In this update mode only
fields with pfInkey in their ProviderFlags property are used in the WHERE clause. If none of the
fields have their pfInKey flag set, then no fields are available for the WHERE clause and the
error message from the beginning of this section will be returned. You can solve the issue by:
Adding a primary key to the table and set TSQLQuery.UsePrimaryKeyAsKey to 'True', or
Setting the pfInkey flag for one or more fields in code.
The UpdateMode property knows two more possible values. 'upWhereAll' can be used to add
all fields with the 'pfInWhere' flag set to the WHERE clause. By default all fields have this flag
set. 'upWhereChanged' can be used to add only those fields that have the 'pfInWhere' flag
set and that are changed in the current record.
(ID
integer,
NAME
Query.SQL.Text
(1,''Name1'');';
Query.ExecSQL;
:=
'insert
into
TBLNAMES
(ID,NAME)
values
Query.SQL.Text
(2,''Name2'');';
Query.ExecSQL;
:=
'insert
into
TBLNAMES
(ID,NAME)
values
Query.Close;
Query.Free;
end;
Pagina 18 din 69
Select query
An example of a select query with parameters would be to change something like this:
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME =
'''+Edit1.Text+''' ORDER BY NAME ';
to something like this:
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME
= :NAMEPARAM ORDER BY NAME ';
Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;
Example
The following example creates the same table as the previous example, but now parameters
are used:
procedure CreateTableUsingParameters;
var
Query : TSQLQuery;
begin
Query := CreateQuery(AConnection, ATransaction);
Query.SQL.Text
:=
'create
table
TBLNAMES
varchar(40));';
Query.ExecSQL;
(ID
integer,
NAME
Pagina 19 din 69
Alternatives are:
1. you can use the debugger to step through the database code if you have built FPC
(and Lazarus) with debugging enabled.
2. if you use ODBC drivers (at least on Windows) you could enable tracelog output in
the ODBC control panel.
3. many databases allow you to monitor all statements sent to it from a certain IP
address/connection.
If you use TSQLConnection logging, two things are required:
1. indicate which event types your TSQLConnection should log
2. point TSQLConnection at a function that receives the events and processes them
(logs them to file, prints them to screen, etc.).
That function must be of type TDBLogNotifyEvent (see sqldb.pp), so it needs this signature:
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType :
TDBEventType; Const Msg : String) of object;
Pagina 20 din 69
FConnection.OnLog:[email protected];
...
// now we can use the connection and the FConnectionLog stringlist
will fill with log messages.
You can also use TSQLConnection's GlobalDBLogHook instead to log everything from
multiple connections.
Pagina 21 din 69
Contents
1 General
2 Official documentation
3 Commonly used controls
4 Updating data
5 Cached Updates
6 Primary key fields
7 Controlling the update
8 Customizing the SQL in TSQLQuery
o
8.1 TSQLQuery.SQL: Basic SQL Customization
o
8.2 TSQLQuery.InsertSQL, TSQLQuery.UpdateSQL and TSQLQuery.DeleteSQL: Basic Use of
Parameters
o
8.3 Parameters in TSQLQuery.SQL
General
TSQLQuery is an object that can embody a dataset coming from a database (RDBMS that uses SQL, such
as Firebird, MS SQL Server, Oracle...). Using a SELECT SQL statement in the TSQLQuery's SQL property,
you can determine what data is retrieved from the database into the dataset. When the dataset is changed
by the program (or user), the changes can be submitted back to the database.
A TSQLQuery can also be used to directly modify data: if you specify the desired INSERT, UPDATE,
DELETE etc SQL statement in the SQL property and call the ExecSQL method of the TSQLQuery, the
query object will send the SQL to the database without retrieving any results.
Apart from its use in FPC, Lazarus also provides a component: TSQLQuery
Official documentation
See TSQLQuery documentation
A lot of context-sensitive documentation is now available in Lazarus. Unfortunately, TSQLQuery does not
appear in the index of Lazarus 1.0 help. If you place your cursor on TSQLQuery methods and properties, try
pressing F1 to see if that code is documented; e.g. this will work:
var
Q: TSQLQuery
...
Q.Open; //<--- place cursor on Open and press F1
Pagina 22 din 69
After this, the Active property can be set to True, and it should be possible to see the data retrieved by the
query. (provided both the TSQLConnection and TSQLTransaction components are active)
Updating data
If you need to be able to DELETE or otherwise modify records, your database table should either
1. contain one PRIMARY KEY column.
2. have a set of fields that uniquely determine the record. Normally, they should be part of a unique
index. This is not required, but will speed up the queries quite a lot
If there is no primary field, or no fields that uniquely determine your record, then a primary key field should
be added. This is done preferably when the table structure is designed, at CREATE time, but can be added
at a later time.
For instance the following example code in your MySQL client will add a unique index to your table:
ALTER TABLE testrig
ADD COLUMN autoid INT
PRIMARY KEY AUTO_INCREMENT;
Adding this field will not hurt, and will allow your applications to update the field.
Cached Updates
The TSQLQuery component caches all updates. That is, the updates are not sent immediately to the
database, but are kept in memory till the ApplyUpdates method is called. At that point, the updates will be
transformed to SQL update statements, and will be applied to the database. If you do not
call ApplyUpdates, the database will not be updated with the local changes.
Pagina 23 din 69
When you assign a SELECT query to an SQLQuery's SQL property the SQLQuery knows how to get data
from the database. However, when using databound controls such as a DBGrid, SQLQuery will also need to
be able to insert, update and delete rows from the database based on the user's actions.
In order to speed development, SQLQuery can try and deduce the required SQL statements. If the SQL
property exists and the ParseSQL property is true (which it is by default), SQLQuery will try to generate
these
statements
by
parsing
the SQL property.
SQLDB
stores
these
statements
in
the InsertSQL, UpdateSQL and DeleteSQL properties.
However, sometimes the generated statements will not work (e.g. when inserting in tables with autoincrement/autonumber primary keys) or will be very slow. If needed, you can manually assign the
statements.
The statements in the InsertSQL, UpdateSQL and DeleteSQL properties accept parameters that represent
fields in the dataset. The following rules apply:
Parameter names must be exactly the same as the field names used in the dataset. The field
names in the dataset may be different from the column names in the table, depending on the used select
statement (see above).
Just as parameters in other SQLDB queries, parameter names must be written preceded by a
colon.
For use in update/delete statements, precede the dataset field name with OLD_ (strictly
uppercase, at least in Lazarus v. 1.0) to get the value of the record before it was edited instead of the new
value.
If you have a table called fpdev and columns id, UserName and InstEmail, linked to a dataset with fields ID,
User and e-mail (see example in select statement), you could write this InsertSQL query:
INSERT INTO fpdev(id, UserName, InstEmail) VALUES(:ID,:USER,:e-mail);
This statement will insert the values of ID, User and e-mail from the current record of the dataset into
the respective fields of table fpdev.
This example statement is actually more or less what SQLDB itself would autogenerate. The given
statement may result in errors when the id field is an auto-increment field in a unique key. Different
databases solve this problem in different ways. For example, the following works for MySQL.
INSERT INTO fpdev(id, UserName, InstEmail) VALUES(0,:USER,:e-mail)
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID();
The above statement tries to insert a new record using 0 (zero) for the id column. If zero is already used as
a key, then a duplicate is detected and the id is updated to use the last inserted id. Well, actually an id one
increment higher than the last one used.
For Firebird, if you emulate autoincrement keys [1] something like this should work:
INSERT INTO fpdev(UserName, InstEmail) VALUES(:USER,:e-mail);(
Pagina 24 din 69
The statement inserts everything except the primary key and lets the Firebird before insert trigger use a
generator/sequence to insert an id value for you.
For an INSERT statement you may want to use the current field values of the selected record.
For UPDATE statements, you will want to use the field values as they were before editing in
the WHERE clause. As mentioned before, the field values before editing must be written as the field name
precede by OLD_ (strictly uppercase, at least in Lazarus v. 1.0). For example, this query:
UPDATE fpdev SET UserName=:USER, InstEmail=:e-mail WHERE UserName=:OLD_User;
The above statement updates the UserName and InstEmail columns of all records where User equals
the old User value.
We leave it as an exercise to the reader to use the current field values and the old field values in DELETE
statements.
See also the official documentation:
InsertSQL documentation
UpdateSQL documentation
DeleteSQL documentation
Parameters in TSQLQuery.SQL
In most situations, the SQL property of TSQLQuery will contain the select statement which in most
situations doesn't need parameters. However, it can contain them. This allows a very easy and powerful
way to filter your records.
Parameters have the following advantages:
no need to format your data as SQL text, date etc arguments (i.e. no need to remember how to
format a date for MySQL, which might differ from the Firebird implementation; no need to escape text data
like O'Malley's "SQL Horror"
possibly increased performance
protection against SQL injection attacks
The use of parameters may help performance of the database. Most databases support prepared
statements, which means that the statement is prepared and cached in the database. A prepared statement
can be used more than once and doesn't require parsing and query planning every time it is used, only the
parameters are changed each time it is used. In situations where the same statement is used a large
number of times (where only the values of the parameters differ), prepared statements can help
performance considerably. Additionally, SQL injection attacks can be mitigated by use of parameters.
The InsertSQL, UpdateSQL and DeleteSQL properties have predefined parameters for current and old field
values. However, the SQL property does not. You can create your own parameters in the Paramsproperty.
:=
Pagina 25 din 69
You can also get metadata: table names, column names etc (if the sqldb connector supports it but most of
them do nowadays):
Pagina 26 din 69
Troubleshooting
Logging
See here: SqlDBHowto#Troubleshooting: TSQLConnection logging for more detail.
Poor performance
Make sure your database queries are optimized (use proper indexes etc). Use your database tools
(e.g. providing query plans) for this.
See #Out of memory errors below for possible performance improvements when moving forward
through an SQLQuery.
Error messages
Out of memory errors
TSQLQuery is a descendant of BufDataset, a dataset that buffers the data it receives into memory. If you
retrieve a lot of records (e.g. when looping through them for an export), your heap memory may become full
(with records you already looped through) and you will get out of memory errors.
Although this situation has improved in the FPC development version, a workaround is to tell bufdataset to
discard the records you have already read by setting the Unidirectional property to true before opening the
query:
MySQLQuery.UniDirectional:=True;
This may also improve performance.
Dataset is read-only
This may happen if you specify a query that you know is updatable but FPC doesn't.
Example:
SELECT p.dob, p.surname, p.sex FROM people p;
The SQL parser in FPC is quite simplistic and when it finds a comma or space in the FROM part it
considers multiple tables are involved and sets the dataset to read only. To its defense, aliasing tables is
usually not done when only one table is involved. Solution: rewrite the query or specify your
own InsertSQL, UpdateSQL and DeleteSQL.
Pagina 27 din 69
SQLdb Tutorial0
Contents
1 Introduction
2 Requirements
3 Firebird installation
o
3.1 Install Firebird client/server
o
3.2 Firebird database libraries on Windows
o
3.3 Firebird database libraries on other systems
4 No Firebird or employee.fdb installed?
o
4.1 Automatic creation
o
4.2 Manual creation
o
4.3 SQLite
o
4.4 PostgreSQL
Introduction
This article will help you set up your database environment with sample tables and data to use in SQLdb
Tutorial 1, 2 and 3.
Although this page is quite long, you will only go through a few of the sections as this article contains
instructions for various database systems.
If you want to get up and running quickly, I'd suggest installing the Firebird server and example database as
described below.
Requirements
This tutorial is written for use with recent Lazarus versions (Laz 1.0); it should also work on older versions
Lazarus 0.9.30.
Furthermore you need an SQL/relational database, such as Firebird (if possible version 2.0 or newer). It's
easiest if you use standard settings (e.g user name SYSDBA and password masterkey), and that you have
the employee sample database installed.
You can use another database (e.g. Microsoft SQL Server, MySQL, PostgreSQL, Oracle, SQLite, Sybase
ASE or another database using ODBC). Please make sure you have the required database client libraries
installed (see the various wiki articles on databases) Please see below (section No Firebird or
employee.fdb installed?) for a way to set up your tables
Firebird installation
In case you haven't yet installed Firebird or the sample database that is used with the tutorials, please find
some instructions below.
they can also be in your Lazarus directory (for design time support in the IDE) and in the output
directory where the executable is (to run the compiled program).
If you haven't installed the Firebird server, an easy way to get the client DLLs is: download Firebird
Embedded 2.5 from [1] Extract these files to your application directory:
fbclient.dll #only if using the server
#or
fbembed.dll #only if using embedded
firebird.msg
ib_util.dll
icudt30.dll
icuin30.dll
icuuc30.dll
Microsoft.VC80.CRT.manifest
msvcp80.dll
msvcr80.dll
Rename fbembed.dll to fbclient.dll (the name for a regular, client-server Firebird client - this helps use on
older Lazarus/FPC versions). The embedded Firebird DLL can also act as a regular Firebird client.
Make sure the employee.fdb database is in your project directory. You can copy it from the
examples/empbuild/ directory of the firebird 2.5 server.
Pagina 28 din 69
Finally, compile your project (even if it empty) once to create the output directory, and copy the dlls, as well
as the employee.fdb database, into that directory.
Automatic creation
The easiest way of setting this up is to create a new empty database on your system, then connect to it with
the TSQLScriptSample sample/utility program. This program is included with current Lazarus development
releases in the ./examples/database/tsqlscript directory, but can also be downloaded from
http://svn.freepascal.org/svn/lazarus/trunk/examples/database/tsqlscript/
Compile and run the program, then connect to the database:
then click the button Copy table creation script, and Run script:
Pagina 29 din 69
The program should indicate success. Now do the same for Copy sample data script, and Run script
In case of problems, you can try the manual steps below.
Manual creation
We will create CUSTOMER and EMPLOYEE tables that will be used in a later tutorial.
Run these SQL commands in your database editor/tool:
CREATE TABLE CUSTOMER
(
CUST_NO INTEGER NOT NULL,
CUSTOMER VARCHAR(25) NOT NULL,
CITY VARCHAR(25),
COUNTRY VARCHAR(15),
CONSTRAINT CT_CUSTOMER_PK PRIMARY KEY (CUST_NO)
);
CREATE TABLE EMPLOYEE
(
EMP_NO INTEGER NOT NULL,
FIRST_NAME VARCHAR(15) NOT NULL,
LAST_NAME VARCHAR(20) NOT NULL,
PHONE_EXT VARCHAR(4),
JOB_CODE VARCHAR(5) NOT NULL,
JOB_GRADE INTEGER NOT NULL,
JOB_COUNTRY VARCHAR(15) NOT NULL,
SALARY NUMERIC(10,2) NOT NULL,
CONSTRAINT CT_EMPLOYEE_PK PRIMARY KEY (EMP_NO)
);
Some data so you can at least show something.... first some clients:
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (1, 'Michael
Design', 'San Diego', 'USA');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (2, 'VC
Technologies', 'Dallas', 'USA');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (3, 'Klmpfl,
Van Canneyt', 'Boston', 'USA');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (4, 'Felipe
Bank', 'Manchester', 'England');
Pagina 30 din 69
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (5, 'Joost
Systems, LTD.', 'Central Hong Kong', 'Hong Kong');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (6, 'Van der
Voort Int.', 'Ottawa', 'Canada');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (7, 'Mrs.
Mauvais', 'Pebble Beach', 'USA');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (8, 'Asinine
Vacation Rentals', 'Lihue', 'USA');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (9, 'Fax',
'Turtle Island', 'Fiji');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (10, 'FPC
Corporation', 'Tokyo', 'Japan');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (11, 'Dynamic
Intelligence Corp', 'Zurich', 'Switzerland');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (12, '3D-Pad
Corp.', 'Paris', 'France');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (13, 'Swen
Export, Ltd.', 'Milan', 'Italy');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (14, 'Graeme
Consulting', 'Brussels', 'Belgium');
INSERT INTO CUSTOMER (CUST_NO, CUSTOMER, CITY, COUNTRY) VALUES (15, 'Klenin
Inc.', 'Den Haag', 'Netherlands');
Then some employees:
INSERT INTO EMPLOYEE (emp_no, first_name, last_name, phone_ext, job_code,
job_grade,
job_country, salary)
VALUES (1,'William','Shatner','1702','CEO',1,'USA',48000);
INSERT INTO EMPLOYEE (emp_no, first_name, last_name, phone_ext, job_code,
job_grade,
job_country, salary)
VALUES (2,'Ivan','Rzeszow','9802','Eng',2,'Russia',38000);
INSERT INTO EMPLOYEE (emp_no, first_name, last_name, phone_ext, job_code,
job_grade,
job_country, salary)
VALUES (3,'Erin','Powell','1703','Admin',2,'USA',45368);
Please create the database, table and insert the data in your database environment.
SQLite
If you are using SQLite, you can create the database mentioned above in your project directory by
running the sqlite executable:
sqlite employee.sqlite
Now copy and paste the above CREATE TABLE and INSERT statements. To test if the right data is
present, enter this query:
SELECT * FROM customer;
End your session with
.quit
A file called employee.sqlite should now be created in your project directory.
Make sure the required sqlite dll/so is installed - e.g. on Windows, sqlite3.dll should be present in
your Lazarus + project output directory or
your system directory
Also, if you have a 32-bit sqlite dll, make sure you have a 32-bit lazarus.
Compile your project (even if it is empty) once to create the output directory, and (on Windows) copy
the dll, as well as the employee.sqlite database, into that directory.
PostgreSQL
This section assumes you're using a Linux server and the shell; comparable steps can be done using
Windows and GUI tools such as pgadmin.
Log in to your server and switch to the postgres account:
su - postgres -c psql # immediately start up psql SQL interpreter
Create a user for the database and the tables:
CREATE USER employee WITH PASSWORD 'hellopassword'; -- of course, adjust
password to taste
-- something like 'CREATE ROLE' should appear indicating success.
-- to later change the password you can use something like
-- alter user employee with password '<newpasswordhere>';
Pagina 31 din 69
-- We're going to let the password never expire; if you want more security,
you can leave this step out:
ALTER USER employee VALID UNTIL 'infinity'; --password never expires
-- Now we're tightening it up a bit again:
-- Don't allow user to create a database or create other users:
ALTER USER employee NOCREATEDB NOCREATEUSER; --restrict object creation
-- something like 'ALTER ROLE' should appear indicating success.
-- Create our database:
CREATE DATABASE employee;
-- something like CREATE DATABASE should appear indicating success.
-- Assign all privileges on database employee to user employee:
GRANT ALL PRIVILEGES ON DATABASE employee TO employee; -- allow user full
permissions to database
-- something like GRANT should appear indicating success.
-We
create
the
table
using
a
autonumber/autoincrement:
CREATE TABLE customer
(
cust_no serial NOT NULL,
customer CHARACTER VARYING(25) NOT NULL,
city CHARACTER VARYING(25),
country CHARACTER VARYING(15),
CONSTRAINT integ_60 PRIMARY KEY (cust_no )
);
serial
datatype
aka
Pagina 32 din 69
# only allow network access to the employee database by the employee user host employee employee
192.168.0.1/24 md5
If there isn't such a line, add the line at the end, save and close your editor. See PostgreSQL
documentation for more details.
Reload PostgreSQL settings:
psql
then
SELECT pg_reload_conf(); --reload settings...
-- ...and exit back to shell:
\q
Test logging in to PostgreSQL.
Note: by default PostgreSQL tries an ident/unix domain socket login which doesn't allow passwords. So
we specify a host to force TCP/IP login:
psql -h 127.0.0.1 -d employee -U employee -W #Log in via tcp/ip. Enter your
db password.
Make sure the required PostgreSQL dll/so and any required other libraries is installed - e.g. on
Windows, they should be either:
in your Lazarus + project output directory or
in your system directory
Compile your project (even if it is empty) once to create the output directory, and (on Windows) copy
the dlls into that directory.
Pagina 33 din 69
SQLdb Tutorial1
Contents
1 Introduction
2 Requirements
3 Basic example
o
3.1 Project and components
o
3.2 Link the components
o
3.3 Connecting to the database
3.3.1 PostgreSQL
3.3.2 SQLite
o
3.4 Choosing what data to show
Introduction
This tutorial shows you how to program databases using practical example code based on the SQLdb
Package. It is primarily targeted at newbies. If somebody is looking for basics about databases and SQL, he
should read relevant books/documentation. For this tutorial I use Firebird with the example database
employee.fdb. Other databases can also be used; some adjustments will need to be made which are
mentioned in the text.
While this tutorial may seem long, it mostly is just a lot of text that explains why you should type what you
type. As you can see at the end, the amount of actual code you will need for a working application is not
that great. More experienced developers will hopefully be able to glance through the instructions and
quickly understand what's going on. Also, you can stop at the end of the Basic example chapter and have a
working program.
This tutorial is based on a German tutorial by Swen, but it is extended, especially after the Basic
example. Swen wants the German version to remain as-is. If this is a problem, we can rename this version
and base a new German translation on that.
From Swen: thanks to Joost and Michael. Without their help this tutorial probably never would have come
about.
Requirements
This tutorial is written for use with recent Lazarus versions (Laz 1.0); it should also work on older versions
Lazarus 0.9.30.
Please see SQLdb_Tutorial0, which will walk you through making sure you have the right sample database
set up.
Basic example
Project and components
First you should create a new Lazarus project.
To get access to our database we need one TIBConnection,
one TSQLQuery component from the 'SQLdb' tab in the component palette:
Pagina 34 din 69
Change
the Transaction property
of
DBConnection
to
'SQLTransaction1'.
This
causes
the Database property of SQLTransaction1 to automatically change to 'DBConnection'.
Then, change the Database property of SQLQuery1 to 'DBConnection'. Lazarus automatically adds the
value for the 'Transaction' property.
Next, change the Dataset property of Datasource1 to 'SQLQuery1'.
Finally we change the Datasource property of DBGrid1 to 'Datasource1'.
We now end up with a connection that links its default transaction to a transaction component. The
transaction component links its database property to the connection object. These two components are
enough to connect and execute instructions, but not enough to show queries. For this, an SQLQuery
component is used, which points to the database (and links to its default transaction). With the SQLQuery,
we'll later on retrieve data and post it back to the database.
Finally, the datasource component, which is linked to the query component, is a sort of place holder. It
keeps track of where in the query dataset we are and the GUI components are linked to that so they all
show the same record.
If this is gibberish to you, don't despair: with just some more work, we'll be able to show our first data.
PostgreSQL
The situation with PostgreSQL is very similar to that on Firebird. The database name does not have a path you just specify a name part (e.g. 'employee'). PostgreSQL has no embedded mode, so you need to fill out
the HostName property for the connection test to work.
SQLite
For SQLite, you can leave the 'HostName', 'UserName', and 'Password' properties empty. Set the
'DatabaseName' to the name of your SQLite file, e.g. employee.sqlite. Note: sqlite will create the database
specified if it doesn't exist, so be careful here.
You should now have something like the following screenshot - todo: this screenshot is actually further
along, we only have a button now:
Pagina 35 din 69
Pagina 36 din 69
Summary
Up to now we have learned how to connect to a database using the SQLdb package and how to display the
contents of a table on the screen. If you want to add more functionality such as editing, please continue
with SQLdb Tutorial2
If you followed the previous steps, then your code should look like:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, IBConnection, sqldb, db, FileUtil, Forms, Controls,
Graphics, Dialogs, DBGrids, StdCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Pagina 37 din 69
Button1: TButton;
Datasource1: TDatasource;
DBGrid1: TDBGrid;
DBConnection: TIBConnection;
SQLQuery1: TSQLQuery;
SQLTransaction1: TSQLTransaction;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
SQLQuery1.Close;
SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
DBConnection.Connected:= True;
SQLTransaction1.Active:= True;
SQLQuery1.Open;
end;
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
SQLQuery1.Close;
SQLTransaction1.Active:= False;
DBConnection.Connected:= False;
end;
end.
Pagina 38 din 69
SQLdb Tutorial2
Contents
1 Overview
o
1.1 Dynamic database connection
Overview
If you have followed SQLdb Tutorial1, you have a basic grid showing database information. While this
application works, we can add some refinements on this.
Pagina 39 din 69
UserName.ReadOnly:=true;
Password.ReadOnly:=true;
end;
SQLQuery1.SQL.Text:= 'select * from CUSTOMER';
DBConnection.Connected:= True;
SQLTransaction1.Active:= True;
SQLQuery1.Open;
end;
Now run and test if you can connect.
Filtering data
Often, tables contain a huge amount of data that the user doesn't want to see (and that might take a long
time to query from the database and travel over the network). Let's assume that only the customers from
the USA should be displayed. Therefore the SQL instruction in 'SQLQuery1' would look like:
SELECT * FROM CUSTOMER WHERE COUNTRY = 'USA'
... which would translate to something like this in our code:
SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = 'USA'';
There are two reasons why we will not use this instruction for our example application:
First there is a problem with the usage of the single quote. The compiler would interpret the quote before
USA as a closing quote (the first quote is before the select from...) and so the SQL instruction would
become invalid. Solution: double the inside quotes:
SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = ''USA''';
The second, more important reason is the fact, that we probably don't know what constraints the user will
want to filter on. We don't want to limit the flexibility of the user.
To get this flexibility, first we change our SQL query statement and replace 'USA' by a placeholder (a
parameter in SQL speak): change the Button1click procedure and replace
SQLQuery1.SQL.Text := 'select * from CUSTOMER';
with
SQLQuery1.SQL.Text:= 'select * from CUSTOMER where COUNTRY = :COUNTRY';
In FPC SQLDB, the SQL parameter is marked by the leading colon (other languages/environments use
other conventions like ?). To allow the user to enter a value for the filter, we place a TEdit component on our
form. Delete the value of its 'Text' property.
We can now take the text entered in the TEdit and fill the SQL COUNTRY parameter by using the 'Params'
property of TSQLQuery. Add this below the previous statement:
SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
The parameter can be specified by its position or name. Using the name should improve the readability of
the source code, and obviously helps if you insert more parameters in the middle of existing parameters.
Pagina 40 din 69
We use .AsString to assign a string value to the parameter; there are equivalent property assignments for
integer parameters, boolean parameters etc.
The code up to now forces us to use a filter. If a user specifies an empty value in the edit box, no record will
be displayed. This is probably not what we want. Let's test for an empty value and build our query
accordingly. We should end up with a procedure like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
SQLQuery1.Close;
//Connection settings for Firebird/Interbase database
//only needed when we have not yet connected:
if not DBConnection.Connected then
begin
DBConnection.HostName := ServerName.Text;
DBConnection.DatabaseName := DatabaseName.Text;
DBConnection.Username := UserName.Text;
DBConnection.Password := Password.Text;
// Now we've set up our connection, visually show that
// changes are not possibly any more
ServerName.ReadOnly:=true;
DatabaseName.ReadOnly:=true;
UserName.ReadOnly:=true;
Password.ReadOnly:=true;
end;
// Show all records, or filter if user specified a filter criterium
if Edit1.Text='' then
SQLQuery1.SQL.Text := 'select * from CUSTOMER'
else
begin
SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = :COUNTRY';
SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
end;
DBConnection.Connected:= True;
SQLTransaction1.Active:= True;
SQLQuery1.Open;
end;
Now you can play around a bit with filtering using Edit1. If you enter a country that's not present in the
database, an empty grid is shown.
Error handling
The application should run, but sometimes problems can occur. Databases, even embedded databases can
crash (e.g. when the database server crashes, the disk is full, or just due to a bug), leaving the application
hanging.
Access to a database (any external process, really) should therefore always be integrated in a try ... except
and/or try ... finally construct. This ensures that database errors are handled and the user isn't left out in the
cold. A rudimentary routine for our example application could look like this:
begin
try
SQLQuery1.Close;
...
SQLQuery1.Open;
except
//We could use EDatabaseError which is a general database error, but we're
dealing with Firebird/Interbase, so:
on E: EDatabaseError do
begin
MessageDlg('Error','A database error has occurred. Technical error
message: ' + E.Message,mtError,[mbOK],0);
Edit1.Text:='';
end;
end;
end;
Pagina 41 din 69
specialized E*DatabaseError; you'd have to use EDatabaseError. PostgreSQL on FPC trunk (development
version) has EPQDatabaseError.
when the users changes the filtering criteria and presses the button to query the database
Pagina 42 din 69
and
procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
begin
SaveChanges; //Saves changes and commits transaction
SQLQuery1.Close;
....
Now test and see if edits made in the dbgrid are saved to the database.
Other databases: a lot of other databases use an 'autonumber' or 'autoinc' type of field to provide
auto-generated field content. Try changing your table definition and see if it works.
Sqlite: the example above works for SQLite as is because we're using an integer primary key. See
the documentation for details.
Deleting data
You can let your users use the mouse to do this. You don't even need to code a single line for this
functionality...
On the 'Data Controls' tab, select a TDBNavigator component and drop it on the form, above the grid.
To indicate what the navigator should be linked to, set its DataSource property to your existing datasource
('DataSource1') using the Object Inspector. Now you can use the button on the DBNavigator to delete
records, but also insert them, and move around the records. Also, when editing cells/fields, you can use
the Cancel button to cancel your edits.
Pagina 43 din 69
To allow users to delete the row they're in on the grid using the
Delete
Delete
Summary
If you followed along up to now, you can retrieve data from the database, filter it, and edit and delete data in
the grid. Your code should look something like this:
unit sqldbtutorial1unit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, sqldb, pqconnection, DB, FileUtil, Forms,
Controls, Graphics, Dialogs, DBGrids, StdCtrls, DbCtrls, LCLType;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
DatabaseName: TEdit;
Datasource1: TDatasource;
DBGrid1: TDBGrid;
Dbnavigator1: Tdbnavigator;
Edit1: TEdit;
Label2: Tlabel;
Label3: Tlabel;
Label4: Tlabel;
Label5: Tlabel;
Password: TEdit;
UserName: TEdit;
ServerName: TEdit;
DBConnection: TIBConnection;
Label1: TLabel;
SQLQuery1: TSQLQuery;
SQLTransaction1: TSQLTransaction;
procedure SaveChanges;
Pagina 44 din 69
Pagina 45 din 69
DBConnection.DatabaseName := DatabaseName.Text;
DBConnection.Username := UserName.Text;
DBConnection.Password := Password.Text;
// Now we've set up our connection, visually show that
// changes are not possibly any more
ServerName.ReadOnly:=true;
DatabaseName.ReadOnly:=true;
UserName.ReadOnly:=true;
Password.ReadOnly:=true;
end;
// Show all records, or filter if user specified a filter criterium
if Edit1.Text='' then
SQLQuery1.SQL.Text := 'select * from CUSTOMER'
else
begin
SQLQuery1.SQL.Text := 'select * from CUSTOMER where COUNTRY = :COUNTRY';
SQLQuery1.Params.ParamByName('COUNTRY').AsString := Edit1.Text;
end;
DBConnection.Connected := True;
SQLTransaction1.Active := True; //Starts a new transaction
SQLQuery1.Open;
{
Make sure we don't get problems with inserting blank (=NULL) CUST_NO
values, i.e. error message:
"Field CUST_NO is required, but not supplied"
We need to tell Lazarus that, while CUST_NO is a primary key, it is not
required
when inserting new records.
}
SQLQuery1.FieldByName('CUST_NO').Required:=false;
{
Hide the primary key column which is the first column in our queries.
We can only do this once the DBGrid has created the columns
}
DBGrid1.Columns[0].Visible:=false;
except
// EDatabaseError is a general error;
// you could also use one for your specific db, e.g.
// use EIBDatabaseError for Firebird/Interbase
on E: EDatabaseError do
begin
MessageDlg('Error', 'A database error has occurred. Technical error
message: ' +
E.Message, mtError, [mbOK], 0);
Edit1.Text := '';
end;
end;
end;
procedure Tform1.Formclose(Sender: TObject; var Closeaction: Tcloseaction);
begin
SaveChanges; //Saves changes and commits transaction
SQLQuery1.Close;
SQLTransaction1.Active := False;
DBConnection.Connected := False;
end;
end.
Pagina 46 din 69
However, you can also copy the employee.fdb database to your application directory, run the
application, clear the Server name TEdit and use Firebird embedded to directly connect to the database file,
without any servers set up.
This is great if you want to deploy database applications to end users, but don't want the hassle of installing
servers (checking if a server is already installed, if it's the right version, having users check firewalls, etc).
See Firebird embedded for more details.
September 2011: in recent development (SVN) versions of Free Pascal, FPC tries to first
load fbembed.dll, so you need not rename fbclient.dll anymore for this to work.
Firebird on Linux/OSX/Unix
There must be a way to get this to work on Linux/OSX. See Firebird for hints and links. Updates to the wiki
are welcome.
SQLite
SQLite certainly offers embedded functionality - it does not allow a client/server setup on the other hand. By
following the tutorial above, you can see that switching between databases (e.g. SQLite and Firebird) is not
so much work at all.
Other databases
Your database might offer similar functionality. Updates of this wiki for other database systems are
welcome.
Pagina 47 din 69
SQLdb Tutorial3
Contents
1 Overview
2 Multi database support
3 Login form
o
3.1 Connection test callback function
o
3.2 Additions/modifications
4 Getting database data into normal controls
5 Adapting SQL for various databases
o
5.1 Revisiting our lowest/highest salary
6 Getting data out of normal controls into the database
o
6.1 Parameterized queries
7 Summary
8 Code
Overview
In this tutorial, you will learn how to
make your application suitable for multiple databases, including using a login form
get data out of the controls and back into the database
the user/programmer can dynamically use any sqldb t*connection, so he can choose between dbs
Disadvantages:
More complicated SQL will likely need to still be adapted. Each database has its own dialect; it is of
course possible to call db-specific SQL, this can grow into a maintenance problem.
You can't use T*connection specific properties (like TIBConnection.Dialect to set Firebird dialect).
To use multi-database support, instead of your specific T*Connection such as TIBConnection,
use TSQLConnector (not TSQLConnection), which dynamically (when the program is running) chooses the
specific T*Connection to use based on its ConnectorType property:
uses
...
var
Conn: TSQLConnector;
begin
Conn:=TSQLConnector.Create(nil);
try
// ...actual connector type is determined by this property.
// Make sure the ChosenConfig.DBType string matches
// the connectortype (e.g. see the string in the
// T*ConnectionDef.TypeName for that connector .
Conn.ConnectorType:=ChosenConfig.DBType;
// the rest is as usual:
Conn.HostName:='DBSERVER';
Conn.DatabaseName:='bigdatabase.fdb';
Conn.UserName:='SYSDBA';
Conn.Password:='masterkey';
try
Conn.Open;
Login form
As mentioned in SQLdb Tutorial1, a user should login to the database using a form (or perhaps a
configuration file that is securely stored), not via hardcoded credentials in the application. Besides security
considerations, having to recompile the application whenever the database server information changes is
not a very good idea.
In dbconfiggui.pas, we will set up a login form that pulls in default values from an ini file, if it exists. This
allows you to set up a default connection with some details (database server, database name) filled out for
Pagina 48 din 69
e.g. enterprise deployments. In the form, the user can add/edit his username/password, and test the
connection before going further.
We use a separate dbconfig.pas unit with a TDBConnectionConfig class to store our chosen connection
details. This class has support for reading default settings from an ini file.
This allows use without GUI login forms (e.g. when running unattended/batch operations), and allows reuse
in e.g. web applications.
This TDBConnectionConfig class is surfaced in the login form as the Config property, so that the main
program can show the config form modally, detect an OK click by the user and retrieve the selected
configuration before closing the config form.
Pagina 49 din 69
LoginForm.ConnectionTestCallback:=@ConnectionTest;
...
function TForm1.ConnectionTest(ChosenConfig: TDBConnectionConfig): boolean;
// Callback function that uses the info in dbconfiggui to test a connection
// and return the result of the test to dbconfiggui
var
// Generic database connector...
Conn: TSQLConnector;
begin
result:=false;
Conn:=TSQLConnector.Create(nil);
Screen.Cursor:=crHourglass;
try
// ...actual connector type is determined by this property.
// Make sure the ChosenConfig.DBType string matches
// the connectortype (e.g. see the string in the
// T*ConnectionDef.TypeName for that connector .
Conn.ConnectorType:=ChosenConfig.DBType;
Conn.HostName:=ChosenConfig.DBHost;
Conn.DatabaseName:=ChosenConfig.DBPath;
Conn.UserName:=ChosenConfig.DBUser;
Conn.Password:=ChosenConfig.DBPassword;
try
Conn.Open;
result:=Conn.Connected;
except
// Result is already false
end;
Conn.Close;
finally
Screen.Cursor:=crDefault;
Conn.Free;
end;
end;
Finally, the code in dbconfiggui.pas that actually calls the callback is linked to the Test button. It tests if the
callback function is assigned (to avoid crashes), for completeness also checks if there is a valid
configuration object and then simply calls the callback function:
...
TDBConfigForm = class(TForm)
...
private
FConnectionTestFunction: TConnectionTestFunction;
public
property
ConnectionTestCallback:
TConnectionTestFunction
write
FConnectionTestFunction;
...
procedure TDBConfigForm.TestButtonClick(Sender: TObject);
begin
// Call callback with settings, let it figure out if connection succeeded and
// get test result back
if assigned(FConnectionTestFunction) and assigned(FConnectionConfig) then
if FConnectionTestFunction(FConnectionConfig) then
showmessage('Connection test succeeded.')
else
showmessage('Connection test failed.')
else
showmessage('Error: connection test code has not been implemented.');
end;
Additions/modifications
Possible additions/modifications for the login form:
Add command line arguments handling for dbconfig to preload suitable defaults, so the program
can be used in batch scripts, shortcuts etc
Add a "Select profile" combobox in the login form; use multiple profiles in the ini file that specify
database type and connection details.
Pagina 50 din 69
Hide database type combobox when only one database type supported.
Hide username/password when you're sure an embedded database is selected.
Add support for specifying port number, or instance name with MS SQL Server connector
Add support for trusted authentication for dbs that support it (Firebird, MS SQL): disable
ussername/password controls
If an embedded database is selected but the file does not exist: show a confirmation request and
create the database
Create a command-line/TUI version of the login form (e.g. using the curses library) for commandline applictions
Updates to this article/the code warmly welcomed.
In previous tutorials, data-bound controls were covered: special controls such as the dbgrid that can bind its
contents to a datasource, get updates from that source and send user edits back.
It is also possible to programmatically retrieve database content and fill any kind of control (or variable) with
that content. As an example, we will look at filling a stringgrid with some salary details for the sample
employee database table.
On the main form, let's add a stringgrid and retrieve the data (e.g. via a procedure LoadSalaryGrid called in
the OnCreate event):
// Load from DB
try
if not FConn.Connected then
FConn.Open;
if not FConn.Connected then
begin
ShowMessage('Error connecting to the database. Aborting data
loading.');
exit;
end;
// Lowest salary
// Note: we would like to only retrieve 1 row, but unfortunately the SQL
// used differs for various dbs. As we'll deal with db dependent SQL
later
// in the tutorial, we leave this for now.
// MS SQL: 'select top 1 '...
FQuery.SQL.Text:='select ' +
'
e.first_name, ' +
'
e.last_name, ' +
'
e.salary ' +
'from employee e ' +
'order by e.salary asc ';
// ISO SQL+Firebird SQL: add
//'rows 1 '; here and below... won't work on e.g. PostgreSQL though
FTran.StartTransaction;
FQuery.Open;
SalaryGrid.Cells[1,1]:=FQuery.Fields[0].AsString; // i.e. Cells[Col,Row]
SalaryGrid.Cells[2,1]:=FQuery.Fields[1].AsString;
SalaryGrid.Cells[3,1]:=FQuery.Fields[2].AsString;
FQuery.Close;
// Always commit(retain) an opened transaction, even if only reading
// this will allow updates by others to be seen when reading again
FTran.Commit;
...
end;
except
on D: EDatabaseError do
begin
MessageDlg('Error', 'A database error has occurred. Technical error
message: ' +
Pagina 51 din 69
Pagina 52 din 69
Count:=Count+1;
FQuery.Next;
end;
// 3. Now calculate the average "squared difference" and take the square
root
if Count>0 then //avoid division by 0
SalaryGrid.Cells[3,3]:=FloatToStr(Sqrt(DifferencesSquared/Count))
else
SalaryGrid.Cells[3,3]:='No data';
end;
FQuery.Close;
Note that we use FQuery.EOF to check for empty data (and avoid division by zero errors etc). The loop
shows how to:
properly check if the query dataset has hit the last record, then stop retrieving data.
The resulting screen should show something like this - note the use of a decimal comma - while your
computer may show a decimal point depending on your locale:
Pagina 53 din 69
SELECT
e.first_name,
e.last_name,
e.salary
FROM
employee
e
WHERE
e.salary=(SELECT MIN(salary) FROM employee)
SQL students would greatly benefit from researching Common Table Expressions.
A CTE allows a virtual temporary table to be used in a following expression, allowing you to clearly code
some very complex queries that otherwise may not be possible. Knowing about CTEs will catapult you
ahead of colleagues who have never heard of them! For example the above may be rewritten (example in
Microsoft SQL Server syntax) as :
WITH TheMinimum AS
(
SELECT MIN(salary) AS MinimumPay FROM Employee
)
SELECT
e.first_name,
e.last_name,
e.salary
FROM
Employee
e
WHERE
e.salary=(SELECT MinimumPay FROM TheMinimum)
Several such temporary tables may be chained together, each using the results from the previous tables.
You can treat these virtual tables as though they were real tables in the database, using JOINs to link
recordsets together. And it can be very useful for quick tests using hardcoded data - this can be run without
any database connection :
WITH TestEmployee AS
(
SELECT 'Fred' AS first_name, 'Bloggs' AS last_name, 10500 AS salary
UNION
SELECT 'Joe' AS first_name, 'Public' AS last_name, 10000 AS salary
UNION
SELECT 'Mike' AS first_name, 'Mouse' AS last_name, 11000 AS salary
),
TheMinimum AS
(
SELECT MIN(salary) AS MinimumPay FROM TestEmployee
)
SELECT
e.first_name,
e.last_name,
e.salary
FROM
TestEmployee
e
WHERE
e.salary=(SELECT MinimumPay FROM TheMinimum)
You can end up with quite long strings for the code of such SQL queries, but it is only one query and may be
called from anywhere where you are limited to a simple single expression - it can be useful to answer
complex queries without resorting to functions or stored procedures.
how to let SQLDB update the database with data-bound controls (earlier tutorials)
how to get data out of the database using queries (the section above)
You can also execute SQL to get arbitrary data back into the database via code. This allows you to use
variables or controls that have no db aware equivalent such as sliders or custom controls to enter data into
the database, at the expense of a bit more coding.
As an example, we are going to allow the user to change the lowest and highest salary in the stringgrid.
For ease of editing, set the grid's Options/goEditing to true; then assign the procedure below to the
OnValidate event for the grid, which will be called every time a user has finished updating the grid.
Parameterized queries
The following code also demonstrates how to use parameterized queries to avoid SQL injection, fiddling
with quoting for string values, date formatting, etc.
As you can see in the code, you can name your parameters whatever you wish and prefix them with : in the
SQL.
In
code,
you
can
set/get
their
values
by <somequery>.Params.ParamByName('<thename>').As'<variabletype>';
the
code
demonstrates .AsFloat and .AsString.
Parameterized queries are especially useful (and can be much faster) if you run the same query, only with
different parameters, in a loop (think e.g. bulk loading of data).
Continuing with our example: after having set up the query SQL and parameters, the transaction is started
(and later on committed) as usual, then the query is run by calling ExecSQL (which does not return a result
set; if the SQL statement were e.g. a SELECT or INSERT...RETURNING that does return data, you would
use Open as in the examples above):
procedure TForm1.SalaryGridValidateEntry(sender: TObject; aCol, aRow: Integer;
const OldValue: string; var NewValue: String);
begin
// Only these cells have min and max salary:
if (aCol=3) and ((aRow=1) or (aRow=2)) then
begin
Pagina 54 din 69
Summary
This tutorial explained:
how to use a login form to decouple db access configuration from your program
Code
Since November 2012, the code can be found in $(lazarusdir)examples/database/sqldbtutorial3
If you have an older version (e.g. Lazarus 1.0.2), you can also download the code via the Lazarus SVN
website
Pagina 55 din 69
SQLdb Tutorial4
Contents
1 Introduction
2 Why use datamodules?
3 Getting started
4 Everyone has their own way
5 More uses of data modules
o
5.1 Additional components
o
5.2 Debugging
Introduction
This tutorial is an attempt to demonstrate the use of Lazarus Data Modules to isolate the data access
components of a project from the program logic associated with the access. Such isolation makes program
maintenance and debugging easier.
The tutorial was developed using Windows 7 and SQLite3 as the database, with Lazarus 1.0.8 with FPC
2.6.2; however it should work with earlier versions. Similarly, other DBMS and Operating Systems should
require minimal change, if any.
SQLdb Tutorial0
SQLdb Tutorial1
SQLdb Tutorial2
SQLdb Tutorial3
developing a 'real' application becomes harder. 'Form1' grows to an exponential size handling the different
Events and Database Queries.
Isolating each table access into a single datamodule makes debugging and maintenance so much easier.
An application may have any number of datamodules - a small application with just one or 2 tables can
probably suffice with just one Datamodule - a larger application could probably benefit from having a data
module for each table or view.
The sample shown here uses just 2 tables with simple queries, but it can be expanded to include more
options with each table.
Getting started
In the Lazarus IDE, create a new Application and then click File --> New --> Data Module
Pagina 56 din 69
Debugging
Debugging a program is also a difficult task. By separating data access and business logic, the code to be
viewed is halved. Data access and business logic can be tested separately to at least halve the problem.
The importance of the DataModule will become even more obvious when developing other applications
using the same database and tables. The data module can of course be reused in the new application.
Pagina 57 din 69
postgres
Contents
1 Overview
2 Direct access to PostgreSQL
3 Zeos
4 SQLDB
o
4.1 Example
o
4.2 Event monitoring
o
4.3 Installation and errors
Overview
You can use Free Pascal/Lazarus to access a PostgreSQL database server. If you are looking for
information
on
the postgres package
in
FPC,
please
see postgres#PostgreSQL_package:_the_low_level_units below.
Advantages of PostgreSQL:
No embedded version
Win64: please see warning here on not using certain FPC/Lazarus Win64 versions.
Zeos
Zeos supports PostgreSQL; please see ZeosDBO
SQLDB
FPC/Lazarus supports PostgreSQL out of the box with a PostgreSQL connection component/class. If you
are using FPC only or want to manually add PostgreSQL support, add pqconnection to your usesclause.
Otherwise, Lazarus provides a component:
Note: The libpq C client contains some memory leaks (at least up till version 9.3 of Postgres) when a library
is repeatedly loaded/unloaded. SQLDB loads the library when the first connection is made, and unloads it
when the last connection closes. This means that whenever the last connection is closed, a small memory
leak is created. To prevent this from happening (and speed up the application), you can load the library
once at the start of the process with the InitialisePostgres3 call.
The charset property is used for client encoding.
The TPQConnection component does not directly support a Port property, but one can pass the port into the
component via the Params parameter:
PQConnection.Params.Add('port=' + VariableContainingPort);
Other PostgreSQL specific connection parameters can be specified using the Params property:
PQConnection.Params.Add('application_name=''yourappname''')
Example
See SQLdb_Tutorial1 for a tutorial on creating a GUI database-enabled program that is written for
PostgreSQL/SQLDB, as well as SQLite/SQLDB, Firebird/SQLDB, basically any RDBMS SQLDB supports).
Pagina 58 din 69
Event monitoring
If you have FPC2.6.2+ and a recent version of Lazarus, you can use the TPQTEventMonitor component to
monitor events coming from PostgreSQL.
It is a thin wrapper around FPC PQEventMonitor; please see the FPC pqeventstest.pp example programs
for details.
or (Windows) to the program output directory (e.g. lib/something/ in your project directory, and the
project directory
A Windows 64 driver is fairly hard to find but can be downloaded here: [1]. The driver library can be installed
in c:\windows\system32; 32 bit driver libraries can be installed in the confusingly named
c:\windows\syswow64
On Linux add the path to the libpq.so file to the libraries section in your /etc/fpc.cfg file. For
example : -Fl/usr/local/pgsql/lib
It may be necessary to create a symbolic link from a specific library version to a general library
name:
ln -s /usr/lib/pqsql.so.5 /usr/lib/pqsql.so
. Alternatively, install the postgresql client -dev package using your distribution's package manager
At least in FPC <= 2.6.2: if you .Clear a parameter (i.e. set it to NULL), PostgreSQL may have difficulty
recognizing the parameter type.
In that case, explicitly specify the type, e.g.:
FWriteQuery.Params.ParamByName('LONGITUDE').ParamType:=ptInput; //required for
postgresql
FWriteQuery.Params.ParamByName('LONGITUDE').Clear
Requirements
You need at least version 0.99.5 of Free Pascal (basically any version of FPC except extremely old ones).
The headers are translated from PostgreSQL version 6.3.1.
Installation
The postgres unit comes with the Free Pascal packages, and is distributed together with the compiler. This
contains a directory postgres with the units, a test program and a makefile. cd to the directory and edit the
Makefile to set the variables for your system. You must provide only 1 thing:
1. The directory where the libpq library resides, usually /usr/local/pgsql/lib
Typing
Pagina 59 din 69
make
Should compile the units and the program. If compilation was succesfull, you can install with
make install
(Remember to set the directory where the units should be installed.)
You can then test the program by running
make test
This will:
Run the test program testpg. It is a straightforward pascal translation of the example program in the
PostGreSQL programmers' guide.
Run a script to create a table in a database, and fill it with some data. (the psql program should be
in your PATH for this) . By default, the used database is testdb.
Pagina 60 din 69
TPSQL
TPSQL is a modified-LGPL postgres database package for Lazarus. It defines two components,
TPSQLDatabase and TPSQLDataset, allowing applications to connect to PostgreSQL database servers
over TCP/IP networks.
The download contains the component Pascal files, the Lazarus package file and resource files and the
modified-LGPL license text files.
This component was designed for applications running on Linux and Win32 platforms.
Note: TPSQL is not the same as TPSQLConnection, the PostgreSQL connector that is part of the SQLDB database
components.
Contents
1 Author
2 License
3 Download
4 Change Log
5 Dependencies / System Requirements
6 Installation
7 Usage
Author
Antonio d'Avino
License
Modified LGPL (read COPYING.modifiedLGPL and COPYING.LGPL included in package).
Download
The latest stable release can be found on the Lazarus CCR Files page or on author's web
pages http://infoconsult.homelinux.net.
Change Log
Installation
In the lazarus/components directory, untar (unzip) the files from psql-laz-package<version>.tar.gz file. The psql folder will be created.
Open lazarus
Open the package psql_laz.lpk with Component/Open package file (.lpk)
(Click on Compile only if you don't want to install the component into the IDE)
Click on Install and answer 'Yes' when you are asked about Lazarus rebuilding. A new tab
named 'PSQL' will be created in the components palette.
Note: Important for Win32 users. In case you experience some difficults in compiling the Pascal files in
the package, you may need to add the following path: <some-drive>:<some-
Pagina 61 din 69
path>\lazarus\lcl\units\i386-win32(ie: c:\programs\lazarus\lcl\units
\i386-win32) to the 'Other unit files ...' in 'Paths' tab of 'Compiler Options' of the lazarus package
manager window.
Usage
Drop a TPSQLDatabase component on a form, for any different PostgreSQL database your
application needs to connect to. Mandatory property of this component you have to set are:
DatabaseName : The name of the PostgreSQL database that your application needs to
connect to.
HostName : the IP address or the URL of the PC hosting the PostgreSQL server.
UserName : The name of an user having permission to access to host/database.
Optionally you may need to set Password property for correct connection to server. Then, to
activate the connection, you neet to set the Connected property to 'True'. An exception will be
raised if connection failed (wrong parameters, user with no permission to access the
host/database, network errors or PostgreSQL server not active). Set this property to 'False' for
closing connection.
Note about the ClientEncoding property. The ClientEncoding property allows user to set a
character set for the client application different from the one defined for the database. The
PostgreSQL server make a 'translation' between the two sets. However a "ClientEncoding change
failed" exception may be the result of a ClientEncoding property change. This may be due to a
database character set incompatible with the clientencoding you selected. IE, the char set 'LATIN9'
is not compatible with a 'WIN1250'. You select the default char set for the whole database cluster
when you create it with the command 'initdb', using the -E option: IE. 'initdb -E UNICODE'. Also,
you can define a different character set for any database you create (different from the default one
of the whole db cluster), also with the -E option: IE. 'createdb -E UNICODE Test' or
Pagina 62 din 69
TSQLConnector
TSQLConnector
is a versatile database connector component for use with any supported database.
The component is found on SQLdb tab of the Component Palette.
To configure database access, most important properties are:
DatabaseName
Hostname
Password
Transaction
For
any
connector
to
function,
it
should
be
able
to
load
the
right
.dll style drivers are found if they are located in the same directory as the corresponding .exe
drivers.
TSQLTransaction
TSQLTransaction
is a non-visual component that intermediates between a database connection and
a TDataSet derivative like a TSQLQuery.
SQLTransaction1.Database := SQLConnection1;
SQLQuery1.Transaction := SQLTransaction1;
DataSource1.DataSet := SQLQuery1;
DBGrid1.DataSource := DataSource1;
TSQLQuery
TSQLQuery
is a fundamental database query component for use with any supported database. The
component is found on SQLdb tab of the Component Palette.
Typical use of a TSQLQuery:
SQLTransaction1.Database := SQLConnection1;
SQLQuery1.Transaction := SQLTransaction1;
DataSource1.DataSet := SQLQuery1;
DBGrid1.DataSource := DataSource1;
Select
Insert a new record into table somelist:
SQLQuery1.SQL.Text := 'SELECT * FROM somelist';
SQLTransaction1.Open();
Insert
Insert a new record into table somelist:
SQLQuery1.SQL.Text := 'INSERT
(1231,2)';
SQLQuery1.ExecSQL();
SQLTransaction1.Commit();
INTO
somelist
(ItemNr,ItemCount)
VALUES
TDataSet
TDataSet is the main link to the actual data in a database. A TDataSet descendant acts like a cursor on a
table or query-result.
SQLTransaction1.Database := SQLConnection1;
SQLQuery1.Transaction := SQLTransaction1;
DataSource1.'''DataSet''' := SQLQuery1;
DBGrid1.DataSource := DataSource1;
A dataset (for example a query restult) can be edited:
ds.Edit();
ds.FieldByName('NAME').AsString := 'Edited name';
ds.Post();
Pagina 63 din 69
ds.UpdateMode := upWhereAll;
ds.ApplyUpdates();
TDBNavigator
TDBNavigator
is a navigation control for use with a connected database. It is available from the Data
Controls tab of the Component Palette.
To be used, a TDBNavigator must be linked to a TDataSource component. Depending on TDataSources
state, more or less buttons on the navigator become active.
TDBGrid
TDBGrid
is a visual component that displays tabular data from database contents through means of
a TDataSet derivative like a TSQLQuery. The TDBGrid component is available from the Data Controls
tab of the Component Palette.
// configure connection with database
SQLConnector1.ConnectorType := 'MySQL 5.1';
SQLConnector1.HostName := 'MyServer';
SQLConnector1.DatabaseName := 'MyDBName';
SQLConnector1.UserName := 'MyName';
SQLConnector1.Password := 'MyPass';
// connect SQLConnector, SQLTransaction, DataSource, SQLQuery and DBGrid
SQLTransaction1.Database := SQLConnector1;
SQLQuery1.Transaction := SQLTransaction1;
DataSource1.DataSet := SQLQuery1;
DBGrid1.DataSource := DataSource1;
// setup query to get (at least) two fields from MyTable
SQLQuery1.SQL := 'SELECT * FROM MyTable';
// setup grid with result from query
DBGrid1.Columns[0].Title.Caption := 'Name';
DBGrid1.Columns[0].FieldName := 'fieldDescription';
DBGrid1.Columns[1].Title.Caption := 'Description';
DBGrid1.Columns[1].FieldName := 'fieldName';
// Open result and show in grid
SQLQuery1.Open();
TDBEdit
TDBEdit
edit control for use with a connected database. It is available from the Data Controls tab of
the Component Palette.
To be used, a TDBEdit must be linked to a TDataSource component. The property DataField determines
what will be displayed from the current row of the datasource.
TDBImage
TDBImage
is a data-bound control that shows images stored in BLOB fields in databases. It is
available from the Data Controls tab of the Component Palette.
Storage
By default, Lazarus stores the binary image data preceded by the file extension to define the file type. This
allows storing multiple image formats at will.
Delphi only allows a single file format (jpg?). Recent Lazarus versions allow this storage format as well and
try to autodetect the image type based on magic data in the beginning of the binary data.
Example
See e.g. the sqlite_mushrooms/image_mushrooms example program in your Lazarus directory
examples\database\
Pagina 64 din 69
SQLdb tab
The SQLdb tab of the Component
with TDataSet and TDataSource.
Icon
Palette contains
non-visible
database
Component
Description
TSQLQuery
SQL query
TSQLTransaction
Transaction
Pagina 65 din 69
connectors
for
use
Icon
Component
Description
TSQLScript
Scripting
TSQLConnector
generic connector
TMSSQLConnection
MS SQL
TSybaseConnection
Sybase
TPQConnection
Postgres
TPQTEventMonitor
TOracleConnection
Oracle
TODBCConnection
ODBC
TMySQL40Connection
MySQL 4.0
TMySQL41Connection
MySQL 4.1
TMySQL50Connection
MySQL 5.0
TMySQL51Connection
MySQL 5.1
TMySQL55Connection
MySQL 5.5
TMySQL56Connection
MySQL 5.6
TSQLite3Connection
SQLite
TIBConnection
InterBase/Firebird
TFBAdmin
Firebird admin
TFBEventMonitor
TSQLDBLibraryLoader
Library loader
Pagina 66 din 69
TSQLScript
TSQLScript
lets you run a batch of SQL statements/multiple SQL statements in one run. It is useful if
you want to set up a new database or update an existing database schema.
TSQLScript is available for both FPC and Lazarus and runs on any database that SQLdb supports. It can
be found on the SQLdb tab of the Component Palette.
Example
Suppose you have a set of SQL statements like the following DDL dump of a Firebird database script in the
FlameRobin tool (in Database Properties/DDL):
Notice that unlike TSQLQuery, TSQLScript requires a semicolon (;) after each block of commands.
This example assumes you have existing SQL connection and transaction objects set up.
uses
...sqldb, TIBConnection,...
const
DBSchemaFile='dbreporter.sql';
var
FBScript:TSQLScript;
ScriptText:TStringList;
TranWasStarted: boolean;
begin
TranWasStarted:=FTran.Active; //Ftran is the transaction, defined somewhere
in our class
if not TranWasStarted then FTran.StartTransaction;
FBScript:=TSQLScript.Create(nil);
ScriptText:=TStringList.Create;
try
if not fileexists(DBSchemaFile) then
raise Exception.CreateFmt('dbreporter: could not load database schema
file %s',[DBSchemaFile]);
ScriptText.LoadFromFile(DBSchemaFile);
FBScript.DataBase:=(FConn as TIBConnection);
FBScript.Transaction:=FTran;
FBScript.Script:=ScriptText;
// Now everything is loaded in, run all commands at once:
FBScript.Execute;
//... and then commit to make them stick and show them to the SQL that
comes
// after the commit
FTran.Commit;
finally
FBScript.Free;
ScriptText.Free;
end;
// Make sure we leave the transaction state as we found it, handy for
switchnig
// between explicit start/commit transaction and commitretaining:
if TranWasStarted then FTran.StartTransaction;
See also \examples\database\tsqlscript in your Lazarus directory for an example program demonstrating
TSQLScript.
Warning: (At least) FPC 2.6.4 and earlier: TSQLScript will not correctly parse all Firebird DDL, so you
may need to test your script in advance. TSQLScript has been improved in FPC trunk (2.7.1). An
alternative could be to call Firebird's isql executable using e.g. TProcess
Note: Setting the CommentsInSQL property to false may help improve TSQLScript's reliability if- e.g. if you have
comments inside stored procedure declarations.
TSQLExporter
TSQLExporter
is a database export component for use with a TDataSet. It may be found on the Data
Export tab of the Component Palette To export the contents of a TDataSet (for instance the result of
a TSQLQuery correctly
setup
to
link
with
a TSQLConnector and TSQLTransaction)
also FormatSettings.TableName need be provided.
In the example below a a TSynEdit is filled with exported contents of table mytable.
type
myForm = class(TForm)
btnExport: TButton;
myConnector: TSQLConnector;
myExporter: TSQLExporter;
myQuery: TSQLQuery;
myTransaction: TSQLTransaction;
myScriptText: TSynEdit;
mySyntax: TSynSQLSyn;
procedure btnExportClick(Sender: TObject);
private
procedure ExportTable( const tblnam: String );
end;
implementation