Acesso a dados – ALIAS e WORKAREA no AdvPL

Introdução

Já que o assunto está rendendo, e eu estou na maior inspiração, uma coisa que muita gente me pergunta, principalmente quem é novo em AdvPL… “O que é um ALIAS” ? E, “o que é uma WORKAREA” ? Estes termos estão presentes em todas as documentações de funções de acesso a dados do AdvPL. Neste post eu vou responder esta pergunta com a maior riqueza de detalhes que eu puder.

Definição de ALIAS

“Alias (termo técnico de informática), apelido designado a caminho de rede ou a uma tabela ou visão em um script ou “query” de banco de dados.” (Wikipedia)

Traduzindo pro AdvPL

A abordagem de acesso a dados usada em aplicações xBase (desde o DBASE II, DBASE III Plus, Clipper) foi criada sob uma abstração de acesso a dados ISAM. Logo, foram criadas na linguagem funções de acesso a dados com um comportamento pré-definido, onde você poderia escolher o “motor” ou engine de acesso a dados, sem precisar reescrever a aplicação inteira, apenas fazendo o link com um novo driver, e trocando o nome da RDD informada nos comandos de criação e abertura de tabela.

No TDN existe uma árvore de funções genéricas, onde eu acredito que quase todas as funções de acesso a dados estão documentadas, veja no link http://tdn.totvs.com/pages/viewpage.action?pageId=6063423

Conceito de RDD

Basicamente usamos a função DbCreate() para criar uma nova tabela de dados, partindo de uma estrutura informada em um array, e para abrir e utilizar efetivamente a tabela, usamos a função DbUseArea(). Em ambas as funções devemos informar a RDD (Driver) a ser utilizado.

Parte-se da premissa que todo o resto das operações, inclusive a criação de índices, pode e deve ser feita com a tabela “aberta” ( inclusive algumas operações exigem acesso “exclusivo”). As operações de inclusão de dados podem ser feitas por múltiplos usuários ao mesmo tempo, cada registro de informação tem um identificador físico único sequencial (RECord Number Order ou RECNO), e cada operação de alteração de dados de um registro exige um pedido de bloqueio explícito para alteração, que pode ser negado caso a tabela esteja aberta em modo compartilhado por outro processo, que já tenha obtido o lock daquele registro.

Com a criação de índices, a navegação pelos dados da tabela poderia ser feito movendo o ponteiro de registro atual para frente ou para trás, na ordem natural do arquivo (ordem de RECNO ou ordem de inserção), ou caso um índice esteja aberto e selecionado, a navegação é determinada pela chave do índice em uso.

No Clipper, originalmente você tinha os drivers nativos (distribuídos com o compilador) chamados DBFNTX e DBFCDX, e haviam também drivers de terceiros, como o SIXCDX. As diferenças significativas entre elas basicamente eram os formatos de armazenamento de campos “M” Memo ( em arquivos com o mesmo nome da tabela, com extensão .DBT ou .FPT), e a forma de armazenamento de índices. O Driver DBFNTX criava índices usando BTREE, sem nenhum tipo de compressão, onde um arquivo continha apenas uma chave de índice, o driver DBFCDX criava índices usando compressão de dados, e permitia mais de uma chave de indexação no mesmo arquivo de índice (extensão .CDX).

E o “ALIAS” ?

Certo … depois de explicar o início do universo, vamos ao ALIAS. Quando abrimos uma tabela no disco, precisamos informar para a função de abertura de tabelas (DbUseArea) qual é o nome do arquivo físico no disco que será aberto, e precisamos informar um ALIAS, ou APELIDO da tabela, pela qual ela será referenciada nas operações de leitura, gravação e etc.

Como podemos abrir e manter abertas mais de uma tabela de dados, precisamos dar para cada uma um ALIAS diferente. E, dentro do programa, fazemos referência ao alias na operação com a tabela e seus dados. Por exemplo, no ERP Microsiga, usando por exemplo o DBAccess com um SGDB homologado, para a empresa “99” (Empresa teste do ERP), o cadastro de clientes será criado no SGDB com o nome de “SA1990”, montado com o prefixo “SA1”, mais a empresa a qual o arquivo de clientes se refere, seguido do sufixo “0” zero).

