Introdução
Muitas vezes, precisamos persistir dados localmente em aplicações mobile — seja em cenários totalmente offline, seja em modelos híbridos com sincronização posterior para banco de dados online. Foi justamente em um desses contextos que me vi diante de uma decisão técnica: como manter a simplicidade e a performance, sem abrir mão da estrutura e da segurança dos dados?
Após analisar diversas soluções do mercado — algumas pagas, outras genéricas ou excessivamente complexas — decidi criar uma abordagem mais direta, leve e sob meu controle. Assim nasceu o SQLite4D
, unindo o poder do FireDAC disponível no Delphi à leveza e gratuidade do SQLite, com uma camada de abstração fluente que me permitisse criar e evoluir estruturas de banco com precisão e autonomia.
O resultado foi tão satisfatório que resolvi torná-lo público, como exemplo prático da forma como enxergo desenvolvimento: resolvendo problemas reais com clareza arquitetural e foco em produtividade.
Por que o SQLite4D?
Em muitos projetos Delphi, especialmente os que envolvem dispositivos móveis ou persistência local, é comum usar TFDQuery
para criar tabelas SQLite manualmente. Mas isso pode gerar código repetitivo, propenso a erros e difícil de manter.
O SQLite4D
muda esse jogo.
Ele oferece uma camada de abstração fluente, onde você define tabelas, índices e chaves estrangeiras com segurança, clareza e controle total. Tudo isso utilizando recursos modernos como:
- Interfaces fluent (
IFieldConfigurator
,ITableDefinition
); - Conversão automática de
TFDMemTable
para tabelas SQLite; - Controle transacional e batch de inserção de dados;
- Criação dinâmica de índices compostos e chaves estrangeiras;
- Suporte a ALTER TABLE, renomear, excluir tabelas, e muito mais.
Alterar tabelas com segurança no SQLite? Use o padrão ExecuteMultipleSQL
Entre os métodos oferecidos pelo TSQLite4D
, um se destaca pela sua relevância técnica: ExecuteMultipleSQL
.
Ele é utilizado para operações críticas como alterações estruturais em tabelas SQLite, que normalmente não são suportadas diretamente pela instrução ALTER TABLE
. Com ele, você implementa o padrão seguro de transformação de schema, executando os seguintes passos:
O que ele faz:
- Desativa temporariamente os constraints (
PRAGMA foreign_keys = OFF
); - Cria uma tabela temporária e transfere os dados atuais para ela;
- Remove a tabela original;
- Recria a tabela com a nova estrutura (novos campos, nomes ou constraints);
- Restaura os dados na nova tabela;
- Apaga a tabela temporária;
- Reativa constraints (
PRAGMA foreign_keys = ON
).
Tudo isso ocorre dentro de uma transação única, com Commit
ao final e Rollback
automático em caso de qualquer falha. Isso garante a integridade dos dados mesmo em operações arriscadas.
Diferencial arquitetural:
Esse método apenas retorna sucesso ou lança exceções. Isso significa que você pode usá-lo tanto em projetos visuais quanto em serviços, consoles ou servidores RESTful, mantendo a responsabilidade de feedback na camada de apresentação.
Se você precisa alterar tabelas SQLite de forma segura, programática e transacional, ExecuteMultipleSQL
é o caminho recomendado.
Exemplo de uso
Nem todo sistema precisa ser 100% online o tempo todo. Neste exemplo, mostramos uma abordagem híbrida que combina o consumo de dados via API com a criação e persistência local em SQLite. A sequência é simples, mas poderosa: criamos o banco SQLite, definimos dinamicamente a TFDMemTable
, geramos a estrutura física no banco e transferimos os dados — tudo isso com base em dados carregados, por exemplo, de um SQL Server remoto.
O que torna esse mecanismo especialmente útil é que a estrutura da tabela SQLite é criada automaticamente, com base nos tipos de dados definidos no TFDMemTable
. Esse mapeamento é feito de forma independente do banco de origem, pois utiliza os tipos oferecidos pela própria estrutura de metadados do FireDAC — ou seja, você não precisa escrever SQL manual nem se preocupar com conversões.
Essa abordagem entrega muito mais do que comodidade técnica. Ela representa resiliência em campo, eficiência mesmo com conexões instáveis, e redução de falhas operacionais. Do ponto de vista do usuário, a experiência é contínua. Para a empresa, isso se traduz em menos suporte técnico e maior autonomia. E para o cliente tomador de decisão, significa inteligência aplicada ao uso da infraestrutura, com uma aplicação pronta para funcionar tanto online quanto offline, sem perder o controle sobre os dados.
Os demais exemplos complementares estarão disponíveis no GitHub, mas este já entrega um recado importante: aplicações modernas não precisam ser complexas — apenas precisam ser bem pensadas.
Criando Banco de Dados
procedure TFormMain.btnCreateDatabaseClick(Sender: TObject);
(*
This method checks if the SQLite database file already exists.
If it does not, it creates the database and ensures the connection is valid.
*)
const
DBName = 'SampleDB.db';
var
LSQLite4D: TSQLite4D;
LDBPath: string;
begin
LDBPath := GetDatabasePath(DBName);
try
if FileExists(LDBPath) then
begin
ShowMessage(Format('The SQLite database "%s" already exists.', [DBName]));
end
else
begin
ShowMessage(Format('The database "%s" does not exist. Creating now...', [DBName]));
LSQLite4D := TSQLite4D.GetInstance(LDBPath);
try
ValidateDatabaseConnection(LSQLite4D);
ShowMessage(Format('Database "%s" created successfully.', [DBName]));
finally
TSQLite4D.ReleaseInstance;
end;
end;
except
on E: Exception do
ShowMessage(Format('Error creating the database "%s": %s - %s', [DBName, E.ClassName, E.Message]));
end;
end;
Criando Tabela
procedure TFormMain.btnCreateTableCustomerClick(Sender: TObject);
(*
Creates the "Customers" table in the SQLite database using a fluent interface.
Includes primary key, NOT NULL, UNIQUE, and a CHECK constraint on the balance
field. Designed for clarity: all logic is kept in this method for immediate
comprehension.
*)
const
DBName = 'SampleDB.db';
TableName = 'Customers';
var
LSQLite4D: TSQLite4D;
LDBPath: string;
begin
LDBPath := GetDatabasePath(DBName);
try
LSQLite4D := TSQLite4D.GetInstance(LDBPath);
try
ValidateDatabaseConnection(LSQLite4D);
LSQLite4D.CreateTable(TableName)
.AddField('ID', sftINTEGER)
.PrimaryKey
.AutoIncrement
.EndField
.AddField('Name', sftTEXT, 100)
.NotNull
.Unique
.EndField
.AddField('Email', sftTEXT)
.NotNull
.EndField
.AddField('Balance', sftREAL)
.Check('Balance >= 0')
.EndField
.BuildTable;
ShowMessage(Format('Table "%s" created successfully in "%s".', [TableName, DBName]));
finally
TSQLite4D.ReleaseInstance;
end;
except
on E: Exception do
ShowMessage(Format('Error creating table "%s": %s - %s', [TableName, E.ClassName, E.Message]));
end;
end;
Inserindo Dados na TFDMemTable
procedure TFormMain.btnInsertDataClick(Sender: TObject);
(*
Inserts 10,000 sample records into the in-memory FDMemTable.
This method fills the dataset with randomized and sequential values to
simulate real-world content. It's useful for testing, performance
benchmarking, and UI demonstrations.
Fields populated:
- ID (Integer, incremental)
- Name (String with suffix)
- BirthDate (Date, increasing by index)
- IsActive (Boolean, alternating true/false)
- Balance (Float, random)
- Notes (Memo, text with ID)
- Created (DateTime, decreasing pattern)
Notes:
- Disables controls for better performance.
- Uses batch operations to reduce overhead.
- Displays elapsed time after insertion.
*)
const
TotalRecords = 10000;
var
I: Integer;
LStopwatch: TStopwatch;
begin
if not Assigned(MemTable) then
begin
ShowMessage('FDMemTable must be created before inserting data.');
Exit;
end;
LStopwatch := TStopwatch.StartNew;
MemTable.DisableControls;
MemTable.LogChanges := False;
MemTable.FetchOptions.RecsMax := TotalRecords;
MemTable.ResourceOptions.SilentMode := True;
MemTable.BeginBatch;
try
for I := 1 to TotalRecords do
begin
MemTable.Append;
MemTable.FieldByName('ID').AsInteger := I;
MemTable.FieldByName('Name').AsString := 'John Doe ' + I.ToString;
MemTable.FieldByName('BirthDate').AsDateTime := EncodeDate(1990, 1, 1) + I;
MemTable.FieldByName('IsActive').AsBoolean := (I mod 2 = 0);
MemTable.FieldByName('Balance').AsFloat := Random * 10000;
MemTable.FieldByName('Notes').AsString := 'Memo for record #' + I.ToString;
MemTable.FieldByName('Created').AsDateTime := Now - (I mod 365);
MemTable.Post;
end;
finally
MemTable.EndBatch;
MemTable.EnableControls;
LStopwatch.Stop;
ShowMessage(Format('Inserted %d records in %d ms.', [TotalRecords, LStopwatch.ElapsedMilliseconds]));
end;
end;
Criando Tabela SQLite a partir da TFDMemTable
procedure TFormMain.btnCreateSQLiteTableFromFDMemTableClick(Sender: TObject);
(*
Creates a SQLite table named "Suppliers" based on the structure of the
in-memory FDMemTable.
This method uses the `CreateSQLiteTableFromMemTable` method from the
`TSQLite4D` class to translate Delphi field definitions into SQLite-compatible
schema automatically.
Key Features:
- Reads structure directly from TFDMemTable (MemTable).
- Avoids overwriting existing tables by setting ForceRecreate = False.
- Ensures database connection is valid before attempting creation.
- Ideal for hybrid architectures or offline data persistence.
Parameters:
- MemTable : Source dataset containing the field definitions.
- "Suppliers" : Target table name in SQLite.
- ForceRecreate: False (do not delete table if it exists).
*)
const
DBFileName = 'SampleDB.db';
TableName = 'Suppliers';
var
LSQLite4D: TSQLite4D;
LDatabasePath: string;
begin
if not Assigned(MemTable) then
begin
ShowMessage('FDMemTable must be created before generating the SQLite table.');
Exit;
end;
LDatabasePath := GetDatabasePath(DBFileName);
LSQLite4D := TSQLite4D.GetInstance(LDatabasePath);
try
ValidateDatabaseConnection(LSQLite4D);
LSQLite4D.CreateSQLiteTableFromMemTable(MemTable, TableName, False);
ShowMessage(Format('SQLite table "%s" created successfully.', [TableName]));
finally
TSQLite4D.ReleaseInstance;
end;
end;
Transferindo Dados da TFDMemTable para a Tabela SQLite
procedure TFormMain.btnTransferDataClick(Sender: TObject);
(*
Transfers records from the in-memory TFDMemTable (MemTable) to the SQLite
table "Suppliers".
This method uses `CopyDataFromMemTableToSQLite` from the `TSQLite4D` class
to copy rows from memory into a physical SQLite table.
Key Features:
- Updates progress through a callback (currently empty, but ready for use).
- Synchronous execution (main thread) – suitable for small datasets or
demos.
- UI button is temporarily disabled to prevent reentry.
Parameters:
- TableName: Target SQLite table name ("Suppliers").
- MemTable : Source in-memory dataset.
Note:
- For large datasets, consider wrapping the operation in a background
thread to avoid UI freeze.
*)
const
DBFileName = 'SampleDB.db';
TableName = 'Suppliers';
var
LSQLite4D: TSQLite4D;
begin
if not Assigned(MemTable) then
begin
ShowMessage('MemTable must be created and filled with data before transferring.');
Exit;
end;
TButton(Sender).Enabled := False;
try
LSQLite4D := TSQLite4D.GetInstance(GetDatabasePath(DBFileName));
try
ValidateDatabaseConnection(LSQLite4D);
LSQLite4D.CopyDataFromMemTableToSQLite(
TableName, MemTable, 1,
procedure(Progress: Integer)
begin
// Optional: Implement progress feedback if needed
end
);
finally
TSQLite4D.ReleaseInstance;
end;
ShowMessage('Records transferred to SQLite successfully.');
except
on E: Exception do
ShowMessage('Error during data transfer - ' + E.ClassName + ': ' + E.Message);
end;
TButton(Sender).Enabled := True;
end;
Outras soluções disponíveis
O ecossistema Delphi oferece diversas abordagens para persistência de dados, incluindo soluções comerciais e open source. Entre elas:
- ORMs como TMS Aurelius, que oferecem recursos avançados de mapeamento e abstração de banco de dados.
- Bibliotecas como ZeosLib, voltadas ao acesso direto e de baixo nível a múltiplos bancos.
- FireDAC puro, que permite acesso nativo robusto a múltiplos SGBDs, incluindo SQLite, com alto controle.
Onde o SQLite4D se destaca
O SQLite4D
foi criado com outro foco: maximizar a produtividade e reduzir a verbosidade para quem trabalha com SQLite embarcado em aplicações mobile ou desktop.
A grande força do SQLite4D está justamente em usar o FireDAC como fundação, aproveitando seus recursos internos de compatibilidade de tipos e estabilidade, mas abstraindo tarefas repetitivas como:
- Criação automática de tabelas baseadas em
TFDMemTable
; - Transferência de dados com suporte a progresso;
- Criação segura e declarativa da estrutura SQLite em tempo de execução.
O resultado é uma mini engine de persistência leve, fluente e reutilizável — sem reinventar a roda, mas colocando ela pra girar mais rápido.
Quando usar
- Aplicações Delphi com SQLite embarcado (mobile ou desktop);
- Situações onde o banco precisa ser gerado em tempo de execução;
- Sincronização de dados vindos de APIs REST;
- Estratégias híbridas online/offline com manipulação local eficiente;
- Prototipagem rápida utilizando
TFDMemTable
e persistência automática.
Conclusão
O SQLite4D
é mais do que um helper — é uma mini engine de persistência declarativa para SQLite com Delphi. Ele simplifica, acelera e fortalece seus projetos com código limpo, reutilizável e seguro. Foi desenvolvido para desenvolvedores que valorizam clareza, robustez e produtividade — sem abrir mão do controle.
🔗 O projeto já está disponível no GitHub:
github.com/TheCodeNaked/SQLite4D