Usando Zeos – Conectando uma base de dados com ZConnection

Neste artigo vamos esmiuçar o componente ZConnection, o principal componente da paleta “Zeos”. Sim, sem ele não conseguiriamos conectar a uma base de dados. Se tiver dúvida em como instalá-lo, veja este artigo intitulado “ZeosLib“.

O componente ZConnection permite que sua aplicação conecte-se a uma lista bem grande de base de dados, mas antes de falar dele, vamos esclarecer dois tipos de acessos a dados, o direto e o indireto:

  • Acesso indireto: É quando o acesso a base de dados depende de um componente terceiro, comumente chamado de API de acesso a dados, exemplos conhecidos são: ADO, ODBC e JODBC. Essas APIs conversam com outra API chamado de ‘SQL Client’ que no Windows refere-se a uma DLL distribuida para conversar com o banco de dados.
  • Acesso direto: É quando o acesso a base de dados é direto ao banco de dados ou por meio da API ‘SQL Client’, no Windows refere-se a uma DLL distribuída para conversar com o banco de dados.

O Zeos tem acesso indireto a virtualmente a qualquer banco de dados compativel com ADO e ODBC(Zeos8+). Neste caso você deve instalar além do ‘SQL Client’ também os drivers ADO em seu sistema.

O Zeos separadamente tem um outro componente chamado de ZODBC, ele permite acesso a qualquer banco de dados que tenha o ‘SQL Client’e drivers ODBC instalados.

Porém a maior vantagem é o acesso direto, pois nesse caso apenas o ‘SQL Client’ é o suficiente para acessar o banco de dados, tornando geralmente o acesso mais rápido emais fácil de distribuir com a sua aplicação. Contudo, há vantagens em usar drivers ADO/ODBC em certas circunstâncias, por exemplo quando desejamos fazer um pool de conexões. É possivel fazer um pool de conexões, mas daí teríamos de programar.

Conheçam o componente ZConnection, este aqui:

Componente ZConnection

Vamos explicar suas propriedades antes de demonstrar como fazemos a conexão:

AutoCommit

Valor Verdadeiro ou Falso. Para explicar como funciona, precisamos entender o que é uma transação, neste contexto, uma transação é um período que iniciamos uma série de modificações no banco de dados que deve ser encerrada com um commit, a confirmação de que tudo que foi feito desde o inicio da transação deve ser mantido ou o rollback que é desfazer toda a transação. Se o Modo AutoCommit estiver ligado não é necessário iniciar uma transação e nem dar commit porque o componente enviará o commit implicitamente a cada operação de escrita que ele detectar, contudo podem existir operações que o Zeos não detecte corretamente como sendo uma operação de escrita, por exemplo:

  • select * from someprocedure;
  • select * from someprocedure(:param1, :param2);

O Zeos não tem como saber se a stored procedure irá ou não escrever dados, este é apenas um exemplo de que o modo Autocommit pode falhar quando objetos são executados dentro do banco de dados que o Zeos não pode ver. Então, se o banco de dados tem suporte a autocommit internamente é melhor habilitá-lo. Algo muito interessante sobre o modo Autocommit é que ao iniciar as transações manualmente com o comando ZConnection.StartTransaction, o Modo Autocommit será desabilitado momentaneamente até que você dê um commit ou rollback.

No link a seguir há uma bela explicação com diagramas e graficos sobre o funcionamento dessa propriedade:

https://sourceforge.net/p/zeoslib/wiki/TZConnection.AutoCommit/

Há um vídeo explicativo que dá outros detalhes sobre o autocommit e descobrir se ele é um vilão ou herói:

Conexão ao banco de dados com ZeosLib/Autocommit – Lazarus

AutoEncodeStrings:

