sexta-feira, 23 de outubro de 2015

Protheus e o GRANT de DBA

Protheus e o GRANT de DBA no Oracle

Obs: Faça por conta e risco e em base de testes

Uma das grandes reclamações de empresas é sobre a segurança com o banco de dados. Tenho visto que muitos DBAs, não aceitam que o Protheus precise ter o grant de DBA para poder funcionar corretamente. Sinceramente concordo com isso.
Pessoalmente não gosto de dar todo esse poder, não que ache o Protheus não é confiável, mas, como muitas consultorias e pessoas acabam tendo acesso ao Protheus, é prudente limitar.
Estou em fase de transição de carreira de Analista de Sistemas para DBA Oracle/Sql Server, como trabalho com os dois mundos Protheus e Oracle, procurei uma forma de dar esse acesso sem parar o Protheus.
O teste será feito da seguinte forma :
Versão do DbAcess 4.2 Build 20141119 64 bits
Versão do Oracle 11.2.0.3

Vou deixar o serviço do DBACCESS no modo console para podermos visualizar tudo o que esta ocorrendo.

Subindo o serviço do Dbaccess




Concedendo as permissões

OBS: Recomendo executar este procedimento por um DBA

Caso já tenha concedido o acesso de DBA, faça o revoke

SQL> REVOKE DBA FROM NOME_SCHEMA;


Conceda as permissões, pode ser que o Protheus reclame de outras, se ocorrer, verifique no log do Dbaccess.


Testando a conexão

Note que ocorreu o erro Ambiente, Usuário ou Senha inválido.



O usuário, senha e ambientes não estão inválidos, a resposta do Protheus esta incorreta. Esta é uma mensagem genérica.

Vamos analisar o log do DBACCESS com mais detalhe :



Observando o log de erro acima, o Protheus tentou fazer um select na schema SYS na tabela OBJ$, mas o usuário não tem esse acesso.

O que o Protheus realmente precisa, é acessar as views de dicionário de dados.

Com isso, se der o grant de DBA, o usuário do Protheus consegue ler o conteúdo desta tabela.

Mas é justamente o que não queremos, então vamos dar acesso de leitura com o comando abaixo :



Ok, feito isso, vamos testar novamente


Pronto, revogamos o grant de DBA e o Protheus consegue se conectar.

Mas ainda não acabou..... O grant acima, foi somente para conexão com a base.

Ao tentar acessar o Protheus, os erros continuam....

Vamos dar mais permissões :

GRANT SELECT ON SYS.COLS$ TO NOME_SCHEMA;
GRANT SELECT ON SYS.OBJ$ TO NOME_SCHEMA;
GRANT SELECT ON SYS.ICOL$ TO NOME_SCHEMA;
GRANT SELECT ON V_$SESSION TO NOME_SCHEMA;
GRANT SELECT ON SYS.IND$ TO NOME_SCHEMA;


A permissão V_$SESSION é feita devido ao Protheus tentar dar select na V$SESSION que na verdade é um sinônimo.

Após tudo isso, fiz os seguintes testes no Protheus :
  • Atualizei o TSS -> Sem erros
  • Rodei os compatibilizadores do Protheus -> Sem erros
  • Criei tabela, indices, campos através do SIGACFG -> Sem erros


Se você gostou, clique nos links patrocinados. Obrigado !!!

quinta-feira, 12 de março de 2015

Exemplo pronto do uso da classe FWMSEXCEL

Sobre a classe FWMSEXCEL

A classe FWMSEXCEL tem como finalidade gerar um arquivo XML que posteriormente pode ser utilizado para ser aberto com o Excel.

Nesta classe,  o arquivo Excel fica mais elaborado e pode ser formatado com cores, tipos de dados, abas, etc. Diferentemente do CSV que não possuem nenhum tipo de formatação.

A desvantagem deste formato é que precisa de um Excel 2003 ou acima, além do arquivo ser bem maior do que um simples CSV.

Abaixo estou colando um exemplo completo que utilizei para exportar a planilha.
No site da Totvs (TDN) existe um exemplo bem completo explicando com detalhes o seu funcionamento.

Segue exemplo :

#INCLUDE "PROTHEUS.CH"
#include "TBICONN.CH"
#include "RWMAKE.CH"
#include 'topconn.ch'
#include "TbiCode.ch"

#DEFINE ENTER CHR(13)+CHR(10)
#DEFINE DIVISOR  ";"

USER FUNCTION Ckpt01
Local cQuery    := ""
Local cCaminho  := "\COCKPIT_OFFLINE
Local nConta    := 0
Local nContador := 0
Local cMensagem := ""
Local aEmail    
Local aTables   := { "SM0" }
Local oExcel := FWMSEXCEL():New()

