Notícias sobre Ubuntu, Debian, Fedora, Linux, Android, Tecnologia, LibreOffice e muito mais!

Como acessar o PostgreSQL com C – Cursores

Introdução

O PostgreSQL
é um banco de dados extremamente eficiente, que não deixa a desejar se
comparado com os grandes bancos de dados comerciais. Neste artigo iremos
ver a recuperação de dados através da linguagem C, usando a libpq de uma forma alternativa para a recuperação de dados, com a utilização de cursores.

Criando o ambiente de testes

Primeiramente
estamos levando em consideração que o PostgreSQL já está instalado e
funcionando. Também presume-se que você tenha algum conhecimento
utilizando a libpq, pelo menos como iniciar uma conexão com o banco de
dados e como executar comandos.
Para os testes, usaremos aqui o mesmo ambiente criado no artigo

A
única alteração necessária será a adição de dados na tabela criada.
Caso não saiba como fazê-lo, de uma olhada no artigo citado
anteriormente.

Uma breve explicação sobre cursores

Um
cursor é uma forma de percorrer um conjunto de resultados, geralmente
retornados através de um comando select.Um cursor sempre aponta para uma
linha dentro do conjunto de dados retornados e, com o auxilio da libpq, podemos além de obter os valores, obter também informações sobre colunas, o que pode ser útil em alguns casos.
Uma
outra vantagem da utilização de cursores é a redução da quantidade de
memória exigida pelo programa que executou o select. Imagine uma
consulta que retorne milhões de registros, isso exigiria uma enorme
quantidade de memória alocada para armazenar o resultado da consulta.
Além disso, geralmente estaremos trabalhando através de uma rede e a
quantidade de dados que estará sendo transmitido pela rede será enorme.
Quando usamos a forma tradicional para recuperação de dados, o PostgreSQL
obtém todos os dados da consulta e então os transmite pela rede de uma
vez. Ao utilizar cursores, apenas uma linha é transmitida de cada vez
conforme o cursor avança ou retrocede dentro do conjunto de dados. Isso,
porém, pode se tornar um problema quando temos um conjunto de dados
muito grande.

Declaração de cursores

Agora
que já vimos bastante teoria, é hora de colocar tudo isso em prática.
Vale ressaltar que não entraremos em detalhes sobre como conectar com o PostgreSQL ou sobre como executar comandos pela libpq, uma vez que existe um artigo aqui sobre este assunto:

O primeiro passo é a declaração do cursor, que em código SQL, tem a seguinte sintaxe:
DECLARE nome_do_cursor CURSOR FOR comando_select
Isso
cria e automaticamente abre um cursor que tem o nome especificado em
nome_do_cursor. O cursor fica então vinculado ao comando de select SQL
que foi especificado em comando_select. Para codificar o nosso programa,
vamos declarar uma variável que irá conter o resultado do comando
PQexec (e claro, consideramos que já existe uma conexão aberta com o
banco, aqui chamada de conn):