Valor do tipo Verdadeiro ou Falso. Quando AutoEncodeStrings está ativado (true), Zeos tenta traduzir seus dados entre o conjunto de caracteres de conexão e a configuração ControlsCodePage. Isso pode levar à perda de dados se nenhuma conversão for possível. AutoEncodeStrings, portanto, só deve ser definido como true se uma conversão for necessária. No Zeos 7.2 ou superior esta propriedade é inoqua e não existe mais, se ela aparece em seu sistema, ignore-a, ela só existe para não quebrar a compatibilidade com versões anteriores.

Observação: Para evitar dores de cabeça com conversão de página de caracteres, sempre que possivel use Unicode como UTF-8. Desenvolver usando o ambiente Windows pode ser complexo porque o mesmo usa o WIN1252(Ansi) para os aplicativos com GUI enquanto o terminal usa o ISO 8859_1/cp850(conhecido como Latin1) e essa sopa de codificações, especialmente em arquivos textuais fazem um inferno se não prestar muita atenção a como se lê ou grava arquivos.

Catalog:

Valor do tipo String. É uma propriedade dedicada a banco de dados Postgree, não tem uso para outros bancos de dados pelo que sei.

Protocol:

Valor do tipo String. Essa string definirá o protocolo(ou driver) de comunicação de banco de dados que o Zeos usará, se for para se comunicar com o FirebirdSQL então é “firebird”. Ao definir o protocolo, algumas propriedades como ClientCodePage tornar-se-ão selecionáveis, isto porque apenas quando sabemos qual é o protocolo poderemos saber quais páginas de código estarão disponíveis ao protocolo selecionadp. A partir da versão Zeos8+ os protocolos ODBC e OLEDB foram adicionados podendo então acessar qualquer banco de dados que tenham drivers nestas frameworks para eles.

No nome de protocolo há uma palavra reservada de prefixo chamada pooled que só deve ser usada quando se implementa um pool de conexões, isto é, manter conexões abertas para pronto-usufruto. Normalmente requerido quando servimos conexões advindas da WEB com proposito de consultas.

ClientCodepage:

Valor do tipo String. Este é o conjunto de caracteres que nosso programa solicitará ao servidor de banco de dados para enviar ao nosso programa. A maioria dos servidores pode converter facilmente caracteres entre o solicitante e o servidor. Em IDEs totalmente unicode como o Lazarus, é melhor solicitar dados de caracteres em unicode e deixar que o sistema hospedeiro lide com o resto seja ele ISO8859_1(Latin1) ou WIN1252(Ansi). No ZConnection esta propriedade também pode ser configurada em “Properties” através dos parâmetros “lc_ctype” ou “Codepage”:
ZConection.Properties.Add (‘lc_ctype=ISO8859_1’); // firebird
ou
ZConnection.Properties.Add (‘Codepage=ISO8859_1’); // outros

Porque você deve dar atenção a isso, afinal o banco de dados armazena o que tiver que armazenar não importa a codificação no lado cliente, certo? É verdade que o servidor aceitará o que o lado cliente enviar e não há um tratamento específico para uma ou outra codificação de página, no entanto, quando o lado cliente indica qual é página de código a qual o texto será armazenado então o banco saberá também como “traduzi-lo” depois. Então identificar o charset na conexão servirá como pedra de roseta traduzindo os “code points” que é a representação do caractere entre o banco de dados e o lado cliente.

Observação muito importante: Muito cuidado ao carregar scripts de arquivos para dentro do banco de dados, geralmente editores como vscode, notepad++ criam arquivos usando a codificação UTF-8, mas se seu banco esta ajustado para ISO8859_1(Latin1) ou WIN1252(Ansi) você poderá ter problemas com alguns tipos de acentuações. Como eu disse, não será culpa do banco de dados em não aceitá-los, é a pedra de roseta sendo bugada achando que está usando um código de página, mas ao carregar um arquivo externo está em outra. Ao desenvolver dentro do Linux/BSD/Mac, o UTF-8 reina absoluto em todas as aplicações, IDE, editores,…. então dificilmente você encontra problemas desse tipo, mas no Windows há uma salada de código de páginas que você precisa ter cuidado ao criar ou usufruir de dados externos numa codificação diferente de sua conexão.

ControlsCodePage:

