Fenner
Biblioteca

Bancos de dados

A camada System.DB e os drivers de Firebird, PostgreSQL, MySQL e SQLite, com dinheiro exato e parâmetros nomeados.

O System.DB é uma camada única de acesso a dados com um driver por banco. O código que fala com a camada não muda quando o banco muda.

DriverMóduloObservação
FirebirdSystem.DB.Firebird
PostgreSQLSystem.DB.PostgreSqltexto e linha em formato binário
MySQLSystem.DB.MySqltexto e binário
SQLiteSystem.DB.SqLiteembutido na RTL: sem servidor, nada a instalar

Um exemplo completo#

db-sqlite.fxp
program SqliteDemo;

uses
  System.DB,
  System.DB.SqLite;

var
  Conn: TDbConnection;

procedure Run(const ASql: UString);
var
  Query: TDbQuery;
begin
  Query := Conn.CreateQuery(ASql);
  try
    Query.SetNoParams;
    Query.Execute;
  finally
    Query.Dispose;
  end try;
end procedure;

procedure AddProduct(const AName: UString; const APrice: Currency64);
var
  Query: TDbQuery;
begin
  Query := Conn.CreateQuery('insert into product (name, price) values (:name, :price)');
  try
    Query.ParamByName('name').AsUString := AName;
    Query.ParamByName('price').AsCurrency64 := APrice;
    Query.Execute;
  finally
    Query.Dispose;
  end try;
end procedure;

var
  Params: TDbConnectParams;
  Query: TDbQuery;
  Price: Currency64;
begin
  Params.FDbTypeId := TDatabaseType.SqLite;
  Params.FDatabase := 'demo_sqlite.db';
  DeleteFile(Params.FDatabase);
  Conn := TDbConnection.StdCreateAndConnect(Params, nil, nil);
  try
    Run('create table product (id integer primary key autoincrement, name text, price decimal(18,4))');

    // Currency64: dinheiro exato, sem passar por ponto flutuante
    Price := 19.99;
    AddProduct('Caneta', Price);
    Price := 3.5;
    AddProduct('Caderno', Price);

    Query := Conn.CreateQuery('select name, price from product order by name');
    try
      Query.SetNoParams;
      Query.Open;
      if not Query.IsEmpty then
        repeat
          WriteLn(Query.FieldByName('name').AsUStringDef, ': ',
            Query.FieldByName('price').AsCurrency64Def);
        until not Query.MoveNext;
    finally
      Query.Dispose;
    end try;
  finally
    Conn.Dispose;
  end try;
end.
Saída
Caderno: 3.5000
Caneta: 19.9900

A forma é sempre a mesma:

  1. Conectar: preencha um TDbConnectParams (o tipo do banco em FDbTypeId, o FDatabase e, nos bancos de servidor, host, porta e credenciais) e chame TDbConnection.StdCreateAndConnect.
  2. Consultar: Conn.CreateQuery(sql) devolve um TDbQuery. Para uma consulta sem parâmetros, SetNoParams; para parâmetros nomeados (:name), ParamByName('name').AsUString := ….
  3. Executar ou abrir: Execute para o que não devolve linhas; Open para um select.
  4. Percorrer: IsEmpty e MoveNext (if not Query.IsEmpty then repeat … until not Query.MoveNext;). Cada coluna sai de FieldByName('col') com um conversor (AsUStringDef, AsInt64Def, AsCurrency64Def, AsFloat64Def, AsDateDef, AsBytesDef…).
  5. Liberar: Query.Dispose e Conn.Dispose, em try…finally.

Transações: Commit e Rollback na conexão. O parâmetro que não foi vinculado vira NULL, não erro.

O que o driver garante#

Os drivers de SQLite, MySQL e PostgreSQL foram escritos e provados contra as armadilhas que mais machucam em aplicativo de dados. Cada armadilha tem uma "sonda" (um programa de teste contra um banco de verdade):

  • Dinheiro exato. O SQLite não tem tipo decimal, e o caminho ingênuo (ler como double) perde centavo em valor grande. A leitura passa por Currency64, e a sonda confere até o valor de oito dígitos com a quarta casa.
  • Nulo não é cadeia vazia. Um driver que escreve '' quando a coluna é nula perde, em silêncio, a diferença entre "não informado" e "informado vazio". Aqui o valor tem um estado de nulo (AsUString.IsValid).
  • BLOB de ida e volta, com byte zero no meio. Byte zero é o que quebra quem trata binário como cadeia terminada em zero.
  • Transação desfeita. Um Rollback que não desfaz é pior que não ter transação.
  • Reabrir a mesma consulta. Close e Open de novo não deixam os campos duplicados.
  • Parâmetro nomeado, inclusive o não vinculado.

Escolhendo o banco no tempo de execução#

O TDatabaseType (Firebird, PostgreSql, SqLite, MySql) e o TDatabaseTypeAndVersion (PostgreSql_9…PostgreSql_13, SqLite_3…) identificam o banco e a versão; a conexão os expõe, e o código pode se adaptar ao dialeto.