Tim Anderson's ITWriting

Simple Sqlite 3.0 for Delphi

 

Want to reproduce this article?

If you would like to reproduce this article on your own publication or web site, please contact Tim Anderson .

A simple Delphi wrapper
for SQLIte 3

SQLite is a superb cross-platform open source database library. I wanted a simple wrapper to use with Delphi, and this is the outcome. Free to download, use and improve. By Tim Anderson.

picture of Delphi sqlite application

 

Most applications use a database, and there are many excellent database engines to choose from, both free and commercial. SQLite is a small C library that has several advantages. It is open source, free, cross-platform, fast, reliable, and well supported. I had a Delphi 7 application using Sqlite 2.0. There are various wrappers available for Delphi, and around 18 months ago I tried all the ones I could get my hands on. Although several of them were of good quality, I found myself running into bugs caused by the complexity of implementing Borland's TDataset and related database components. Since I didn't require databinding, I chose a wrapper that implemented very simple access to Sqlite - it was written by Ben Hochstrasser and amended by Pablo Pissanetzky. An advantage for me was that I could easily see what the wrapper did and make my own amendments. I actually made rather a lot of changes, adding basic transaction support and implementing a crude dataset based on a TList. Despite its simplicity, I found it effective and reliable.

The main author of Sqlite, Dr D Richard Hipp, has since released Sqlite 3.0. This adds some useful features, including BLOB support and the ability to create tables that support case-insensitive comparisons. I decided to update my wrapper for Sqlite 3.0. This meant changing the code from using SQLite's callback interface to use Sqlite3_Prepare and Sqlite3_Step instead (see the description of the Sqlite C interface). Most of the code written by Ben and Pablo has now gone, which I say not to demean their efforts, but to emphasise their innocence. I've also had a go at adding BLOB support. The new wrapper is not yet extensively tested, but so far it is working well.