Valor do tipo Verdadeiro ou Falso. Isso permite determinar qual codificação de caracteres você prefere em seus TDatasets (TZQuery, TZReadOnlyQuery e TZTable). Existem 3 opções, porém apenas uma verdadeiramente funciona:

  • cCP_UTF8 : Esta é a configuração padrão do Lazarus. As strings passadas são esperadas como UTF8 e os TStringFields são passados. Como TStringField fornece apenas um byte para caracteres multibyte, Zeos aumenta o tamanho de TStringField quatro vezes. Uma string no formato VarChar com 50 caracteres tem um TSTringField.Size de 200 caracteres.
  • cCP_UTF16 e cGET_ACP: são implementados por causa do Delphi e não devem ser usados ​​no Lazarus.

Quando se usa UTF-8 no banco, mas sua conexão for Win1252 como código de página então há uma nota muito importante que refere-se a um bug até o Zeos 7.2 que merece sua atenção.

Database:

Valor do tipo String. Refere-se a localização do banco de dados, seja a forma literal com C:\Caminho\para\o\banco.fdb” ou em forma de string de conexão como:

localhost/3050:C:\Caminho\para\o\banco.fdb (remoto no próprio computador)
intranet.local/3050:banco.fdb (remoto em outro computador)
xnet://C:\Caminho\para\o\banco.fdb (local)

Ou outras formas reconhecidas pelo ADO ou ‘SQL Client’ de seu banco de dados.

Alguns bancos de dados como o FirebirdSQL, MSSQL, PostgreSQL,… uma string de conexão pode suprir tudo que o ‘SQL Client’ precise para se conectar a base, de forma que as propriedades como Host, Port, Protocol, ClientCodepage, UserName e Password fiquem desnecessárias.

DesignConnection:

Valor do tipo Verdadeiro ou Falso. Se o valor for definido como true(verdadeiro), a conexão estará ativa em tempo de design, mas permanecerá desconectado quando você der um ‘run‘ no projeto ou executar o aplicativo standalone. Isso evita conexões ativas esquecidas dentro do executável que só rodam em localhost e quando vai para o ambiente de produção causam problemas. Nem precisamos dizer que essa opção é muito útil.

HostName:

Valor do tipo String. Nome do host que servirá o banco de dados, pode ser um IP, nome qualificado de rede ou simplesmente “localhost”, em todos estes casos estamos reivindicando o uso cliente/servidor de acesso. É importante dizer que “localhost” não é exatamente um acesso sem rede como alguns afirmam, mas que o host é o próprio computador que estamos usando, em poucas palavras você usa a infraestrutura de rede para localizar a si mesmo. Quando o acesso é direto ao arquivo físico dizemos que se trata uma conexão local e nessa situação não é necessário informar o hostname e nem port.

LibraryLocation:

Valor do tipo String. Indica onde a biblioteca de acesso(SQL Client) estará, no caso do FirebirdSQL chama-se fbclient.dll no Windows ou libfbclient.so no Linux. Deixar este parâmetro em branco implica em que a localização seja automática e isso pode ser um problema para alguns bancos que ao longo do tempo mudaram o nome da biblioteca, por exemplo, o FirebirdSQL 1.x usava como ‘SQL Client’ um arquivo chamado de gds32.dll, depois virou fbclient.dll, mas por retrocompatibilidade ainda aceita gds32.dll e nessa situação nem sempre é possível garantir que o “automático” reconheça qual é a DLL certa quando ambas estão no sistema. Por isso recomendo que ao menos deixe o nome correto do ‘SQL Client’ que pretender usar, por exemplo, “fbclient.dll” e evitar que o “automatico” encontre a DLL errada primeiro.