/*Ponteiro de resultado*/ PGresult *result;
e a nossa chamada a função PQexec irá ficar assim:
result = PQexec(conn, “DECLARE curr CURSOR FOR SELECT * FROM contatos”);
Até
aqui, a única novidade é a declaração do cursor. Vale observar que
operações com cursores devem sempre estar entre blocos de transações
(assunto grande para ser tratado aqui, nos limitaremos a listar os
comandos apenas).Após a declaração do cursor, iremos recuperar os dados
retornados (se algum). Esta operação tem o nome de FETCH, e tem a
seguinte sintaxe em SQL:
FETCH [FORWARD|BACKWARD] [number|ALL|NEXT] [IN nome_do_cursor];
Onde:
FORWARD e BACKWARD indicam a direção que o cursor irá ler os dados,
para frente e para trás, respectivamente. O padrão é FORWARD e
normalmente é omitido;
number, ALL e NEXT indicam quantas linhas
iremos recuperar: number deve ser trocado por um número qualquer (10,
por exemplo, que indica que 10 linhas serão lidas por vez no cursor).
ALL recupera todos os dados e NEXT movimenta o cursor fazendo-o apontar
para a próxima linha.
IN indica o cursor em que daremos o fetch.
No nosso caso, podemos declarar o fetch da seguinte forma:
result = PQexec(conn, “FETCH ALL IN curr”);
Agora
que já temos material o suficiente para criar um cursor e dar um fetch,
vamos ver como fica o nosso programa. Omitimos o ponto onde fazemos a
conexão com o banco de dados.
result = PQexec(conn, “BEGIN WORK”); /*Executa o comando*/
result = PQexec(conn, “DECLARE curr CURSOR FOR SELECT * FROM contatos”); if(!result)
{
   printf(“Erro executando comando. “);
}
else
{
   PQclear(result);
   result = PQexec(conn, “FETCH ALL IN curr”);
   switch(PQresultStatus(result))
   {
       case PGRES_EMPTY_QUERY:
           printf(“Nada aconteceu. “);
           break;
       case PGRES_TUPLES_OK:
           printf(“A query retornou %d linhas. “, PQntuples(result));
           break;
       case PGRES_FATAL_ERROR:
           printf(“Error in query: %s “, PQresultErrorMessage(result));
           break;
       case PGRES_COMMAND_OK:
           printf(“%s linhas afetadas. “, PQcmdTuples(result));
           break;
       default:
           printf(“Algum outro resultado ocorreu. “);
           break;
   }
   result = PQexec(conn, “COMMIT WORK”);
          
   /*Libera o nosso objeto de resultado*/
   PQclear(result);
}
Algumas
observações sobre o código acima: os comandos ‘BEGIN WORK’ e ‘COMMIT
WORK’ iniciam e finalizam uma transação, respectivamente. O programa
acima irá apenas imprimir na tela a quantidade de linhas que foram
retornadas pelo comando select.

Retornando uma linha por vez

Antes
de darmos os próximos passos com cursores, vamos alterar nosso código
para ficar um pouco mais legível, uma vez que para retornar uma linha de
cada vez teremos que fazer a verificação do retorno da função PQexec
cada vez que ela for chamada (ou seja, uma vez por linha!), o que
resultaria em um código cheio de switch, o que ficaria “feio”, mas dizer
assim. Criaremos uma função que ficará assim:
int ExecutaComando(const char *comando, PGresult **ptr_resultado)
{
   int codigo_retorno = 1;
   const char *str_resultado;
  
   PGresult *resultado_local;
  
   printf(” Executando comando: %s “, comando);
      
   /*executa o comando e armazena localmente*/
   resultado_local = PQexec(conn, comando);
  
   /*passa o resultado local para o segundo parâmetro da função, para que seja acessível dentro de MAIN*/
   *ptr_resultado = resultado_local;
  
   /*Verifica se o comando foi bem sucedido*/
   if(!resultado_local)
   {
      /*Se falhou, imprime mensagem na tela e seta o código de retorno da função como 0 (erro)*/
      printf(“O comando falhou. “);
      codigo_retorno = 0;
   }
   else
   {
      /*Se foi sucedido, chamamos PQresultStatus para verificar qual o código de retorno*/
      switch(PQresultStatus(resultado_local))
      {
         case PGRES_COMMAND_OK:
            printf(“Comando ok, %s linhas afetadas. “, PQcmdTuples(resultado_local));
            break;
         case PGRES_TUPLES_OK:
            printf(“A query retornou %d linhas. “, PQntuples(resultado_local));
            break;
         default:
            printf(“Error in query: %s “, PQresultErrorMessage(resultado_local));
            PQclear(resultado_local);
            codigo_retorno = 0;
            break;
      }
   }
  
   /*retorna código de retorno*/
   return codigo_retorno;
}
Esta
função não tem nenhum mistério, tudo aqui já foi visto, apenas
colocamos a execução de um comando e a verificação de retorno dentro da
função.Agora que nosso código vai ficar mais legível, vamos continuar
com o básico. Vamos no código abaixo retornar apenas uma linha por vez.
Não há mistério nisso, lembra que na declaração do FETCH podemos dizer
quantos dados queremos retornar? Nosso código vai ficar assim (lembrando
que estamos omitindo a parte aonde abrimos e fechamos a conexão com o
banco):
    comando_ok = ExecutaComando(“BEGIN WORK”, &result);
    
    if(comando_ok)
    {
       PQclear(result);
      
       /*Executa o comando*/
       comando_ok = ExecutaComando(“DECLARE curr CURSOR FOR SELECT * FROM contatos”, &result);
      
       if(comando_ok)
       {
          PQclear(result);
          comando_ok = ExecutaComando(“FETCH 1 IN curr”, &result);
          
          while(comando_ok && PQntuples(result) > 0)
          {
             PQclear(result);
             ExecutaComando(“FETCH NEXT IN curr”, &result);
          }
       }
      comando_ok = ExecutaComando(“COMMIT WORK”, &result);
    }     if(comando_ok)
       PQclear(result);
