Working with Database
Photo by Jan Antonin Kolar on Unsplash
IRdbms interface
IRdbms
interface is just a thin wrapper for Free Pascal SQLdb package. Currently supported RDBMS systems are
- MySQL via
TMysqlDb
class - PostgreSQL via
TPostgreSqlDb
class - Firebird via
TFirebirdDb
class, - SQLite via
TSQLiteDb
class - Any databases which support ODBC, via
TOdbcDb
class.
Creating Database Connection
MySQL
var db : IRdbms;
...
db := TMySQLDb.create('MySQL 5.7');
db.connect('localhost', 'your_db_name', 'your_db_username', 'yoursecretpassword', 3306);
It will open database connection to MySQL 5.7 server on localhost on port 3306.
Replace 5.7
with your MySQL version, for example 5.5
to connect to MySQL 5.5 database server. Please note that string MySQL 5.7
is compared case-insentively. So MySQL 5.7
or mysql 5.7
are same.
PostgreSQL
var db : IRdbms;
...
db := TPostgreSqlDb.create();
db.connect('localhost', 'your_db_name', 'your_db_username', 'yoursecretpassword', 5432);
It will open database connection to PostgreSQL server on localhost on port 5432.
Firebird
var db : IRdbms;
...
db := TFirebirdDb.create();
db.connect('localhost', 'your_db_name', 'your_db_username', 'yoursecretpassword', 3050);
It will open database connection to Firebird server on localhost on port 3050.
SQLite
SQLite is lightweight non client-server database engine. So you can leave host, port, username and password empty. Database name must be set to path where database file is stored.
var db : IRdbms;
...
db := TSQLiteDb.create();
db.connect('', 'your_data.db', '', '', 0);
It will open database which is stored in your_data.db
file.
ODBC
If you use database which not yet supported directly by FreePascal sqldb library, you may use ODBC connection.
TOdbcDb
class is thin wrapper for TODBCConnection
class which implements IRdbms
interface.
For example, if you have /etc/odbcinst.ini
with content as follows
[my-mariadb-odbc-driver]
Description = MariaDB Connector/ODBC v.3.0
Driver = /usr/lib/libmaodbc.so
and content of /etc/odbc.ini
[my-app-db]
Description=My App Database
Driver=my-mariadb-odbc-driver
SERVER=localhost
PORT=3306
USER=<your username>
PASSWORD=<your password>
DATABASE=<database name>
To connect to database using my-app-db
DSN, set database parameter with name of DSN
like so
var db : IRdbms;
...
db := TOdbcDb.create();
db.connect('', 'my-app-db', '', '', 0);
If you want to change value, for example to use different port than what is defined in /etc/odbc.ini
, just fill port parameter with desired value
var db : IRdbms;
...
db := TOdbcDb.create();
db.connect('', 'my-app-db', '', '', 3307);
Registering IRdbms instance in dependency container
You can register IRdbms
instance in dependency container so that you can access its instance easily.
var container : IDependencyContainer;
...
container.add(
'db',
TMysqlDbFactory.create(
'mysql 5.7',
'localhost',
'your_db_name',
'your_db_username',
'yoursecretpassword',
3306
)
);
Replace TMysqlDbFactory
with TPostgreSqlDbFactory
, TFirebirdSqlDbFactory
,
TSQLiteDbFactory
, TOdbcDbFactory
for Postgresql, Firebird, SQLite database and ODBC respectively.
For TOdbcDbFactory
, using ODBC with DSN, you can register simply by using its DSN name for example
container.add(
'db',
TOdbcDbFactory.create()
.database('my-app-db')
);
Retrieve IRdmbs instance from dependency container
To get instance of IRdbms
instance, just get it from dependency container as shown in following code.
var db : IRdbms;
...
db := container.get('db') as IRdbms;
or with array-like syntax
var db : IRdbms;
...
db := container['db'] as IRdbms;
Executing SQL Query
var
db : IRdbms;
resultSet : IModelResultSet;
...
resultSet := db.prepare('SELECT * FROM users').execute();
Unlike SQLdb which separates how you execute SQL command that returns result set and ones that do not return result set such
open()
for SELECT
and execSQL()
for INSERT
or UPDATE
, IRdbms interface
abstracts this, so you always call execute()
method for SELECT
, INSERT
or UPDATE
.
fRdbms.prepare(
'INSERT INTO atable ' +
'(id, operation, resetTimestamp) VALUES ' +
'(:idCol, :oprCol, :resetTmp)'
).paramStr('idCol', 'abc')
.paramInt('oprCol', 10)
.paramInt('resetTmp', 2000)
.execute();
Passing parameters to SQL query
To avoid SQL injection, it is recommended to use prepared statement with parameter
resultSet := db.prepare('SELECT * FROM users WHERE user_email = :userEmail')
.paramStr('userEmail', 'john@fanoframework.github.io')
.execute();
To pass integer, float, datetime data, use paramInt()
, paramFloat()
, paramDateTime()
respectively.
Get total rows in result set
var totData : integer;
...
totData := resultSet.resultCount();
Read data from result set
var userId : integer;
userEmail : string;
...
userId := resultSet.fields().fieldByName('user_id').asInteger;
userEmail := resultSet.fields().fieldByName('user_email').asString;
Advance cursor to next position
fields()
will read data in current cursor position. To read next data in result set, it is required that you call next()
to advance cursor position.
resultSet.next();
Test if at end of result set
fieldByName()
method will throws exception if you try to read data when cursor is at end of file. To avoid it you need to check end of file condition with eof()
. So to read all data in result set, you need following loop.
while not resultSet.eof() do
begin
userId := resultSet.fields().fieldByName('user_id').asInteger;
//do something with userId
resultSet.next();
end;
Executing Transaction
db.beginTransaction();
try
//do multiple database operations
finally
db.commit();
end;
What can go wrong
You may find thing does not work due to missing library for example you do not have MySQL client library or ODBC client library installed.