quarta-feira, 28 de fevereiro de 2018

DROP Wiki #5 - Abstração de comandos SQL

Uma das premissas do DROP é fornecer interfaces uniformes para que o código seja escrito uma vez, e este opere sobre múltiplos SGBDs. O tema deste post é sobre um dos principais pilares do DROP para garantir a veracidade desta premissa, que são as interfaces de abstração de comandos SQL.

Sabemos que os recursos oferecidas pelos SGDBs e respectivas DMLs são muito parecidos. Todos entregam praticamente as mesmas funcionalidades, por estruturas de SQLs ligeiramente diferentes.

As units AqDrop.DB.SQL.Intf e AqDrop.DB.SQL fornecem, respectivamente, interfaces e classes que abstraem as funcionalidades de DML, sendo que as instâncias destas classes são traduzidas para a respectiva DML do SGBD apontado pela conexão do DROP que for executar o comando.

Vamos começar por algo simples: 'select * from country'. Sabemos que praticamente todo SGBD relacional aceitará este comando, desde que exista a uma tabela chamada country no esquema selecionado. O código abaixo demonstra como é simples usar as interfaces e classes de abstração de comandos SQL para executar o comando:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
procedure TfrmDW0005.TestSelect;
var
  lSelect: IAqDBSQLSelect;
  lReader: IAqDBReader;
begin
  MemoCountries.Lines.Clear;
  MemoCountries.Lines.Add('List of all registered contries:');

  lSelect := TAqDBSQLSelect.Create('country');

  lReader := FEmployeeConnection.OpenQuery(lSelect);
  while lReader.Next do
  begin
    MemoCountries.Lines.Add(lReader.Values['country'].AsString);
  end;
end;

Detalhes importantes sobre o código acima:
  • Note que a variável lSelect, que armazena a referência para o comando, é do tipo IAqDBSQLSelect;
  • Na linha 9, é instanciado um objeto do tipo select, onde a origem deste select (utilizada na cláusula from) é uma tabela de nome country (a origem pode ser também uma view, uma string contendo um subselect, ou um subselect na forma de outra interface do tipo IAqDBSQLSelect);
  • Não é informada nenhuma coluna de retorno no select, logo, o DROP assume o * como lista de colunas a serem retornadas;
  • Tanto o comando OpenQuery quanto o funcionamento do reader foram apresentados no DROP Wiki #3, mas na oportunidade, a sobrecarga utilizada do comando foi um select escrito manualmente em uma constante string. Já neste caso, estamos vendo o uso da sobrecarga que recebe uma interface do tipo IAqDBSQLSelect (linha 11);
  • A partir do início do while segue a mesma lógica já apresentada no Wiki #3.
Vamos partir agora para um comando que continua simples, mas que já exige um select diferente para (quase) cada um dos SGDBs suportados, que é a limitação da quantidade de registros que devem ser retornados pelo select. Segue novo método que retorna no máximo 3 registros da tabela country:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
procedure TfrmDW0005.TestSelectLimited;
var
  lSelect: IAqDBSQLSelect;
  lReader: IAqDBReader;
begin
  MemoCountries.Lines.Clear;
  MemoCountries.Lines.Add('List of up to 3 countries:');

  lSelect := TAqDBSQLSelect.Create('country');
  lSelect.Limit := 3;

  lReader := FEmployeeConnection.OpenQuery(lSelect);
  while lReader.Next do
  begin
    MemoCountries.Lines.Add(lReader.Values['country'].AsString);
  end;
end;

Ao executar o comando acima, o DROP traduz a informação para os recursos nativos de cada um dos SGDBs, limitando a quantidade de registros retornados.

Visando uma melhor performance do comando, vamos tornar explícito que o select deve trazer somente a coluna country de cada registro, e somente para ampliar os recursos demonstrados, vamos fazer com que esta coluna seja retornada com o alias 'country_name'. Segue método demonstrando as alterações necessárias (para deixar claro, o alias não é obrigatório ao definir uma coluna):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
procedure TfrmDW0005.TestSelectWithColumnAndAlias;
var
  lSelect: IAqDBSQLSelect;
  lReader: IAqDBReader;
begin
  MemoCountries.Lines.Clear;
  MemoCountries.Lines.Add('List of up to 3 countries:');

  lSelect := TAqDBSQLSelect.Create('country');
  lSelect.AddColumn('country', 'country_name');
  lSelect.Limit := 3;

  lReader := FEmployeeConnection.OpenQuery(lSelect);
  while lReader.Next do
  begin
    MemoCountries.Lines.Add(lReader.Values['country_name'].AsString);
  end;
end;