// Montagem das colunas
oExcel:AddworkSheet("Contas a pagar")
oExcel:AddTable ("Contas a pagar","Relação de títulos em aberto",)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Filial",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Prefixo",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Titulo",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Parcela",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Tipo",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Natureza",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Cod.Forn",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Nome do fornecedor",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Emissão",1,4)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Vencimento real",1,4)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Valor do título",1,2)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Valor em reais",1,3)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Data da baixa",1,4)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Histórico do título",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Saldo do título",1,2,.F.)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Moeda",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Número da DI",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Número da PO",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Número da Invoice",1,1)
oExcel:AddColumn("Contas a pagar","Relação de títulos em aberto","Data da Invoice",1,4)

////////////////////

cQuery := "SELECT SE2010.E2_FILIAL, " + ENTER
cQuery += "       SE2010.E2_PREFIXO, " + ENTER
cQuery += "       SE2010.E2_NUM, " + ENTER
cQuery += "       SE2010.E2_PARCELA, " + ENTER
cQuery += "       SE2010.E2_TIPO, " + ENTER
cQuery += "       SE2010.E2_NATUREZ, " + ENTER
cQuery += "       SE2010.E2_FORNECE, " + ENTER
cQuery += "       SE2010.E2_NOMFOR, " + ENTER
cQuery += "       SE2010.E2_EMISSAO, " + ENTER
cQuery += "       SE2010.E2_VENCREA, " + ENTER
cquery += "       SE2010.E2_VALOR, " + ENTER
cQuery += "       SE2010.E2_VLCRUZ," + ENTER
cquery += "       SE2010.E2_BAIXA, " + ENTER
cquery += "       SE2010.E2_HIST, " + ENTER
cquery += "       SE2010.E2_SALDO, " + ENTER
cquery += "       DECODE(SE2010.E2_MOEDA,1,'REAL',2,'DOLAR') E2_MOEDA, " + ENTER
cquery += "       SE2010.E2_NUMDI, " + ENTER
cquery += "       SE2010.E2_NUMPO, " + ENTER
cQuery += "   SE2010.E2_INVOICE, " + ENTER
cQuery += "   SE2010.E2_DTENTRA " + ENTER
cQuery += "  FROM SE2010 SE2010 " + ENTER
cQuery += " WHERE SE2010.E2_SALDO > 0 " + ENTER
cQuery += "   AND SE2010.D_E_L_E_T_ = ' ' "+ ENTER
cQuery += " ORDER BY SE2010.E2_FILIAL, " + ENTER
cQuery += "          SE2010.E2_EMISSAO, " + ENTER
cQuery += "          SE2010.E2_PREFIXO, " + ENTER
cQuery += "          SE2010.E2_NUM, " + ENTER
cQuery += "          SE2010.E2_PARCELA, " + ENTER
cQuery += "          SE2010.E2_TIPO " + ENTER

If Select("QRY") > 0
QRY->( dbCloseArea() )
EndIf
    
//cArquivo := AllTrim(cCaminho) + "\" + "Contas_Pagar_01_" + DTOS(DATE()) + "_.csv"
cArquivo := AllTrim(cCaminho) + "\" + "Contas_Pagar_01_" + DTOS(DATE()) + "_.XML"
    
If !File(ALLTRIM(cArquivo))
    //nHandle :=MSFCreate(ALLTRIM(cArquivo))
Else
    fErase(ALLTRIM(cArquivo))
    Conout("..... Limpando arquivo temporário .....")
    //nHandle:= MSFCreate(ALLTRIM(cArquivo))
Endif
 
ConOut("..... Abrindo tabela temporária ....." )
//RPCSetType( 3 )
//RpcSetEnv ( "01", "01", Nil, Nil, "FAT", Nil, aTables )
    
dbUseArea( .T., "TopConn", TCGenQry(,,cQuery), "QRY", .F., .F. )
    
QRY->(dbEval({|| nConta++})) // Contador de linhas da tabela acima.
QRY->( dbGoTop() )   
    
ConOut("..... Gerando arquivo CSV ....." )
    
// Montagem do cabeçalho do relatório
//cLinha  := "Filial;Prefixo;Titulo;Parcela;Tipo;Natureza;Cod.Forn.;Nome Fornecedor;Data Emissao;Vencimento Real" + ENTER
    
