SQLite3 at the Core
mORMot 2's persistence architecture is centered on SQLite3 but not limited to it. The framework supports multiple database backends, all accessible through a unified interface.
The core database of the framework uses SQLite3 - a free, secure, zero-configuration, server-less, cross-platform database engine.
┌─────────────────────────────────────────────────────────────────────┐
│ mORMot ORM / REST │
└─────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ SQLite3 │ │ External DB │ │ NoSQL │
│ (native) │ │ (via SQL) │ │ (MongoDB) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
File/Mem PostgreSQL/Oracle Document Store
MSSQL/MySQL/etc.
| Storage Backend | Unit(s) | Use Case |
|---|---|---|
| Internal SQLite3 | mormot.orm.sqlite3 |
Default, embedded, full SQL |
In-Memory TObjectList |
mormot.orm.storage |
Fastest, no ACID, limited SQL |
| External RDBMS | mormot.orm.sql + mormot.db.sql. |
Enterprise databases |
| MongoDB | mormot.orm.mongodb |
NoSQL document store |
The framework uses compiled SQLite3 code, included natively in Delphi/FPC:
sqlite3.dllFastMM4 / FPC memory managerREGEXP operatorSQLite3's Virtual Table mechanism allows mORMot to:
TObjectList storage with SQL queries// One model, multiple backends
Model := TOrmModel.Create([
TOrmCustomer, // Internal SQLite3
TOrmProduct, // External PostgreSQL
TOrmOrder, // External PostgreSQL
TOrmAuditLog // MongoDB
]);
// Mix in single query (via virtual tables)
Server.Orm.ExecuteList([TOrmOrder, TOrmCustomer],
'SELECT Order.ID, Customer.Name FROM Order, Customer ' +
'WHERE Order.CustomerID = Customer.ID');
mormot.db.raw.sqlite3.pas → Low-level SQLite3 C API wrapper
↓
mormot.db.sql.sqlite3.pas → TSqlDB* implementation for SQLite3
↓
mormot.orm.sqlite3.pas → ORM integration (TRestServerDB)
| Mode | Unit | Deployment |
|---|---|---|
| Static | mormot.db.raw.sqlite3.static |
Embedded in EXE (~1MB) |
| Dynamic | mormot.db.raw.sqlite3 |
External sqlite3.dll |
uses
mormot.db.raw.sqlite3.static, // Include static .obj
mormot.orm.sqlite3;
Dynamic linking (required for some platforms):
uses
mormot.db.raw.sqlite3,
mormot.orm.sqlite3;
// Load external DLL
sqlite3 := TSqlite3LibraryDynamic.Create;
uses
mormot.orm.sqlite3,
mormot.rest.sqlite3;
var
Server: TRestServerDB;
begin
// File-based (default - ACID, persistent)
Server := TRestServerDB.Create(Model, 'data.db3');
// In-memory (fast, non-persistent)
Server := TRestServerDB.Create(Model, ':memory:');
// Performance tuning
Server.DB.Synchronous := smOff; // Faster writes (less safe)
Server.DB.LockingMode := lmExclusive; // Single-process access
end;
| Mode | Safety | Speed | Use Case |
|---|---|---|---|
smFull (default) |
ACID | Slow writes | Production with crash safety |
smOff |
Risk on crash | Fast | Batch imports, dev/test |
lmExclusive |
Single process | Fastest | Embedded applications |
Server.DB.Synchronous := smOff;
Server.DB.LockingMode := lmExclusive;
try
// 100x faster bulk insert
for i := 1 to 100000 do
Server.Orm.Add(CreateRecord(i), True);
finally
Server.DB.Synchronous := smFull; // Restore safety
end;
mORMot automatically caches prepared SQL statements for reuse.
Use ? placeholders for parameters:
// Parameters bound safely (no SQL injection)
Rec := TOrm.CreateAndFillPrepare(Orm,
'Name LIKE ? AND Active = ?', ['John%', True]);
// Date parameters
Rec := TOrm.CreateAndFillPrepare(Orm,
'Created >= ?', [DateToSql(EncodeDate(2024, 1, 1))]);
The framework caches prepared statements internally:
// First call: Parse SQL, prepare statement, execute
Orm.Retrieve('Name = ?', [], ['John'], Rec);
// Subsequent calls: Reuse prepared statement, rebind parameters
Orm.Retrieve('Name = ?', [], ['Jane'], Rec); // Much faster
Internally, parameters are encoded as :(value): in JSON:
// API call
Orm.Retrieve('ID = ?', [], [42], Rec);
// Transmitted as JSON
'{"Where":"ID=:(42):"}'
// Prepared SQL
'SELECT * FROM TableName WHERE ID = ?' // With 42 bound
R-Trees provide fast range queries for multi-dimensional data (geospatial, temporal).
type
TOrmMapBox = class(TOrmRTree)
private
fMinX, fMaxX: Double;
fMinY, fMaxY: Double;
published
property MinX: Double read fMinX write fMinX;
property MaxX: Double read fMaxX write fMaxX;
property MinY: Double read fMinY write fMinY;
property MaxY: Double read fMaxY write fMaxY;
end;
// Find all boxes containing point (10, 20)
Boxes := TOrmMapBox.CreateAndFillPrepare(Orm,
'MinX <= ? AND MaxX >= ? AND MinY <= ? AND MaxY >= ?',
[10, 10, 20, 20]);
// Or use RTreeMatch for complex queries
Orm.RTreeMatch(TOrmMapData, 'BlobField', TOrmMapBox,
MapData.BlobField, ResultIDs);
FTS5 provides fast full-text search capabilities.
| Class | Tokenizer | Use Case |
|---|---|---|
TOrmFTS5 |
Simple | Basic text search |
TOrmFTS5Porter |
Porter stemmer | English text |
TOrmFTS5Unicode61 |
Unicode61 | Non-Latin languages |
type
TOrmArticleFTS = class(TOrmFTS5Porter)
private
fTitle: RawUtf8;
fBody: RawUtf8;
published
property Title: RawUtf8 read fTitle write fTitle;
property Body: RawUtf8 read fBody write fBody;
end;
// Link FTS to main table via DocID
FTS := TOrmArticleFTS.Create;
FTS.DocID := Article.ID; // Link to TOrmArticle
FTS.Title := Article.Title;
FTS.Body := Article.Content;
Orm.Add(FTS, True);
// Optimize after bulk inserts
TOrmArticleFTS.OptimizeFTS5Index(Orm);
var
IDs: TIDDynArray;
begin
// Basic search
Orm.FTSMatch(TOrmArticleFTS, 'database optimization', IDs);
// With field weighting (Title=2x, Body=1x)
Orm.FTSMatch(TOrmArticleFTS, 'database', IDs, [2.0, 1.0]);
// Complex queries
Orm.FTSMatch(TOrmArticleFTS, 'Title:database AND Body:performance', IDs);
end;
| Pattern | Meaning |
|---|---|
word |
Match exact word |
word |
Prefix match |
"exact phrase" |
Match phrase |
word1 AND word2 |
Both required |
word1 OR word2 |
Either matches |
word1 NOT word2 |
Exclude word2 |
NEAR(word1 word2, 5) |
Within 5 tokens |
Title:word |
Match in specific column |
Ultra-fast storage using TObjectList:
uses
mormot.orm.storage;
// Register before server creation
Model.Props[TOrmCache].SetStorage(TRestStorageInMemory);
// Or explicitly
Server.StaticDataAdd(TRestStorageInMemory.Create(TOrmCache, Server));
| Feature | In-Memory | SQLite3 |
|---|---|---|
| Speed | Fastest | Fast |
| ACID | No | Yes |
| SQL Joins | Limited | Full |
| Max Size | RAM | Disk |
| Persistence | Optional (JSON/Binary) | Yes |
| Unique Index | O(1) hash | B-Tree |
// Save to JSON file
Storage := TRestStorageInMemory.Create(TOrmCache, Server);
Storage.SaveToJson('cache.json');
Storage.LoadFromJson('cache.json');
// Save to binary (faster, smaller)
Storage.SaveToBinary('cache.data');
Storage.LoadFromBinary('cache.data');
Register in-memory as virtual table for SQL access:
// Static mode (no SQL joins)
Server.StaticDataAdd(Storage);
// Virtual mode (SQL joins available)
Model.VirtualTableRegister(TOrmCache, TOrmVirtualTableJson);
| Class | Storage | Persistence |
|---|---|---|
TOrmVirtualTableJson |
In-memory | JSON file |
TOrmVirtualTableBinary |
In-memory | Binary file |
TOrmVirtualTableExternal |
External DB | Database |
TOrmVirtualTableMongoDB |
MongoDB | MongoDB |
// Must register BEFORE creating server
Model.VirtualTableRegister(TOrmTempData, TOrmVirtualTableJson);
Model.VirtualTableRegister(TOrmCustomer, TOrmVirtualTableExternal);
// Then create server
Server := TRestServerDB.Create(Model, 'main.db3');
type
TMyVirtualTable = class(TOrmVirtualTable)
public
class function ModuleName: RawUtf8; override;
function Prepare(var Prepared: TOrmVirtualTablePrepared): boolean; override;
function Search(var Prepared: TOrmVirtualTablePrepared): boolean; override;
end;
mORMot adds JSON manipulation functions to SQLite3:
Extract values from JSON columns:
-- Get property value
SELECT JsonGet(DataColumn, 'name') FROM Table WHERE ID=1;
-- Get nested property
SELECT JsonGet(DataColumn, 'address.city') FROM Table;
-- Get multiple properties
SELECT JsonGet(DataColumn, 'name,email') FROM Table;
-- Wildcard match
SELECT JsonGet(DataColumn, 'user.*') FROM Table;
Check property existence:
-- Check if property exists
SELECT * FROM Table WHERE JsonHas(DataColumn, 'premium') = 1;
-- Check nested property
SELECT * FROM Table WHERE JsonHas(DataColumn, 'settings.darkMode') = 1;
// Hot backup (while server is running)
Server.DB.BackupBackground('backup.db3', 100, 10,
procedure(Sender: TSqlDatabase; Step: integer)
begin
WriteLn('Backup progress: ', Step);
end);
// Stop server, copy backup file, restart
Server.Free;
CopyFile('backup.db3', 'data.db3');
Server := TRestServerDB.Create(Model, 'data.db3');
1. Use transactions for bulk operations
2. Use batch operations (TRestBatch) for inserts
3. Use prepared statements (automatic with ? parameters)
4. Index foreign keys and frequently queried columns
5. Use smOff temporarily for bulk imports
6. Use lmExclusive for single-process applications
Typical performance on modern hardware (SSD, Core i7):
| Operation | In-Memory | SQLite3 (File) | External PostgreSQL |
|---|---|---|---|
| Insert (single) | 300,000/s | 500/s | 5,000/s |
| Insert (batch) | 500,000/s | 200,000/s | 50,000/s |
| Read (by ID) | 900,000/s | 130,000/s | 10,000/s |
| Read (all) | 900,000/s | 550,000/s | 150,000/s |
| mORMot 1 | mORMot 2 |
|---|---|
SynSQLite3.pas |
mormot.db.raw.sqlite3.pas |
SynSQLite3Static.pas |
mormot.db.raw.sqlite3.static.pas |
mORMotSQLite3.pas |
mormot.orm.sqlite3.pas + mormot.rest.sqlite3.pas |
| mORMot 1 | mORMot 2 |
|---|---|
TSQLRestServerDB |
TRestServerDB |
TSQLDatabase |
TSqlDatabase |
TSQLRecordFTS3 |
TOrmFTS3 |
TSQLRecordFTS5 |
TOrmFTS5 |
TSQLRecordRTree |
TOrmRTree |
TSQLRestStorageInMemory |
TRestStorageInMemory |
Next Chapter: External SQL Database Access
| Previous | Index | Next |
|---|---|---|
| Chapter 6: Daily ORM | Index | Chapter 8: External SQL Database Access |