I'm now offering the wrapper for download. You're welcome to use it, although naturally it comes with no warranty. I'd be grateful for any comments, bug reports or improvements, though I'd like to keep the wrapper simple. Note this is for Delphi 7, not Delphi .NET (though I've also used Sqlite with .NET - see here).

More about the wrapper

This wrapper has two units and three main classes. Sqlite3.pas has the external declarations for sqlite3.dll. I've included a binary build of sqlite3.dll, made with Visual C++ 2003. It should work with other builds, so you can upgrade to later versions of the DLL without making changes to the wrapper (unless the Sqlite API itself changes).

Sqlitetable3.pas implements three classes, ESqliteException, TSqliteDatabase and TSqliteTable. Currently TSqliteDatabase has the following methods:

GetTable: execute an SQL query and return a resultset as a TSqliteTable.

ExecSQL: execute an SQL query that does not return data.
UpdateBlob: Update a blob field with data from a TStream object.
BeginTransaction, Commit, Rollback: sends SQL statements for transaction support.

TableExists: Returns true if the specified table exists in the database.

There is also an IsTransactionOpen property.

TSqliteTable represents a resultset. It maintains no link to the source database, so it is disconnected: you can keep a TSqliteTable in memory after freeing the source TSqliteDatabase. When created it is set to the first row. Navigate the resultset using Next and Previous, until EOF is True. At BOF the resultset is on the first row, but at EOF there is no valid row. RowCount retrieves the number of rows, which may be zero. To retrieve data, first use FieldIndex to get the index number of a particular field. Then use the appropriate Field... method to get the value: FieldAsString, FieldAsInteger, FieldAsDouble, FieldAsBlob or FieldAsBlobText. For other datatypes such as Currency or TDateTime, you currently need to convert to String or one of the other types - I'm planning to add some more types soon. I've not tested the Blob functionality extensively. Since the entire resultset must fit in memory, be cautious about retrieving large resultsets or resultsets with large amounts of Blob data.

Currently the only way to determine if a field contains a null value is with the FieldIsNull method. The other methods return zero, false or empty strings for null values.

I will be publising a basic tutorial on using the wrapper in the UK magazine PC Plus. I will also keep this page up-to-date with the latest version.

Update 19 February 2005: I've updated the wrapper for Sqlite 3.1.2. This changes the way column names are returned, so I've added a call to set the Pragma full_column_names on. I've also amended the field type detection to use the actual type when the declared type is not available, and added the utility function TableExists. The test application now shows a possible way to load, save and display images in a Sqlite database.

Update 15 August 2005: Thanks to Lukas Gebauer who has made the wrapper compatible with Delpi 4+ and added some new methods. See the readme for details. I've also followed Lukas's suggestion in removing FieldAsBool - he points out that it is not a natural sqlite3 type. If this causes problems for anyone, let me know. I've left the previous version available for download just in case. Finally, I've included a Visual C++ 2003 release build of Sqlite3 version 3.2.2.

Update 27 August 2007: Thanks to Marek Janáč who emailed me to say that the wrapper did not work with the latest Sqlite3 dll (3.4.2). The problem was that Sqlite now requires pathnames to be in UTF8 format when the path contains accented characters. I've made a small change to fix this. I've also included a new MSVC 6.0 build of the DLL. Finally, I've created a repository for the wrapper here:

http://www.itwriting.com/repos/sqlitewrapper/trunk

You will always find the latest version here.

Links

Download the Simple Delphi Wrapper

Sqlite home page

Other Sqlite wrappers including some for Delphi

My notes on using Sqlite 2 with Delphi, .NET and Java

My interview with the main author of Sqlite, Dr D Richard Hipp

Copyright Tim Anderson 21st December 2004. All rights reserved.

Copyright ©2004 Tim Anderson


 
 
46 comments
Comment posted on 2008-05-11 18:20:07 by: Pasquale Esposito.
There must be something wrong with my code. How can a database which is less than 70 negabytes make an application crash on a machine provided with 500 MB of RAM?

If you want to help me, I would like to remind you that you can download the original Delphi source code I have used clicking on this link:

http://www.espositosoftware.it/sqlite_db.zip

Thanks.

Comment posted on 2008-05-11 11:52:49 by: Pasquale Esposito.
I am desperate! I have stored thousands of records and now I can't use my application anymore. Please help me.

You can download the original Delphi source code I have used clicking on this link:

http://www.espositosoftware.it/sqlite_db.zip

Please consider that my app works just fine with a small quantity of records, but when the DB size increases, you get an error message informing you that the memory is insufficient.

The error is triggered by the following code:

sl3tb := sl3db.GetTable('SELECT * FROM password');

Any help will be greatly appreciated.

Comment posted on 2008-05-11 07:58:07 by: Pasquale Esposito.
Hello, I am in hot water using SQLITE.

My film database is now more than 70 megabytes big and when I run the statement

sl3tb := sl3db.GetTable('SELECT * FROM film');

it gives me an error message telling me there's a memory overflow.

So, I think I should sort out the problem by limiting the range of records satisfying the query. For example, I could use

sl3tb := sl3db.GetTable('SELECT * FROM film WHERE ID < 50');

Nevertheless, when I have to move to the last record, I am forced to select the rows from start to finish and, on doing so, my app crashes.

What am I missing?

Thanks in advance for your help.

Comment posted on 2008-04-27 02:52:50 by: Serge.
Could you possible add SQLite3_Bind... for all types, not just for BLOBs (SQLite3_BindBlob)? I'd appreciate this very much...
Comment posted on 2008-04-26 01:27:00 by: Christian.
It doesn't have a "Move To" position in the opened table, so here's the function you may add to SQLiteTable3.pas:

function TSQLiteTable.MoveTo(position: Integer): boolean;
begin
Result := False;
if (self.fRowCount > 0) and (self.fRowCount > position) then
begin
fRow := position;
Result := True;
end;
end;

And in the TSQLiteTable class you may add the following public function:

function MoveTo(position:Integer): boolean;


Comment posted on 2008-03-11 03:53:26 by: amo.
If create a table by sql:
"Create table testtable(id integer primary key, name string, pic blob"
(the fields name is lowercase), then the code:
tbl = sldb.GetTable('SELECT pic FROM testtable where ID = ' inttostr(iID));
can't handle correctly, it would raise ESQLiteException.

In the SQLiteTable3.pas:
for i := 0 to Pred(fColCount) do
fCols.Add(AnsiUpperCase(Sqlite3_ColumnName(stmt, i)));
for i := 0 to Pred(fColCount) do
begin
new(thisColType);
DeclaredColType := Sqlite3_ColumnDeclType(stmt, i);
if DeclaredColType = nil then
thisColType^ := Sqlite3_ColumnType(stmt, i) //use the actual column type instead
//seems to be needed for last_insert_rowid
else
if (DeclaredColType = 'INTEGER') or (DeclaredColType = 'BOOLEAN') then
thisColType^ := dtInt
else
if (DeclaredColType = 'NUMERIC') or
(DeclaredColType = 'FLOAT') or
(DeclaredColType = 'DOUBLE') or
(DeclaredColType = 'REAL') then
thisColType^ := dtNumeric
else
if DeclaredColType = 'BLOB' then
thisColType^ := dtBlob
else
thisColType^ := dtStr;

The DeclaredColType is lowercase string, but in follow codes, it be compared to uppercase string, so all fields that use lowercase name would be dtStr.

Comment posted on 2007-12-24 16:57:37 by: Tim Anderson.
Lloyd

I don't know of any reason that would stop it working in Delphi 5. You can remove the reference to variants. Try it!

Tim

Comment posted on 2007-12-24 01:03:04 by: Lloyd Edwards.
Does this wrapper work with Delphi 5.0? Better, how do I make it work with D5?
It's a great piece of work, and I'd like to use it, but it calls for a "variants" unit that is not included in D5.
Thanks -- Lloyd

Comment posted on 2007-10-16 11:26:00 by: Tim Anderson.
Re. commercial license: you can use the code as you like.

Tim

Comment posted on 2007-07-09 17:13:24 by: Stephen Roesner.
Great wrapper - used in Delphi6 to generate a UTF-16 compliant SQLite file, asynch to WinCE4.2 device which uses a native program to alter the database.

Microshaft's attempt at a suitable database (SqlCE) had created a non-UTF-16 compliant abortion only suitable for PIM's and not for data gathering in an industrial environment (Memo's, Blobs, Oracle, iSeries, SAP R/3 etc...). Synchronizing with Access/SQLServer is a no-no as well - the data has to be validated and cleaned before it goes there.

Comment posted on 2007-07-08 20:46:11 by: Tim Anderson.
> What is the licence for your unit? In particular on
> commercial use.

The code is free for any purpose.

Tim

Comment posted on 2007-07-04 11:17:57 by: Caine.
Hello, great job. Unfortunally i don't found pas unit for PostgreSql and an internet search lead me to this page.

SqlLite is a candidate to replace MySql on a future commercial project.

What is the licence for your unit? In particular on commercial use.

Thanks for your response.

Best regards.

Comment posted on 2007-02-27 09:25:12 by: george.
How do I retrieve the fieldnames from a database?
I have a project where a stringgrid is populated automatically with the data from a table and I need to get the fieldDef names. Is this possible?
Also, I need to get the number of columns in the table.
Thank you.

Comment posted on 2006-12-04 05:18:36 by: geek.
sorry, i forgot begin end ,here is correct code
Try
tblmuhet := dbmuhet.GetTable('SELECT * FROM Luget where Word = ''' myword '''');
Try
If Not tblmuhet.Count = 0 Then
mmmuhet.Text := 'No rows in the muhet database. '
Else
begin
Notes := tblmuhet.FieldAsBlobText(tblmuhet.FieldIndex['Name']);
mmmuhet.Text := notes;
end;
except
mmmuhet.Text := 'No rows in the muhet database. ';
End;
except
End;

Comment posted on 2006-12-04 05:16:26 by: avar.
hello, thanks for this great wrapper.... i have piece of code give me error message

Try
tblmuhet := dbmuhet.GetTable('SELECT * FROM Luget where Word = ''' myword '''');
Try
If Not tblmuhet.Count = 0 Then
mmmuhet.Text := 'No rows in the muhet database. '
Else
Notes := tblmuhet.FieldAsBlobText(tblmuhet.FieldIndex['Name']);
mmmuhet.Text := notes;
except
mmmuhet.Text := 'No rows in the muhet database. ';
End;
except
End;

the reason i put secobd "mmmuhet.Text := 'No rows in the muhet database. ';" inside exception section that it sometimes give me error saying there is no field, why it gives me this error if it already passed "If Not tblmuhet.Count = 0 Then" being true??

thanks

Comment posted on 2006-11-27 14:28:39 by: Michael Hooker.
>>Michael, you can email it to me if you like; I'll take a look.<<

Sorry, I was diverted to other projects. Database file is now 24 megabytes and growing daily, so I don't think that's an option! I'll tinker around a bit more, and see if I can create a backup of it and purge some records from the backup with SQLiteSpy.
Thanks
Michael

Comment posted on 2006-11-20 07:12:40 by: Tim Anderson.
> As I said it's an SQLite3 table.

Michael, you can email it to me if you like; I'll take a look. tim (at) itwriting.com

Tim

Comment posted on 2006-11-19 17:34:56 by: Michael.
As I said it's an SQLite3 table. The program that creates it says so. And it comes with the v3 dll.

Michael

Comment posted on 2006-11-17 07:53:04 by: Tim Anderson.
> Can you tell me what this means please? The file has
> an .sqb extension, and works fine with SQLiteSpy. It's a
> commercial product,definitely SQLite3.

Can you open it with the Sqlite command line? Some Sqlite utilities are designed to work with both 2.x and 3.x databases so I'd suggest making absolutely sure that it is not a 2.x file.

Tim

Comment posted on 2006-11-16 22:21:42 by: Michael.
---------------------------
Debugger Exception Notification
---------------------------
Project SQLiteProj.exe raised exception class ESQLiteException with message 'Error executing SQL "PRAGMA SYNCHRONOUS=NORMAL;" : unsupported file format'. Process stopped. Use Step or Run to continue.
----------------------------
Can you tell me what this means please? The file has an .sqb extension, and works fine with SQLiteSpy. It's a commercial product,definitely SQLite3.

I'm desperate for a Delphi wrapper for SQLite3 that (a) works in the low-end versions (excludes ASGQLite), (b) I can get to install (excludes LibSQL) and (c) has examples based on native Delphi controls not an incomprehensible external package (excludes DiSQLite - looks good but I can't work out what to do!). Thought I'd found it here but I've fallen at the first hurdle... :(
Thanks
Michael

Comment posted on 2006-11-16 07:45:49 by: Tim Anderson.
> I've written my own Delphi-interface for SQLite and am
> fighting with a totally undefined occurring "access
> violation in NtDll.dll" (e.g. it never occurs when
> running within the Delphi-debugger).

Can't help on this unfortunately; it's not something I've seen.

Tim

Comment posted on 2006-11-15 21:30:34 by: Willi.
As I'm developing commercial applications and therefore do not want to utilize people providing their work for free, I've written my own Delphi-interface for SQLite and am fighting with a totally undefined occurring "access violation in NtDll.dll" (e.g. it never occurs when running within the Delphi-debugger).
As your wrapper is by far the best, do you have any idea or maybe also already expierence this ?
It would be really a great help if you had any tip.

Thanks in advance - Willi

Comment posted on 2006-10-19 22:34:06 by: Isaac.
Thank you for providing this binding, I use it heavily in several projects.
Comment posted on 2006-10-14 20:03:09 by: Ljbog72.
Hello Guys,
Tell me if this RDBMS load the database directly in memory.
If yes how.

Comment posted on 2006-09-16 02:45:20 by: Thanks.
Just want to think you for developing this. We really appreciate your hardwork. This wrapper really rocks!
Comment posted on 2006-08-02 20:27:19 by: Marcus.
I need to use SQLite with Borland C Builder v3.0. Can your wrapper be used for this?
Comment posted on 2006-04-23 13:49:28 by: Rix0r.
Err, that should be sqlite3_prepare, obviously ;)
Comment posted on 2006-03-22 21:58:09 by: Tim Anderson.
Thanks for pointing out the problem with sqlite3_open. I'll take a look.

Tim

Comment posted on 2006-03-22 21:54:23 by: Unknown.

There's a misinterpretation of the return value of sqlite3_open in the wrapper :<br /> <br /> If the returned statement is NULL, that does not necessarily mean that there was an error in the SQL. It could also mean that there was no work to do by the SQL statement, and that situation is not an error.<br /> <br /> This gives problems when using -- for example -- CREATE TABLE IF NOT EXISTS.

Comment posted on 2006-02-08 17:10:11 by: Tim Anderson.
Carl,

You could try inserting a call to sltb.MoveFirst in case the row pointer is not where you expect?

Does the table look OK if you access it from the Sqlite command line or another client?

Tim

Comment posted on 2006-02-08 06:55:43 by: Carl Nielsen.
hi, I'm having some problems with sqlite. Everything was working fine untill a few days ago, then it stopped working.

The problems is that code like this

if sltb.Count > 0 then
begin
while not sltb.EOF do
begin
testLine := sltb.FieldAsstring(sltb.FieldIndex['name']) ' ';
memo1.Lines.Add(testline);
testline :='';
end;
sltb.Next;
end;

always returns EOF true for the first record (and the loop quits) I've tried everything I can think of, replecing the dll, rehacking code, writing test programs etc. The sqlite sample program works but nothing I do will work. I get a raised error saying the the Table is at the EOF..

Going nuts...

Thanks

Comment posted on 2006-01-25 16:04:19 by: Tim Anderson.
Keith,

I can't reproduce your result - I tried entering é into a field and it worked fine.

Is your build of Sqlite compiled with UTF-8 support?

Tim

Comment posted on 2006-01-25 10:00:32 by: Keith Giddings.
Hi Tim,

I've discovered that if I have national characters (eg é) in a string, then when I post to a char field, the field is left completely empty. Any ideas on how to fix?

Regards
Keith

Comment posted on 2006-01-22 09:45:59 by: Tim Anderson.
Hans-Peter,

In general, yes, a Sqlite database will accumulate wasted space over time. You can reclaim it by calling:

ExecSQL('VACUUM;');

on a TSqliteDatabase.

As to why the 2nd LoadImage doubles the size of the database, but subsequent ones do not - I don't know the reason, but presumably it is because of how Sqlite is designed internally.

Tim

Comment posted on 2006-01-22 09:15:37 by: HPW.
After downloading the latest version, I test again this great piece of delphi code.

Some comments to the testapp:

When I first press the first testbutton I get a 3KB test.db.
Then I press 'Load image' and get a 72 KB file.
Then I can use the 'Display image' button.
When I press 'Load image' again file size raise to 141 KB.
There it stay even when I use first button or 'load' button again.

It that by design that SQLite need a special command to released unused file-space?

Thanks again for this nice work,

Hans-Peter
Germany

Comment posted on 2005-11-29 21:22:05 by: Unknown.
SQLiteTeble3, string 421:
DeclaredColType := Sqlite3_ColumnDeclType(stmt, i);
The Engine returns strings in lower case, wrapper expects in upper case.

Comment posted on 2005-11-11 03:08:29 by: kartika.
i want to use sqlite4delphi for my tasks but i don;t know how to install it. could anyone of you help me and tell about it clearly. I'm still confuse...
i've download SQLite4Delphi-0.4.4_full from the internet but i'm still get it how to use it..
please help me to find out

Comment posted on 2005-11-09 19:40:20 by: NoN.
I'd downloaded this library. THis is very good wrapper? but I found some bug.
TSQLiteDatabase.TableExists cannot work with TEMP TABLES

But replace:
sql := 'select [sql] from sqlite_master where [type] = ''table'' and lower(name) = '''
lowercase(TableName) ''' ';

with:
sql := 'select [sql] from sqlite_master where [type] = ''table'' and lower(name) = '''
lowercase(TableName) ''' UNION ALL select [sql] from sqlite_temp_master where [type] = ''table'' and lower(name) = '''
lowercase(TableName) ''' ';

and all will work great.

Fix it in next relises.

Thanks from Russia!

Comment posted on 2005-10-26 04:26:11 by: Neith.
Does the app add an image file into an SQLite database? If so, it's is exactly what I need, except not in Delphi. If someone could compile it and send me the exe, I'd be more than grateful.

~Neith

Comment posted on 2005-10-16 08:12:53 by: Nikolai.
Thanks Tim
I am going to use SQLite for experimental data storage
I made an interface-based variant of your wrapper
I also slightly enhanced your example: Added navigation, Insert/Delete record, Post changes (Notes only).
Code/Demo at http://www.shokhirev.com/nikolai/programs/code/SQLiteInterface.html
Regards,
Nikolai

Comment posted on 2005-09-13 14:57:46 by: Elcio.
Hello!

Because to use

TSqliteTable.FieldAsInteger(I: cardinal): int64;

then

TSqliteTable.FieldAsInteger(FieldName: string): integer;

Thank's

Comment posted on 2005-06-13 13:08:10 by: Dumitru Florin.
Great Delphi bind. Juat what i was looking for my server. Wow, thanks !
Comment posted on 2005-06-07 08:16:46 by: Manfred.
I would like an opportunity to install, register and use functions written in Delphi called by the SQLite3 engine to enhance i.e. for statistic standard deviation (stdev) in addition to the existing average (avg) aggregation.
Could be used also to fire an event on triggers which have been fired by other threads, tasks or processes.
Thank you for this great work, it has really helped me a lot!
With minor changes it works very well even with Delphi 2.
Please continue and keep it up to date...
Manfred

Comment posted on 2005-04-28 06:21:18 by: Dien Phan.
A great work done and many thanks for this.
I hope Tim will keep us up to date with his development.

Comment posted on 2005-04-04 16:04:41 by: Lars.
Absolutely fantastic binding. Exactly what I've been looking for - SQLite seems to be a really lean and mean database. Combine that with a really lean and mean IDE (Delphi) and you experience a fat productivity boost :-)
Comment posted on 2005-01-01 08:42:34 by: Matt.
Note: Please begin subject line with "[WRAPPER]" if responding.

Dude, your timing could NOT have been better! I'm putting together an app and have pretty well settled on SQLite, after a few experiments.

Been struggling with other third-party components for weeks now, and SQLite has been the bright spot, though today I FINALLY tried a tree component which is neither a pig nor, apparently, bug infested.

I'd be glad to let you know of any problems I have with your code, as I seem to have an uncanny knack for finding them within minutes. Lucky me... heh.

THANKS AGAIN!

Matt

You are welcome to comment on this page. For your guidance, HTML is not supported and URLs will be displayed as plain text. Add your comment here:

Your name: 

Your email (optional - will not be displayed or made public):

Type this code:    

Your comment