Share Button

Executar um “select” em paralelo na verdade é bem simples, mas complexo se não deter algumas informações elementares sobre o tratamento que o FireDAC dá ao isolamento de conexão.

Basicamente o processo se dá pelo isolamento da “connection” ao fazer a chamada no banco de dados, talvez aí o maior problema já que é necessário ter uma conexão individual para cada chamada ao banco.

No FireDAC esta disponível na IDE o componente TFDMonitor que é responsável em gerar um novo componente de conexão para cada chamada feita. Este é o caminho mais fácil para utilizar “multithreaded”, assunto que não irei tratar aqui já que o interessante é explorar recursos que entregue mais conteúdo a quem quer entender como o processo acontece.

em construção …. ajude a escrever este texto enviando comentários. Gratidão

Base para o texto:
Unit: Data.FireDAC.Helper
Ver: Exemplo no GIT

Share Button

Vou abordar hoje uma das maneira mais fáceis de inserir código malicioso em comando SQL. O título indicando o Firebird é só um direcionamento do teste, mas se aplica praticamente a todos os banco de dados relacional (ou quase todos).

1. Um código Vulnerável

Uma simples janela de “login” pode significar uma entrada a explorar, como veremos.

Em geral uma janela de “login” pede o código/senha de usuário.

Exemplos de “select” para validar o usuário:

Caso 1:

considere o select: ‘select codigo,nome from usuario where codigo=’ +edit1.text+’ and senha=’+edit2.text;

como testar:

  1. digite uma aspas    no edit1.text e tente entrar; se o resultado for um erro, então você receberá informações sobre o que ocorreu e poderá indicar que o código do aplicativo é vulnerável.
  2. tente digitar no edit1.text:
    ' or 1=1 --

    note que com o select indicado o usuário ganhará acesso ao sistema.

Caso 2:

considere o select:    format(‘select codigo,nome from usuario where codigo=%s and senha=%s,[edit1.text,edit2.text]);

  • tente digitar no edit1.text: 
    ' or 1=1 --

 

2. Como quebrar a vulnerabilidade

Para quebrar a vulnerabilidade, o passo mais seguro é utilizar parâmetros nos “select” ou forçar as aspas por código, vejamos como fazer o correto:

  • ‘select codigo,nome from usuario where codigo=:codigo and senha=:senha’
  • ‘select codigo,nome from usuario where codigo=’+quotedstr(edit1.text)+’ and senha=’+quotedstr(edit2.text);

 

Share Button

Nestes primeiros meses de 2017, o grupo MVCBr dedicou a maior parte do tempo em implementar um servidor OData que permite o acesso a base de dados utilizando protocolo RESTful via HTTP.

FIREBIRD !!!

Sim….. o servidor OData  implementado no MVCBr é um servidor que expõe recursos (resources) armazenados em um servidor FIREBIRD 3.0

Simplificando, o servidor MVCBrServer é um servidor RESTful que utiliza o protocolo OData que suporta o Firebird3.

Arquitetura do Servidor

O servidor é um servidor implementado utilizando componentes INDY para fazer o processamento das requisições. Ao receber a chamada do CLIENTE o motor INDY passa para o framework  “Delphi MVC Framework” implementado pelo “Daniele Teti”. Seguindo os padrões RESTful, o servidor analisa o tipo de pacote que esta recebendo (GET, PUT, POST, PATCH, DELETE, OPTIONS) e passa a requisição para um PARSER OData implementado no framework MVCBr. Ao receber a requisição o PARSER prepara a requisição que será feito ao banco de dados (ex: select * from produtos), avalia o “metadata” contendo as regras de acesso e constrói a solicitação através da estrutura do FireDAC do Delphi, que remete ao driver o FIREBIRD. Recebendo o retorno tudo é empacotado utilizando JSON e devolvido para o cliente.

 

O “metadata” – Onde o modelo relacional é descrito

Junto com o projeto MVCBr irá encontrar um banco FIREBIRD3  (MVCBr.fdb)  que possui uma estrutura básica de teste utilizada no “framework”