Porém, quando o cadastro de clientes é aberto dentro do ERP, explicitamente pela função ChkFile() ou implicitamente sob demanda, a tabela de clientes da empresa atual que o usuário está logado no sistema sempre será aberta com o ALIAS SA1, e todos os programas que vão ler ou gravar dados no cadastro de clientes referenciam apenas o alias “SA1”.

E “WorkArea”, o que é ?

Definindo de modo bem simplista, uma WorkArea é o nome dado ao espaço utilizado para referenciar cada ALIAS aberto no processo atual. O Clipper originalmente permitia até 250 WORKAREAS, isto significa que um processo poderia abrir e manter abertas até 250 tabelas com ALIAS distintos. Usando o Protheus, poderiamos ter até 512 alias abertos ao mesmo tempo, e a partir do Protheus 10, podemos ter até 1024 alias abertos simultaneamente por processo.

E como eu uso isso ?

Se eu posso abrir mais de uma tabela no meu ambiente, cada tabela precisa ter um ALIAS, para ser possível referenciá-la. Porém, para inserir um novo registro de uma tabela, ou pedir um bloqueio de registro, como cada tabela aberta não é um “objeto” armazenado em uma variável, eu precisaria informar para a função da RDD qual é o alias que eu quero utilizar.

A especificação XBASE encontrou duas formas elegante de fazer isso, sem passar o ALIAS como parâmetro:

– WORKAREA ATUAL
– Expressão ALIASADA

Quando abrimos uma tabela sob um alias qualquer, a WORKAREA criada para este alias passa a ser a minha WORKAREA ATUAL. Isto é, todas as funções da RDD chamadas a partir deste momento irão agir sob a WORKAREA ATUAL. A documentação do AdvPL refere-se a WORKAREA ATUAL usando a terminologia “área de trabalho atual”, que significa o alias atualmente em uso. Podemos consultar a WorkArea atual através da função ALIAS(), que retorna o nome do ALIAS atualmente em uso. Caso a função Alias() retorne uma string em branco, não há um alias selecionado na WorkArea atual — o que não quer dizer que não existam outros alias abertos, apenas que nenhum alias está “selecionado”. Podemos trocar a WorkArea atual usando a função DbSelectArea(), informando o nome do ALIAS aberto que desejamos selecionar.

Já a EXPRESSÃO ALIASADA é uma forma elegante de fazer referência a um ALIAS, inclusive sem mexer na WORKAREA ATUAL. Por exemplo, para pegar o conteúdo de um dos campos de uma tabela, no registro atualmente posicionado, podemos usar por exemplo a Função FIELDGET(), onde passamos como parâmetro o número do campo na estrutura da tabela, e a função retornará o conteúdo deste campo. Caso não saibamos o número do campo, podemos descobrir o número do campo usando a função FIELDPOS(), onde informamos uma string contendo o nome do campo desejado, e a função retorna um número maior que zero caso o campo exista na WORKAREA ATUAL, ou zero caso contrário.

Agora, imagine escrever um código inteiro assim:

DbSelectArea("SA1")
DbGoTop()
conout("Codigo ... "+fieldget(fieldpos("A1_COD")))
conout("Nome ..... "+fieldget(fieldpos("A1_NOME")))

Fica legível, vai funcionar, mas para recuperar cada um dos dados dos campos, você precisa pegar a posição de cada um, para então recuperar o conteúdo daquela posição … nada prático. Fica muito mais fácil usar a EXPRESSÃO ALIASADA. A expressão aliasada possui duas variantes, a primeira nada mais é do que o alias da tabela, seguido do operador aliasado “->”, seguido pelo nome do campo do alias a ser acessado. Veja o fonte acima como fica:

DbSelectArea("SA1")
DbGoTop()
conout("Codigo ... "+SA1->A1_COD)
conout("Nome ..... "+SA1->A1_NOME)

A segunda forma de expressão aliasada é composta pelo alias, mais o operador aliasado “->”, seguido de uma expressão Advpl entre parênteses. Na prática, a expressão colocada entre parênteses vai ser executada considerando que a WORKAREA atual é o ALIAS que prefixa a expressão.

