Mostrando postagens com marcador postgresql. Mostrar todas as postagens
Mostrando postagens com marcador postgresql. Mostrar todas as postagens

sábado, 6 de fevereiro de 2010

Suporte a múltiplos bancos de dados para Awful

A partir da versão 0.10, awful passa a oferecer suporte a múltiplos bancos de dados. Atualmente há suporte para PostgreSQL (através da extensão postgresql) e SQLite3 (através da extensão sqlite3 ou sql-de-lite).

Há uma extensão para cada tipo de base de dados: awful-postgresql, awful-sqlite3 e awful-sql-de-lite. No caso de SQLite3, pode-se optar por usar tanto a extensão sqlite3 ou sql-de-lite (duas extensões diferentes para acesso a bases SQLite3).

A forma de acesso a bases de dados continua praticamente a mesma: através do procedimento $db, o qual faz uso das facilidades oferecidas por awful (conexão transparente, fechamento automático). A única diferença é que agora é necessário carregar a extensão relativa ao tipo de base de dados que pretende-se acessar (e.g., (use awful awful-postgresql)) e usar enable-db como um procedimento sem argumentos, não um parâmetro com argumento booleano, como antes.

Abaixo está um exemplo completo de uma aplicação que gera uma página HTML com uma tabela contendo dados de nome e endereço da tabela users de uma base de dados PostgreSQL:

(use awful awful-sql-de-lite html-utils)

(enable-db)

(db-credentials "db.db")

(define-page (main-page-path)
(lambda ()
(tabularize ($db "select name, address from users"))))


Para publicar esta página, basta executar (supondo que o código acima está no arquivo users.scm):

$ awful users.scm


(ficará disponível em http://localhost:8080).

Se um dia for necessário migrar essa base de dados para PostgreSQL, basta usar (use awful-postgresl) em vez de (use awful-sql-de-lite) e ajustar a configuração de credenciais para acesso ao banco (no caso de SQLite3, a credencial é o caminho para o arquivo com a base de dados). Se a estrutura da base de dados não for modificada, e se forem usadas consultas SQL portáveis, o código Scheme não precisará ser alterado.

Opcionalmente, a página pode ser compilada:

$ csc -s users.scm
$ awful users.so



Simples assim. :-)

quarta-feira, 6 de agosto de 2008

REPL de Chicken para acesso a bases de dados do Postgres

A seguir está uma forma de usar o REPL de Chicken (csi) como um REPL para bases de dados do Postgres.

Com isso, tem-se um REPL que possibilita a execução de consultas SQL e código Scheme. A implementação usa o próprio REPL do sistema Chicken e alguns eggs como: postgresql (para acesso so Postgres), readline (para edição de linhas de comando, histórico) e stty (para configuração do terminal na leitura de senhas).

O programa db-repl.scm usa como argumentos não interativos (opcionais) o usuário do banco de dados, o nome do host e a base de dados, os quais devem ser fornecidos no seguinte formato:

<usuario>@<host>/<base de dados>

Quando executado, o programa pede para o usuário digitar a senha.

A associação do REPL de Chicken com a base de dados é feita através da definição de comandos do REPL (toplevel-command). Na implementação mostrada abaixo, são definidos três comandos:

  • tables: mostras as tabelas da base de dados.

  • table: mostra estrutura da tabela dada como argumento (nome e tipo das colunas e se podem ou não ser nulas).

  • -: executa a consulta SQL dada como argumento.


Exemplos:

csi -s db-repl.scm mario@localhost/sgpc
Senha: *****

#;1> ,tables
perms
news
users_sites
ticket_comments
ticket_attachments
obj_type
acervos
autores
videos
audios
images
objects
texts
scanners
users
wiki
sites
tickets

#;1> ,table news
news_id integer NO
user_id integer NO
site_id integer NO
timestamp timestamp without time zone YES
title character varying(100) NO
news text YES

#;1> ,- select * from news
(#(2
1
1
#(2008 7 30 19 37 54 271625)
"Teste."
"teste

=== titulo")
#(3
1
1
#(2008 7 30 19 40 27 180765)
"Outra notícia!"
"Aqui vai o texto da notícia.

[[image:http://subversion.tigris.org/branding/images/logo.gif|Logo]]"))

Obviamente, os comandos para acesso ao banco de dados disponibilizados através do REPL podem ser estendidos. O texto PostgreSQL INFORMATION_SCHEMA fornece várias dicas de como extrair informações de bases de dados do Postgres.

Nesta implementação, o parâmetro pg-repl:conn armazena o objeto que representa a conexão com o banco de dados, de forma que ele pode ser usado pelos procedimentos do egg postgresql para a execução de consultas:


#;1> (define query "select * from news")
#;2> (vector-ref (car (pg:query-tuples query (pg-repl:conn))) 3)
#(2008 7 30 19 37 54 271625)

O código do programa (db-repl.scm) está a seguir:

(use utils postgresql readline stty regex (srfi 13))

(define pg-repl:conn (make-parameter #f))

(define (pg-repl:query . query)
(pg:query-tuples
(string-intersperse (map ->string query) "")
(pg-repl:conn)))

(toplevel-command
'-
(lambda ()
(pp (pg-repl:query
(with-output-to-string
(cut print (read-line)))))))

(toplevel-command
'table
(lambda ()
(let* ((table (string-trim-both (read-line)))
(cols
(pg-repl:query
"select column_name,data_type,"
"character_maximum_length,is_nullable "
"from information_schema.columns "
"where table_name = '" table "'")))
(if (null? cols)
(print "Tabela \"" table "\" nao existe.")
(for-each
(lambda (f)
(let ((colname (vector-ref f 0))
(type (vector-ref f 1))
(size (let ((size (vector-ref f 2)))
(if (pg:sql-null-object? size)
""
(conc "(" size ")"))))
(nullable (vector-ref f 3)))
(print
colname
(make-string
(- 30 (string-length colname)))
type size
(make-string
(- 30 (string-length (conc type size))))
nullable)))
cols)))))

(toplevel-command
'tables
(lambda ()
(for-each
(lambda (item)
(print (vector-ref item 0)))
(pg-repl:query
"select table_name from information_schema.tables "
"where table_type = 'BASE TABLE' "
"and table_schema not in "
"('pg_catalog', 'information_schema')"))))

(define (pg-repl:usage #!optional exit-code)
(print (program-name) " [<user>@<server>/<database>]")
(when exit-code (exit exit-code)))

(let ((args (command-line-arguments))
(user "postgres")
(host "localhost")
(db "template")
(passwd #f))

;; Restaura o terminal em caso de termino via C-c
(set-signal-handler! signal/int
(lambda (_) (stty '(echo))))

(unless (or (null? args) (equal? "\"\"" (car args)))
(let* ((cred (string-trim-both
(car args)
(cut memq <> '(#\space #\newline #\")))) ;"
(@tokens (string-split cred "@"))
(/tokens (if (null? @tokens)
'()
(string-split (cadr @tokens) "/"))))
(if (and (null? @tokens) (null? /tokens))
(pg-repl:usage 1)
(begin
(unless (null? @tokens)
(set! user (car @tokens)))
(unless (null? /tokens)
(set! host (car /tokens))
(set! db (cadr /tokens)))))))
(display "Senha: ")
(pg-repl:conn
(pg:connect
`((user . ,user)
(dbname . ,db)
(host . ,host)
(password . ,(with-stty '(not echo) read-line)))))
(current-input-port (make-gnu-readline-port))
(gnu-history-install-file-manager
(string-append (or (getenv "HOME") ".")
"/.csi.history"))
(newline)
(repl))

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.