Configurar o “databases.conf” do firebird:

 #
# Live Databases:
#
mvcbr=[path]/mvcbr.fdb

Como configurar um resource

Resource é o identificar a ser utilizado no HTTP para acessar um determinado recurso do banco de dados.
Veja o exemplo:

    {
      "resource": "produtos",
      "collection": "produtos",
      "keyID": "codigo",
      "maxpagesize": 100,
      "fields": "*",
      "method": "GET,PATCH,POST,PUT,DELETE",
      "relations": [
        {
          "resource": "grupos",
          "sourceKey": "grupo",
          "targetKey": "codigo",
          "join": "left join grupos on (produtos.grupo=grupos.grupo)"
        }
      ]
    }
  • resource:  apelido para o URL utilizada no HTTP
  • collection: nome da tabela fisica no banco de dados
  • keyID: coluna de acesso rápido ás linhas da tabela    ex: http://…./OData/OData.svc/produtos(‘789112313311’)
  • maxpagesize: número máximo de linhas a retornar caso não seja indicado o comando   $top
  • fields: lista de colunas a retornar quando o comando  $select não for indicado
  • method: quais as permissões serão publicadas aos clientes
  • relations: quais relacionamento podem ser executados com “resource” corrente  ( é um DETAIL)
    • relations.resource: qual o apelido do relacionamento com o resource MASTER
    • relations.sourceKey: qual a coluna de recionamento no resource MASTER
    • relations.targetKey: qual a coluna de relacionamento no resource DETAIL
    • relations.join: utilizado para JOINs mais complexos ignorando “sourceKey” e “targetKey”

Listagem completa do metadata de exemplo

{
  "@odata.context": "http://localhost:8080/OData/OData.svc",
  "__comment": "Services list all resource available to OData.Service",
  "OData.Services": [
    {
      "resource": "produtos",
      "collection": "produtos",
      "keyID": "codigo",
      "maxpagesize": 100,
      "fields": "*",
      "method": "GET,PATCH,POST,PUT,DELETE",
      "relations": [
        {
          "resource": "grupos",
          "sourceKey": "grupo",
          "targetKey": "codigo",
          "join": "left join grupos on (produtos.grupo=grupos.grupo)"
        }
      ]
    },
    {
      "resource": "grupos",
      "collection": "grupos",
      "keyID": "codigo",
      "fields": "*",
      "method": "GET,PATCH,DELETE,PUT,POST",
      "maxpagesize": 100,
      "relations": [
        {
          "resource": "produtos",
          "sourceKey": "codigo",
          "targetKey": "grupo",
          "join": "join produtos on (grupos.codigo=produtos.grupo)"
        }
      ]
    },
    {
      "resource": "fornecedores",
      "collection": "fornecedores",
      "maxpagesize": 100,
      "fields": "*",
      "keyID": "codigo"
    },
    {
      "resource": "clientes",
      "collection": "clientes",
      "keyID": "codigo",
      "method": "GET,POST,PATCH,UT,DELETE",
      "searchFields": "nome",
      "maxpagesize": 100,
      "fields": "*"
      "relations": [
        {
          "resource": "vendas",
          "join": "join vendas on (vendas.cliente=clientes.codigo)"
        },
        {
          "resource": "vendas_item",
          "join": "join vendas a  on (clientes.codigo=a.cliente) join vendas_item b on (b.documento=a.documento)"
        }
      ]
    },
    {
      "resource": "vendas",
      "collection": "vendas",
      "maxpagesize": 100,
      "keyID": "documento",
      "fields": "*",
      "method": "GET,POST,PATCH,PUT,DELETE"
    },
    {
      "resource": "vendas_item",
      "collection": "vendas_item",
      "maxpagesize": 100,
      "keyID": "documento"
      "method": "GET,POST,PATCH,PUT,DELETE"
    }

  ]


}

 

Share Button

 

Chegar a um CRUD é um grande desafio. Associar um MVC no desenvolvimento e a utilização de RESTful para persistir os dados (com OData) só faz crescer o desafio.