Há um problema muito comum quando o sistema operacional do host é 64bits, pois comumente os programas para Windows ainda são 32bits. Programadores confundem achando que devem instalar o ‘SQL Client’ igual a da arquitetura do sistema operacional e isso dará errado porque o ‘SQL Client’ deve ser da mesma arquitetura dos programas que iremos usar para acessar a base de dados. A Microsoft implementou uma maneira muito eficiente de resolver este problema nos sistemas 64bits, pois bibliotecas são instaladas em C:\Windows\System32 e bibliotecas de 32bits em C:\Windows\SysWow64 e qualquer programa que instalar a DLLs usará o diretório correspondente a arquitetura dele. Contudo o desconhecimento dessa informação leva programadores a muita confusão, espalhando DLLs desnecessariamente e posteriormente confundindo a resolução do problema. Portanto, quando temos um sistema operacional 64bits, instalamos o SQL Client de nosso banco de dados duas vezes, o ‘SQL Client’ de 32 bits e depois a de 64bits. Alguns bancos já sabem disso e simplificam essa operação, o FirebirdSQL 3+ ao instalar o ‘SQL Client’ de 64bits, já instala também a versão de 32bits.

É digno de nota que se você especificar um caminho como “c:\app\bin\fbclient.dll” como seu Librarylocation então o sistema não a procurará no path do sistema, mas apenas a que foi especificada, chamaremos isso de ‘engessar a DLL’. Muitos programadores tomam essa atitude por considerar uma forma mais simples, contudo isso cria outro problema: você será o responsável por atualizar a ‘SQL Client’ manualmente e pbter correção de bugs e falhas de segurança. Se estiver implantando um sistema na nuvem, nunca deveria engessar a ‘SQL Client’ a uma localização. Ambientes Linux atualizam programas obtidos de seus repositórios automaticamente e atualizará o ‘SQL Client’ também.

LoginPrompt:

Valor do tipo Verdadeiro ou Falso. Se for “true”, perguntará o nome de usuário e senha toda vez que conectar-se, mesmo que os parâmetros de UserName e Password tenham sido fornecidos.

User:

Valor do tipo String. O nome de usuário do banco de dados. Por exemplo, “SYSDBA”, “dbo”, “sa”,…

Password:

Valor do tipo String. Senha para conectar-se ao banco de dados, exemplo: masterkey

Port:

Valor do tipo Inteiro(0-65535). Especifica a porta de conexão, no exemplo do Firebird, geralmente é 3050. Se deixar “0” o Zeos vai usar o default conforme o protocolo(driver) do banco de dados.

TransactIsolationLevel (TIL):

Valores: tiNone, tiReadCommitted, tiReadUncommitted, tiRepeatableRead, tiSerializable. Determina qual será o Transaction Isolation Level(TIL daqui em diante). Explicação para cada um deles:

  • tiNone: Como o nome sugere, significa “nenhum”, como “nenhum” é uma escolha impossível para um banco de dados relacional, isto significa que você usará properties (parametros daqui em diante) com um set de comandos que definirá o TIL. Deixar tiNone e não ter nenhum tipo de properties que formem um TIL causará uma mensagem de erro fatal porque a característica ACID de um banco de dados só faz sentido se houver um TIL definido.
  • tiRepeatableRead: Corresponde ao TIL “REPEATABLE READ”. Uma transação vê durante seu tempo de vida apenas os dados confirmados antes que a transação tenha sido iniciada.
  • tiReadCommitted: Corresponde ao TIL “READ COMMITTED”. Uma transação vê apenas os dados confirmados antes que a instrução seja executada. Essa é uma sutil diferença para o “repeatable read” .
  • tiReadUnCommitted: Corresponde ao TIL “READ UNCOMMITTED”. Uma transação vê as alterações feitas por transações não confirmadas. Também é conhecida em outros bancos de dados como TIL “SNAPSHOT”.
  • tiSerializable: Corresponde ao TIL “SERIALIZABLE”. Este é o nível de isolamento mais estrito, que impõe a serialização da transação, isto significa que em todas as vezes você inicia a transação e depois dá um commit ou rollback. Se usar uma clausula WHERE o bloqueio poderá impedir outros de iniciarem a atualização dos mesmos registros. Os dados acessados no contexto de uma transação SERIALIZABLE não podem ser acessados por nenhuma outra transação, exceto o TIL “SNAPSHOT”. Também é conhecido em outros sistemas como “concurrency control”.

