quinta-feira, 19 de junho de 2008

Acessando base de dados SQL com Scheme

Em alguns projetos em que estou trabalhando seguidamente tenho que acessar tabelas de bases de dados. Costumo usar o Postgres através do egg postgresql do sistema Chicken.

Para evitar de esquecer de fechar as conexões com o banco, normalmente uso um procedimento que recebe uma query como argumento. Este procedimento abre a conexão com o banco, executa a query e fecha a conexão automaticamente (o desempenho que se dane :-)). As credenciais do banco mantenho em um parâmetro (definido com make-parameter). O procedimento é algo como:

(define db-credentials (make-parameter '()))

(define (db-query query)
(let* ((db (pg:connect (db-credentials)))
(output (pg:query-tuples query db)))
(pg:close db)
output))

Mesmo com o uso do procedimento db-query, o acesso a colunas do banco não é das tarefas mais simples. Abaixo está um exemplo em que quero acessar as colunas username e email de uma tabela e associar o valor delas à variáveis em Scheme:

(db-credentials '((host . "localhost")
(user . "usuario")
(password . "****")
(dbname . "nome-da-base")))

(let* ((results
(let ((results
(db-query
"select username,email from users where user_id=1")))
(if (null? results)
#f
(car results))))
(username (and results (vector-ref results 0)))
(email (and results (vector-ref results 1))))
(print username)
(print email))

Como pode ser visto no exemplo, associar valores de colunas da base de dados a variáveis em Scheme é uma certa novela. Para facilitar esta tarefa, fiz o esquema mostrado abaixo:

(use postgresql)

(define db-map:credentials (make-parameter '()))

(define db-map:create-object
(let ()
(define (db-query query)
(let* ((db (pg:connect (db-map:credentials)))
(output (pg:query-tuples query db)))
(pg:close db)
output))
(lambda (query fields)
(let* ((query-results (let ((results (db-query query)))
(if (null? results)
#f
(car results)))))
(lambda (field)
(and query-results
(let ((pos (list-index (cut eq? <> field)
fields)))
(vector-ref query-results pos))))))))

O procedimento db-map:create-object recebe uma query SQL e uma lista de símbolos a serem associados com os valores das colunas obtidos como resultado da execução da query. db-map:create-object retorna um procedimento que recebe como argumento um símbolo representando uma coluna da base de dados e que retorna o valor associado ao símbolo.

Assim, para acessar o valor das colunas username e email, faço o seguinte:

(let ((obj (db-map:create-object
"select username,email from users where user_id=1"
'(username email))))
(print (obj 'username))
(print (obj 'email)))

A ordem dos símbolos da lista passada como segundo argumento deve ser a mesma dos valores das colunas resultantes da query SQL.

3 comentários:

Anônimo disse...

Mario, eu gostaria de saber qual e-mail vc está utilizando atualmente, pois gostaria de verificar com você a licensa de uma de suas publicações.
Se possivel me informe atraves do ligia.gabriele@gmail.com

Aguardo contato.

Lígia.

Ron Jeremy disse...

2 comentarios

1) o layout do blog truncou a query string

2) parabens! tu quase conseguiste construir um procedimento presente na maioria das SQL APIs modernas

exceto por duas diferenças básicas:
a) os nomes dos campos vêm automaticamente
b) o resultado também permite acessar as linhas

é possivel chegar numa construcao desse estilo?

// retorna um handle para pegar as linhas
result = query("select * from clientes")

// row é uma colecao de pares contendo nome e valor
row = fetch(result)
enquanto row nao vazio
infere em row
row = fetch(result)
fim enquanto

abraços
Carlão Bazuca

Mario Domenech Goulart disse...

Alô Engenheiro Diego (ou Carlão) :-)

Quanto ao truncamento da query string: esse esquema de colocar código no blogger é meio sacal. Dependendo da configuração do browser, o truncamento pode ou não ocorrer. De qualquer forma, se selecionares o texto no browser, copiares e colares em um editor de textos, deve aparecer tudo.

Sobre o acesso aos dados: na realidade, este é um hack simples e rápido para acessar os dados de forma mais agradável. Não é uma solução ideal. Nunca vi como são implementadas essas APIs modernas em outras linguagens, mas, suponho, elas devem fazer um parsing da query SQL.