O desafio vem do fato de tudo ser novidade e forçar a quebrar paradigmas antes impensáveis utilizando a ferramenta do dia-a-dia, o Delphi.

Vamos ligar os componentes:

TIdHTTPRestClientTIdHTTPRestClient é responsável em fazer a
comunicação entre o cliente e o servidor RESTful.

É o canal para buscar dados no servidor, bem como enviar dados para o servidor (é um componente de transporte).

ver mais…

 

 

Os preenchimento das propriedades do RestClient pode ser feito manualmente, ou ainda, de forma automática se for utilizado em conjunto com um TODataBuilder.

 

ODataCom o TODataBuilder permite construir a soliciatação GET a ser enviada ao servidor OData.

Ligar ao TODataBuilder a propriedade RestClient com o TIdHTTPRestClient. Com isto o comando construído no Builder será repassado (preenchido automático) no RestClient.  ver mais….

 

 

 

O passo seguinte é ligar o TODataDatasetAdpater.

TODataDatasetAdapterO Adapter recebe os dados do RestClient e liga com o Dataset.

Ligar o Builder, Dataset e o ResponseJSON( RestClient).

Para obter os dados com os servidor, chamar o método execute. ver mais…

Feito estas ligações nos componentes, já é possível chamar o EXECUTE do Adapter que irá preencher o Dataset com a resposta do servidor.

Para enviar um INSERT, DELETE ou UPDATE para o servidor ( ver exemplo).

O código a seguir mostra como montar os dados a serem enviado para o servidor persistir as alterações.

Os seguintes métodos do Adapter são importantes, como no exemplo:

ODataDatasetAdapter1.AddRowSet(rctInserted, DataSet); /// põe na pilha um INSERT;
ODataDatasetAdapter1.AddRowSet(rctModified, DataSet);  /// põe na pilha um UPDATE
ODataDatasetAdapter1.AddRowSet(rctDeleted, DataSet);  /// põe na pilha um DELETE;
ODataDatasetAdapter1.ApplyUpdates(nil, rmPATCH);  /// atualiza o servidor com os dados da pilha;
ODataDatasetAdapter1.Execute;   /// faz um SELECT no banco de dados
ODataDatasetAdapter1.ClearChanges;  /// limpa a pilha

 

Código de exemplo que esta no projeto MVCBr.  no git