Por exemplo, em um determinado ponto do código, você não sabe (e nem lhe interessa saber) qual é a WorkArea atual, mas você precisa fazer uma busca em um alias aberto. Você poderia fazer de duas formas: Guardando o alias corrente, trocando o alias corrente para o alias desejado, realizando a operação e restaurando o alias corrente, ou usando expressões aliasadas entre parênteses.

cAliasAntr := Alias()
DbSelectArea("SA2")
DbSetOrder(2)
DbGoTop()
conout("Primeiro fornecedor = "+SA2->A2_NOME)
DbSelectArea(cAliasAnt)

Agora, imagine que você queira fazer estas operações no alias SA2, sem mexer na Workarea atual.

SA2->(DbSetOrder(2))
SA2->(DbGoTop())
conout("Primeiro fornecedor = "+SA2->A2_NOME)

Na prática, as funções DbSetOrder(2) e DbGoTop(), executadas de dentro das expressões aliasadas prefixadas com o alias SA2, serão executadas atuando sobre o alias da expressão, mesmo que a área de trabalho atual em uso seja outra. Isto permite fazer coisas mais rebuscadas na aplicação, como por exemplo ler o conteúdo de um campo de um alias e atribuir em outro, na mesma linha.

ALIAS1->CAMPO1 := ALIAS2->CAMPO2

Ou ainda, fazer uma busca por chave de índice composta em um determinado alias, partindo do conteúdo dos campos de outro alias:

ALIAS2->(DbSeek( ALIAS1->CAMPO1+ALIAS1->CAMPO2 ))

A operação de busca será feita no ALIAS2, mas a montagem da chave informada como parâmetro para a busca será montada baseado nos valores do registro atualmente posicionado no ALIAS1.

Vamos a mais um exemplo: Você precisa armazenar em um array todos os dados de um determinado registro do alias corrente. Logo, você cria algo como:

Static Function GetRecord()
Local nI, nT := len(DbSTruct())
Local aDados := {}
For nI := 1 to nT
 aadd(aDados,fieldget(nI))
Next
Return aDados

Esta função sempre vai atual na workarea atual. Agora, para pegar os dados de dois alias distintos, você pode chamar a função assim:

DbSelectArea("SB1")
aDadosSA1 := SA1->(GetRecord())
aDadosSA2 := SA2->(GetRecord())

Mesmo que, neste ponto do fonte, o alias corrente seja o SB1, a função GetRecord() foi chamada de dentro da expressão aliasada SA1->() … Logo, ela vai considerar o alias corrente como o SA1, e na próxima chamada, o alias corrente para a função será o SA2. Isto é uma “mão na roda”.

Expressão aliasada com alias variável

Quase que eu ia esquecendo … Quando você fizer uma função que recebe por exemplo uma string com um alias como parâmetro, você não precisa saber qual o alias informado, você pode usar macro-substituição apenas do alias, ou uma forma mais elegante, a expressão aliasada com alias variável. Basta colocar a variável do alias entre parênteses, seguido do operador aliasado “->”, seguido do campo ou expressão entre parênteses desejada. Por exemplo:

Static function InsertRec(cAlias)
(cAlias)->(DbAppend())
Return

Funcionalidades adicionais

Cada WorkArea possui um ALIAS em uso, mas também possui um número sequencial de controle, atribuído internamente quando um novo ALIAS é aberto. Para saber qual é o número da WorkArea, pode ser utilizado a função Select(). Se nada for informado como parâmetro, será retornado o número da workarea atual. Pode ser informado nesta função uma string com um ALIAS. Caso este ALIAS esteja aberto, será retornado um número maior que zero. E, a função DbSelectArea() pode receber o número da WorkArea como parâmetro.

Logo, a operação de salvar o alias corrente e restaurá-lo após uma operação onde o mesmo foi ou pode ter sido trocado, ficaria assim:

nArea := select()
DbSelectArea("SA1")
// Faz alguma coisa...
DbSelectArea(nArea)

Esta forma de salvar e restaurar a área corrente de trabalho é mais interessante do que guardar a string do alias, por duas razões:

  1. Caso não exista alias aberto na workarea atual, a função alias() retorna uma string em branco. Se esta string em branco for passada como parâmetro para a função DbSelectArea(), a mesma vai gerar um erro de “Alias does not exist”. Quando usamos o número da WorkArea, caso não haja workarea em uso, a função Select() retorna 0, e o DbSelectArea() para a área 0 deixa novamente a workarea sem alias relacionado.
  2. A busca pelo número da área de trabalho ao invés do nome da área é alguns microsegundos mais rápida do que a busca por nome.