While QRY->( !Eof() )
oExcel:AddRow("Contas a pagar","Relação de títulos em aberto",{AllTrim(QRY->E2_FILIAL),;
AllTrim(QRY->E2_PREFIXO),;
AllTrim(QRY->E2_NUM),;
AllTrim(QRY->E2_PARCELA),;
AllTrim(QRY->E2_TIPO),;
AllTrim(QRY->E2_NATUREZ),;
AllTrim(QRY->E2_FORNECE),;
AllTrim(QRY->E2_NOMFOR),;
AllTrim(u_xSTtoSF(QRY->E2_EMISSAO)),;
AllTrim(u_xSTtoSF(QRY->E2_VENCREA)),;
QRY->E2_VALOR,;
QRY->E2_VLCRUZ,;
AllTrim(u_xSTtoSF(QRY->E2_BAIXA)),;
AllTrim(QRY->E2_HIST),;
QRY->E2_SALDO,;
AllTrim(QRY->E2_MOEDA),;
AllTrim(QRY->E2_NUMDI),;
AllTrim(QRY->E2_NUMPO),;
AllTrim(QRY->E2_INVOICE),;
AllTrim(u_xSTtoSF(QRY->E2_DTENTRA));
})
    nContador ++
    QRY->( dbSkip() )
    ConOut("..... Andamento : " + AllTrim(Str(ROUND(100 / nConta * nContador,2))) + " %")
Enddo

ConOut("..... Salvando arquivo XML  ....." )
oExcel:Activate()
oExcel:GetXMLFile(cArquivo)
   
If Select("QRY") > 0
QRY->( dbCloseArea() )
EndIf
    
//fclose(nHandle)

ConOut("..... Gerando e-mail  ....." )    
// Envia o e-mail para o usuário
cMensagem   := "Email enviado automaticamento pelo Protheus " + ENTER + ENTER
cMensagem   += "Relatório de títulos do contas a pagar emitido dia " + dToc(DATE()) + ENTER + ENTER
cMensagem   += "OBS : ABRIR COM O EXCEL 2003 EM DIANTE OU SIMILAR " + ENTER + ENTER

cMensagem   += "Relatório disponível no caminho \\BONETE\PROTHEUS11\PROTHEUS_DATA" + cArquivo 
    
aEmail := StrTokArr(AllTrim(GetMv("MV_COCKT01")), ";")

for i:=1 to Len(aEmail)
U_xEEmail(cMensagem,"Relatório Cockpit Pagar gerado ",AllTrim(aEmail[i]))
    ConOut("..... Email enviado para " + aEmail[i])
next i
    
RETURN

terça-feira, 2 de dezembro de 2014

Desabilitando o HINT automático do Top Connect

Desabilitando o HINT do Top Connect com o Oracle


OBS : Faça por conta e risco, teste primeiro ou consulte um Dba.

Para quem trabalha com o Protheus utilizando o Top Connect e o Oracle, já teve por curiosidade ou necessidade, percebido que em todos os selects feitos pela base, sempre tem um HINT adicionado após a palavra SELECT.

Observe uma query gerada pelo Protheus logo abaixo :

SELECT /*+ FIRST_ROWS(50) */
 R_E_C_N_O_
  FROM SA2010
 WHERE R_E_C_N_O_ > 50186
   AND D_E_L_E_T_ = ' '
 ORDER BY R_E_C_N_O_

Observe na query acima que existe uma linha /*+ FIRST_ROWS(50) */ que pode variar um pouco mas o significado é o mesmo.

Este trecho na query faz com que o banco ignore o plano de execução que o Oracle iria executar e de forma forçada, executa a instrução que o Protheus impôs.

De certa forma não de todo ruim, porque, se a base de dados não estiver bem configurada, o Protheus poderia ficar com uma performance bem até pior.

Particularmente, o FIRST_ROWS(50) significa que o Oracle deve retornar os primeiros 50 registros retornados de um Select de forma imediata e posteriormente, o restante o banco poderia carregar com menos prioridade.

Não esta errado, mas vale lembrar que estamos dizendo que o plano de execução gerado pelo Protheus, é melhor do que o plano que seria gerado pelo Oracle.

Se o Top Connect não executasse esta instrução, o plano de execução resultado da query poderia ser o mesmo para ambos os casos e iria depender de como esta configurado o parâmetro OPTIMIZER_MODE.

Atualmente o Top Connect não permite que o Hint seja desabilitado, mas podemos fazer o desligamento desse parâmetro diretamente pelo banco de dados.

Como havia dito anteriormente, este procedimento deve ser executado por um Dba. Este parâmetro desabilita o uso do Hint inclusive para o próprio funcionamento do Oracle.

Então, mão na massa.

Para desabilitar o HINT no banco de dados, temos que executar o seguinte comando no Oracle :

