PostgreSQl e C utilizando cursor
January 5th, 2008 Posted in C, PostgreSQL, TecnologiaA 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.
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.
int PQnfields(PGresult *res);
Recebe como parâmetro o ponteiro resultante da função PQexec e retorna o número de campos da consulta.
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 <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;
}