PostgreSQl e C utilizando cursor

January 5th, 2008 Posted in C, PostgreSQL, Tecnologia

A utilização de um cursor é fundamental em alguns casos, como uma consulta que retorna um elevado número de registros.

Acredito que seja possível traçar uma linha comparativa entre a utilização de um cursor no banco de dados e a leitura de um arquivo muito grande. Quando trabalhamos com arquivos criamos um ponteiro, que é utilizado pelas funções de leitura. O arquivo é lido em partes e os bytes lidos são copiados para um buffer. Após cada leitura o ponteiro do arquivo é modificado, apontando para o próximo ponto de leitura ou o final de arquivo. É possível alterar este ponteiro de leitura (seek), retrocedendo ou avançando conforme a necessidade do programador. O cursor trabalha de maneira muito semelhante, ele é o ponteiro para a consulta e o comando FETCH a função de leitura.

DECLARE - Declarando o cursor

DECLARE name [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ]

    CURSOR [ { WITH | WITHOUT } HOLD ] FOR query

    [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] } ]

O comando declare é utilizado para criar um cursor, as seguintes opções podem ser utilizadas como parâmetro:

  • name: Nome do cursor a ser criado
  • BINARY: Faz com que o cursor retorne os dados de maneira binária e não textual (string).
  • INSENSITIVE: Este parâmetro foi inserido apenas para manter o padrão SQL e não está implementado, conforme descrito na documentação do PostgreSQL.
  • SCROLL e NO SCROLL: A utilização de SCROLL permite retroceder na leitura, e por padrão é definida. Em consultas que não irão retroceder na leitura é interessante utilizar NO SCROLL, pois assim a consulta será mais otimizada - conforme a documentação.
  • WITH HOLD e WITHOUT HOLD: Com WITHOUT HOLD o cursor precisa ser declarado dentro de uma transação, já com WITH HOLD não, ele pode ser utilizado fora de uma transação.
  • query: A query em si.
  • FOR READ ONLY e FOR UPDATE: FOR READ ONLY declara o cursor somente para leitura e FOR UPDATE para leitura e escrita. Porém FOR UPDATE não está implementada e sua utilização ocasionaria em um erro.
  • column: A coluna a ser alterada caso fosse especificado FOR UPDATE.

FETCH - Obtendo as linhas retornadas pela consulta

FETCH [ direction { FROM | IN } ] cursorname

O parâmetro direction pode ser definido como:

  • NEXT: Retorna a próxima linha da consulta.
  • PRIOR: Retorna a linha anterior.
  • FIRST: Retorna a primeira linha da consulta.
  • LAST: Retorna a ultima linha da consulta.
  • ABSOLUTE count: Retorna a linha especificada por count.
  • RELATIVE count: Retorna a linha especificada por count + número de linhas já lidas (Posição atual + count).
  • count: Retorna as próximas count linhas.
  • ALL: Retorna todas as linhas da consulta.
  • FORWARD: Retorna a próxima linha.
  • FORWARD count: Retorna as próximas count linhas.
  • FORWARD ALL: Retorna todas as próximas linhas .
  • BACKWARD: Retorna a linha anterior.
  • BACKWARD count: Retorna as count linhas anteriores.
  • BACKWARD ALL: Retorna todas as linhas anteriores.

Utilizando cursor do PostgreSQL em C com a libpq

A libpq já foi discutida em outro texto, por este motivo não irei entrar em detalhes sobre as funções já discutidas. As únicas funções novas utilizadas neste exemplo serão: PQntuples, PQnfields e PQgetvalue.

PQntuples

int PQntuples(PGresult *res);

Recebe como parâmetro o ponteiro resultante da função PQexec e retorna o número de linhas obtidas pela consulta.

PQnfields

int PQnfields(PGresult *res);

Recebe como parâmetro o ponteiro resultante da função PQexec e retorna o número de campos da consulta.

PQgetvalue

   char* PQgetvalue(PGresult *res, int tup_num, int field_num);

Recebe como parâmetro o ponteiro resultante da função PQexec, o número da linha(tup_num) e do campo(field_num) a ser consultado e retorna seu conteúdo.

Código exemplo:

Abaixo segue o código exemplo com alguns comentários, o mesmo pode ser visto também aqui.

#include <postgresql/libpq-fe.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>// String de configuração para efetuar a conexão

#define DB_CONNECT_STRING "host=192.168.1.201 port=5432 dbname=dbdaniel user=postgres connect_timeout=8"

int main(void)

{

 // Ponteiro utilizado para minha conexão

 PGconn *bancoDeDados = NULL;

// Variavel para verificar o status da conexão

 ConnStatusType retConn;

// Variavel de retorno do aplicativo

 int retval = 0;

// Efetua a conexão

 bancoDeDados = PQconnectdb( DB_CONNECT_STRING );

// Verifica o status da conexão

 retConn = PQstatus(bancoDeDados);

 if ( retConn != CONNECTION_OK )

 {

   // Em caso de falha, obter o erro

   char *retString;

   retString = PQerrorMessage(bancoDeDados);

   printf("Falha efetuando a conexão.\n%s", retString);

   free(retString);

   retval = -1;

 }

 else

 {

   // Conexão efetuada com sucesso

   PGresult *result;

// Iniciando a transação

   result = PQexec(bancoDeDados, "BEGIN");

   if (!result || PQresultStatus(result) != PGRES_COMMAND_OK)

   {

     // Em caso de falha exibir a mensagem

     retval = -1;

   }

   else

   {

     PQclear(result);

// Declarando o cursor

     result = PQexec(bancoDeDados, "DECLARE cursor1 NO SCROLL CURSOR FOR select * from tabela_daniel;");

     if (!result || PQresultStatus(result) != PGRES_COMMAND_OK)

     {

       // Em caso de falha exibir a mensagem

       retval = -1;

     }

     else

     {

       while ( 1 )

       {

         PQclear(result);

         // Percorrendo o cursor

         result = PQexec(bancoDeDados, "FETCH FORWARD 1 in cursor1;");

         if (  ! result || \

           ( PQresultStatus(result) != PGRES_COMMAND_OK && \

             PQresultStatus(result) != PGRES_TUPLES_OK ) )

         {

           retval = -1;

         }

         else if (PQresultStatus(result) == PGRES_TUPLES_OK && \

               PQntuples(result) > 0)

         {

           // Exibindo o resultado

           int i;

           for ( i = 0 ; i < PQnfields(result) ; i ++ )

           {

             printf("%-20s", PQgetvalue(result, 0, i));

           }

           printf("\n");

           continue;

         }

         break;

       }

       if ( retval != -1 )

       {

         PQclear(result);

// Fechando o cursor

         result = PQexec(bancoDeDados, "CLOSE cursor1");

         if ( !result || PQresultStatus(result) != PGRES_COMMAND_OK )

         {

           retval = -1;

         }

         else

         {

           PQclear(result);

// Finalizando a transação

           result = PQexec(bancoDeDados, "COMMIT");

           if ( !result || PQresultStatus(result) != PGRES_COMMAND_OK )

           {

             retval = -1;

           }

         }

       }

     }

   }

   // Em caso de falha, exibir a mensagem

   if ( retval == -1 && result != NULL)

   {

     char *retString;

     retString = PQresultErrorMessage(result);

     printf("Falha executando sql.\n%s", retString);

   }

   if ( result ) PQclear(result);

   PQfinish(bancoDeDados);

 }

 return retval;

}

Post a Comment