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

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

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

O protocolo OData não trata sobre o controle de transação do banco de dados. Esta é na verdade um decisão do server que implementação será feita. Como o OData nasce em um ambiente noSQL com forte presença é fácil imaginar que o controle de transação é uma preocupação mais presente no legado e menos presente nas novas tecnologia.
Aqui um paradigma bom a ser vencido ao longo dos anos – como sair de um ambiente transacional e abandonar o ACID no noSQL…

Aqui no MVCBrServer o banco de transação é o Firebird e a transação faz parte da estrutura de controle. Pensando nisto, quando o cliente solicita um conjunto de linhas em um ARRAY o server faz um STARTTRANSACTION e somente após a conclusão de todas as linhas irá enviar um COMMIT para o servidor.
Caso ocorra algum erro durante o processo um ROLLBACK é enviado para o servidor cancelando todo o lote da transação.

introdução a OData | INSERT | UPDATE

Share Button

A execução de um UPDATE no banco de dados envolve duas categorias de informações. A primeira delas é uma lista de colunas a atualizar no servidor, na segunda parte de importância é a indicação de quais linhas serão envolvidas na atualização (a WHERE).

Com base na especificação OData para RESTful, o METHOD PUT indica que o cliente deseja fazer uma atualização da tabela. As colunas a serem atualizadas devem ser enviadas no BODY da mensagem ao servidor RESTful. O servidor MVCBrServer esta preparado para receber uma linha simples de colunas/valores, bem como um ARRAY com um conjunto de linhas.

Possibilidades  de formato do BODY para atualizar a(s) linha(s):

  1. Uma linha simples:
      { "id" : "1" ,  "nome":"DESCRICAO TESTE" }
  2. Um ARRAY com uma lista de linhas:
      
          [  { "id": "1",  "nome": "DESCRICAO 1"},
             { "id": "2",  "nome": "DESCRICAO 2"},....
          ] 
    

Se o BODY indica os valores das colunas a serem atualizadas no servidor, próximo passo é tratar a seleção das linhas que iram receber atualização já que em geral queremos atualizar uma linha ou um conjunto delas e raramente desejamos atualizar todas as linhas.
Preferencialmente a definição da WHERE deve ser indicada como parâmetro para o RESOURCE (a tabela ou coleção):

     http://localhost:8080/OData/OData.svc/produtos(id='1')  -> aplica atualiza sobre a linha com ID = "1"
     
     caso o RESOURCE requeira mais de uma coluna - pode-se separa-las por vírgulas

     http://localhost:8080/OData/OData.svc/produtos(id='1',outra='x')  -> aplica atualiza sobre a linha com ID = "1"  AND outra="x"

Caso a coluna não seja indicada, o MVCBrServer irá utilizar a chave indicada no metadata (modelo de dados).

Quando o BODY indica uma lista de linhas em um ARRAY – não é possível indicar a chave nos parâmetros já que cada linha possui uma chave diferente (WHERE). Neste caso o servidor irá utilizar o seu “keyID” do metadata e aplicar o valor de cada linha na WHERE para escolher as linhas que irão receber atualização ou seja, as linhas enviadas no ARRAY devem conter as colunas correspondente a chave do RESOURCE.

Ao caso cabe uma questão – A especificação OData indica que no comando PUT, todas as colunas devem ser enviadas para o servidor. A implementação no MVCBrServer não possui esta exigência – é possível enviar somente as colunas da chave e as colunas a serem atualizadas, ignorando as colunas que não foram enviadas na lista. Nos casos que for necessário enviar um NULL para uma coluna, este desejo deve constar na linha enviada… Ex: {…., “colunaxxx”: NULL, … }

Introdução ao OData | INSERT | DELETE |

Share Button

O texto anterior mostrei um exemplo de como enviar um INSERT para o servidor usando o METHOD POST padrão RESTful utilizado no MVCBrServer.