A saída do programa será algo assim (no meu caso, a minha tabela tem 5 linhas):Executando comando: BEGIN WORK
Comando ok, linhas afetadas.Executando comando: DECLARE curr CURSOR FOR SELECT * FROM contatos
Comando ok, linhas afetadas.
Executando comando: FETCH 1 IN curr
A query retornou 1 linhas.
Executando comando: FETCH NEXT IN curr
A query retornou 1 linhas.
Executando comando: FETCH NEXT IN curr
A query retornou 1 linhas.
Executando comando: FETCH NEXT IN curr
A query retornou 1 linhas.
Executando comando: FETCH NEXT IN curr
A query retornou 1 linhas.
Executando comando: FETCH NEXT IN curr
A query retornou 0 linhas.
Executando comando: COMMIT WORK
Comando ok, linhas afetadas.
Note que quando o cursor atinge o final do conjunto de dados, o comando FETCH NEXT irá retornar zero.

Informações sobre colunas

Uma das utilidades de se usar um cursor pela libpq
é obter informações sobre as colunas retornadas pelo cursor. Podemos
saber tanto o tamanho da coluna quanto o nome da coluna. Para isso,
precisamos conhecer três novas funções, que são:

PQnfields

Esta função nos permite saber quantas colunas foram retornadas na query passada ao cursor. O protótipo desta função é:
int PQnfields(const PGresult *res);

PQfname

Esta
função nos permite saber o nome de uma determinada coluna, associada ao
field_num, aonde a primeira coluna começa no zero. O protótipo desta
função é:
char *PQfname(const PGresult *res, int field_num);

PQfsize

Esta
função nos permite ter uma idéia do tamanho do campo. Não temos como
saber precisamente pois esta função retorna o tamanho em bytes usado
internamente pelo PostgreSQL e mesmo assim, para tipos de dados
variáveis como VARCHAR esta função retorna -1. O protótipo desta função
é:
int PQfsize(const PGresult *res, int field_num);
Uma
boa idéia é sempre especificar pelo nome a coluna que você precisa
acessar, evitando que quando novas colunas forem acrescentadas o seu
programa se perca na quantidade de campos. Para descobrir o índice de um
campo através do nome da coluna, podemos usar a função PQfnumber, que
tem o seguinte protótipo:
int PQfnumber(const PGresult *res, const char *field_name);
Esta função retorna -1 quando o nome passado como parâmetro não é reconhecido como nome de coluna.
Conhecidas
estas funções, é hora de colocar a teoria em prática. Vamos ver como
fica o nosso código. Primeiro, vamos criar uma função que irá mostrar as
informações sobre as colunas:

