Sign in Join
Wiki copytemporary

SQLite: what changed

Revision 5 to 6. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
1+---
2+[[Portal:Databases|**Databases portal**]]
3+References: · [[Databases|General info]] · [[Database libraries|Libraries]] · [[Database field type|Field types]] · [[Data Controls tab|Controls]] · [[Lazarus DB Faq|FAQ]] · [[SqlDBHowto|SQL how-to]] · [[Working With TSQLQuery]] · [[How to write in-memory database applications in Lazarus/FPC|In-memory database applications]] · Tutorials/practical articles: · [[Lazarus Database Overview|Overview]] · [[SQLdb Tutorial0|0 - Database set-up]] · [[SQLdb Tutorial1|1 - Getting started]] · [[SQLdb Tutorial2|2 - Editing]] · [[SQLdb Tutorial3|3 - Queries]] · [[SQLdb Tutorial4|4 - Data modules]] · [[SQLdb Programming Reference]] · Databases · [[Advantage Database Server|Advantage]] \- [[MySQLDatabases|MySQL]] \- [[mssqlconn|MSSQL]] \- [[postgres|Postgres]] \- [[Firebird|Interbase]] \- [[Firebird]] \- [[Oracle]] \- [[ODBCConn|ODBC]] \- [[TParadox|Paradox]] \- SQLite \- [[Lazarus Tdbf Tutorial|dBASE]] \- [[MS Access]] \- [[Zeos tutorial|Zeos]]
4+
5+
16## SQLite and FPC/Lazarus support
27
3-SQLite is an embedded (non-server) single-user database that can be used in FPC and Lazarus applications. Various drivers can be used to access SQLite from FPC/Lazarus programs. All drivers do need the SQLite library/dll in the executable directory (which can be your project directory or e.g. (projectdir)/lib/architecture/ depending on your Lazarus project settings) (and distributed with your executable) in order to work.
8+SQLite is an embedded (non-server) single-user database that can be used in FPC and Lazarus applications. Various drivers can be used to access SQLite from FPC/Lazarus programs. All drivers do need the SQLite library/dll in the executable directory (which can be your project directory or e.g. (projectdir)/lib/architecture/ depending on your Lazarus project settings) (and distributed with your executable) in order to work.
49
5-This might also need to be included in you Lazarus IDE directory. See [1](https://forum.lazarus.freepascal.org/index.php?topic=46118.msg327940#msg327940) And read on especially for TSQLite3Dataset and TSQLiteDataset See below
10+This might also need to be included in you Lazarus IDE directory. See [[1]](<https://forum.lazarus.freepascal.org/index.php?topic=46118.msg327940#msg327940>) And read on especially for TSQLite3Dataset and TSQLiteDataset See below
611
7-Most Linux distributions have sqlite3 (eg libsqlite3.so.0) installed by default but Debian based distros, at least, also require the matching Dev package to get libsqlite3.so. Both should be install via system package manager and marked as a dependency rather than being distributed with your application.
12+Most Linux distributions have sqlite3 (eg libsqlite3.so.0) installed by default but Debian based distros, at least, also require the matching Dev package to get libsqlite3.so. Both should be install via system package manager and marked as a dependency rather than being distributed with your application.
813
9-Win64: please see warning [[Windows Programming Tips|here]] on not using certain FPC/Lazarus Win64 versions.
14+Win64: please see warning [[Windows Programming Tips|here]] on not using certain FPC/Lazarus Win64 versions.
1015
1116### Direct access to SQLite
1217
13-You can use an easy way to connect SQLite with Lazarus. Components you are named LiteDAC. SQLite Data Access Components (LiteDAC) is a library of components that provides native connectivity to SQLite from Lazarus (and Free Pascal) on Windows, macOS, iOS, Android, Linux, and FreeBSD for both 32-bit and 64-bit platforms. LiteDAC is designed for programmers to develop truly cross-platform desktop and mobile SQLite database applications with no need to deploy any additional libraries. You can download a trial version of this commercial product at [Lazarus components](https://www.devart.com/litedac/download.html).
18+You can use an easy way to connect SQLite with Lazarus. Components you are named LiteDAC. SQLite Data Access Components (LiteDAC) is a library of components that provides native connectivity to SQLite from Lazarus (and Free Pascal) on Windows, macOS, iOS, Android, Linux, and FreeBSD for both 32-bit and 64-bit platforms. LiteDAC is designed for programmers to develop truly cross-platform desktop and mobile SQLite database applications with no need to deploy any additional libraries. You can download a trial version of this commercial product at [Lazarus components](<https://www.devart.com/litedac/download.html>).
1419
1520### Built-in SQLDB
1621
17-FPC/Lazarus offers the built-in SQLDB components that include support for SQLite databases ([[TSQLite3Connection]]) from the [[SQLdb tab]] of the [[Component Palette]], which allow you to e.g. create GUIs with database components such as [[TDBGrid|TDBGrids]]. The advantage of using SQLDB is that it is fairly easy to change to a different database such as Firebird or PostgreSQL without changing your program too much. See below for details.
22+FPC/Lazarus offers the built-in SQLDB components that include support for SQLite databases ([[TSQLite3Connection]]) from the [[SQLdb tab]] of the [[Component Palette]], which allow you to e.g. create GUIs with database components such as [[TDBGrid|TDBGrids]]. The advantage of using SQLDB is that it is fairly easy to change to a different database such as Firebird or PostgreSQL without changing your program too much. See below for details.
1823
1924#### Spatialite support
2025
21-Spatialite are GIS extensions to SQLite which you can use from within SQLDB. See [[Spatialite]].
26+Spatialite are GIS extensions to SQLite which you can use from within SQLDB. See [[Spatialite]].
2227
2328#### Support for SQLite encryption
2429
25-In recent FPC versions (beginning with FPC 3.0.0, implemented March 2012), SQLDB included support for some extended versions of SQLite3 which encrypt the SQLite database file using the AES algorithm. Use the Password property to set the encryption key.
30+In recent FPC versions (beginning with FPC 3.0.0, implemented March 2012), SQLDB included support for some extended versions of SQLite3 which encrypt the SQLite database file using the AES algorithm. Use the Password property to set the encryption key.
2631
27-Examples:
32+Examples:
2833
29-- [SQLCipher](http://sqlcipher.net/): open source, e.g. Windows binaries not for free (you have to compile them yourself)
30-- [System.Data.SQLite](http://system.data.sqlite.org/): open source, Windows (32, 64, CE) binaries available, download e.g one of the Precompiled Binaries and rename SQLite.Interop.dll to sqlite3.dll (if you're using the Statically Linked ones, presumably you need to rename System.Data.SQLite.DLL to sqlite3.dll)
31-- [wxSQLite3](http://wxcode.sourceforge.net/docs/wxsqlite3/): open source, some binaries for Linux available (ex: <https://launchpad.net/ubuntu/oneiric/+package/libwxsqlite3-2.8-0>)
34+ * [SQLCipher](<http://sqlcipher.net/>): open source, e.g. Windows binaries not for free (you have to compile them yourself)
35+ * [System.Data.SQLite](<http://system.data.sqlite.org/>): open source, Windows (32, 64, CE) binaries available, download e.g one of the Precompiled Binaries and rename SQLite.Interop.dll to sqlite3.dll (if you're using the Statically Linked ones, presumably you need to rename System.Data.SQLite.DLL to sqlite3.dll)
36+ * [wxSQLite3](<http://wxcode.sourceforge.net/docs/wxsqlite3/>): open source, some binaries for Linux available (ex: <https://launchpad.net/ubuntu/oneiric/+package/libwxsqlite3-2.8-0>)
3237
33-Depending on what you use it might also be necessary to enable the encryption separately [as written here](https://www.sqlite.org/see/doc/trunk/www/readme.wiki) at the point "5.0 C interface", where you need to invoke sqlite3_activate_see() with your product key before accessing an encrypted database.
38+Depending on what you use it might also be necessary to enable the encryption separately [as written here](<https://www.sqlite.org/see/doc/trunk/www/readme.wiki>) at the point "5.0 C interface", where you need to invoke sqlite3_activate_see() with your product key before accessing an encrypted database.
3439
3540#### sqlite3backup
3641
37-sqlite3backup is a unit provided with FPC (not in Lazarus but can be used programmatically) that provides backup/restore functionality for SQLite3. It uses SQLDB's sqlite3conn.
42+sqlite3backup is a unit provided with FPC (not in Lazarus but can be used programmatically) that provides backup/restore functionality for SQLite3. It uses SQLDB's sqlite3conn.
3843
3944### Zeos
4045
41-[Zeos](https://sourceforge.net/projects/zeoslib/)
46+[Zeos](<https://sourceforge.net/projects/zeoslib/>)
4247
4348### SQLitePass
4449
45-[SQLitePass](http://source.online.free.fr/) components. Last code update in 2010. Last forum activity in 2011.
50+[SQLitePass](<http://source.online.free.fr/>) components. Last code update in 2010. Last forum activity in 2011.
4651
4752### TSQLite3Dataset and TSQLiteDataset
4853
49-There are also separate TSQLiteDataset (unit sqliteds) and TSQLite3Dataset (unit sqlite3ds) packages; see below for a description on how to use them. Visit the [sqlite4fpc homepage](http://sqlite4fpc.yolasite.com/) to find the API reference and more tutorials.
54+There are also separate TSQLiteDataset (unit sqliteds) and TSQLite3Dataset (unit sqlite3ds) packages; see below for a description on how to use them. Visit the [sqlite4fpc homepage](<http://sqlite4fpc.yolasite.com/>) to find the API reference and more tutorials.
5055
51-TSqliteDataset and TSqlite3Dataset are [[TDataSet]] descendants that access, respectively, 2.8.x and 3.x.x sqlite databases. For new projects, you would presumably use TSQlite3Dataset as SQLite 3.x is the current version.
56+TSqliteDataset and TSqlite3Dataset are [[TDataSet]] descendants that access, respectively, 2.8.x and 3.x.x sqlite databases. For new projects, you would presumably use TSQlite3Dataset as SQLite 3.x is the current version.
5257
53-Below is a list of the principal advantages and disadvantages compared to other FPC/Lazarus SQLite drivers/access methods:
58+Below is a list of the principal advantages and disadvantages compared to other FPC/Lazarus SQLite drivers/access methods:
5459
55-Advantages:
60+Advantages:
5661
57-- Flexible: programmers can choose to use or not to use the SQL language, allowing them to work with simple table layouts or any complex layout that SQL/sqlite allows
62+ * Flexible: programmers can choose to use or not to use the SQL language, allowing them to work with simple table layouts or any complex layout that SQL/sqlite allows
5863
59-Disadvantages:
64+Disadvantages:
6065
61-- Changing to other databases is more difficult than if you use the SQLDB or Zeos components
66+ * Changing to other databases is more difficult than if you use the SQLDB or Zeos components
6267
63-> **Note:** Given the above, many users will use SQLDB or Zeos due to the advantages unless they need lower-level access to the SQLite library
68+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Given the above, many users will use SQLDB or Zeos due to the advantages unless they need lower-level access to the SQLite library
6469
6570## Using the SQLdb components with SQLite
6671
67-These instructions are focused on SQLDB (the TSQLite3Connection) specifics for SQLite. For a general overview, have a look at [[SqlDBHowto]] which has some useful information about the SQLdb components.
68-
69-See [[SQLdb Tutorial1]] for a tutorial on creating a GUI database-enabled program that is written for SQLite/SQLDB (as well as for Firebird/SQLDB, PostgreSQL/SQLDB, basically any RDBMS SQLDB supports).
72+These instructions are focused on SQLDB (the TSQLite3Connection) specifics for SQLite. For a general overview, have a look at [[SqlDBHowto]] which has some useful information about the SQLdb components.
7073
71-We will use a combination of three components from the Lazarus SQLdb tab: TSQLite3Connection, TSQLTransaction and TSQLQuery. The TSQLQuery acts as our TDataset; in the simplest case it just represents one of our tables. For the sake of simplicity: make sure you already have an existing SQLite database file and don't need to create a new one now. TSQLite3Connection can be found in the *sqlite3conn* unit, if you want to declare it yourself or are working in FreePascal.
74+See [[SQLdb Tutorial1]] for a tutorial on creating a GUI database-enabled program that is written for SQLite/SQLDB (as well as for Firebird/SQLDB, PostgreSQL/SQLDB, basically any RDBMS SQLDB supports).
7275
73-The three components are connected with each other as usual: In the TSQLQuery set the properties Database and Transaction, in the TSQLTransaction set the property Database. There is not much to do in the Transaction and Connection components, most of the interesting things will be done in the TSQLQuery. Configure the components as follows:
76+We will use a combination of three components from the Lazarus SQLdb tab: TSQLite3Connection, TSQLTransaction and TSQLQuery. The TSQLQuery acts as our TDataset; in the simplest case it just represents one of our tables. For the sake of simplicity: make sure you already have an existing SQLite database file and don't need to create a new one now. TSQLite3Connection can be found in the _sqlite3conn_ unit, if you want to declare it yourself or are working in FreePascal.
7477
75-TSQLite3Connection:
78+The three components are connected with each other as usual: In the TSQLQuery set the properties Database and Transaction, in the TSQLTransaction set the property Database. There is not much to do in the Transaction and Connection components, most of the interesting things will be done in the TSQLQuery. Configure the components as follows:
7679
77-- DatabaseName: Set this property to the file name (absolute path!) of your SQLite file. Unfortunately, you cannot simply use a relative path that works unchanged at designtime and at runtime ***is this still true? Can't you just copy the db file in a post-build shell script or symlink it?***. You should make sure that at application start the correct path to the file is always set programmatically, no matter what it contained at designtime.
80+TSQLite3Connection:
7881
79-Note: To set the full library path (if you place your sqlite dll/so/dylib in a place where the OS won't find it, like the application directory on Linux/OSX), you can set the *SQLiteLibraryName* property (BEFORE any connection is established e.g. in the OnCreate event of the main form), like this:
82+ * DatabaseName: Set this property to the file name (absolute path!) of your SQLite file. Unfortunately, you cannot simply use a relative path that works unchanged at designtime and at runtime ***is this still true? Can't you just copy the db file in a post-build shell script or symlink it?***. You should make sure that at application start the correct path to the file is always set programmatically, no matter what it contained at designtime.
8083
81-``` pascal
84+Note: To set the full library path (if you place your sqlite dll/so/dylib in a place where the OS won't find it, like the application directory on Linux/OSX), you can set the _SQLiteLibraryName_ property (BEFORE any connection is established e.g. in the OnCreate event of the main form), like this:
85+```
8286SQLiteLibraryName:='./sqlite3.so';
8387```
8488
85-TSQLQuery:
89+Note: _SQLiteLibraryName_ has been depreciated and using it will generate an error: Warning: Symbol "SQLiteLibraryName" is deprecated: "use sqlite3dyn.SQLiteDefaultLibrary instead". Including sqlite3dyn in your uses clause and referencing _sqlite3dyn.SQLiteDefaultLibrary_ instead solves this issue.
90+```
91+uses sqlite3dyn;
92+```
93+```
94+sqlite3dyn.SQLiteDefaultLibrary:='./sqlite3.so';
95+```
8696
87-- SQL: Set it to some simple select query on one of your tables. For example, if you have a table 'foo' and want this dataset to represent this table then just use the following:
88- ``` SQL
89- SELECT * FROM foo
90- ```
97+TSQLQuery:
9198
92-- Active: Set this to True from within the IDE to test whether it is all set up correctly. This will also automatically activate the transaction and the connection objects. If you receive an error then either the DatabaseName of the connection is not correct or the SQL query is wrong. Later, when we are done adding the fields (see below) set them all to inactive again, we don't want the IDE to lock the SQLite database (single user!) when testing the application.
99+ * SQL: Set it to some simple select query on one of your tables. For example, if you have a table 'foo' and want this dataset to represent this table then just use the following:
100+```
101+SELECT * FROM foo
102+```
93103
94-- *Probably not necessary for proper operation - will need to be checked (June 2012)* Now we can add Fields to our TSQLQuery. While the components are still set to active do a right click and "edit fields...". Click the "+" button and add fields. It will list all fields your SQL query returned. Add every field you will need, you can also add lookup fields here; in this case just make sure you have already defined all needed fields in the other datasets before you start adding lookup fields that refer to them. If your table has many columns and you don't need them all you can just leave them out, you can also make your SQL a bit more specific.
104+ * Active: Set this to True from within the IDE to test whether it is all set up correctly. This will also automatically activate the transaction and the connection objects. If you receive an error then either the DatabaseName of the connection is not correct or the SQL query is wrong. Later, when we are done adding the fields (see below) set them all to inactive again, we don't want the IDE to lock the SQLite database (single user!) when testing the application.
95105
96-- In your code you need to call SQLQuery.ApplyUpdates and SQLTransaction.Commit, TSQLQuery.AfterPost and AfterInsert events are a good place for this when using it with data aware controls but of course you can also postpone these calls to a later time. If you don't call them, the database will not be updated.
106+ * _Probably not necessary for proper operation - will need to be checked (June 2012)_ Now we can add Fields to our TSQLQuery. While the components are still set to active do a right click and "edit fields...". Click the "+" button and add fields. It will list all fields your SQL query returned. Add every field you will need, you can also add lookup fields here; in this case just make sure you have already defined all needed fields in the other datasets before you start adding lookup fields that refer to them. If your table has many columns and you don't need them all you can just leave them out, you can also make your SQL a bit more specific.
97107
98-- "Database is locked": The IDE might still be locking the database (SQLite is a single user database), you probably forgot to set the components to inactive and disconnected again after you were done defining all the fields of your TSQLQuery objects. Use the Form's OnCreate event to set the path and activate the objects at runtime only. Most of the things you set in the TSQLQuery from within the IDE don't require (and some don't even allow) them to be active at design time, the only exception is defining the fields where it wants to read the table design, so inactive at design time should be the normal state.
108+ * In your code you need to call SQLQuery.ApplyUpdates and SQLTransaction.Commit, TSQLQuery.AfterPost and AfterInsert events are a good place for this when using it with data aware controls but of course you can also postpone these calls to a later time. If you don't call them, the database will not be updated.
99109
100-- Your tables should all have a primary key and you must make sure that the corresponding field has pfInKey and nothing else in its PoviderFlags (these flags control how and where the field is used when automatically constructing the update and delete queries).
110+ * "Database is locked": The IDE might still be locking the database (SQLite is a single user database), you probably forgot to set the components to inactive and disconnected again after you were done defining all the fields of your TSQLQuery objects. Use the Form's OnCreate event to set the path and activate the objects at runtime only. Most of the things you set in the TSQLQuery from within the IDE don't require (and some don't even allow) them to be active at design time, the only exception is defining the fields where it wants to read the table design, so inactive at design time should be the normal state.
101111
102-- If you are using lookup fields
103- - make sure the ProviderFlags for the lookup field is completely empty so it won't attempt to use its name in an update query. The lookup field itself is not a data field, it only acts on the value of another field, the corresponding key field, and only this key field will later be used in the update queries. You can set the key field to hidden because usually you don't want to see it in your DBGrid but it needs to be defined.
104- - LookupCache must be set to True. At the time of this writing for some reason the lookup field will not display anything otherwise (but still work) and strangely the exact opposite is the case when working with the TSQLite3Dataset or other TXXXDataset components, here it must be set to False. I'm not yet sure whether this is intended behavior or a bug.
112+ * Your tables should all have a primary key and you must make sure that the corresponding field has pfInKey and nothing else in its PoviderFlags (these flags control how and where the field is used when automatically constructing the update and delete queries).
105113
106-- Usually with simple tables you won't need to set any of the InsertSQL, UpdateSQL and DeleteSQL properties, just leave them empty. If you have the ProviderFlags of all your fields set correctly it should be able to create the needed SQL on the fly. For more details on InsertSQL, UpdateSQL and DeleteSQL, see [[Working With TSQLQuery|Working_With_TSQLQuery#InsertSQL.2C_UpdateSQL_and_DeleteSQL_-_Basic_use_of_parameters]].
114+ * If you are using lookup fields
115+ * make sure the ProviderFlags for the lookup field is completely empty so it won't attempt to use its name in an update query. The lookup field itself is not a data field, it only acts on the value of another field, the corresponding key field, and only this key field will later be used in the update queries. You can set the key field to hidden because usually you don't want to see it in your DBGrid but it needs to be defined.
116+ * LookupCache must be set to True. At the time of this writing for some reason the lookup field will not display anything otherwise (but still work) and strangely the exact opposite is the case when working with the TSQLite3Dataset or other TXXXDataset components, here it must be set to False. I'm not yet sure whether this is intended behavior or a bug.
107117
108-After the above is all set up correctly, you should now be able to use the TSQLQuery like any other TDataset, either by manipulating its data programmatically or by placing a TDatasouce on the Form, connecting it to the TSQLQuery and then using data contols like TDBGrid etc.
118+ * Usually with simple tables you won't need to set any of the InsertSQL, UpdateSQL and DeleteSQL properties, just leave them empty. If you have the ProviderFlags of all your fields set correctly it should be able to create the needed SQL on the fly. For more details on InsertSQL, UpdateSQL and DeleteSQL, see [[Working With TSQLQuery|Working_With_TSQLQuery#InsertSQL.2C_UpdateSQL_and_DeleteSQL_-_Basic_use_of_parameters]].
109119
110-### Creating a Database
120+After the above is all set up correctly, you should now be able to use the TSQLQuery like any other TDataset, either by manipulating its data programmatically or by placing a TDatasouce on the Form, connecting it to the TSQLQuery and then using data contols like TDBGrid etc.
111121
112-The [TSQLite3Connection.CreateDB](http://www.freepascal.org/docs-html/fcl/sqldb/tsqlconnection.createdb.html) method inherited from the parent class actually does nothing; to create a database if no file exists yet, you simply have to write table data as in the following example:
122+### Creating a Database
113123
114-(Code extracted from sqlite_encryption_pragma example that ships with Lazarus 1.3 onwards)
124+The [TSQLite3Connection.CreateDB](<http://www.freepascal.org/docs-html/fcl/sqldb/tsqlconnection.createdb.html>) method inherited from the parent class actually does nothing; to create a database if no file exists yet, you simply have to write table data as in the following example:
115125
116-``` pascal
126+(Code extracted from sqlite_encryption_pragma example that ships with Lazarus 1.3 onwards)
127+```
117128var
118- newFile : Boolean;
119-begin
120-
121- SQLite3Connection1.Close; // Ensure the connection is closed when we start
122-
123- try
124- // Since we're making this database for the first time,
125- // check whether the file already exists
126- newFile := not FileExists(SQLite3Connection1.DatabaseName);
127-
128- if newFile then
129+ newFile : Boolean;
129130 begin
130- // Create the database and the tables
131+
132+ SQLite3Connection1.Close; // Ensure the connection is closed when we start
133+
131134 try
132- SQLite3Connection1.Open;
133- SQLTransaction1.Active := true;
134-
135- // Here we're setting up a table named "DATA" in the new database
136- SQLite3Connection1.ExecuteDirect('CREATE TABLE "DATA"('+
137- ' "id" Integer NOT NULL PRIMARY KEY AUTOINCREMENT,'+
138- ' "Current_Time" DateTime NOT NULL,'+
139- ' "User_Name" Char(128) NOT NULL,'+
140- ' "Info" Char(128) NOT NULL);');
141-
142- // Creating an index based upon id in the DATA Table
143- SQLite3Connection1.ExecuteDirect('CREATE UNIQUE INDEX "Data_id_idx" ON "DATA"( "id" );');
144-
145- SQLTransaction1.Commit;
146-
147- ShowMessage('Succesfully created database.');
135+ // Since we're making this database for the first time,
136+ // check whether the file already exists
137+ newFile := not FileExists(SQLite3Connection1.DatabaseName);
138+
139+ if newFile then
140+ begin
141+ // Create the database and the tables
142+ try
143+ SQLite3Connection1.Open;
144+ SQLTransaction1.Active := true;
145+
146+ // Here we're setting up a table named "DATA" in the new database
147+ SQLite3Connection1.ExecuteDirect('CREATE TABLE "DATA"('+
148+ ' "id" Integer NOT NULL PRIMARY KEY AUTOINCREMENT,'+
149+ ' "Current_Time" DateTime NOT NULL,'+
150+ ' "User_Name" Char(128) NOT NULL,'+
151+ ' "Info" Char(128) NOT NULL);');
152+
153+ // Creating an index based upon id in the DATA Table
154+ SQLite3Connection1.ExecuteDirect('CREATE UNIQUE INDEX "Data_id_idx" ON "DATA"( "id" );');
155+
156+ SQLTransaction1.Commit;
157+
158+ ShowMessage('Succesfully created database.');
159+ except
160+ ShowMessage('Unable to Create new Database');
161+ end;
162+ end;
148163 except
149- ShowMessage('Unable to Create new Database');
164+ ShowMessage('Unable to check if database file exists');
150165 end;
151- end;
152- except
153- ShowMessage('Unable to check if database file exists');
154- end;
155- end;
166+ end;
156167```
157168
158169### Creating user defined collations
159-
160-``` pascal
170+```
161171// utf8 case-sensitive compare callback function
162-function UTF8xCompare(user: pointer; len1: longint; data1: pointer; len2: longint; data2: pointer): longint; cdecl;
163-var S1, S2: AnsiString;
164-begin
165- SetString(S1, data1, len1);
166- SetString(S2, data2, len2);
167- Result := UnicodeCompareStr(UTF8Decode(S1), UTF8Decode(S2));
168-end;
169-
170-// utf8 case-insensitive compare callback function
171-function UTF8xCompare_CI(user: pointer; len1: longint; data1: pointer; len2: longint; data2: pointer): longint; cdecl;
172-var S1, S2: AnsiString;
173-begin
174- SetString(S1, data1, len1);
175- SetString(S2, data2, len2);
176- Result := UnicodeCompareText(UTF8Decode(S1), UTF8Decode(S2));
177-end;
178-
179-// register collation using SQLite3 API (requires sqlite3dyn unit):
180-sqlite3_create_collation(SQLite3.Handle, 'UTF8_CI', SQLITE_UTF8, nil, @UTF8xCompare_CI);
181-// or using method of TSQLite3Connection:
182-CreateCollation('UTF8_CI',1,nil,@UTF8xCompare_CI);
183-
184-// now we can use case-insensitive comparison in SQL like:
185-// SELECT * FORM table1 WHERE column1 COLLATE UTF8_CI = 'á'
186-
187-// but this does not work for LIKE operator
188-// in order to support also LIKE operator we must overload default LIKE function using sqlite3_create_function()
189-// http://www.sqlite.org/lang_corefunc.html#like
172+ function UTF8xCompare(user: pointer; len1: longint; data1: pointer; len2: longint; data2: pointer): longint; cdecl;
173+ var S1, S2: AnsiString;
174+ begin
175+ SetString(S1, data1, len1);
176+ SetString(S2, data2, len2);
177+ Result := UnicodeCompareStr(UTF8Decode(S1), UTF8Decode(S2));
178+ end;
179+
180+ // utf8 case-insensitive compare callback function
181+ function UTF8xCompare_CI(user: pointer; len1: longint; data1: pointer; len2: longint; data2: pointer): longint; cdecl;
182+ var S1, S2: AnsiString;
183+ begin
184+ SetString(S1, data1, len1);
185+ SetString(S2, data2, len2);
186+ Result := UnicodeCompareText(UTF8Decode(S1), UTF8Decode(S2));
187+ end;
188+
189+ // register collation using SQLite3 API (requires sqlite3dyn unit):
190+ sqlite3_create_collation(SQLite3.Handle, 'UTF8_CI', SQLITE_UTF8, nil, @UTF8xCompare_CI);
191+ // or using method of TSQLite3Connection:
192+ CreateCollation('UTF8_CI',1,nil,@UTF8xCompare_CI);
193+
194+ // now we can use case-insensitive comparison in SQL like:
195+ // SELECT * FORM table1 WHERE column1 COLLATE UTF8_CI = 'á'
196+
197+ // but this does not work for LIKE operator
198+ // in order to support also LIKE operator we must overload default LIKE function using sqlite3_create_function()
199+ // http://www.sqlite.org/lang_corefunc.html#like
190200```
191201
192202### Creating user defined functions
193-
194-``` pascal
203+```
195204// example overloading default LOWER() function with user supplied function
196-// to run this demo, you must add units 'sqlite3dyn' and 'ctypes' to your uses-clause
197-// and add a const 'SQLITE_DETERMINISTIC' with value $800
198-
199-procedure UTF8xLower(ctx: psqlite3_context; N: cint; V: ppsqlite3_value); cdecl;
200-var S: AnsiString;
201-begin
202- SetString(S, sqlite3_value_text(V[0]), sqlite3_value_bytes(V[0]));
203- S := UTF8Encode(AnsiLowerCase(UTF8Decode(S)));
204- sqlite3_result_text(ctx, PAnsiChar(S), Length(S), sqlite3_destructor_type(SQLITE_TRANSIENT));
205-end;
206-
207-// register function LOWER() using SQLite3 API (requires sqlite3dyn unit):
208-sqlite3_create_function(SQLite3.Handle, 'lower', 1, SQLITE_UTF8 or SQLITE_DETERMINISTIC, nil, @UTF8xLower, nil, nil);
205+ // to run this demo, you must add units 'sqlite3dyn' and 'ctypes' to your uses-clause
206+ // and add a const 'SQLITE_DETERMINISTIC' with value $800
207+
208+ procedure UTF8xLower(ctx: psqlite3_context; N: cint; V: ppsqlite3_value); cdecl;
209+ var S: AnsiString;
210+ begin
211+ SetString(S, sqlite3_value_text(V[0]), sqlite3_value_bytes(V[0]));
212+ S := UTF8Encode(AnsiLowerCase(UTF8Decode(S)));
213+ sqlite3_result_text(ctx, PAnsiChar(S), Length(S), sqlite3_destructor_type(SQLITE_TRANSIENT));
214+ end;
215+
216+ // register function LOWER() using SQLite3 API (requires sqlite3dyn unit):
217+ sqlite3_create_function(SQLite3.Handle, 'lower', 1, SQLITE_UTF8 or SQLITE_DETERMINISTIC, nil, @UTF8xLower, nil, nil);
209218```
210219
211220### SQLite3 and Dates
212221
213-- SQLite 3 doesn't store dates as a special DateTime value. It can stores them as strings, doubles or integers - see
222+ * SQLite 3 doesn't store dates as a special DateTime value. It can stores them as strings, doubles or integers - see
214223
215-<http://www.sqlite.org/datatype3.html#datetime>.
224+<http://www.sqlite.org/datatype3.html#datetime>.
216225
217-- In strings, the date separator is '-' as per SQL standard/ISO 8601. Thus, if you do an INSERT using the built-in DATE function, it will store it as something like 'YYYY-MM-DD'.
218-- Reading a DateTime value can cause problems for DataSets if they are stored as strings: the .AsDateTime qualifier can stall on an SQLite 'string date' but this can be overcome by using something like `strftime(`*`%d/%m/%Y`*`,recdate) AS sqlite3recdate` in your SQL SELECT statement, which forces SQLite3 to return the date record in a specified format. (the format string %d/%m/%d corresponds to your locale date format which .AsDateTime will understand) **==\> Please open a bug report with an example application demonstrating the problemif this is the case**
219-- When comparing dates stored as strings (using for example the BETWEEN function) remember that the comparison will always be a string comparison, and will therefore depend on how you have stored the date value.
226+ * In strings, the date separator is '-' as per SQL standard/ISO 8601. Thus, if you do an INSERT using the built-in DATE function, it will store it as something like 'YYYY-MM-DD'.
227+ * Reading a DateTime value can cause problems for DataSets if they are stored as strings: the .AsDateTime qualifier can stall on an SQLite 'string date' but this can be overcome by using something like `strftime(_%d/%m/%Y_ ,recdate) AS sqlite3recdate` in your SQL SELECT statement, which forces SQLite3 to return the date record in a specified format. (the format string %d/%m/%d corresponds to your locale date format which .AsDateTime will understand) **== > Please open a bug report with an example application demonstrating the problemif this is the case**
228+ * When comparing dates stored as strings (using for example the BETWEEN function) remember that the comparison will always be a **string** comparison, and will therefore depend on how you have stored the date value.
220229
221230#### Default values in local time instead of UTC
222231
223-CURRENT_TIME, CURRENT_DATE and CURRENT_TIMESTAMP return current UTC date and/or time. For local date and/or times we can use:
224-
225-```text
226- DEFAULT (datetime('now','localtime')) for datetime values formated YYYY-MM-DD HH:MM:SS
227- DEFAULT (date('now','localtime')) for date value formated YYYY-MM-DD
228- DEFAULT (time('now','localtime')) for time value formated HH:MM:SS
232+CURRENT_TIME, CURRENT_DATE and CURRENT_TIMESTAMP return current UTC date and/or time. For local date and/or times we can use:
229233```
234+DEFAULT (datetime('now','localtime')) for datetime values formated YYYY-MM-DD HH:MM:SS
235+ DEFAULT (date('now','localtime')) for date value formated YYYY-MM-DD
236+ DEFAULT (time('now','localtime')) for time value formated HH:MM:SS
237+```
230238
231239### SQLDB And SQLite troubleshooting
232240
233-- Keep in mind that for designtime support to work (fields etc) Lazarus must find sqlite3.dll too.
234-- The same goes for the database filename. Always use absolute path if you use components to extract e.g. fieldnames at designtime. Otherwise the IDE will create an empty file in its directory. In case of trouble, check if the lazarus/ directory doesn't hold a zero byte copy of the database file.
235-- If you have master/detail relationship, you need to refresh master dataset after each insert, in order to get value for slave dataset foreign key field. You can do that in AfterPost event of the master dataset, by calling one of the following overloaded procedures:
241+ * Keep in mind that for designtime support to work (fields etc) Lazarus must find sqlite3.dll too.
242+ * The same goes for the database filename. Always use absolute path if you use components to extract e.g. fieldnames at designtime. Otherwise the IDE will create an empty file in its directory. In case of trouble, check if the lazarus/ directory doesn't hold a zero byte copy of the database file.
243+ * If you have master/detail relationship, you need to refresh master dataset after each insert, in order to get value for slave dataset foreign key field. You can do that in AfterPost event of the master dataset, by calling one of the following overloaded procedures:
236244
237-``` pascal
245+```
238246interface
239- procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery);overload;
240- procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery; pKeyField: string);overload;
241-
242-implementation
243-
244-procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery; pKeyField: string);
245-//This procedure refreshes a dataset and positions cursor to last record
246-//To be used if Dataset is not guaranteed to be sorted by an autoincrement primary key
247-var
248- vLastID: Integer;
249- vUpdateStatus : TUpdateStatus;
250-begin
251- vUpdateStatus := pDataset.UpdateStatus;
252- //Get last inserted ID in the database
253- pDataset.ApplyUpdates;
254- vLastID:=(pDataSet.DataBase as TSQLite3Connection).GetInsertID;
255- //Now come back to respective row
256- if vUpdateStatus = usInserted then begin
257- pDataset.Refresh;
258- //Refresh and go back to respective row
259- pDataset.Locate(pKeyField,vLastID,[]);
260- end;
261-end;
262-
263-procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery);
264-//This procedure refreshes a dataset and positions cursor to last record
265-//To be used only if DataSet is guaranteed to be sorted by an autoincrement primary key
266-var
267- vLastID: Integer;
268- vUpdateStatus : TUpdateStatus;
269-begin
270- vUpdateStatus := pDataset.UpdateStatus;
271- pDataset.ApplyUpdates;
272- vLastID:=(pDataSet.DataBase as TSQLite3Connection).GetInsertID;
273- if vUpdateStatus = usInserted then begin
274- pDataset.Refresh;
275- //Dangerous!
276- pDataSet.Last;
277- end;
278-end;
279-
280-procedure TDataModule1.SQLQuery1AfterPost(DataSet: TDataSet);
281-begin
282- RefreshADatasetAfterInsert(Dataset as TSQLQuery); //If your dataset is sorted by primary key
283-end;
284-
285-procedure TDataModule1.SQLQuery2AfterPost(DataSet: TDataSet);
286-begin
287- RefreshADatasetAfterInsert(Dataset as TSQLQuery, 'ID'); //if you are not sure that the dataset is always sorted by primary key
288-end;
247+ procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery);overload;
248+ procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery; pKeyField: string);overload;
249+
250+ implementation
251+
252+ procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery; pKeyField: string);
253+ //This procedure refreshes a dataset and positions cursor to last record
254+ //To be used if Dataset is not guaranteed to be sorted by an autoincrement primary key
255+ var
256+ vLastID: Integer;
257+ vUpdateStatus : TUpdateStatus;
258+ begin
259+ vUpdateStatus := pDataset.UpdateStatus;
260+ //Get last inserted ID in the database
261+ pDataset.ApplyUpdates;
262+ vLastID:=(pDataSet.DataBase as TSQLite3Connection).GetInsertID;
263+ //Now come back to respective row
264+ if vUpdateStatus = usInserted then begin
265+ pDataset.Refresh;
266+ //Refresh and go back to respective row
267+ pDataset.Locate(pKeyField,vLastID,[]);
268+ end;
269+ end;
270+
271+ procedure RefreshADatasetAfterInsert(pDataSet: TSQLQuery);
272+ //This procedure refreshes a dataset and positions cursor to last record
273+ //To be used only if DataSet is guaranteed to be sorted by an autoincrement primary key
274+ var
275+ vLastID: Integer;
276+ vUpdateStatus : TUpdateStatus;
277+ begin
278+ vUpdateStatus := pDataset.UpdateStatus;
279+ pDataset.ApplyUpdates;
280+ vLastID:=(pDataSet.DataBase as TSQLite3Connection).GetInsertID;
281+ if vUpdateStatus = usInserted then begin
282+ pDataset.Refresh;
283+ //Dangerous!
284+ pDataSet.Last;
285+ end;
286+ end;
287+
288+ procedure TDataModule1.SQLQuery1AfterPost(DataSet: TDataSet);
289+ begin
290+ RefreshADatasetAfterInsert(Dataset as TSQLQuery); //If your dataset is sorted by primary key
291+ end;
292+
293+ procedure TDataModule1.SQLQuery2AfterPost(DataSet: TDataSet);
294+ begin
295+ RefreshADatasetAfterInsert(Dataset as TSQLQuery, 'ID'); //if you are not sure that the dataset is always sorted by primary key
296+ end;
289297```
290298
291299### Vacuum and other operations that must be done outside a transaction
292300
293-SQLDB seems to always require a connection, but some operations like Pragma and Vacuum must be done outside a transaction. The trick is to end transaction, execute what you must and start transaction again (so that sqldb doesn't get confused:)
294-
295-``` pascal
296- // commit any pending operations or use a "fresh" sqlconnection
297- Conn.ExecuteDirect('End Transaction'); // End the transaction started by SQLdb
298- Conn.ExecuteDirect('Vacuum');
299- Conn.ExecuteDirect('Begin Transaction'); //Start a transaction for SQLdb to use
301+SQLDB seems to always require a connection, but some operations like Pragma and Vacuum must be done outside a transaction. The trick is to end transaction, execute what you must and start transaction again (so that sqldb doesn't get confused:)
300302```
303+// commit any pending operations or use a "fresh" sqlconnection
304+ Conn.ExecuteDirect('End Transaction'); // End the transaction started by SQLdb
305+ Conn.ExecuteDirect('Vacuum');
306+ Conn.ExecuteDirect('Begin Transaction'); //Start a transaction for SQLdb to use
307+```
301308
302309## Using TSQLite3Dataset
303310
304-This section details how to use the TSQLite2Dataset and TSQLite3Dataset components to access SQlite databases. by Luiz Américo luizmed(at)oi(dot)com(dot)br
311+This section details how to use the TSQLite2Dataset and TSQLite3Dataset components to access SQlite databases. by Luiz Américo luizmed(at)oi(dot)com(dot)br
305312
313+
314+
306315#### Requirements
307316
308-- For sqlite2 databases (legacy):
309- - FPC 2.0.0 or higher
310- - Lazarus 0.9.10 or higher
311- - SQLite runtime library 2.8.15 or above*
317+ * For sqlite2 databases (legacy):
318+ * FPC 2.0.0 or higher
319+ * Lazarus 0.9.10 or higher
320+ * SQLite runtime library 2.8.15 or above*
312321
313-- Sqlite2 is not maintained anymore and the binary file cannot be found in the sqlite site
322+ * Sqlite2 is not maintained anymore and the binary file cannot be found in the sqlite site
314323
315-- For sqlite3 databases:
316- - FPC 2.0.2 or higher
317- - Lazarus 0.9.11 (svn revision 8443) or higher
318- - sqlite runtime library 3.2.1 or higer (get it from [www.sqlite.org](http://www.sqlite.org))
324+ * For sqlite3 databases:
325+ * FPC 2.0.2 or higher
326+ * Lazarus 0.9.11 (svn revision 8443) or higher
327+ * sqlite runtime library 3.2.1 or higer (get it from [www.sqlite.org](<http://www.sqlite.org>))
319328
320329**Before initiating a lazarus project, ensure that:**
321330
322-- the sqlite library is either
323- - in the system PATH or
324- - in the executable output directory and Lazarus (or current project) directories - this option might work on Windows only
325-- under Linux, put cmem as the first unit in uses clause of the main program
326- - In Debian, Ubuntu and other Debian-like distros, in order to build Lazarus IDE you must install the packages libsqlite-dev/libsqlite3-dev, not only sqlite/sqlite3 (Also applies to OpenSuSe)
331+ * the sqlite library is either
332+ * in the system PATH or
333+ * in the executable output directory and Lazarus (or current project) directories - this option might work on Windows only
334+ * under Linux, put cmem as the first unit in uses clause of the main program
335+ * In Debian, Ubuntu and other Debian-like distros, in order to build Lazarus IDE you must install the packages libsqlite-dev/libsqlite3-dev, not only sqlite/sqlite3 (Also applies to OpenSuSe)
327336
328337#### How To Use (Basic Usage)
329338
330-Install the package found at /components/sqlite directory (see instructions [[Install Packages|here]])
339+Install the package found at /components/sqlite directory (see instructions [[Install Packages|here]])
331340
332-At design time, set the following properties:
341+At design time, set the following properties:
333342
334-- FileName: path of the sqlite file \[required\]
335-- TableName: name of the table used in the sql statement \[required\]
336-- SQL: a SQL select statement \[optional\]
337-- SaveOnClose: The default value is false, which means that changes are not saved. One can change it to true. \[optional\]
338-- Active: Needs to be set at design time or at program startup. \[required\]
343+ * FileName: path of the sqlite file [required]
344+ * TableName: name of the table used in the sql statement [required]
345+ * SQL: a SQL select statement [optional]
346+ * SaveOnClose: The default value is false, which means that changes are not saved. One can change it to true. [optional]
347+ * Active: Needs to be set at design time or at program startup. [required]
339348
340349**Creating a Table (Dataset)**
341350
342-Double-click the component icon or use the 'Create Table' item of the popup menu that appears when clicking the right mouse button. A simple self-explaining table editor will be shown.
351+Double-click the component icon or use the 'Create Table' item of the popup menu that appears when clicking the right mouse button. A simple self-explaining table editor will be shown.
343352
344-Here are all field types supported by TSqliteDataset and TSqlite3Dataset:
353+Here are all field types supported by TSqliteDataset and TSqlite3Dataset:
345354
346-- Integer
347-- AutoInc
348-- String
349-- Memo
350-- Bool
351-- Float
352-- Word
353-- DateTime
354-- Date
355-- Time
356-- LargeInt
357-- Currency
355+ * Integer
356+ * AutoInc
357+ * String
358+ * Memo
359+ * Bool
360+ * Float
361+ * Word
362+ * DateTime
363+ * Date
364+ * Time
365+ * LargeInt
366+ * Currency
358367
359368**Retrieving the data**
360369
361-After creating the table or with a previously created Table, open the dataset with the Open method. If the SQL property was not set then all records from all fields will be retrieved, the same if you set the SQL to:
362-
363-``` pascal
370+After creating the table or with a previously created Table, open the dataset with the Open method. If the SQL property was not set then all records from all fields will be retrieved, the same if you set the SQL to:
371+```
364372SQL := 'Select * from TABLENAME';
365373```
366374
367375**Applying changes to the underlying datafile**
368376
369-To use the ApplyUpdates function, the dataset must contain at least one field that fulfills the requirements for a Primary Key (values must be UNIQUE and not NULL)
370-
371-It's possible to do that in two ways:
377+To use the ApplyUpdates function, the dataset must contain at least one field that fulfills the requirements for a Primary Key (values must be UNIQUE and not NULL)
372378
373-- Set PrimaryKey property to the name of a Primary Key field
374-- Add an AutoInc field (This is easier since the TSqliteDataSet automatically handles it as a Primary Key)
379+It's possible to do that in two ways:
375380
376-If one of the two conditions is set, just call
381+ * Set PrimaryKey property to the name of a Primary Key field
382+ * Add an AutoInc field (This is easier since the TSqliteDataSet automatically handles it as a Primary Key)
377383
378-``` pascal
384+If one of the two conditions is set, just call
385+```
379386ApplyUpdates;
380387```
381388
382-> **Note:** If both conditions are set, the field corresponding to PrimaryKey is used to apply the updates.
389+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** If both conditions are set, the field corresponding to PrimaryKey is used to apply the updates.
383390
384-> **Note:** Setting PrimaryKey to a field that is not a Primary Key will lead to loss of data if ApplyUpdates is called, so ensure that the chosen field contains not Null and Unique values before using it.
391+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Setting PrimaryKey to a field that is not a Primary Key will lead to loss of data if ApplyUpdates is called, so ensure that the chosen field contains not Null and Unique values before using it.
385392
386393##### Master/detail example
387394
388-Various examples of master/detail relations (e.g. the relation between customer and orders):
395+Various examples of master/detail relations (e.g. the relation between customer and orders):
389396
390-- the generic SQLDB way of doing this using the `DataSource` property: [[MasterDetail]]
391-- TSQLite3 specific example using locate: [[TSqlite3 Master Detail Example]].
397+ * the generic SQLDB way of doing this using the `DataSource` property: [[MasterDetail]]
398+ * TSQLite3 specific example using locate: [[TSqlite3 Master Detail Example]].
392399
393400#### Remarks
394401
395-- Although it has been tested with 10,000 records and worked fine, TSqliteDataset keeps all the data in memory, so remember to retrieve only the necessary data (especially with Memo Fields).
396-- The same datafile (Filename property) can host several tables/datasets
397-- Several datasets (different combinations of fields) can be created using the same table simultaneously
398-- It's possible to filter the data using WHERE statements in the sql, closing and reopening the dataset (or calling RefetchData method). But in this case, the order and number of fields must remain the same
399-- It's also possible to use complex SQL statements using aliases, joins, views in multiple tables (remember that they must reside in the same datafile), but in this case ApplyUpdates won't work. If someone wants to use complex queries and to apply the updates to the datafile, mail me and i will give some hints how to do that
400-- Setting filename to a sqlite datafile not created by TSqliteDataset and opening it is allowed but some fields won't have the correct field type detected. These will be treated as string fields.
402+ * Although it has been tested with 10,000 records and worked fine, TSqliteDataset keeps all the data in memory, so remember to retrieve only the necessary data (especially with Memo Fields).
403+ * The same datafile (Filename property) can host several tables/datasets
404+ * Several datasets (different combinations of fields) can be created using the same table simultaneously
405+ * It's possible to filter the data using WHERE statements in the sql, closing and reopening the dataset (or calling RefetchData method). But in this case, the order and number of fields must remain the same
406+ * It's also possible to use complex SQL statements using aliases, joins, views in multiple tables (remember that they must reside in the same datafile), but in this case ApplyUpdates won't work. If someone wants to use complex queries and to apply the updates to the datafile, mail me and i will give some hints how to do that
407+ * Setting filename to a sqlite datafile not created by TSqliteDataset and opening it is allowed but some fields won't have the correct field type detected. These will be treated as string fields.
401408
402-Generic examples can be found at fpc/fcl-db/src/sqlite SVN directory
409+Generic examples can be found at fpc/fcl-db/src/sqlite SVN directory
403410
404411## See also
405412
406-- [[SQLdb Tutorial1]] Tutorial for Sqlite
407-- [[SqlDBHowto]] General information on SQLDB
408-- [[Working With TSQLQuery]] Information on SQLDB's TSQLQuery
409-- [SQLite site](http://www.sqlite.org)
413+ * [[SQLdb Tutorial1]] Tutorial for Sqlite
414+ * [[SqlDBHowto]] General information on SQLDB
415+ * [[Working With TSQLQuery]] Information on SQLDB's TSQLQuery
416+ * [SQLite site](<http://www.sqlite.org>)
410417
411418---
412-*From the Free Pascal wiki, page "SQLite", as it stood on 2022-06-05 after a change by Thierrybo, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
419+*From the Free Pascal wiki, page "sqlite", as the Internet Archive kept it on 2025-05-16, last changed on 2024-07-30. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history