Agora vamos olhar como enviar comando DELETE (METHOD DELETE) para o servidor e excluir um linha na tabela.

Há duas possibilidade para excluir registros de uma tabela na estrutura do servidor MVCBrServer. A primeira é enviar uma única linha diretamente pela URI ao servidor indicando o RESOURCE e como parâmetro o identificador padrão da linha – em geral ligado a chave primária:

  • http://localhost:8080/OData/OData.svc/grupos(’08’) – com METHOD DELETE, exclui a(s) linha(s) com chave ’08’ – neste formato o servidor irá utilizar como chave para encontrar a linha a excluir o “keyID” da tabela indicado no metadata – Um cuidado a observar é que neste formato pode ocorrer do metadata se referir a mais de uma linha e neste caso o servidor irá excluir todas as linhas que contenham a mesma informação – exemplo: se for o documento da venda e o RESOURCE for os itens irá excluir todos os itens da venda que contenham o mesmo documento;
  • http://localhost:8080/OData/OData.svc/grupos(grupo=’08’) – neste modelo o servidor irá utilizar a coluna indicada para selecionar as linhas a serem escolhidas o que permite melhor controle pelo “Coder”. Caso seja necessário mais de uma coluna, basta separar por vírgula ex:  …/ItemDaVenda(dcto=’00001′,ordem=1);

Outro formato é o envio no “BODY” com um ARRAY contendo uma lista de linhas a serem excluídas:

	http://localhost:8080/OData/OData.svc/ItemDaVenda

        no BODY enviar o JSONArray:
           [ { "dcto":"00001", "ordem":1},
             { "dcto":"00002", "ordem":1}
              ....
           ]

Introdução ao OData | Fazendo INSERT

Share Button

Tomando emprestado do padrão RESTful, para enviar um INSERT para o banco de dados com o protocolo OData é preciso formatar uma mensagem com METHOD POST e enviar no BODY da mensagem o JSON correspondente aos dados das colunas a serem inseridas no banco de dados.

 

POST_Cliente

 

No exemplo, o METHOD POST enviou uma linha contendo o registro a persistir no banco de dados. Como a repetição de envios de comandos ao servidor pode se tornar de alto custo, dado ao uso de conectividade de baixa velocidade ou instável – o recomendado é enviar um pacote de dados juntos para que sejam executados no servidor. Para isto, o servidor MVCBrServer prevê a possibilidade de receber um   ARRAY JSON com uma lista de registros a serem inseridos.

Sendo assim, é possível enviar um pacote de dados no seguinte formado:

URL-POST: http://localhost:8080/OData/OData.svc/clientes

[
{"codigo":9999997,
"nome":"Jose da Silva",
"fone":"055-111-12312",
"cidade":"São Paulo"
},

{"codigo":9999998,
"nome":"Jose da Silva8",
"fone":"055-111-12312",
"cidade":"São Paulo"
},

{"codigo":9999999,
"nome":"Jose da Silva9",
"fone":"055-111-12312",
"cidade":"São Paulo"
}
]

Codigo GIT MVCBr

Primeira Parte

FB

 

Share Button

Introdução
Tomando emprestado o WIKI “plugin” é um componente computacional que adiciona recursos a um programa existente. Quando um programa suporta “plugins” ele permite ser customizado para responder a necessidades não previstas no projeto original.

Uma interface de “plugin” deve prever a possibilidade de um conjunto de código ou janela permitir ser inserida em partes do programa principal.

Em um primeiro instante – é comum encontrar “plugins” que publicam alguma função ou procedimento a ser chamado pelo aplicativo principal. Este modelo limita as funcionalidade dos “plugins” que em geral ficam mais estáticos a serem um item de menu ou uma ou outra funcionalidade.

O “plugin” que irá assinar algum serviço do aplicativo

A idéia que motiva esta publicação é a construção de um modelo de aplicativo principal HOST que publica serviços implementados em seu código e ficam disponíveis para que os “plugins” possam assinar estes serviços.