Usos interessantes

Como um ALIAS é um identificador de uma tabela aberta, eu não posso abrir uma tabela usando um alias já aberto no meu processo, porém nada impede de eu abrir a mesma tabela física duas vezes usando alias diferentes. É um cenário não muito comum de ser necessário, mas já foi usado anteriormente em situações específicas, em determinadas funcionalidades escritas por código totalmente ISAM, sem uso de Queries.

Por exemplo, em um cenário hipotético onde você precisa ler uma faixa de dados específica de uma tabela para serem processados, e o resultado deste processamento vai gerar novos dados para a mesma tabela. Você teria que, por exemplo, fazer um filtro na tabela principal, iniciar um loop de processamento enquanto houvessem registros a serem processados, e antes de cada nova inserção, você teria que salvar o registro atual da tabela, fazer a inserção, depois reposicionar no último registro processado, para então pular para o próximo. Porém, a rotina de gravação possui um ponto de entrada, onde é assumido que a tabela não está filtrada. Isto faria você ter que desligar o filtro antes de chamar o ponto de entrada, guardar a posição atual, chamar a sub-rotina, e na volta setar novamente o filtro e reposicionar o registro.

Esta operação em ambientes como o ADS Server, ou o TOPConnect2 para AS400, eram lentas, pois cada vez que um filtro era definido, ele era enviado ao ADS Server e/ou ao AS400, onde a faixa de dados deste filtro já era separada na montagem do filtro, e não sob demanda. Para resolver isso, nada mais elegante do que abrir a tabela para filtro com um outro alias, fazer o filtro nesta tabela, e manter a tabela para manutenção de dados aberta com o alias original, sem o filtro. Assim, um alias apontava para a tabela filtrada, e outro alias apontava para a tabela sem os filtros, sendo possível realizar este processamento sem ficar ligando e desligando filtros e reposicionando registros. No final do processo, o alias filtrado era fechado.

Nunca mais vi alguém usar isso, mas foi uma saída bem interessante para a época. Hoje normalmente abrimos um alias com uma Query no SGDB, onde os dados já vem filtrados e ordenados de acordo com a query, e fechamos a query no final do processo. Codificar e trabalhar 100 % sem query exigia alguns movimentos criativos naquela época 😉

Conclusão

Neste final de semana já foram 4 artigos publicados acerca do acesso a dados em AdvPL, e eu sinto em informar que este post encerra a penas a “Introdução” ao assunto. Porém, esta abordagem é necessária para sabermos um pouco mais sobre como o circo funciona por baixo da lona. Isto é a base da abordagem. A partir daqui é possível entrar em mais detalhes das demais funcionalidades, e nos comportamentos dos operadores de dados do AdvPL.

Valeu moçada, até o próximo post 😀

Referências

ALIAS. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2014. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Alias&oldid=40855180>. Acesso em: 25 out. 2015.