SQL> ALTER SYSTEM SET "_optimizer_ignore_hints"= TRUE SCOPE=BOTH;

Obs: Para surtir efeito, é necessário que a sessão do usuário não seja a mesma.

Este comando não ira alterar o resultado da query, somente ira deixar o plano de execução ser gerado pelo Oracle ao invés de ser imposto pelo Protheus.

Veja como ficou o resultado da mesma query :

SELECT /*+  */
 R_E_C_N_O_
  FROM SA2010
 WHERE R_E_C_N_O_ > 50186
   AND D_E_L_E_T_ = ' '
 ORDER BY R_E_C_N_O_

Observe que a query e o resultado dela sera o mesmo, a diferença é o plano de execução.

Para quem não sabe o que é um plano de execução, vou resumir de forma simplificada.

Quando executamos uma query, o banco antes de qualquer coisa faz um PARSE, ou seja, primeiro ele verifica se a query esta escrita corretamente, se o usuário tem permissão, se a tabela existe, verifica se a instrução já estava na memória, etc, não vou entrar no assunto DBA, vou deixar para outro dia.

Feito isso, o Oracle vai decidir como ele ira retornar esses dados, de uma forma bem grotesca imagine ima viagem para o interior. Existem diversos caminhos e rodovias que podemos pegar mas dependendo do trânsito podemos pegar o caminho mais longo para chegarmos mais rápido.

Essa inteligência o Oracle tem e já faz sozinho se a sua base estiver bem configurada e com as estatísticas atualizadas.

Com o parâmetro dos HINTS desligados ao invés do Protheus impor um caminho, fica a cargo do Oracle fazer e montar o melhor trajeto.

Desligar ou não o HINT é uma decisão de cada administrador, por isso faça a conta do ganho de tempo ao desligar este parâmetro.

Abraços a todos.

O que não fazer ao utilizar o dbUseArea

O que não fazer ao montar uma query

É muito comum por falta de experiência, por pressa ou falta de conhecimento, independentemente da linguagem esquecermos de fazer uma análise melhor dos dados que puxamos em nosso banco de dados.

Certo dia estava naquela correria para montar um Web Service para cancelamento de notas fiscais e sai fazendo daquele jeito.

Depois fui fazer uma manutenção no programa e encontrei a seguinte linha :

//PREENCHENDO OS ITENS DA NOTA FISCAL (SD1) 
cQuery := " SELECT * FROM " + RetSqlName("SD1") // * FROM é coisa porca. Depois vou arrumar
cQuery += "    WHERE D1_FORNECE = '"+ cfornecedor + "'" 
cQuery += "    AND D1_LOJA = '"     + cLoja       + "'" 
cQuery += "    AND D1_DOC = '"      + cNota       + "'" 
cQuery += "    AND D1_SERIE = '"    + cSerie      + "'"                    
cQuery += "    AND D_E_L_E_T_ = ' '" 


Na linha destacada acima, veja que utilizei o SELECT * FROM ao invés de mencionar o nome dos campos como D1_FORNECE, D1_LOJA, D1_DOC, D1_SERIE, etc....

Parece preciosismo mas não é, um usuário rodando este programa a perca é imperceptível mas quando rodamos em um ambiente com muitos usuários teremos uma boa perca de performance.

quarta-feira, 26 de novembro de 2014

Webservice no Protheus - Parte 3 - Final

Notas do WebService

Algumas pessoas questionaram porque em alguns casos o Webservice não esta ativo ou porque retorna mensagens de erro.

No exemplo, quando o WebService não esta HABILITADO procure verificar se existe algum erro na compilação do programa.
Mesmo compilado com sucesso não quer dizer que a rotina esta correta. Como sabemos, o Protheus não é fortemente tipado, ou seja, qualquer coisa ele deixa passar.

Se no meio do código escrever PARALELEPÍPEDO e compilar, esta compilação sera feita com sucesso.

Outra coisa, caso retorno Nulo e ocorra um erro, é devido eu não ter tido tempo de fazer a tratativa das exceções, ando meio corrido e sem tempo para fazer publicações.

Uma correção :

Em NameSpace onde o nome esta ServiceReference1, troquei para ClientWSProtheus.

Vale lembrar que o WebService MeuPrimeiroWebService é possível adicionar diversos métodos.
Sempre que fizer alguma alteração em métodos, na aplicação do Visual Studio ou Java, é necessário atualizar a referência.

Como premissa, você deve conhecer alguma coisa de ADVPL e do Visual Studio.

Se você gostou do artigo, clique nos links patrocinados.

Saudações.