A preocupação de todo “novato” é impedir o infame “dead lock”, quando na realidade, eles são bem vindos dependendo do cenário. A preocupação do DBA mais experiente são os registos “fantasmas” , elas ocorrem quando no decorrer de uma transação, novas linhas ou registros são adicionados por uma outra transação concorrente. Imagine a estação #1 num SELECT com uma clausula WHERE status=’A’ e enquanto o select esta em execução a estação #2 faz alguns INSERTs onde o STATUS é ‘A’ então sem esperar por nada a estação #1 ganha alguns registros extras que podem não estar sendo vistos dependendo do TIL e podem ou não estar de fora de um UPDATE, daí nasce o termo “registros fantasmas”. Em todas as aplicações, mas especialmente as de grande porte, um comando como UPDATE ou MERGE levanta a pergunta: “Se existirem fantasmas, o que fazer com eles, atualizo ou ignoro?” e então selecionar o TIL adequado sendo “SERIALIZABLE” o mais estrito e que provavelmente resolverá o problema. Os pormenores sobre os TILs podem ser lidos neste artigo da wikipedia.

Para usuários do banco de dados FirebirdSQL há algumas considerações(ignore se estiver usando outro banco de dados):

  • TIL “READ COMMITTED”: No FirebirdSQL corresponde a combinação dos parâmetros isc_tpb_read_committed, isc_tpb_rec_version e isc_tpb_nowait juntos. Uma transação vê apenas os dados confirmados antes que a instrução seja executada. O parâmetro “rec_version” para o Firebird é responsável pelo comportamento de que os valores mais recentes que foram “commitados” por outros usuários serão considerados. O parâmetro “nowait” é responsável pelo comportamento de que não há espera pela liberação de um registro bloqueado. Neste nível o servidor é mais sobrecarregado que no TIL “REPEATABLE READ”, porque tem que fazer todos os “refresh” para adquirir estes valores novamente. O FirebirdSQL é mais usado com esta configuração.
  • TIL “READ UNCOMMITTED”: A documentação do Zeos diz que essa opção não existe no Firebird, no entanto, ela está documentada no site oficial. Mas ao tentar usar essa opção, o Zeos responde dizendo que essa opção não é suportada. Independente de qual informação esteja correta, ler informações ainda não confirmadas podem não fazer sentido, mas pode haver cenários em que isso seja necessário. Esse TIL pode ser simulado pela ausência da property(parâmetro, daqui em diante) “isc_tpb_rec_version” ficando apenas os parâmetros: isc_tpb_concurrency e isc_tpb_nowait.
  • TIL “REPEATABLE READ” é conhecido como “SNAPSHOT”. É uma combinação dos parâmetros de transação “concurrency” e “nowait”.
  • TIL “SERIALIZABLE” é conhecido como “Snapshot table stability”. É uma combinação dos parâmetros de transação “concurrency” e “nowait”.
  • Enquanto o Zeos usa o TIL tiReadCommitted, tiReadUncommitted, tiRepeatableRead, tiSerializable que define bem o que desejamos, o Firebird é flexível em poder combinar várias características por meio de parâmetros, se entender bem como eles funcionam: isc_tpb_concurrency, isc_tpb_nowait, isc_tpb_read, isc_tpb_write, isc_tpb_consistency, isc_tpb_read_committed, isc_tpb_rec_version você terá um TIL personalizado.
  • Uma explicação por menor e com exemplos é feita pelo IbSurgeon neste artigo.

Esses TILs são a dor de cabeça de inexperientes quando se queixam que o sistema está lento, tendo dead lock ou resultando em valores errados, por isso, ter um DBA (especialista em banco de dados) ajuda em orientar programadores a usar o TIL mais adequado para o cenário em questão. Veja estes cenários a seguir e tente por conta própria decidir qual o TIL mais adequado:

