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))

2 comentários:

Ron Jeremy disse...

o que é um repl? :P

Mario Domenech Goulart disse...

Read-Eval-Print-Loop.:-)

http://en.wikipedia.org/wiki/REPL