{"id":450,"date":"2016-08-30T00:25:55","date_gmt":"2016-08-30T03:25:55","guid":{"rendered":"http:\/\/www.pascalscada.com\/?page_id=450"},"modified":"2026-09-17T09:36:56","modified_gmt":"2026-09-17T12:36:56","slug":"hmidbconnection","status":"publish","type":"page","link":"https:\/\/www.pascalscada.com\/pb\/hmidbconnection\/","title":{"rendered":"HMIDBConnection"},"content":{"rendered":"<h5>Introdu\u00e7\u00e3o<\/h5>\n<p><img decoding=\"async\" alt=\"\" src=\"https:\/\/www.pascalscada.com\/wp-content\/uploads\/2026\/09\/THMIDBConnection.png\"> O <code>THMIDBConnection<\/code> \u00e9 o componente de acesso a banco de dados do PascalSCADA: conecta a um SGBD e executa comandos SQL <strong>de forma ass\u00edncrona<\/strong>, sem travar a interface enquanto a consulta roda. Fica no pacote <code>pascalscada_db<\/code> (que n\u00e3o depende da LCL, ent\u00e3o tamb\u00e9m funciona em aplica\u00e7\u00f5es de console) e \u00e9 a base de componentes como o <a href=\"\/pb\/event-and-alarm-loggers\/\"><code>THMIEventLogger<\/code> e o <code>THMIAlarmLogger<\/code><\/a>, al\u00e9m de servir para telas pr\u00f3prias \u2014 cadastro de usu\u00e1rios, receitas, hist\u00f3rico.<\/p>\n<p>Por baixo, ele usa o <strong>SQLdb<\/strong> do Free Pascal (n\u00e3o mais o ZeosLib, removido como depend\u00eancia a partir da vers\u00e3o 0.7.7 \u2014 veja <a href=\"\/pb\/how-install-pascalscada\/\">Como instalar<\/a>). Isso significa que o <code>THMIDBConnection<\/code> j\u00e1 sai pronto para os bancos com conector SQLdb; n\u00e3o \u00e9 um ORM nem esconde SQL de voc\u00ea \u2014 voc\u00ea escreve o comando e ele cuida da fila, da thread e do retorno do resultado.<\/p>\n<h5>Bancos de dados suportados<\/h5>\n<p>A propriedade <strong><code>Protocol<\/code><\/strong> escolhe o banco. No Object Inspector ela vem com uma lista suspensa fixa:<\/p>\n<table>\n<thead>\n<tr>\n<th>Protocol<\/th>\n<th>Conector SQLdb usado<\/th>\n<th>Observa\u00e7\u00e3o<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>postgresql<\/td>\n<td>PostgreSQL<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>sqlite<\/td>\n<td>SQLite3<\/td>\n<td><code>Database<\/code> \u00e9 o caminho do arquivo <code>.db<\/code>\/<code>.sqlite<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>mysql<\/td>\n<td>MySQL 5.7<\/td>\n<td>S\u00f3 o driver de fio do MySQL 5.7 \u00e9 suportado no momento \u2014 mesmo em servidores mais novos, funciona pelo protocolo de compatibilidade.<\/td>\n<\/tr>\n<tr>\n<td>firebird<\/td>\n<td>Firebird<\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Valores herdados de instala\u00e7\u00f5es antigas com sufixo de vers\u00e3o (por exemplo <code>mysql-5.7<\/code>, formato que o ZeosLib usava) continuam funcionando: s\u00f3 a parte antes do tra\u00e7o \u00e9 considerada para escolher o conector.<\/p>\n<h5>Propriedades de conex\u00e3o<\/h5>\n<table>\n<thead>\n<tr>\n<th>Propriedade<\/th>\n<th>Descri\u00e7\u00e3o<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Protocol<\/td>\n<td>Um dos quatro protocolos da tabela acima.<\/td>\n<\/tr>\n<tr>\n<td>HostName<\/td>\n<td>Endere\u00e7o ou nome da m\u00e1quina do banco. Em SQLite n\u00e3o \u00e9 usado.<\/td>\n<\/tr>\n<tr>\n<td>Port<\/td>\n<td>Porta de conex\u00e3o. <code>0<\/code> deixa a porta padr\u00e3o do conector.<\/td>\n<\/tr>\n<tr>\n<td>Database<\/td>\n<td>Nome do banco (Postgres\/MySQL\/Firebird) ou caminho do arquivo (SQLite).<\/td>\n<\/tr>\n<tr>\n<td>User, Password<\/td>\n<td>Credenciais de acesso.<\/td>\n<\/tr>\n<tr>\n<td>Catalog<\/td>\n<td>S\u00f3 tem efeito em bancos cujo conector SQLdb tem o conceito de cat\u00e1logo; na maioria \u00e9 ignorado.<\/td>\n<\/tr>\n<tr>\n<td>Properties<\/td>\n<td>Lista de <code>chave=valor<\/code> repassada direto para os par\u00e2metros de conex\u00e3o do SQLdb (por exemplo, op\u00e7\u00f5es de charset ou de SSL espec\u00edficas do conector).<\/td>\n<\/tr>\n<tr>\n<td>LibraryLocation<\/td>\n<td>Caminho de uma biblioteca cliente nativa (<code>libpq.so<\/code>, <code>libmysqlclient.so<\/code>\u2026) quando ela n\u00e3o est\u00e1 no caminho padr\u00e3o do sistema.<\/td>\n<\/tr>\n<tr>\n<td>Connected<\/td>\n<td>Abre ou fecha a conex\u00e3o.<\/td>\n<\/tr>\n<tr>\n<td>ReadOnly<\/td>\n<td>Ver se\u00e7\u00e3o pr\u00f3pria abaixo.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h5>Como os comandos s\u00e3o executados<\/h5>\n<p>O <code>THMIDBConnection<\/code> n\u00e3o exp\u00f5e um <code>TDataset<\/code> conectado ao vivo: ele enfileira comandos SQL numa thread pr\u00f3pria (<code>TProcessSQLCommandThread<\/code>) que os executa <strong>em ordem<\/strong>, um de cada vez, sobre a mesma conex\u00e3o. Isso evita duas telas concorrendo pela mesma conex\u00e3o SQLdb (que n\u00e3o \u00e9 thread-safe) e mant\u00e9m a interface responsiva mesmo com uma consulta lenta.<\/p>\n<p>Os dois m\u00e9todos que colocam trabalho na fila:<\/p>\n<pre class=\"brush: delphi; tab-size: 2; title: ; notranslate\" title=\"\">procedure ExecSQL(sql: UTF8String; ReturnDatasetCallback: TReturnDataSetProc;\n                   ReturnSync: Boolean = true; NewConnection: Boolean = false);\n\nprocedure ExecTransaction(statements: THMIDBConnectionStatementList;\n                   ReturnTransactionResult: TReturnTransactionStatementsProc;\n                   FreeStatemensAfterExecute: Boolean;\n                   ReturnSync: Boolean = true; NewConnection: Boolean = false);<\/pre>\n<ul>\n<li><strong><code>ExecSQL<\/code><\/strong> executa um \u00fanico comando. Se ele come\u00e7ar com <code>SELECT<\/code>, o resultado chega pronto \u2014 um <code>TFPSBufDataSet<\/code> (um dataset em mem\u00f3ria, veja abaixo) \u2014 em <code>ReturnDatasetCallback<\/code>; para <code>INSERT<\/code>\/<code>UPDATE<\/code>\/<code>DELETE<\/code>\/DDL, passe <code>nil<\/code> no callback ou ignore o par\u00e2metro do dataset. Cada <code>ExecSQL<\/code> roda dentro da sua pr\u00f3pria transa\u00e7\u00e3o, com commit autom\u00e1tico ao final se n\u00e3o houver erro.<\/li>\n<li><strong><code>ExecTransaction<\/code><\/strong> executa uma <strong>lista<\/strong> de comandos como uma \u00fanica transa\u00e7\u00e3o: se qualquer um falhar, todos s\u00e3o desfeitos. <code>FreeStatemensAfterExecute<\/code> decide se a lista de strings \u00e9 liberada pelo componente ao terminar. O callback informa sucesso\/falha, em qual linha da lista parou (<code>LineOfError<\/code>) e a exce\u00e7\u00e3o, se houve uma.<\/li>\n<li><strong><code>ReturnSync<\/code><\/strong> decide <strong>em qual thread<\/strong> o seu callback roda: <code>True<\/code> (padr\u00e3o) o traz de volta para a thread principal via <code>Synchronize<\/code> \u2014 seguro para tocar em controles visuais; <code>False<\/code> chama o callback <strong>na pr\u00f3pria thread de banco<\/strong>, mais r\u00e1pido mas s\u00f3 seguro para c\u00f3digo que n\u00e3o mexe na interface.<\/li>\n<li><strong><code>NewConnection<\/code><\/strong>: quando <code>True<\/code>, for\u00e7a fechar e reabrir a conex\u00e3o antes de rodar o comando. \u00datil para isolar um comando de um estado de conex\u00e3o suspeito, ao custo do tempo de reconectar.<\/li>\n<li><strong><code>GetPendingSQLCommands<\/code><\/strong> informa quantos comandos ainda est\u00e3o na fila \u2014 d\u00e1 para usar como indicador visual de fila cheia numa tela que dispara muitas escritas.<\/li>\n<\/ul>\n<p>O dataset que chega em <code>ReturnDatasetCallback<\/code> (assinatura <code>procedure(Sender: TObject; DS: TFPSBufDataSet; error: Exception)<\/code>) \u00e9 criado exclusivamente para aquela chamada \u2014 voc\u00ea \u00e9 dono dele a partir da\u00ed: ligue-o a um <code>TDataSource<\/code>\/<code>TDBGrid<\/code> e libere-o (<code>DS.Free<\/code>) quando n\u00e3o precisar mais. Em caso de erro, <code>DS<\/code> vem <code>nil<\/code> e <code>error<\/code> traz a exce\u00e7\u00e3o original do SQLdb.<\/p>\n<h5>Acesso somente leitura<\/h5>\n<p>Com <strong><code>ReadOnly = True<\/code><\/strong>, todo comando que n\u00e3o seja um <code>SELECT<\/code> \u00e9 recusado antes de chegar ao banco \u2014 o callback recebe erro, nada \u00e9 executado. Use numa tela de consulta ou numa segunda inst\u00e2ncia de conex\u00e3o que s\u00f3 deve exibir dados, nunca alter\u00e1-los.<\/p>\n<h5>Acesso s\u00edncrono direto<\/h5>\n<p>Para os casos em que voc\u00ea quer usar componentes de dados do SQLdb (<code>TSQLQuery<\/code>, <code>TDataSource<\/code>) ligados diretamente \u00e0 conex\u00e3o \u2014 um <code>DBGrid<\/code> edit\u00e1vel, por exemplo \u2014 em vez de passar pela fila ass\u00edncrona, o <code>THMIDBConnection<\/code> exp\u00f5e a conex\u00e3o interna:<\/p>\n<pre class=\"brush: delphi; tab-size: 2; title: ; notranslate\" title=\"\">function  GetSyncConnection: TSQLConnector;\nprocedure LockSyncConnection;\nprocedure UnlockSyncConnection;<\/pre>\n<p><code>GetSyncConnection<\/code> devolve o <code>TSQLConnector<\/code> usado internamente. Como ele \u00e9 compartilhado com a thread ass\u00edncrona, cerque qualquer uso direto com <code>LockSyncConnection<\/code>\/<code>UnlockSyncConnection<\/code> (a mesma se\u00e7\u00e3o cr\u00edtica que a fila usa) para n\u00e3o colidir com um <code>ExecSQL<\/code> em andamento.<\/p>\n<h5>Formatando literais SQL<\/h5>\n<p>Montar comandos SQL por concatena\u00e7\u00e3o de string \u00e9 a porta de entrada cl\u00e1ssica para inje\u00e7\u00e3o de SQL e para bugs de localidade (um <code>FloatToStr<\/code> que vira v\u00edrgula decimal no Brasil e quebra o <code>INSERT<\/code>). Para isso o <code>THMIDBConnection<\/code> tem quatro fun\u00e7\u00f5es de classe \u2014 chamadas sem precisar de uma inst\u00e2ncia:<\/p>\n<table>\n<thead>\n<tr>\n<th>Fun\u00e7\u00e3o<\/th>\n<th>Faz<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>FormatSQLString(str, EmptyIsNull=False)<\/td>\n<td>Envolve o texto em aspas simples e <strong>dobra<\/strong> cada aspa simples interna (<code>O'Brien<\/code> vira <code>'O''Brien'<\/code>), neutralizando tentativas de inje\u00e7\u00e3o. Com <code>EmptyIsNull = True<\/code>, uma string vazia vira o literal <code>NULL<\/code> em vez de <code>''<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>FormatSQLNumber(numero, casasDecimais=0)<\/td>\n<td>Formata sempre com <strong>ponto<\/strong> como separador decimal, independente da localidade do sistema operacional.<\/td>\n<\/tr>\n<tr>\n<td>FormatPGDatetime(dataHora)<\/td>\n<td>Formata como <code>'AAAA-MM-DD HH:NN:SS.ZZZ'<\/code>, entre aspas \u2014 o literal de timestamp aceito pelo PostgreSQL (e pela maioria dos bancos), com milissegundos, sem depender da localidade.<\/td>\n<\/tr>\n<tr>\n<td>FormatSQLUUID(uuid)<\/td>\n<td>Formata um <code>TGuid<\/code> como literal de texto entre aspas, sem chaves \u2014 o formato que Postgres e a maioria dos bancos esperam para colunas UUID.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre class=\"brush: delphi; tab-size: 2; title: ; notranslate\" title=\"\">SQL := &#039;INSERT INTO eventos (mensagem, valor, quando, id) VALUES (&#039; +\n        THMIDBConnection.FormatSQLString(Mensagem) + &#039;, &#039; +\n        THMIDBConnection.FormatSQLNumber(Valor, 2) + &#039;, &#039; +\n        THMIDBConnection.FormatPGDatetime(Now) + &#039;, &#039; +\n        THMIDBConnection.FormatSQLUUID(NovoGuid) + &#039;)&#039;;\nDBConn.ExecSQL(SQL, nil, false);<\/pre>\n<h5>Exemplo passo a passo<\/h5>\n<p>Gravar um evento de forma ass\u00edncrona, sem travar a tela:<\/p>\n<ol>\n<li>Solte um <code>THMIDBConnection<\/code>: <code>Protocol = 'postgresql'<\/code>, <code>HostName<\/code>, <code>Database<\/code>, <code>User<\/code>, <code>Password<\/code>, <code>Connected = True<\/code>.<\/li>\n<li>No clique de um bot\u00e3o (ou em qualquer evento):<\/li>\n<\/ol>\n<pre class=\"brush: delphi; tab-size: 2; title: ; notranslate\" title=\"\">procedure TForm1.Button1Click(Sender: TObject);\nvar\n  SQL: String;\nbegin\n  SQL := &#039;INSERT INTO eventos (mensagem, quando) VALUES (&#039; +\n          THMIDBConnection.FormatSQLString(EdMensagem.Text) + &#039;, &#039; +\n          THMIDBConnection.FormatPGDatetime(Now) + &#039;)&#039;;\n  HMIDBConnection1.ExecSQL(SQL, nil, false);\nend;<\/pre>\n<ol start=\"3\">\n<li>Para uma consulta que devolve dados, implemente o callback:<\/li>\n<\/ol>\n<pre class=\"brush: delphi; tab-size: 2; title: ; notranslate\" title=\"\">procedure TForm1.CarregarEventos;\nbegin\n  HMIDBConnection1.ExecSQL(&#039;SELECT * FROM eventos ORDER BY quando DESC LIMIT 100&#039;,\n                            @MostrarEventos, true);\nend;\n\nprocedure TForm1.MostrarEventos(Sender: TObject; DS: TFPSBufDataSet; error: Exception);\nbegin\n  if Assigned(error) then begin\n    ShowMessage(&#039;Erro: &#039; + error.Message);\n    Exit;\n  end;\n  DataSource1.DataSet := DS;   \/\/ DBGrid1.DataSource = DataSource1\nend;<\/pre>\n<p>Como <code>ReturnSync = true<\/code>, o callback j\u00e1 chega na thread principal \u2014 pode ligar <code>DS<\/code> direto num <code>TDataSource<\/code> sem <code>Synchronize<\/code> extra.<\/p>\n<h5>Exemplos relacionados<\/h5>\n<ul>\n<li><a href=\"https:\/\/github.com\/fluisgirardi\/pascalscada_v0\/tree\/master\/examples\/laz_hmialarms\"><code>examples\/laz_hmialarms<\/code><\/a> \u2014 <code>THMIDBConnection<\/code> alimentando um <code>THMIAlarmLogger<\/code>, com <code>DBGrid<\/code> mostrando os alarmes ativos.<\/li>\n<li><a href=\"https:\/\/github.com\/fluisgirardi\/pascalscada_v0\/tree\/master\/examples\/laz_hmieventlogger\"><code>examples\/laz_hmieventlogger<\/code><\/a> \u2014 <code>THMIDBConnection<\/code> com <code>THMIEventLogger<\/code>, gravando o hist\u00f3rico de eventos.<\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Introdu\u00e7\u00e3o O THMIDBConnection \u00e9 o componente de acesso a banco de dados do PascalSCADA: conecta a um SGBD e executa comandos SQL de forma ass\u00edncrona, sem travar a interface enquanto &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"open","ping_status":"closed","template":"page-templates\/full-width.php","meta":{"footnotes":""},"class_list":["post-450","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/pages\/450","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/comments?post=450"}],"version-history":[{"count":6,"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/pages\/450\/revisions"}],"predecessor-version":[{"id":1240,"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/pages\/450\/revisions\/1240"}],"wp:attachment":[{"href":"https:\/\/www.pascalscada.com\/pb\/wp-json\/wp\/v2\/media?parent=450"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}