Então o HOST se torna um “publisher” é o plugin um “subscriber”, onde o plugin que solicita a assinatura de um determinado serviço do HOST.

Com a mecânica em que o “plugin” assina aos serviços do aplicativo servidor permitirá que o “plugin” tome a decisão sobre qual tipo de serviço ele quer assinar no aplicativo principal . Um exemplo é um “plugin” assinar  para ser um item do menu –  em outros casos poderá assinar funcionalidade de uma “aba” de janela ou adicionar “frames” a alguma interface do usuário – entregando mais poder de escolha ao “plugin”.

Segurança x Flexibilidade

Ainda que “flexibilidade” seja o principal objetivo, segurança segue o mesmo caminho da flexibilidade. Há que se questionar o quanto um HOST pode ser seguro o bastante para não permitir um assinante malicioso. Não tenho resposta para a questão, já que um “plugin” maldoso poderia assinar a serviços para aplicar táticas maliciosas – questão que precisa ser avaliada.

diagrama

Uma interface para publicar serviços

Considerando que o programa principal tem um formulário:

TMeuMenu = class(TForm)

end;

o que precisamos fazer é dotar o formulário principal de uma interface que permita publicar os seus serviços:

  IPluginApplication = interface
    ['{6ED989EA-E8B5-4435-A0BC-33685CFE7EEB}']
    procedure RegisterMenuItem(const AParentMenuItemName, ACaption: string; ADoExecute: IPluginMenuItem);
    procedure RegisterToolbarItem(const AParentItemName, ACaption: string;  ADoExecute: IPluginToolbarItem);
    procedure RegisterAttributeControl(const AType,ASubType: Int64;  ADoExecute: IPluginControl);
  end;

Com isto o formulário passa a implementar a interface de serviço de “plugins” da seguinte forma:

TMeuMenu = class(TForm, IPluginApplication)
….
end;

 

Registrando o Plugin para o Aplicativo Principal

Antes de carregar um “plugin” o aplicativo host precisa conhece-lo (uma lista de plugins registrados). Uma forma simplista, é guardar uma lista de “plugins” disponíveis em um arquivo INI do host. A lista é necessária para que o aplicativo possa fazer carga dos seus “plugins”. Uma vez feito a carga do plugin, estes  irão assinar os serviços a que desejam interagir com o host.

O framework utiliza a implementação de DLLs para troca de código com o aplicativo principal, publicando duas chamadas – uma para carga inicial da DLL e outra para quando o aplicativo principal esta encerrando o uso do plugin:

function LoadPlugin(AAplication: IPluginApplication): IPluginItems;
procedure UnloadPlugin;
...
exports LoadPlugin, UnloadPlugin;

Carregando o Plugin
Sabedor destas duas entradas disponíveis no “plugin”, resta escrever os métodos de carga do plugin (ver TPluginManager nos fontes):

function LoadPluginService(APlugin: string;
  AAppliction: IPluginApplication): Integer;
var
  F: function(APApplication: IPluginApplication): IPluginItems;
  H: THandle;
begin
  result := -1;
  H := LoadLibrary(PWideChar(APlugin));
  if H > 0 then
  begin
    try
      @F := GetProcAddress(H, 'LoadPlugin');
      if assigned(F) then
        result := LPluginManager.FPlugins.Add(H, F(AAppliction))
      else
        raise Exception.Create('Não carregou o plugin');
    except
      FreeLibrary(H);
    end;
  end;
end;

Note que a função “LoadPlugin” do plugin espera que seja enviado um IPluginApplication e retorna uma lista de plugins existente na mesma DLL, já que uma DLL pode conter 1 ou mais plugins.

O IPluginApplication, representa a interface do HOST que publica os serviços que os plugins poderão assinar no HOST – Internamente a DLL irá montar uma lista de plugins disponíveis. Cada plugin da DLL se encarrega de assinar os serviços do HOST.