9 respostas em “Acesso a dados – ALIAS e WORKAREA no AdvPL

  1. 1) De forma simplista e grosseira e comparando WorkArea e Alias com um aquivo em Excel Teríamos: 1) WorkArea =>Planilha (Worksheet) ; 2) Alias=>Nome dado à Planilha (Worksheet Name);
    2) “Nunca mais vi alguém usar isso, mas foi uma saída bem interessante para a época. Hoje normalmente abrimos um alias com uma Query no SGDB, onde os dados já vem filtrados e ordenados de acordo com a query, e fechamos a query no final do processo. Codificar e trabalhar 100 % sem query exigia alguns movimentos criativos naquela época”. Para este caso teríamos o seguinte exemplo: Browse de uma Tabela ao que possui um campo chave referenciado na própria tabela e para o qual você quer trazer a descrição em um campo Virtual. Ex.: A1_COD como campo chave e, hipotéticamente, A1_CODSUP. Para trazer corretamente o conteudo de A1_NOME referenciado por A1_CODSUP usando Posicione, teremos que abrir SA1 em uma outra WorkArea e com outro nome (Isso tanto para X3_INIBRW quando para gatilho). Caso contrário o sistema se perde.

    Curtido por 1 pessoa

    • Exemplo Real:

      1) Tabela SQB (Departamentos). Possui o campo QB_DEPSUP que faz referência ao campo QB_DEPTO. Inaginando que criemos o campo “Virtual” e “Visual” QB_XDESSUP, para retornar a descrição referente ao código informado em QB_DEPSUP e referente a QB_DEPTO poderíamos, por exemplo, usar o fragmento de código abaixo:

      #include “totvs.ch”
      static __cQBxDescSup as character
      static __cQBTableName as character
      static __cQBKeyEmpFil as character
      static __cQBKeyVAlias as character
      /*
      Programa:uPetros-SQB.prw
      Funcao:QBXDESSUPTrg()
      Autor:Marinaldo de Jesus (marinaldo.jesus@totvspartners.com.br)
      Data:21/10/2015
      Finalidade:Retornar Descricao do Departamento a partir de Alias Virtual
      */
      user function QBXDESSUPTrg(lMemVar as logical)
      local cQBDEPSUP as character
      local cEmpFilKey as character
      local cSQBFilial as character
      local cQBxDescSup as character
      local cSQBKeySeek as character
      local lSQBChange as logical
      local nSQBOrder as numeric
      DEFAULT __cQBxDescSup:=Space(GetSx3Cache(“QB_XDESSUP”,”X3_TAMANHO”))
      DEFAULT __cQBTableName:=RetSQLName(“SQB”)
      DEFAULT __cQBKeyEmpFil:=”@__cQBKeyEmpFil@”
      DEFAULT __cQBKeyVAlias:=GetNextAlias()
      cEmpFilKey:=(cEmpAnt+cFilAnt)
      if .not.(__cQBKeyEmpFil==cEmpFilKey)
      __cQBKeyEmpFil:=cEmpFilKey
      if ((Select(__cQBKeyVAlias)==0).or.(.not.(__cQBTableName==RetSQLName(“SQB”))))
      lSQBChange:=.T.
      endif
      endif
      if (lSQBChange)
      if .not.(Empty(__cQBKeyVAlias))
      if (Select(__cQBKeyVAlias)>0)
      (__cQBKeyVAlias)->(dbCloseArea())
      endif
      endif
      ChkFile(“SQB”,.F.,__cQBKeyVAlias)
      endif
      if (Select(__cQBKeyVAlias)>0)
      DEFAULT lMemVar:=IsInCallStack(“RUNTRIGGER”)
      cQBDEPSUP:=if(lMemVar,GetMemVar(“QB_DEPSUP”),SQB->QB_DEPSUP)
      cSQBFilial:=xFilial(“SQB”,if(lMemVar,NIL,SQB->QB_FILIAL))
      cSQBKeySeek:=cSQBFilial
      cSQBKeySeek+=cQBDEPSUP
      nSQBOrder:=RetOrder(“SQB”,”QB_FILIAL+QB_DEPTO”)
      cQBxDescSup:=GetAdvFVal(__cQBKeyVAlias,”QB_DESCRIC”,cSQBKeySeek,nSQBOrder,__cQBxDescSup)
      else
      cQBxDescSup:=__cQBxDescSup
      endif
      return(cQBxDescSup)

      E, no inicializador padrão (X3_RELACAO) do campo QB_XDESSUP e Inicializador de Browse (X3_INIBRW) informariamos a função U_QBXDESSUPTrg() ao invés de Posicione ou, como no exemplo, GetAdvFVal(). O mesmo para o Trigger (SX7).

      Curtido por 1 pessoa

  2. Gente tenho tenho que fechar alguns alias Temp’s que são abertos por uma rotina padrão

    tem como eu ter uma lista dos alias abertos em determinado momento, pra saber quais devo fechar
    ?

    Curtido por 1 pessoa

    • Pergunta interessante …. Infelizmente não existe uma lista pronta, mas é possível montar uma lista varrendo a lista de WorkAreas abertas a partir do número da WorkArea 😀

      Abraços

      Curtir

  3. Olá, colegas!
    Sei que existe uma sintaxe para criar uma query resultando diretamente num array (sem precisar de laço para montagem do array). Lembram dessa função?
    Abs. Obrigado

    Curtido por 1 pessoa

  4. Pingback: Select - ProtheusAdvpl

Deixe um comentário