in

Como acessar o PostgreSQL com C – Cursores

Potencialize o uso da linguagem C, veja como acessar o PostgreSQL e recuperar dados!

Liberada a versão 11.0 do PostgreSQL

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 como acessar o PostgreSQL, assim como, 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 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 acessar 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 ideia 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));
       }
    }
 }[terminal]
 
A listagem completa do programa, incluindo a rotina de conexão fica assim:
[terminal]#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

Escrito por Emanuel Negromonte

Fundador do SempreUPdate. Acredita no poder do trabalho colaborativo, no GNU/Linux, Software livre e código aberto. É possível tornar tudo mais simples quando trabalhamos juntos, e tudo mais difícil quando nos separamos.

aprenda-como-criar-pacotes-rpm

Aprenda como criar pacotes RPM

Fedora 31 removerá pacotes que dependem do Python 2

Fedora 31 removerá pacotes que dependem do Python 2