Estendendo os serviços do HOST

A interface que publica os serviços no HOST é o IPluginApplication que já possui três serviços básicos para a assinatura sendo eles:

  1. MenuItem – Assinatura para se registrar em um item de menu do HOST;
  2. ToolbarItem – Assinatura para se registrar em um item da Toolbar;
  3. AttributeControl – Uma assinatura a utilizar o plugin como um “control” ou atributo de um janela.

Caso deseje implementar outros serviços para o HOST, estendendo suas funcionalidades é possível estender a interface IPluginApplication e implementa-la no HOST:

IMyPluginApplication  =  interface(IPluginAppliction)
     procedure   RegisterXXX(....);
end;

// no host
 TMyMainMenu = class(TForm,  IMyPluginApplication )
 ...
 end;

 

Parte 2 – Construindo o Plugin

(No final o código estará disponível no GIT)
….

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

Como é de conhecimento da comunidade o FireDac não tem suporte completo ao Firebird3, já que o lançamento do FB3 veio depois do lançamento do Berlin.

Quando se trabalha com Package (novidade no FB3) não é possível escolher na IDE qual o procedimento a executar no componente TFDStoredProc.
Uma forma de fazer isto é escrevendo um editor (delphi way) para auxiliar a propriedade StoredProcName…


unit Data.fireStoredProcEditor;

interface

uses
  SysUtils, Classes, DesignIntf, DesignEditors, DB;

type
  TFireStoredProcNames = class(TStringProperty)
  private
    procedure GetValues(Proc: TGetStrProc); override;

  public
    function GetAttributes: TPropertyAttributes; override;
  end;

procedure Register;

implementation

uses FireDAC.Comp.Client, FireDAC.Phys.Intf;

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(string), TFDCustomStoredProc,
    'StoredProcName', TFireStoredProcNames);
end;

{ TFireStoredProcNames }

function TFireStoredProcNames.GetAttributes: TPropertyAttributes;
begin
  result := [paValueList];
end;

procedure TFireStoredProcNames.GetValues(Proc: TGetStrProc);
var
  DB: TFDCustomStoredProc;
  qry: TFDQuery;
  eh3:boolean;
  oMetaIntf: IFDPhysConnectionMetadata;
  function iff(b:boolean;t,f:string):string;
  begin
    if b then result := t else result := f;
  end;
begin
  if (GetComponent(0).InheritsFrom(TFDCustomStoredProc)) then
  begin
    DB := TFDCustomStoredProc(GetComponent(0));
    if assigned(DB.Connection) then
    begin
      if (DB.Connection.DriverName = 'FB') then
      begin
          oMetaIntf := DB.Connection.ConnectionMetaDataIntf;
          eh3 := oMetaIntf.ServerVersion.ToString[1]='3';
          qry := TFDQuery.create(nil);
          try
            qry.Connection := DB.Connection;
            qry.SQL.Text := 'select rdb$procedure_name sName from rdb$procedures ';
            if eh3 then
               qry.SQL.Text := qry.SQL.Text+ iff(db.PackageName<>'', ' where rdb$package_name = ' + QuotedStr(DB.PackageName.ToUpper),' where rdb$package_name is null ');
            qry.Open;
            with qry do
              while eof = false do
              begin
                Proc(fieldByName('sName').asString);
                next;
              end;
          finally
            qry.Free;
          end;
      end
      else
        inherited;
    end;
  end
  else
    inherited;

end;

end.

Exemplo de uma package no FB3: DateUtils Package

Criando um Packege no Delphi para a Integração
Para integrar o novo editor é necessário criar um novo projeto Package no Delphi e incluir o código do editor.

// exemplo do projeto do Package (mínimo)
package FireEditores;
{$R *.res}
requires
  DesignIDE;
contains
  Data.fireStoredProcEditor in 'Data.fireStoredProcEditor.pas';
end.