Cenário #1: Digamos que você queira um relatório de fechamento qualquer, o relatório é demorado e antes que termine, durante o processo, alguns valores se modificam e no final do relatório percebe-se por exemplo que o total de cada mês não bate com o final do ano, isso aconteceu porque valores processados foram alterados enquanto o relatório ainda estava em execução, como resolveria?

Cenário #2: Digamos que seu sistema de bilhetagem de poltrona de avião tenha de resolver o seguinte problema, você não deseja que quando alguém esta decidindo a compra de uma numeração específica que outro mais rapidinho ou que more numa região geográfica com internet mais abundante passe na frente e então confirme a compra da mesma poltrona que o primeiro que vem de uma conexão mais lenta ainda não concretizou a compra. Nessa situação, qual TIL seria mais adequado? “SNAPSHOT TABLE STABILITY” ou “SERIALIZABLE” e então SELECT FOR UPDATE WITH LOCK?

Usar o mesmo TIL para uma aplicação inteira pode não ser razoável, talvez algumas características em partes do programa requeiram um TIL diferente, caso contrário, pode causar problemas lógicos de difícil compreensão. Mas se você entendeu bem os TILs, uma aplicação usando o TIL como o “SERIALIZABLE” (conhecido no FirebirdSQL como SNAPSHOT TABLE STABILITY) evitaria edições simultâneas indesejadas, mas se não souber como usar será um festival de travamentos(tecnicamente aguardando registros serem liberados) ou dead locks.

Se você não sabe bem como usufruir dos TILs, recomendo que crie aplicativos que simulem os efeitos que exemplifiquei antes de implementar uma solução dentro do projeto, se preferirem poderão usar o meu projeto de exemplo feito Lazarus/FPC chamado “lazdemo_transacoes” hospedado no github:

https://github.com/gladiston/lazdemos_gsl

Uma dificuldade com o Zeos até a versão 7.2 é que não é possível mudar de TIL após estar conectado, assim deve-se criar uma nova conexão com o TIL adequado e fazer o que precisa ser feito sob esta nova conexão. Outras suítes como Firedac, SQLdb, … tem um componente intitulado Transaction que permite ser associado a uma conexão vigente e trocá-la sem precisar abrir uma nova conexão com TIL diferente. A partir do Zeos8+ já existe um componente ZTransaction para esse proposito, se quiser ajudar o projeto, o mesmo está em:

https://sourceforge.net/projects/zeoslib/

E para fazer perguntas ou reportar bugs:

https://zeoslib.sourceforge.io/

A capacidade de trocar de TIL sem ter que estabelecer uma nova conexão é bastante interessante porque as vezes experimentamos cenários diferentes dentro do mesmo programa, por exemplo, usar “READ COMMITED” para pesquisas, mas um “SNAPSHOT” quando a situação for relatórios ou ainda “SERIALIZABLE” quando for editar registros específicos e bloquear outros de editá-las. Além disso, alguns RDBMS cobram suas licenças de uso “por conexão” ao banco ao invés de “por dispositivo”.

Properties(propriedades ou parâmetros):

Valor será uma Lista de parametros ao estilo PairList(variável=conteúdo).

Properties são parâmetros de conexão de banco de dados que podem definir comportamentos do componente Zeos ou do banco de dados, por exemplo. Como exemplo de properties que mudam o comportamento do Zeos podemos citar sua capacidade de criar um banco de dados com a properties CreateNewDatabase, veja esse exemplo:

// O codigo abaixo esta criando o banco de dados, porém o charset UTF8 que 
// deveria ter o collate UNICODE_CI_AI ficou sem nenhum collation. Este foi bug 
// que reportei e aguardo estarem resolvidos na próxima revisão.
try
  if ZConnection1.Connected then
    ZConnection1.Disconnect;
  ZConnection1.Properties.Clear;
  ZConnection1.Properties.Values['dialect']:='3';
  ZConnection1.Properties.Values['CreateNewDatabase'] :=
    'CREATE DATABASE ' + QuotedStr('C:\PATH\TO\DATABASE.FDB') +  
    ' USER ' + QuotedStr('SYSDBA') +
    ' PASSWORD ' + QuotedStr('masterkey') +
    ' PAGE_SIZE ' + intToStr(8192) +
    ' DEFAULT CHARACTER SET '+QuotedStr('UTF8')
    ' COLLATION '+QuotedStr('UNICODE_CI_AI') +';'+sLineBreak;
  ZConnection1.Connect;
  ShowMessage('banco criado!'); 