{ //************************************************************// }
{ //                                                            // }
{ //         Código gerado pelo assistente                      // }
{ //                                                            // }
{ //         Projeto MVCBr                                      // }
{ //         tireideletra.com.br  / amarildo lacerda            // }
{ //************************************************************// }
{ // Data: 10/03/2017 21:21:54                                  // }
{ //************************************************************// }
///
/// Uma View representa a camada de apresentação ao usuário
/// deve esta associado a um controller onde ocorrerá
/// a troca de informações e comunicação com os Models


....
    DBGrid1: TDBGrid;

    DataSource1: TDataSource;
    /// Componente de Envio de mensagem HTTP para o servidor OData RESTful
    IdHTTPRestClient1: TIdHTTPRestClient;
    /// Construção de URL a ser enviado ao servidor
    ODataBuilder1: TODataBuilder;
    /// Desempacota a resposta do servidor e preenche o Dataset
    ODataDatasetAdapter1: TODataDatasetAdapter;

    /// Dataset que recebe a resposta do servidor - preenchida com o Adpater
    FDMemTable1: TFDMemTable;   /// FireDAC
    Button1: TButton;  ///  botão GET
    Button2: TButton;  ///  botão ApplayUpdates
....
  private
    FLoading: boolean;  /// Flag para marcar se esta fazendo carga inicial da tabela.
    FState: TDataSetState;  /// Guarda o STATE do Dataset a ser usando no AfterPost
  protected
  public
    { Public declarations }
...
  end;

Implementation

{$R *.DFM}

.....
/// No evento AfterPost do Dataset, armazena na lista de
/// alterações a serem enviadas para o servidor OData RESTful
procedure TRestODataAppView.FDMemTable1AfterPost(DataSet: TDataSet);
begin
  if not FLoading then /// trata somente os registros alterados pelo usuário... (para não pegar os dados iniciais na carga da resposta do servidor)
    case FState of
      dsInsert:  /// empilha os dados de INSERT - inserted
        ODataDatasetAdapter1.AddRowSet(rctInserted, DataSet);
      dsEdit:    /// empilha os dados do POST - modified
        ODataDatasetAdapter1.AddRowSet(rctModified, DataSet);
    end;

end;

/// No ApplyUpdates do Dataset despachar para o servidor
/// as alterações armazenadas para serem notificadas
/// ao servidor
procedure TRestODataAppView.FDMemTable1BeforeApplyUpdates(DataSet: TFDDataSet);
begin
  // envia os dados alterados para o servidor OData RESTful
  ODataDatasetAdapter1.ApplyUpdates(nil, rmPATCH);
end;

procedure TRestODataAppView.FDMemTable1BeforeDelete(DataSet: TDataSet);
begin
  /// empilha os dados a serem de DELETE - deleted a ser enviado ao servidor
  ODataDatasetAdapter1.AddRowSet(rctDeleted, DataSet);
end;

procedure TRestODataAppView.FDMemTable1BeforePost(DataSet: TDataSet);
begin
  /// guarda o estado da linha (dsEdit, dsInsert) para uso no AfterPost
  FState := DataSet.State;
end;

procedure TRestODataAppView.FormCreate(Sender: TObject);
begin
  /// um exemplo de como adicionar CustomHeaders a mensagem que será enviada ao servidor
  IdHTTPRestClient1.IdHTTP.Request.CustomHeaders.AddValue('token', 'abcdexz');
end;

procedure TRestODataAppView.Button1Click(Sender: TObject);
begin
  FLoading := true;  /// controle para não gerar linhas de alterações na pilhas
  try
    ///  executa um GET no servidor RESTful
    ///  o servidor fazer um SELECT e devolve o conteúdo as linhas do SELECT
    ODataDatasetAdapter1.Execute;
    ///  limpa alterações que estejam armazenadas
    ODataDatasetAdapter1.ClearChanges;
  finally
    FLoading := false;
  end;
end;

procedure TRestODataAppView.Button2Click(Sender: TObject);
begin
  FDMemTable1.ApplyUpdates();   ///  processa a alteração do Dataset
end;


 

Share Button

O componente TODataDatasetAdapter é um construtor associado ao Dataset convertendo o JSON enviado pelo servidor em um DATASET navegável no formulário. Vejamos suas propriedades.

TODataDatasetAdapter Onde:
Builder: ligação para o TODataBuilder;
Datasert: ligação para um TFDMemTable (aka. TClientDataset !!!);
ResponseJSON: ligação com o TIdHTTPRestClient;
ResponseType: no momento só aceita texto plano sem compactação – pureJSON;
RootElement:  caminho onde se encontra o ARRAY de valores no JSON enviado pelo servidor (no OData o padrão é “value”);
Introdução ao OData | TODataBuilder | TIdHTTPRestClient

 

Share Button

TODataBuilder é um construtor de URI para chamada ao OData Server.

OData

O construtor  é uma facilidade para criar a URI de chamada ao OData através da interface gráfica do Delphi onde:

  • BaseURL – Servidor e porta do OData;
  • Count – indica se é para retornar o número total de linhas do select – usado para paginação;
  • Expand – lista de Resource DETAIL   (não implementado no MVCBrServer neste momento);
  • Filter – filtro de contrução auxiliar para o cláusula WHERE de busca no banco de dados (aceita os operadores do OData);
  • Resource – lista de Collections/Tabelas e respectivos parâmetros aninhadas ( max 3 );
  • Select – lista de colunas a retornar pelo consulta no servidor;
  • Service – nome do serviço no servidor;
  • ServicePreffix – prefixo de composição da URI associado ao Serviço;
  • SkipRows – indica quantas linhas saltar antes de retornar o conjunto de linhas (default=0);
  • TopRows – indica a quantidade de linhas a serem retornadas (default=0  todas);

O componente expõe o metódo “execute” que tem por atribuição preencher os parâmetros do TIdHTTPRestClient e chamar o seu “execute”;

RestClient | Introdução ao OData

 

Share Button

TIdHTTPRestClient é um cliente RESTful implementado sobre o componente INDY  TIdHTTP de uso geral.

TIdHTTPRestClient

 

O procedimento “execute” do componente envia uma solicitação ao servidor HTTP e aguarda resposta pela propriedade “content”.

Propriedades

  • BaseURL – servidor e porta de acesso;
  • Method – identifica o método a ser executa no servidor;
  • Resource – URL de acesso;
  • ResourcePreffix – Serviço de acesso no servidor;

 

Informações adicionais de HEADER deve ser enviado através da propriedade do componente “IdHTTP” que expõe o componente TIdHTTP instanciado internamente.

Exemplo de envio de “token” no HEADER:

   IdHTTPRestClient1.IdHTTP.Request.CustomHeaders.AddValue('token','abcdexz');

Se necessário obter informações adicionais é possível obter a partir de documentação do TIdHTTP e pode ser aplicado a propriedade IdHTTPRestClient1.IdHTTP…

Introdução ao OData

Share Button

Trabalhar com RECORD é mais fácil administrar memória quando comparado com classes.

Como cada variável RECORD ocupa um endereço diferente com os dados e controla a sua retirada da memória com mais facilidade, prefiro usar RECORD para fazer CACHE de dados.

Por outro lado, temos vários acessos ao banco de dados que possuem parâmetros que estão nestes RECORD (alguém lembrará um CRUD). Uma solução é utilizar RTTI para ler os valores dos parâmetros que estão armazenados no RECORD e passar os valores para os parâmetros (TParams).

function TALQuery.FillParams<T>(rec: T): TALQuery;
var
  LList: TJsonValuesList;  // unit System.uJson
  LPair:TJsonPair;
  i:integer;
  prm:TParamBase;
begin
  result := self;
  LList := TJsonValuesList.Create();    // unit System.uJson
  try
    TJsonValue.GetRecordList<T>(LList, rec); // carrega os valores do RECORD em um LIST
    for I := 0 to params.count-1 do
         begin
            prm:= params[i];
            LPair := LList.names[ prm.Name ];
            if assigned(LPair) then
              case prm.DataType of
                 ftSmallint,ftInteger:
                   prm.AsInteger := LPair.JsonValue.GetValue<integer>;
                 ftFloat:
                   prm.AsFloat := LPair.JsonValue.GetValue<double>;
                 ftCurrency:
                   prm.AsCurrency := LPair.JsonValue.GetValue<Currency>;
                 ftDateTime,ftDate,ftTime :
                   prm.asDateTime := LPair.JsonValue.getValue<TDateTime>;
               else
                 prm.Value := LPair.JsonValue.getValue<string>;
              end;
         end;
  finally
    LList.free;
  end;
end;


/* aplicando */


Type
    TPedidoRecord = record
      ...
      pedido:integer;
      filial:integer;
      data:TDatetime;
    end;


var qry:   TMinhaQuery;
    rec : TPedidoRecord;
begin
    ....
    qry.fillParams<TPedidoRecord>(rec);
    ...
end;

 

Dependência: System.uJson

 

Share Button

Sim…. replicação para firebird… não tem ? tem sim… só trabalhar um pouco – nem tudo é como visão de brigadeiro..

A replicação consistem manter DUAS ou mais tabelas com conteúdo iguais – de tal forma que se obtém o mesmo resultado em qualquer uma das duas bases de dados.

Existe dois lados na replicação – um que tem os dados que precisam ser levados para o outro (publisher-aquele que publica) – o outro que recebe os dados publicados pelo servidor (subscriptor-aquele que se candidata a receber os dados). Por último a combinação de ambos em um só – ele é tanto publisher como subscriptor da mesma tabela;


Fase do desenvolvimento

  1. Preparar o Publisher para publicar dados para replicação (banco de origem);
  2. Preparar o Subscriptor para receber os dados publicados (banco de destino);
  3. Codificar os componente que monitoram as alterações do Publisher;
  4. Codificar a transferências dos dados da origem para o destino;

 

A título ilustrativo, vou adotar a tabela de PEDIDOs como base

Considere a seguinte tabela:


create table  PEDIDOS  (dcto varchar(10), 
                        data Date, 
                        cliente integer, 
                        total numeric(15,4));


Preparando o banco de origem em firebird

Como base para a replicação, vou criar uma coluna nova na tabela de pedidos na origem para indicar a chave de referência para a replicação – para fugir dos tipos auto-incrementos (já escrevi como fazer isto aqui no blog) vou usar uma coluna para identificar a chave de replicação que receberá uma representação texto para um GUID

alter table PEDIDOS add GID varchar(38);

[OFF] antes que alguém questione… não dá para usar auto-incremento em razão de atender a pré-condição de que o dado dever ser o mesmo nos dois banco de dados (traduzindo – não podem ser diferentes)

A coluna GID vai receber um valor por “trigger” toda vez que for incluída na tabela

SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS 
begin 
  /* Trigger Utilizado para Replicacao */ 
  if ((new.gid is null)) then 
     new.gid =  UUID_TO_CHAR( gen_uuid() ); 
end^
SET TERM ; ^

[OFF] como se nota inclui na trigger o UPDATE – fiz isto para pegar linhas antigas que ainda não foram replicadas em nenhum processo anterior – caso ocorre uma replicação parcial;

Até aqui já temos a marcação de chave na tabela de origem para localizar as linhas que serão publicadas para replicação. Agora vamos criar uma tabela de controle de publicação das replicações

CREATE TABLE REPL_ITENS
  (
    TABELA Varchar(128),   // recebe o nome da tabela alvo
    GID Varchar(38),       // recebe o GID da tabela alvo 
    TIPO Char(1),          // I-insert U-update D-delete
    DATA Date,             // data e hora da alteração
    ID integer,            // sequencial interna
    SESSION_ID integer     // sessão da transação que criou - um luxo
  );
CREATE INDEX REPL_ITENSDATA ON REPL_ITENS (DATA);
CREATE INDEX REPL_ITENSGID ON REPL_ITENS (GID);
CREATE INDEX REPL_ITENSID ON REPL_ITENS (ID);
CREATE INDEX REPL_ITENSTABELA ON REPL_ITENS (TABELA);
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON REPL_ITENS TO SYSDBA WITH GRANT OPTION;

Vamos precisar de um GENERATOR para popular a coluna ID da tabela e uma trigger para popular com o valor do GENERATOR. ver também

CREATE GENERATOR REPL_ITENS_GEN_ID;
SET TERM ^ ;
CREATE TRIGGER REPL_ITENS_ID FOR REPL_ITENS ACTIVE
BEFORE INSERT POSITION 0
AS 
begin /* Replicacao Storeware */ 
  new.id = gen_id(REPL_ITENS_GEN_ID,1); 
  new.session_id = rdb$get_context('SYSTEM','SESSION_ID'); 
  new.data = cast('now' as date); 
end^
SET TERM ; ^

[OFF] para este caso não usei o GUID – vamos precisar garantir uma sequência na tabela que seja em ordem crescente – isto vai facilitar a codificação a frente.

 

Para fechar, vamos ensinar a tabela PEDIDOS como publicar as suas alterações:

SET TERM ^ ;
CREATE TRIGGER REPL_PEDIDOS_REG FOR PEDIDOS ACTIVE
AFTER INSERT OR UPDATE OR DELETE POSITION 0
AS 
begin 
  /* Replicacao Storeware */ 
  in autonomous transaction do 
  begin 
   if (inserting) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',new.gid,'I'); 
   if (updating) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',new.gid,'U'); 
   if (deleting) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',old.gid,'D'); 
  end 
end 
^
SET TERM ; 

 

Preparando o banco de dados de destino

No banco de dados destino temos a mesma tabela de pedidos (o ideal que possua a mesma estrutura).

Considerando que durante a replicação dos dados ocorrerão momentos em que haverá UPDATEs e DELETEs para fazer – será necessário ter uma chave correspondente para a alteração do registro. Esta chave não pode ser controlado localmente pelo banco, ela precisa ser uma chave que tanto o banco de origem como o banco de destino sejam cooperativos. A este requisito a chave GID que recebe um GUID atente perfeitamente.

alter table PEDIDOS add GID varchar(38);

A “trigger” para gerenciar a coluna GID segue o mesmo comportamento do banco de origem.

SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS 
begin 
  /* Trigger Utilizado para Replicacao */ 
  if ((new.gid is null)) then 
     new.gid =  UUID_TO_CHAR( gen_uuid() ); 
end^
SET TERM ; ^

[OFF] Note que a trigger checa se o GID é null – isto é importante pois quem vai gerar a chave para o GID é o banco que criou o registro (onde ele nasceu) e durante toda a sua vida precisa receber o mesmo valor nos dois banco de dados.

Agora já temos tudo que precisamos no banco de dados para controlar o que será replicado. Passemos a construir os códigos que vão fazer a replicação em si.


Codificando o select para monitorar as alterações na Origem

O select na origem, ou no publisher, será feito considerando que queremos somente as linhas alteradas para serem transferidas para o banco de destino.

Para isto precisamos fazer controle de quais linhas foram e quais linhas ainda não foram para o destino. Para isto foi que usei um GENERATOR para a tabela de controle   REPL_ITEMS->id

Uma vez executado a replicação precisamos guardar qual foi o ultimo ID utilizado para ser utilizado na próxima chamada:

  select a.*, b.tipo repl_tipo from PEDIDOS a, REPL_ITEMS b
  where  a.gid = b.gid and b.id>:id
  order by b.id

Para tabelas com muitas linhas, pode ser interessante usar um derivação incluindo o número de linhas a sincronizar a cada chamada:

  select first 1000 a.*, b.tipo repl_tipo from PEDIDOS a, REPL_ITEMS b
  where  a.gid = b.gid and b.id>:id
  order by b.id

Codificando a transferência dos dados para o destino

Chegou o momento de enviar o dados da Origem para o Destino….
Dependendo da coluna REPL_TIPO que vem do Select na Origem:
I – Faz um INSERT na tabela de destino;
U – Faz um UPDATE;
D – Faz um DELETE;

Esteja preparado para tratar algumas exceções:

  • quando for fazer um UPDATE e o registro ainda não se encontra no destino, precisando fazer o INSERT para iniciar o registro;
  • quando for fazer DELETE de uma registro que não existe;
  • fazer INSERT de registro que já tem chave primária idêntica no destino;
  • se esta inciando a sincronização de uma tabela que já existe, sinalizar a tabela de eventos com os dados já existentes;

 


 

 

Projeto no Git

 

 

 

Share Button

Este artigo foi revisão – acesse o link aqui

Sim…. replicação para firebird… não tem ? tem sim… só trabalhar um pouco – nem tudo é como visão de brigadeiro..

A replicação consistem manter DUAS ou mais tabelas com conteúdo iguais – de tal forma que se obtém o mesmo resultado em qualquer uma das duas bases de dados.

Existe dois lados na replicação – um que tem os dados que precisam ser levados para o outro (publisher-aquele que publica) – o outro que recebe os dados publicados pelo servidor (subscriptor-aquele que se candidata a receber os dados). Por último a combinação de ambos em um só – ele é tanto publisher como subscriptor da mesma tabela;


Fase do desenvolvimento

  1. Preparar o Publisher para publicar dados para replicação (banco de origem);
  2. Preparar o Subscriptor para receber os dados publicados (banco de destino);
  3. Codificar os componente que monitoram as alterações do Publisher;
  4. Codificar a transferências dos dados da origem para o destino;

 

A título ilustrativo, vou adotar a tabela de PEDIDOs como base

Considere a seguinte tabela:


create table &nbsp;PEDIDOS &nbsp;(dcto varchar(10), 
                        data Date, 
                        cliente integer, 
                        total numeric(15,4));


Preparando o banco de origem em firebird

Como base para a replicação, vou criar uma coluna nova na tabela de pedidos na origem para indicar a chave de referência para a replicação – para fugir dos tipos auto-incrementos (já escrevi como fazer isto aqui no blog) vou usar uma coluna para identificar a chave de replicação que receberá uma representação texto para um GUID

alter table PEDIDOS add GID varchar(38);

[OFF] antes que alguém questione… não dá para usar auto-incremento em razão de atender a pré-condição de que o dado dever ser o mesmo nos dois banco de dados (traduzindo – não podem ser diferentes)

A coluna GID  vai receber um valor por “trigger”  toda vez que for incluída na tabela

SET TERM ^ ;
ALTER TRIGGER REPL_PEDIDOS_GID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS 
begin 
  /* Trigger Utilizado para Replicacao */ 
  if ((new.gid is null)) then 
     new.gid =  UUID_TO_CHAR( gen_uuid() ); 
end^
SET TERM ; ^

[OFF] como se nota inclui na trigger o UPDATE – fiz isto para pegar linhas antigas que ainda não foram replicadas em nenhum processo anterior – caso ocorre uma replicação parcial;

Até aqui já temos a marcação de chave na tabela de origem para localizar as linhas que serão publicadas para replicação. Agora vamos criar uma tabela de controle de publicação das replicações

CREATE TABLE REPL_ITENS
  (
    TABELA Varchar(128),   // recebe o nome da tabela alvo
    GID Varchar(38),       // recebe o GID da tabela alvo 
    TIPO Char(1),          // I-insert U-update D-delete
    DATA Date,             // data e hora da alteração
    ID integer,            // sequencial interna
    SESSION_ID&nbsp;integer     // sessão da transação que criou - um luxo
  );
CREATE INDEX REPL_ITENSDATA ON REPL_ITENS (DATA);
CREATE INDEX REPL_ITENSGID ON REPL_ITENS (GID);
CREATE INDEX REPL_ITENSID ON REPL_ITENS (ID);
CREATE INDEX REPL_ITENSTABELA ON REPL_ITENS (TABELA);
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON REPL_ITENS TO SYSDBA WITH GRANT OPTION;

Vamos precisar de um GENERATOR para popular a coluna ID da tabela e uma trigger para popular com o valor do GENERATOR. ver também

CREATE GENERATOR REPL_ITENS_GEN_ID;
SET TERM ^ ;
CREATE TRIGGER REPL_ITENS_ID FOR REPL_ITENS ACTIVE
BEFORE INSERT POSITION 0
AS 
begin /* Replicacao Storeware */ 
  new.id = gen_id(REPL_ITENS_GEN_ID,1); 
  new.session_id = rdb$get_context('SYSTEM','SESSION_ID'); 
  new.data = cast('now' as date); 
end^
SET TERM ; ^

[OFF] para este caso não usei o GUID – vamos precisar garantir uma sequência na tabela que seja em ordem crescente – isto vai facilitar a codificação a frente.

 

Para fechar, vamos ensinar a tabela PEDIDOS como publicar as suas alterações:

SET TERM ^ ;
CREATE TRIGGER REPL_PEDIDOS_REG FOR PEDIDOS ACTIVE
AFTER INSERT OR UPDATE OR DELETE POSITION 0
AS 
begin 
  /* Replicacao Storeware */ 
  in autonomous transaction do 
  begin 
   if (inserting) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',new.gid,'I'); 
   if (updating) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',new.gid,'U'); 
   if (deleting) then 
     insert into repl_itens ( tabela,gid,tipo) 
            values('PEDIDOS',old.gid,'D'); 
  end 
end 
^
SET TERM ; 

Parte 2