void Mostra_Info_Colunas(PGresult *result)
{
   int numero_colunas;
   int i;
  
   if(!result)
      return;
  
   /*Obtém o número de colunas*/
   numero_colunas = PQnfields(result);
   printf(“O conjunto de dados tem %d colunas “, numero_colunas);
  
   for(i = 0; i <numero_colunas; i++)
   {
      printf(“Campo: %d. Nome: %s Tamanho Interno: %d “, i, PQfname(result, i), PQfsize(result, i));
   }
}
Depois, a nossa função main vai ficar assim:
    comando_ok = ExecutaComando(“BEGIN WORK”, &result);
    
    if(comando_ok)
    {
       PQclear(result);
      
       /*Executa o comando*/
       comando_ok = ExecutaComando(“DECLARE curr CURSOR FOR SELECT * FROM contatos”, &result);
      
       if(comando_ok)
       {
          PQclear(result);
          comando_ok = ExecutaComando(“FETCH 1 IN curr”, &result);
          
          if(comando_ok)
             Mostra_Info_Colunas(result);
          
          while(comando_ok && PQntuples(result) > 0)
          {
             PQclear(result);
             ExecutaComando(“FETCH NEXT IN curr”, &result);
          }
       }
      comando_ok = ExecutaComando(“COMMIT WORK”, &result);
    }     if(comando_ok)
       PQclear(result);
A saída deve ficar assim (estamos mostrando somente a saída para a função Mostra_Info_Colunas):O conjunto de dados tem 2 colunas
Campo: 0.
Nome: email
Tamanho Interno: -1Campo: 1.
Nome: nome
Tamanho Interno: -1

Acessando os dados recuperados

A
última parte da manipulação de cursores é a forma de acessar os dados
que foram retornados no nosso cursor. Uma coisa que não temos que nos
preocupar é como os dados serão retornados, pois a libpq sempre
retorna os dados em forma de string e cabe a quem estiver escrevendo o
programa converter os dados, caso seja necessário.Somente no caso de
cursores binários (BINARY CURSORS) esta afirmação não é verdade, mas
este assunto está fora de nosso escopo.
Vamos conhecer as funções que irão nos auxiliar com a tarefa de acessar os dados retornados:

PQgetvalue

Esta função retorna uma string terminada em NULL, e tem o seguinte protótipo:
char *PQgetvalue(const PGresult *res, int tup_num, int field_num);
Onde
*res é um ponteiro para uma estrutura PGresult, tup_num é o número da
linha que queremos acessar e field_num é o número do campo. Lembrando
que número de linhas e colunas sempre começam em zero.
Um ponto
importante sobre esta função: a string retornada encontra-se dentro de
uma estrutura PGresult, logo, é preciso copiar os dados caso seja
necessário manter os dados para fazer qualquer coisa com eles.

PQgetisnull

Esta
função nos permite verificar se o valor a ser retornado pelo banco é
uma string de comprimento nulo ou uma string em branco cujo campo contém
o valor NULL (lembrando que NULL dentro de uma coluna no banco não
significa vazio, e sim desconhecido).
int PQgetisnull(const PGresult *res, int tup_num, int field_num);
Onde
*res é um ponteiro para uma estrutura PGresult, tup_num é o número da
linha que queremos acessar e field_num é o número do campo. Lembrando
que número de linhas e colunas sempre começam em zero. A função irá
retornar 1 se o campo for nulo e 0 se tiver um valor não-nulo.
Vamos ao nosso código agora. Vamos criar uma função para poder ver os dados retornados:

void Exibe_Dados(PGresult *result)
{
   int coluna;
  
   for(coluna = 0; coluna < PQnfields(result); coluna++)
   {
      /*Verifica se o valor da coluna é nulo*/
      if(PQgetisnull(result, 0, coluna))
      {
         printf(“DATA: <NULL> “);
      }
      else
      {
         printf(“DATA: %s “, PQgetvalue(result, 0, coluna));
      }
   }
}
A listagem completa do programa, incluindo a rotina de conexão fica assim:
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h> /*Objeto de conexão*/
PGconn *conn = NULL; /*Protótipo de funções*/
int ExecutaComando(const char *, PGresult **);
void Mostra_Info_Colunas(PGresult *);
void Exibe_Dados(PGresult *);
int main()
{
   int comando_ok;
   PGresult *result;
    /*realiza a conexão*/
    conn = PQconnectdb(“host=localhost dbname=TESTE”);
    
    if(PQstatus(conn) == CONNECTION_OK)
    {
        printf(“Conexão com efetuada com sucesso. “);
    }
    else
    {
        printf(“Falha na conexão. Erro: %s “, PQerrorMessage(conn));
        PQfinish(conn);
        return -1;
    }
    comando_ok = ExecutaComando(“BEGIN WORK”, &result);
    
    if(comando_ok)
    {
       PQclear(result);
      
       /*Executa o comando*/
       comando_ok = ExecutaComando(“DECLARE curr CURSOR FOR SELECT * FROM contatos”, &result);
      
       if(comando_ok)
       {
          PQclear(result);
          comando_ok = ExecutaComando(“FETCH 1 IN curr”, &result);
          
          if(comando_ok)
             Mostra_Info_Colunas(result);
          
          while(comando_ok && PQntuples(result) > 0)
          {
             Exibe_Dados(result);
             PQclear(result);
             ExecutaComando(“FETCH NEXT IN curr”, &result);
          }
       }
      comando_ok = ExecutaComando(“COMMIT WORK”, &result);
    }
    if(comando_ok)
       PQclear(result);
    /*Verifica se a conexão está aberta e a encerra*/
    if(conn != NULL)
        PQfinish(conn);
}
int ExecutaComando(const char *comando, PGresult **ptr_resultado)
{
   int codigo_retorno = 1;
   const char *str_resultado;
  
   PGresult *resultado_local;
  
   printf(” Executando comando: %s “, comando);
      
   /*executa o comando e armazena localmente*/
   resultado_local = PQexec(conn, comando);
  
   /*passa o resultado local para o segundo parâmetro da função, para que seja
   acessível dentro de MAIN*/
   *ptr_resultado = resultado_local;
  
   /*Verifica se o comando foi bem sucedido*/
   if(!resultado_local)
   {
      /*Se falhou, imprime mensagem na tela e seta o código de retorno da função como 0 (erro)*/
      printf(“O comando falhou. “);
      codigo_retorno = 0;
   }
   else
   {
      /*Se foi sucedido, chamamos PQresultStatus para verificar qual o código de retorno*/
      switch(PQresultStatus(resultado_local))
      {
         case PGRES_COMMAND_OK:
            printf(“Comando ok, %s linhas afetadas. “, PQcmdTuples(resultado_local));
            break;
         case PGRES_TUPLES_OK:
            printf(“A query retornou %d linhas. “, PQntuples(resultado_local));
            break;
         default:
            printf(“Error in query: %s “, PQresultErrorMessage(resultado_local));
            PQclear(resultado_local);
            codigo_retorno = 0;
            break;
      }
   }
  
   /*retorna código de retorno*/
   return codigo_retorno;
}
void Mostra_Info_Colunas(PGresult *result)
{
   int numero_colunas;
   int i;
  
   if(!result)
      return;
  
   /*Obtém o número de colunas*/
   numero_colunas = PQnfields(result);
   printf(“O conjunto de dados tem %d colunas “, numero_colunas);
  
   for(i = 0; i <numero_colunas; i++)
   {
      printf(“Campo: %d. Nome: %s Tamanho Interno: %d “, i, PQfname(result, i), PQfsize(result, i));
   }
}
void Exibe_Dados(PGresult *result)
{
   int coluna;
  
   for(coluna = 0; coluna < PQnfields(result); coluna++)
   {
      /*Verifica se o valor da coluna é nulo*/
      if(PQgetisnull(result, 0, coluna))
      {
         printf(“DATA: <NULL> “);
      }
      else
      {
         printf(“DATA: %s “, PQgetvalue(result, 0, coluna));
      }
   }
}

Considerações finais

A utilização da libpq
é bem mais simples do que parece. Neste artigo e no anterior, vimos
como fazer quase todas as ações possíveis pela libpq, claro, ainda
existem outros tópicos a serem abordados, mas com o que temos até o
momento, já temos o suficiente para escrever uma aplicação completa.
Um grande abraço!!!

Autor: Poleto
[ads-post]

Comentários