except
on e:exception do ShowMessage(e.Message);
end;   

Observação: Usar database com charset UNICODE como no exemplo acima traz algumas limitações, visto que um caractere pode consumir mais bytes que outros charsets, o tamanho de página máximo será reduzido a metade, se o tamanho de página máximo for 16.384 bytes para outros charsets, provavelmente em UNICODE não passará de 8.192 bytes.

Pessoalmente, prefiro rodar scripts para criar banco de dados porque a ferramenta iSQL é otimizada e testada para isso. Criar banco de dados usando um componente não há garantias que o mesmo foi criado sem nenhum bug.

Properties também pode ser usado para mudar a forma como o banco deve se comportar em algumas situações. No exemplo abaixo, estou usando Properties para modificar o isolamento das transações(TIL) num banco FirebirdSQL:

// propriedades de read commited para o FirebirdSQL
ZConnection1.Connection.TransactIsolationLevel := tiNone;
ZConnection1.Properties.Clear;
ZConnection1.Properties.Add('isc_tpb_read_committed');
ZConnection1.Properties.Add('isc_tpb_rec_version');
ZConnection1.Properties.Add('isc_tpb_nowait'); 

As propriedades e características variam conforme o banco de dados, mas especificamente para o TIL não é preciso usar elas já que temos a propriedade TransactIsolationLevel especializada nisso. Mudar o tipo de isolamento por meio de Properties e não usar TransactIsolationLevel := tiNone pode resultar em problema porque uma pode descaracterizar outra e o Zeos impede isso dando uma mensagem de erro fatal.

ReadOnly:

Valor do tipo Verdadeiro ou Falso. Quando “true”, nenhuma operação de escrita no banco de dados funcionará. Isso não quer dizer que não possa haver Commit ou RollBack, pois eles tem a ver também com o TIL onde registros poderão estar disponíveis apenas depois de um commit seguido de um refresh.

SQLHourGlass:

Valor Verdadeiro ou Falso. Quando SQLHourGlass é “true”, no momento em que um comando SQL é submetido, a aparência cursor incluirá as iniciais “SQL” dentro da mesma.

UseMetadata:

Valor do tipo Verdadeiro ou Falso. Se “true”, os metadados (tabelas internas do banco de dados, não as tabelas criadas por você) serão usados para determinar se as colunas(campos) são graváveis ou não, isso é necessários para que comandos SQL como insert, update e delete gerados automaticamente sejam funcionais. Boa parte dos componentes como TZUpdateSQL ou TFields precisam disso então a boa politica aqui é manter ligado, isto é, verdadeiro.

Mantenha “falso” apenas se tiver certeza de que o acesso a metadados não serão necessários, geralmente desejamos isso quando estamos escrevendo um backend ou classes onde todas as atualizações em SQL serão escritas e executadas manualmente sem as automações que falei. Alguns programadores evitam os data-awares – componentes associados a TFields – e tem seus SQLs de INSERT/UPDATE/DELETE escritos manualmente e assim também não precisam dessa opção ligada. Evitar que a aplicação faça consulta aos metadados é também uma forma de otimizar o sistema, já que sua aplicação fará menos consulta ao banco de dados, útil especialmente em serviços hospedados na nuvem como consultas REST.

Se você usa métodos como RowAffected ou RowCount, que retornam o numero de registros afetados pelo ultimo select/insert/update/delete não é muito saudável deixar essa propriedade em Verdadeiro, pois seus resultados obtidos podem ter sido influenciados por consulta ao metadados depois que você concluiu um comando SQL.