Também no DROP Wiki #3 comentei que o objetivo de um ORM não é fazer com que o usuário escreva manualmente os comandos SQL. Pois bem, o objetivo do DROP também não é fazer o desenvolvedor criar os próprios comandos de SQL através destas interfaces e classes. Então, por que fiz questão de passar por este conteúdo antes de chegar no ORM propriamente dito?

Eventualmente (ou algo com frequência muito maior que eventualmente), teremos que customizar os comandos que serão disparados contra o SGDB. Para fazer esta customização, o DROP conta diversas variações e recursos, no entanto, a variação mais indicada passa por conhecermos as interfaces de abstração de SQLs.

Mais adiante veremos que o ORM do DROP pode nos retornar objetos instanciados e configurados para as interfaces dos tipos IAqDBSQLSelect, IAqDBSQLInsert, IAqDBSQLUpdate e IAqDBSQLDelete. Manipular estas referências já devidamente configuradas é a maneira mais rápida e simples para customizar os comandos enviados para o SGDB.

Através destas interfaces, é possível realizar uma série de customizações, como adicionar cláusulas where, joins (use com moderação, afinal você está em um ambiente que visa o uso do ORM ;-), funções de agregação de dados, ordenação, etc. E sempre que for necessário mudar o texto resultante do comando de DML, o DROP faz isso por você.

Segue uma última variação do método de busca de países, onde uma cláusula where é adicionada, trazendo somente países em que a moeda seja o Euro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
procedure TfrmDW0005.TestSelectWithCondition;
var
  lSelect: IAqDBSQLSelect;
  lReader: IAqDBReader;
begin
  MemoCountries.Lines.Clear;
  MemoCountries.Lines.Add('List of countries where the currency is Euro:');

  lSelect := TAqDBSQLSelect.Create('country');
  lSelect.CustomizeCondition.AddColumnEqual('currency', 'Euro');

  lReader := FEmployeeConnection.OpenQuery(lSelect);
  while lReader.Next do
  begin
    MemoCountries.Lines.Add(lReader.Values['country'].AsString);
  end;
end;

Sobre o método acima, é importante dizer que foi utilizado o método AddColumnEqual, que monta uma condição com o operador de igualdade entre valores, mas existem diversos outros métodos que mudam o operador a ser utilizado. Cada um destes métodos possui várias sobrecargas, sendo que cada uma das sobrecargas permite o uso de tipos primitivos para seus respectivos fins, e o DROP traduz o tipo primitivo do Delphi para o respectivo tipo primitivo do SGDB, se este existir.

Por exemplo, alguns SGDBs trazem suporte nativo ao tipo Boolean, no entanto, o uso deste tipo varia de SGDB para SGDB. Em alguns deles a constante True se escreve desta forma, em outros se escreve como se fosse uma string ('True'), e em outros ela sequer existe. Para todos estes casos, o DROP faz a devida tradução para a constante do SGDB utilizado (ou para o formato padrão, como no caso de datas), e para os SGDBs sem suporte a esta constante, o DROP propõe outras como 1 ou 'T'. E se você não estiver satisfeito com estas propostas, você ainda pode informar ao DROP como traduzir cada um dos tipos de dados do Delphi para os tipos de dados do seu SGDB.

Finalizando, segue uma pequena demonstração destas variações de métodos e suas sobrecargas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
procedure TfrmDW0005.ShowSQL;
var
  lSelect: IAqDBSQLSelect;
begin
  lSelect := TAqDBSQLSelect.Create('any_table');

  lSelect.CustomizeCondition.AddColumnEqual('intcolumn', 1).
    AddColumnEqual('boolcolumn', True).
    AddColumnGreaterEqualThan('datecolumn', EncodeDate(2018, 2, 28)).
    AddColumnIsNull('nullablecolumn');

  ShowMessage(FEmployeeConnection.Adapter.SQLSolver.SolveSelect(lSelect));
end;

E o resultado obtido (formatado para melhor leitura):

1
2
3
4
5
6
7
8
9
select
  *
from
  any_table
where (
  intcolumn = 1 and
  boolcolumn = True and
  datecolumn >= '2018.02.28 00:00:00:000' and 
  nullablecolumn is null)


E para o momento era isto! Em algum momento da série voltaremos às interfaces de abstração para cobrir todas as variações implementadas, mas acredito que o conteúdo até aqui descrito já seja suficiente para orientar o usuário do DROP, mostrando de onde estas interfaces vêm, como vivem, como se reproduzem, e do que se alimentam.

Até o próximo episódio!

Episódio Anterior

Nenhum comentário:

Postar um comentário

Você está fazendo isso errado! Tópico #2

 Fala galera! Como estão todos? Sejam bem-vindos ao primeiro post de 2021. Continuando o tema "Você está fazendo isso errado", va...