Autor: Simonov Denis
Versão: 1.0 de 2025-06-25
Prefácio
A partir do Firebird 3.0, você pode escrever várias extensões (plugins) para o Firebird SQL. A extensão mais simples é uma procedure/função externa (UDR) (veja o artigo Escrevendo UDF em Pascal). UDR não é um plugin do Firebird no verdadeiro sentido da palavra, mas permite expandir significativamente as capacidades do SGBD. No entanto, a engine do Firebird tem mais capacidades para estender a funcionalidade com vários plugins.
O Firebird possui os seguintes tipos de plugins (constantes da classe IPluginManager
):
TYPE_PROVIDER
— providers;
TYPE_AUTH_SERVER
— autenticação no lado do servidor;
TYPE_AUTH_CLIENT
— autenticação no lado do cliente;
TYPE_AUTH_USER_MANAGEMENT
— gerenciamento de usuários;
TYPE_EXTERNAL_ENGINE
— engines externas. Elas são intermediárias entre o código de stored procedures/funções/triggers externos escritos em qualquer linguagem de programação e o Firebird. Exemplos de tais plugins são udr_engine
para interação com UDRs escritas em C++/Delphi ou libfbjava
para UDRs em Java;
TYPE_TRACE
— plugin de tracing;
TYPE_WIRE_CRYPT
— criptografia de tráfego de rede;
TYPE_DB_CRYPT
— criptografia de banco de dados;
TYPE_KEY_HOLDER
— detentor de chave para o plugin de criptografia de banco de dados;
TYPE_REPLICATOR
— plugin de replicação.
Neste artigo, veremos talvez o tipo de plugin mais complexo e interessante — os providers.
1. Como tudo começou?
Em 2023, comecei a trabalhar no desenvolvimento de um plugin para o HQbird para acessar bancos de dados de outros SGBDs (que não o Firebird) via EXECUTE STATEMENT ON EXTERNAL DATA SOURCE
(doravante EDS). Vlad Khorsun, desenvolvedor principal do Firebird, foi o consultor deste projeto.
Atualmente, no HQbird 2024, dois plugins estão disponíveis para o Firebird 4.0 e 5.0: MySQLEngine
e ODBCEngine
. O MySQLEngine
foi criado primeiro e foi um cavalo de testes; os principais esforços foram focados no ODBCEngine
.
2. Como a funcionalidade do EDS pode ser estendida?
Existem duas opções:
- Estender o mecanismo EDS no núcleo do Firebird;
- Implementar um dos plugins do Firebird.
Vou ser direto com você - não importa qual caminho você siga, você precisará mergulhar no código-fonte do Firebird. Simplesmente não há como contornar isso, porque você não encontrará essa informação documentada em nenhum outro lugar.
Primeiro, vamos considerar a primeira opção.
3. Extensão do mecanismo EDS
A implementação do EDS está localizada no diretório /src/jrd/extds/
. O mecanismo EXECUTE STATEMENT
em si pode ser estendido a partir do Firebird 2.5. Para este propósito, os chamados EDS Providers foram inventados. Existem dois EDS providers no Firebird:
- Provider Firebird. O
EXECUTE STATEMENT
é usado para acessar bancos de dados Firebird externos.
- Provider Internal. O
EXECUTE STATEMENT
é usado para acessar o banco de dados atual.
O código fornece a capacidade de adicionar novos providers (veja /src/jrd/extds/ExtDS.cpp
).
void Manager::addProvider(Provider* provider)
{
// TODO: se/quando o uso de providers do usuário for implementado,
// é necessário verificar o nome do provider quanto a caracteres permitidos (regras do sistema de arquivos?)
// ...
}
Embora esses providers estejam ocultos de você, você pode especificar explicitamente o provider a ser usado na string de conexão do EXECUTE STATEMENT ON EXTERNAL
.
SQL = 'SELECT MON$ATTACHMENT_NAME FROM MON$ATTACHMENTS';
FOR
EXECUTE STATEMENT :SQL
ON EXTERNAL 'Firebird::inet://localhost/ext_db'
AS USER 'SYSDBA' PASSWORD 'masterkey'
INTO NAME
DO SUSPEND;
-- retorna ext_db
FOR
EXECUTE STATEMENT :SQL
ON EXTERNAL 'Internal::inet://localhost/ext_db'
AS USER 'SYSDBA' PASSWORD 'masterkey'
INTO NAME
DO SUSPEND;
-- retorna self_db (a string de conexão é ignorada)
Então, se planejamos estender o mecanismo do provider EDS ODBC, o EXECUTE STATEMENT ON EXTERNAL
ficaria assim:
FOR
EXECUTE STATEMENT :SQL
ON EXTERNAL 'ODBC::<conn_string>'
AS USER 'user' PASSWORD 'secret'
INTO NAME
DO SUSPEND;
</conn_string>
Isso é possível, mas existem desvantagens significativas nesta abordagem:
- Os dados do EDS Provider não são implementados como plugins (bibliotecas dinâmicas), o que significa que é necessária a modificação dos códigos-fonte da engine do Firebird.
- A solução é muito mais difícil de manter. É necessária a portabilidade manual da funcionalidade para novas versões, por exemplo, quando o Firebird 6.0 e posteriores forem lançados.
No entanto, tendo estudado as características do EXECUTE STATEMENT ON EXTERNAL
, pode-se notar um detalhe importante — o provider Firebird::
funciona através da API usual do Firebird, que é usada em aplicações, ou seja, o EDS atua como um cliente regular. E isso leva a outro pensamento: e se a API de uma fonte de dados externa for encapsulada na API usada para acessar o BD do Firebird?
4. Plugin do tipo Provider
Se você estudar cuidadosamente a documentação existente do Firebird, descobrirá que para esses propósitos, a partir do Firebird 3.0, foi fornecido um tipo especial de plugin chamado Provider (não confundir com EDS Provider). Uma breve descrição do que é pode ser encontrada em doc/README.providers.html
.
Um plugin Provider fornece uma única API para interagir com o Firebird. Não importa como a interação física ocorre, pela rede ou diretamente com a engine do Firebird.
Onde você encontra os providers? Abra o firebird.conf
e você verá a seguinte linha lá:
Providers = Remote, Engine13, Loopback
O provider Remote
é responsável pela interação pela rede, e o Engine13
é o núcleo para interação com ODS13, usado diretamente ao trabalhar em modo embarcado. Os providers são tentados sequencialmente, e aquele que não se recusa a trabalhar é o ativo.
Talvez o exemplo mais conhecido que demonstra o trabalho dos providers seja a configuração:
Providers = Remote,Engine13,Engine12,Loopback
Esta configuração permite que o Firebird 5.0 funcione tanto com bancos de dados ODS 13.1 nativos quanto com bancos de dados ODS 12.0 (Firebird 3.0).
Se fornecermos uma API do Firebird para trabalhar com MySQL ou ODBC com a ajuda do nosso provider, a configuração pode ser a seguinte:
Providers = Remote,Engine13,ODBCEngine,Loopback
Providers = ODBCEngine,Remote,Engine13,Loopback
Providers = MySQLEngine,Remote,Engine13,Loopback
5. Implementação do Provider
Agora vamos falar diretamente sobre a implementação de nossos próprios providers. Quais interfaces de API precisam ser implementadas?
IProvider
IAttachment
ITransaction
IStatement
IResultSet
IBlob
Observe que nem todos os métodos dessas interfaces precisam ser implementados. Implementaremos apenas aqueles que são necessários para que o EXECUTE STATEMENT ON EXTERNAL
funcione, e faremos stubs para o resto.
Além do próprio provider, sua factory deve ser implementada. Você pode dar uma olhada na implementação da factory e no registro do provider usando o exemplo dos providers EngineXX em /src/jrd/jrd.cpp
(.h
).
namespace {
template <class P>
class Factory : public IPluginFactoryImpl<Factory<P>, CheckStatusWrapper>
{
public:
// Implementação de IPluginFactory
IPluginBase* createPlugin(CheckStatusWrapper* status, IPluginConfig* factoryParameter)
{
try {
IPluginBase* p = new P(factoryParameter);
p->addRef();
return p;
}
catch (const std::exception& e) {
Firebird::setStatusError(status, e.what());
}
return nullptr;
}
};
static Factory<OdbcEngine::OdbcProvider> engineFactory;
} // namespace
void registerEngine(IPluginManager* iPlugin)
{
UnloadDetectorHelper* module = getUnloadDetector();
module->setCleanup(shutdownBeforeUnload);
module->setThreadDetach(threadDetach);
iPlugin->registerPluginFactory(IPluginManager::TYPE_PROVIDER, ODBC_ENGINE_NAME, &engineFactory);
module->registerMe();
}
extern "C" FB_DLL_EXPORT void FB_PLUGIN_ENTRY_POINT(IMaster * master)
{
CachedMasterInterface::set(master);
registerEngine(PluginManagerInterfacePtr());
}
</p></class>
5.1. Implementação da interface IProvider
O que precisa ser implementado na interface IProvider
(ODBCProvider
, MySQLProvider
)?
A interface IProvider
possui as seguintes funções:
IAttachment* attachDatabase(...)
(obrigatório)
IAttachment* createDatabase(...)
(lançar erro isc_unavailable
)
IService* attachServiceManager(...)
(lançar erro isc_unavailable
)
void shutdown(...)
(não necessário)
void setDbCryptCallback(...)
(não necessário)
Nesta interface, o principal é implementar o método IProvider::attachDatabase
. Ele deve fazer o seguinte:
- Analisar a string de conexão e extrair o prefixo nela
- Se o prefixo corresponder ao nosso prefixo, crie uma conexão com o BD, caso contrário, lance o erro
isc_unavailable
.
- O prefixo é necessário para determinar rapidamente se deve ser feita uma tentativa de conexão, o que pode não ser barato, ou deixar o próximo provider tentar se conectar ao BD.
- Os seguintes prefixos são fornecidos:
- Para ODBC, é:
:odbc:
ou odbc://
- Para MySQL, é:
:mysql:
ou mysql://
Aqui está um pequeno fragmento desta função:
IAttachment* ODBCProvider::attachDatabase(CheckStatusWrapper* status, const char* fileName,
unsigned dpbLength, const unsigned char* dpb)
{
debug_print_call();
status->clearException();
std::string dbFileName(fileName);
auto poviderPos = dbFileName.find(":odbc:");
std::string connStr;
if (poviderPos == 0) {
connStr = dbFileName.substr(6);
}
else if (poviderPos = dbFileName.find("odbc://"); poviderPos == 0) {
connStr = dbFileName.substr(7);
}
else {
// É importante definir o erro com o status isc_unavailable
// para passar o controle para o próximo provider
const ISC_STATUS statusVector[] = {
isc_arg_gds, isc_unavailable,
isc_arg_end
};
status->setErrors(statusVector);
return nullptr;
}
....
}
As funções IProvider::createDatabase
e IProvider::attachServiceManager
não são necessárias para o funcionamento do EDS, mas ainda precisam ser implementadas e o erro isc_unavailable
lançado nelas. Isso é necessário para que o trabalho das cadeias de providers não seja interrompido.
5.2. Implementação da interface IAttachment
Os seguintes métodos devem ser implementados na interface IAttachment
(MySQLAttachment
, ODBCAttachment
):
void getInfo(status, ...)
ITransaction* startTransaction(status, ...)
IBlob* createBlob(status, ...)
IBlob* openBlob(status, ...)
IStatement* prepare(status, ...)
ITransaction* execute(status, ...)
IResultSet* openCursor(status, ...)
— este método nunca é chamado no EDS, pois ele sempre executa apenas statements preparados
void detach(status)
void dropDatabase(status)
— em teoria, este método não é necessário, mas às vezes o fluxo de controle entra nele, então simplesmente chamamos IProvider::detach
nele.
5.2.1. Implementação de IAttachment::getInfo
Neste método, você precisa retornar uma resposta para solicitações com as tags isc_info_db_sql_dialect
e fb_info_features
. A tag fb_info_features
é usada para retornar a funcionalidade suportada do seu provider. Os valores possíveis são descritos pela seguinte enumeração:
enum info_features // resposta para fb_info_features
{
fb_feature_multi_statements = 1, // Múltiplos statements preparados em um único attachment
fb_feature_multi_transactions= 2, // Múltiplas transações concorrentes em um único attachment
fb_feature_named_parameters= 3, // Parâmetros de consulta podem ser nomeados
fb_feature_session_reset= 4, // ALTER SESSION RESET é suportado
fb_feature_read_consistency= 5, // Read consistency TIL é suportado
fb_feature_statement_timeout= 6, // Timeout de statement é suportado
fb_feature_statement_long_life = 7, // Statements preparados não são descartados no final da transação
fb_feature_max // Não é realmente um recurso. Mantenha este por último.
};
5.2.2. Implementação de IAttachment::startTransaction
A função IAttachment::startTransaction
deve fazer o seguinte:
- Deve retornar uma instância de
ITransaction
, mesmo que as transações não sejam suportadas.
- Incrementa o contador interno de transações.
- Deve processar o Transaction Parameter Buffer (tpb). Ou seja, definir os parâmetros da transação no driver de destino (nível de isolamento, modo de leitura/escrita, parâmetros para aguardar a resolução de locks, etc.) de acordo com os parâmetros do Firebird.
Os seguintes métodos devem ser implementados na própria interface ITransaction
:
void commit(status)
void commitRetaining(status)
void rollback(status)
void rollbackRetaining(status)
Há uma particularidade aqui. Quando uma transação é concluída, é necessário liberar os recursos associados a ela, por exemplo, BLOBs. Voltaremos a essa particularidade mais tarde.
5.2.3. Implementação de IAttachment::prepare
A função IAttachment::prepare
deve fazer o seguinte:
- Criar e retornar uma instância da interface
IStatement
;
- Preparar a consulta
- Determinar o tipo da consulta:
isc_info_sql_stmt_select
, isc_info_sql_stmt_ddl
, isc_info_sql_stmt_exec_procedure
, isc_info_sql_stmt_insert
.
- Preparar os flags da consulta:
IStatement::FLAG_HAS_CURSOR
— se for um cursor
IStatement::FLAG_REPEAT_EXECUTE
— se houver parâmetros de entrada
- Preparar mensagens de entrada e saída
Ao preparar as mensagens de entrada e saída, você deve determinar qual tipo de dados do Firebird corresponde ao tipo do seu provider e vice-versa.
Tabela 1. Correspondência de tipos de dados
Firebird |
MySQL |
ODBC |
VARCHAR(N) , tamanho < 32765 bytes |
VARCHAR(N) , BIT(N) |
SQL_VARCHAR , SQL_WVARCHAR |
CHAR(N) , tamanho < 32767 bytes |
CHAR(N) , BIT(N) |
SQL_CHAR , SQL_WCHAR |
VARBINARY(N) , tamanho < 32765 bytes |
VARBINARY(N) |
SQL_VARBINARY |
BINARY(N) , tamanho < 32767 bytes |
BINARY(N) |
SQL_BINARY |
SMALLINT (para INTEGER unsigned) |
TINYINT , SMALLINT , YEAR |
SQL_TINYINT , SQL_SMALLINT |
INTEGER (para BIGINT unsigned) |
MEDIUMINT , INTEGER |
SQL_INTEGER |
BIGINT (para VARCHAR(20) unsigned) |
BIGINT |
SQL_BIGINT |
FLOAT |
FLOAT |
SQL_REAL |
DOUBLE PRECISION |
DOUBLE |
SQL_DOUBLE , SQL_FLOAT |
DATE |
DATE |
SQL_TYPE_DATE |
TIME |
TIME |
SQL_TYPE_TIME |
TIMESTAMP |
TIMESTAMP , DATETIME |
SQL_TYPE_TIMESTAMP |
VARCHAR(N) , onde N = precisão + 2 |
DECIMAL |
SQL_DECIMAL , SQL_NUMERIC |
BLOB SUB_TYPE TEXT |
TINYTEXT , TEXT , MEDIUMTEXT , LONGTEXT , JSON |
SQL_LONGVARCHAR , SQL_WLONGVARCHAR |
BLOB SUB_TYPE 0 |
TYNYBLOB , BLOB , MEDIUMBLOB , LONGBLOB |
SQL_LONGVARBINARY |
BINARY(16) |
SQL_GUID |
|
BOOLEAN |
SQL_BIT |
|
5.3. Implementação da interface IStatement
Os seguintes métodos devem ser implementados na interface IStatement
(MySQLStatement
, ODBCStatement
):
void getInfo(status, ...)
void free(status)
ISC_UINT64 getAffectedRecords(status)
IMessageMetadata* getOutputMetadata(status)
IMessageMetadata* getInputMetadata(status)
unsigned getType(status)
ITransaction* execute(status, ...)
IResultSet* openCursor(status, ...)
unsigned getFlags(status)
unsigned int getTimeout(status)
void setTimeout(status, timeout)
Os métodos IStatement::getOutputMetadata
, IStatement::getInputMetadata
, IStatement::getType
e IStatement::getFlags
simplesmente retornam os valores preparados dos campos da classe, uma vez que os flags, o tipo e os metadados de entrada e saída foram preparados durante a execução de IAttachment::prepare
.
5.3.1. Implementação de IStatement::execute
O método IStatement::execute
faz o seguinte:
- Converte parâmetros de entrada para os tipos necessários
- Copia parâmetros de entrada do tipo BLOB
- Executa a consulta
- Converte parâmetros de saída para os tipos necessários
- Copia parâmetros de saída de alguns tipos para BLOB
5.3.2. Implementação de IStatement::openCursor
O método IStatement::openCursor
faz o seguinte:
- Converte parâmetros de entrada para os tipos necessários
- Copia parâmetros de entrada do tipo BLOB
- Executa a consulta
- Cria e retorna uma instância de
IResultSet
5.4. Implementação da interface IResultSet
Os seguintes métodos devem ser implementados na interface IResultSet
(MySQLResultSet
, ODBCResultSet
):
int fetchNext(Status* status, void* message)
IMessageMetadata* getMetadata(Status* status)
void close(Status* status)
void setDelayedOutputFormat(Status* status, IMessageMetadata* format)
Os métodos IResultSet::fetchFirst
, IResultSet::fetchPrior
, IResultSet::fetchLast
, IResultSet::fetchAbsolute
e IResultSet::fetchRelative
nunca são chamados dentro do subsistema EDS, então simplesmente criamos stubs para eles.
5.4.1. Implementação de IStatement::fetchNext
O método IStatement::fetchNext
deve fazer o seguinte:
- Mover para o próximo registro no cursor
- Converter parâmetros de saída para os tipos necessários
- Copiar parâmetros de saída de alguns tipos para BLOB
- Retornar
IStatus::RESULT_OK
se o registro do cursor foi buscado com sucesso e IStatus::RESULT_NO_DATA
se o cursor não tiver mais registros.
5.4.2. Implementação de IStatement::setDelayedOutputFormat
O método IStatement::setDelayedOutputFormat
destina-se a definir a mensagem de saída para o cursor. Em princípio, a mensagem de saída do cursor poderia ser criada no construtor, mas a implementação do EDS ainda chama IStatement::setDelayedOutputFormat
e poderia potencialmente sobrescrever nossa mensagem.
5.5. Implementação da interface IBlob
Os seguintes métodos devem ser implementados na interface IBlob
(MySQLBlob
, ODBCBlob
):
int getSegment(Status* status, ...)
void putSegment(Status* status, ...)
void close(Status* status)
void cancel(Status* status)
void seek(Status* status, int mode, int offset)
Características da implementação do IBlob
:
- O conteúdo do tipo BLOB é armazenado na memória no nível do
IAttachment
- Os BLOBs têm um identificador do tipo
ISC_QUAD
. Ele é formado da seguinte maneira:
gds_quad_high
— número da transação (incrementado em IAttachment::startTransaction
)
gds_quad_low
— número do BLOB dentro da transação (incremento)
- A criação de uma nova instância de BLOB é feita pelo método
IBlob* IAttachment::createBlob(status, ...)
. Este método é chamado tanto para retornar campos de alguns tipos mapeados para BLOB dentro do provider, quanto no lado do Firebird na implementação do EDS ao passar parâmetros de entrada do tipo BLOB.
- A abertura de uma instância de BLOB existente por seu identificador é feita pelo método
IBlob* IAttachment::openBlob(status, ...)
- Quando uma transação é concluída (
ITransaction::commit
, ITransaction::rollback
), o conteúdo dos BLOBs que pertencem a essa transação deve ser limpo.
5.5.1. Otimizando o tempo de vida do BLOB
Ao investigar a operação do EXECUTE STATEMENT ON EXTERNAL
, o seguinte foi descoberto:
- O conteúdo do BLOB é sempre completamente copiado entre o EDS e o provider (sem encaminhamento de segmento);
- O EDS implementa apenas cursores unidirecionais
Isso me levou à ideia de que o tempo de vida dos BLOBs na memória pode ser reduzido. Ou seja, o conteúdo dos campos BLOB de cursores só precisa ser armazenado até a próxima chamada ao método IResultSet::fetchNext
, após o qual os BLOBs antigos podem ser limpos. Isso nos permitiu reduzir o consumo de memória do provider ao buscar cursores com campos BLOB.
Isso é tudo sobre a implementação.
6. Quais implementações de provider existem?
Existem as seguintes implementações de acesso a bancos de dados externos via EDS:
- ODBCEngine, MySQLEngine (HQbird)
- jdbc_provider (Red Database)
- Magpie (ODBC Dmitry Sibiryakov)
7. Exemplos
Agora vamos ver exemplos de acesso ao banco de dados via MySQL e ODBC.
Você pode baixar a versão de avaliação do HQbird para Windows em https://firebirdsql.org/hqbird
Instale o HQbird marcando as caixas ODBCEngine e MySQLEngine no instalador.
Os plugins ODBCEngine e MySQLEngine funcionam a partir do Firebird 4.0 e superior.
Edite o arquivo firebird.conf
alterando o parâmetro de configuração Providers
da seguinte forma
Providers = MySQLEngine,Remote,Engine13,Loopback
Agora você pode executar uma consulta SQL em seu banco de dados MariaDB ou MySQL:
dsn_mysql = 'mysql://host=localhost;port=3306;database=employees';
for
execute statement (q'{
select
emp_no, birth_date,
first_name, last_name,
gender, hire_date
from employees
where emp_no = ?
}') (10020)
on external dsn_mysql
as user 'root' password 'sa'
into
emp_no, birth_date, first_name, last_name, gender, hire_date
do
suspend;
O mesmo, mas usando parâmetros nomeados:
dsn_mysql = 'mysql://host=localhost;port=3306;database=employees';
for
execute statement (q'{
select
emp_no, birth_date,
first_name, last_name,
gender, hire_date
from employees
where emp_no = :emp_no
}') (emp_no := 10020)
on external dsn_mysql
as user 'root' password 'sa'
into
emp_no, birth_date, first_name, last_name, gender, hire_date
do
suspend;
Se você precisar de acesso via ODBC, edite o arquivo firebird.conf
e altere o parâmetro de configuração Providers
da seguinte forma:
Providers = ODBCEngine,Remote,Engine13,Loopback
Agora você pode executar uma consulta SQL em um banco de dados MariaDB ou MySQL através da interface ODBC:
conn_str = 'odbc://DRIVER={MariaDB ODBC 3.1 Driver};SERVER=server;PORT=3306;DATABASE=test;CHARSET=utf8mb4';
sql = Q'{
SELECT
id, title,
body, bydate
FROM article
}';
for execute statement (:sql)
on external :conn_str
as user 'root' password 'play'
into id, title, body, bydate
do
suspend;
ODBC com parâmetros nomeados:
xConnStr = 'odbc://DRIVER={MariaDB ODBC 3.1 Driver};SERVER=server;PORT=3306;DATABASE=test;TCPIP=1;CHARSET=utf8mb4';
xSQL = '
SELECT
CODE_SEX, NAME, NAME_EN
FROM sex
WHERE CODE_SEX = :A_CODE_SEX
';
for
execute statement (:xSQL) (A_CODE_SEX := xCODE_SEX)
on external xConnStr
as user 'test' password '12345'
into CODE_SEX, NAME, NAME_EN
do
suspend;
Usando a interface ODBC, você pode executar consultas SQL em qualquer banco de dados para o qual exista um driver ODBC.
8. Quais são os problemas com as implementações existentes?
- O prefixo na string de conexão depende da configuração
- A passagem de parâmetros de autenticação depende da configuração
- Tratamento de erros de conexão
- Nem todos os SGBDs conseguem retornar os tipos dos parâmetros de entrada
- Parâmetros nomeados são processados apenas para operadores SQL cuja sintaxe é semelhante à do Firebird
- O operador CALL não funciona com stored procedures que retornam um conjunto de dados
- É necessário garantir que a codificação dos parâmetros de string corresponda
Vamos dar uma olhada mais de perto no porquê isso acontece.
8.1. Prefixos na string de conexão
Para que servem os prefixos na string de conexão?
- Os providers tentam abrir uma conexão na ordem especificada no parâmetro
Provider
(firebird.conf
)
- Se a tentativa de conexão falhar, o próximo provider é tentado
- Tentar estabelecer uma conexão pode não ser barato
- O prefixo na string de conexão permite determinar rapidamente se o provider é adequado ou não
Temos dois tipos de prefixos na implementação atual:
- Para ODBC é:
:odbc:
ou odbc://
- Para MySQL é:
:mysql:
ou mysql://
Cada prefixo tem suas vantagens e desvantagens.
Um prefixo do tipo URL, mais familiar, exige que nosso provider seja especificado antes do provider Remote
, ou seja,