Connected

Valor do tipo Verdadeiro ou Falso. Quando “true” conecta ao banco de dados e permanece como verdadeiro, se a conexão falhar então retorna como falso. Se já estiver conectado e for informado falso então a conexão será interrompida. Ex:

    if ZConnection1.Connected then
      ZConnection1.Connected:=false;  

Mas para alguns é estranho usar do jeito acima, por isso, há esse outro jeito:

if ZConnection1.Connected then
  ZConnection1.Disconnect;

Jeitos diferentes de fazer a mesma coisa é comum na programação, muito do que conhecemos hoje vem de padrões criados em outras linguagens, isso facilita muito para que programadores de outras linguagens possam se sentir a vontade.

Conectando a uma base de dados

Agora que sabemos todas as propriedades de conexão do Zeos, podemos então conectar a uma base de dados:

try
  if zConnection1.Connected then
    zConnection1.Disconnect;
  zConnection1.AutoCommit:=false;
  //zConnection1.AutoEncodeStrings:=false; // ela é inoqua
  zConnection1.Catalog:=''; // voce usa postgre?
  zConnection1.Protocol:='firebird';
  zConnection1.ClientCodePage:='ISO8859_1';
  // A constante cCP_UTF8 precisa da unit ZCompatibility no uses
  // as constantes cCP_UTF16 e cGET_ACP não são usados no Lazarus.
  zConnection1.ControlsCodePage:=cCP_UTF8;
  zConnection1.Database:='c:\caminho\para\o\banco.fdb';
  zConnection1.Hostname:='localhost';
  zConnection1.LibraryLocation:='fbclient.dll';
  zConnection1.LoginPrompt:=false;
  zConnection1.User:='SYSDBA';
  zConnection1.Password:='masterkey';
  zConnection1.Port:=3050;
  // As constantes dentro de TransactIsolationLevel 
  // estão dentro da unit ZDbcIntfs 
  zConnection1.TransactIsolationLevel := tiReadCommitted;
  // propriedades de read commited para o FirebirdSQL irão mudar o isolamento
  // mesmo com TransactIsolationLevel=tiReadCommitted
  zConnection1.Properties.Clear;
  zConnection1.Properties.Add('isc_tpb_read_committed');
  zConnection1.Properties.Add('isc_tpb_rec_version');
  zConnection1.Properties.Add('isc_tpb_nowait');
  zConnection1.ReadOnly:=false;
  zConnection1.SQLHourGlass:=true;
  zConnection1.UseMetadata:=true;
  ZConnection1.Connected:=true;
finally
end; 

Pool de conexões

Este é um recurso muito útil para quem servirá múltiplas conexões e não necessariamente uma conexão por usuário, é quase uma exigência para serviços WEB, pois ao manter conexões abertas pré-dispostas não é necessário conectar a base todas as vezes, e se uma conexão já estiver em uso, podemos usufruir de outra disponível ou abrir uma nova. Saiba que é possível pool de conexões usando o Zeos, o método é bastante simples envolvendo uma nova propriedade em Properties e renomear a conexão e o Protocol para ter o sufixo pooled nas conexões. Mas falta material para que eu possa demonstrar como fazê-la, mas se pretende implementá-la sugiro que leia o link:

https://sourceforge.net/p/zeoslib/wiki/Connection%20Pooling/

Conclusão

Tudo que aprendemos com o Zeos tem em outras suítes de componentes de acesso a dados como o SQLdb(Lazarus), FireDAC, IBO e tantos outros. Então aprender como o Zeos funciona também nos ensina como outras suítes funcionam, aprendeu uma, praticamente aprendeu todas.

Se ainda estiver em dúvida, assista ao vídeo:

No vídeo a seguir, vamos entender a diferença entre uma conexão embarcada, local e remota usando o Banco de dados FirebirdSQL:

Caso queira estudar os exemplos, os mesmos podem ser obtidos aqui:

https://github.com/gladiston/lazdemos_gsl