db: use log-based-eval for most examples

This commit is contained in:
Ryan Culpepper 2012-08-20 23:01:52 -04:00
parent 062a8ef5e7
commit 9bd5a9189b
8 changed files with 899 additions and 329 deletions

View File

@ -1,5 +1,6 @@
#lang racket/base
(require racket/class)
(require racket/class
racket/serialize)
(provide connection<%>
dbsystem<%>
prepared-statement<%>
@ -115,8 +116,8 @@
;; - (simple-result alist)
;; - (rows-result Header data)
;; for user-visible rows-results: headers present, data is (listof vector)
(struct simple-result (info) #:transparent)
(struct rows-result (headers rows) #:transparent)
(serializable-struct simple-result (info) #:transparent)
(serializable-struct rows-result (headers rows) #:transparent)
;; A cursor-result is
;; - (cursor-result Header prepared-statement ???)

View File

@ -1,6 +1,8 @@
#lang racket/base
(require scribble/manual
scribble/eval
unstable/sandbox
racket/runtime-path
(for-label racket/base
racket/contract))
(provide (all-defined-out)
@ -18,18 +20,26 @@
;; ----
(define the-eval (make-base-eval))
(void
(interaction-eval #:eval the-eval
(require racket/class
db/base
db/util/datetime))
(interaction-eval #:eval the-eval
(define connection% (class object% (super-new))))
(interaction-eval #:eval the-eval
(define connection-pool% (class object% (super-new)))))
#|
The log-based-eval should be run in an environment that defines
the DSN 'db-scribble-env as a PostgreSQL data source.
|#
(define-syntax-rule (examples/results [example result] ...)
(examples #:eval the-eval (eval:alts example result) ...))
(define-syntax-rule (my-interaction [example result] ...)
(interaction #:eval the-eval (eval:alts example result) ...))
(define-runtime-path example-log "example-log.rktd")
(define the-eval (make-log-based-eval example-log 'replay))
(the-eval '(require racket/class
db
db/util/postgresql
db/util/datetime))
#|
The fake eval is for eg connection examples
|#
(define fake-eval (make-base-eval))
(fake-eval '(begin (require racket/class)
(define connection% (class object% (super-new)))))
(define-syntax-rule (fake-examples [example result] ...)
(examples #:eval fake-eval (eval:alts example result) ...))

View File

@ -100,7 +100,7 @@ Base connections are made using the following functions.
If the connection cannot be made, an exception is raised.
@(examples/results
@fake-examples[
[(postgresql-connect #:server "db.mysite.com"
#:port 5432
#:database "webappdb"
@ -119,7 +119,7 @@ Base connections are made using the following functions.
[(postgresql-connect #:socket 'guess (code:comment "or (postgresql-guess-socket-path)")
#:user "me"
#:database "me")
(new connection%)])
(new connection%)]]
}
@defproc[(postgresql-guess-socket-path)
@ -162,7 +162,7 @@ Base connections are made using the following functions.
If the connection cannot be made, an exception is raised.
@(examples/results
@fake-examples[
[(mysql-connect #:server "db.mysite.com"
#:port 3306
#:database "webappdb"
@ -181,7 +181,7 @@ Base connections are made using the following functions.
[(mysql-connect #:socket (mysql-guess-socket-path)
#:user "me"
#:database "me")
(new connection%)])
(new connection%)]]
}
@defproc[(mysql-guess-socket-path)
@ -234,12 +234,12 @@ Base connections are made using the following functions.
If the connection cannot be made, an exception is raised.
@(examples/results
@fake-examples[
[(sqlite3-connect #:database "/path/to/my.db")
(new connection%)]
[(sqlite3-connect #:database "relpath/to/my.db"
#:mode 'create)
(new connection%)])
(new connection%)]]
}
@defproc[(odbc-connect [#:dsn dsn string?]
@ -348,20 +348,19 @@ Creates a @tech{connection pool}. The pool consists of up to
@racket[connect] function must return a fresh connection each time it
is called.
@examples/results[
[(define pool
@examples[#:eval the-eval
(eval:alts
(define pool
(connection-pool
(lambda () (displayln "connecting!") (sqlite3-connect ....))
#:max-idle-connections 1))
(void)]
[(define c1 (connection-pool-lease pool))
(displayln "connecting!")]
[(define c2 (connection-pool-lease pool))
(displayln "connecting!")]
[(disconnect c1)
(void)]
[(code:line (define c3 (connection-pool-lease pool)) (code:comment "reuses actual conn. from c1"))
(void)]
(define pool
(connection-pool
(lambda () (displayln "connecting!") (sqlite3-connect #:database 'memory)))))
(define c1 (connection-pool-lease pool))
(define c2 (connection-pool-lease pool))
(disconnect c1)
(code:line (define c3 (connection-pool-lease pool)) (code:comment "reuses actual conn. from c1"))
]
See also @racket[virtual-connection] for a mechanism that eliminates
@ -434,10 +433,9 @@ closing its own connections. In particular, a @tech{virtual
connection} backed by a @tech{connection pool} combines convenience
with efficiency:
@examples/results[
[(define the-connection
@racketblock[
(define the-connection
(virtual-connection (connection-pool (lambda () ....))))
(void)]
]
The resulting virtual connection leases a connection from the pool on
@ -451,27 +449,25 @@ causes the current actual connection associated with the thread (if
there is one) to be disconnected, but the connection will be recreated
if a query function is executed.
@examples/results[
[(define c
@examples[#:eval the-eval
(eval:alts
(define c
(virtual-connection
(lambda ()
(printf "connecting!\n")
(postgresql-connect ....))))
(void)]
[(connected? c)
(values #f)]
[(query-value c "select 1")
(begin (printf "connecting!\n") 1)]
[(connected? c)
(values #t)]
[(void (thread (lambda () (displayln (query-value c "select 2")))))
(begin (printf "connecting!\n") (displayln 2))]
[(disconnect c)
(void)]
[(connected? c)
(values #f)]
[(query-value c "select 3")
(begin (printf "connecting!\n") 3)]
(define c
(virtual-connection
(lambda ()
(printf "connecting!\n")
(dsn-connect 'db-scribble-env)))))
(connected? c)
(query-value c "select 1")
(connected? c)
(void (thread (lambda () (displayln (query-value c "select 2")))))
(disconnect c)
(connected? c)
(query-value c "select 3")
]
Connections produced by @racket[virtual-connection] may not be used
@ -479,18 +475,16 @@ with the @racket[prepare] function. However, they may still be used to
execute parameterized queries expressed as strings or encapsulated via
@racket[virtual-statement].
@examples/results[
[(prepare c "select 2 + $1")
(error 'prepare "cannot prepare statement with virtual connection")]
[(query-value c "select 2 + $1" 2)
4]
[(define pst (virtual-statement "select 2 + $1"))
(void)]
[(query-value c pst 3)
5]
@examples[#:eval the-eval
(prepare c "select 2 + $1")
(query-value c "select 2 + $1" 2)
(define pst (virtual-statement "select 2 + $1"))
(query-value c pst 3)
]
}
@(the-eval '(begin (set! c #f) (set! pst #f)))
@;{========================================}
@section[#:tag "kill-safe"]{Kill-safe Connections}
@ -555,7 +549,7 @@ ODBC's DSNs.
for @racket[dsn], an exception is raised. If @racket[dsn] is a
@racket[data-source], then @racket[dsn-file] is ignored.
@examples/results[
@fake-examples[
[(put-dsn 'pg
(postgresql-data-source #:user "me"
#:database "mydb"

View File

@ -0,0 +1,670 @@
;; This file was created by make-log-based-eval
((require racket/class db db/util/postgresql db/util/datetime)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require db) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define pgc (dsn-connect 'db-scribble-env))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-exec
pgc
"create temporary table the_numbers (n integer, d varchar(20))")
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-exec pgc "insert into the_numbers values (0, 'nothing')")
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-exec pgc "insert into the_numbers values (1, 'the loneliest number')")
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-exec pgc "insert into the_numbers values (2, 'company')")
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query pgc "insert into the_numbers values (3, 'a crowd')")
((3)
1
(((lib "db/private/generic/interfaces.rkt")
.
deserialize-info:simple-result-v0))
0
()
()
(c values c (0 (c (c command u . "INSERT 0 1")))))
#""
#"")
((query pgc "select n, d from the_numbers where n % 2 = 0")
((3)
1
(((lib "db/private/generic/interfaces.rkt")
.
deserialize-info:rows-result-v0))
0
()
()
(c
values
c
(0
(c
(c
(c name u . "n")
c
(c typeid . 23)
c
(c type-size . 4)
c
(c type-mod . -1))
c
(c
(c name u . "d")
c
(c typeid . 1043)
c
(c type-size . -1)
c
(c type-mod . 24)))
(c (v! 0 (u . "nothing")) c (v! 2 (u . "company"))))))
#""
#"")
((query-rows pgc "select n, d from the_numbers where n % 2 = 0")
((3)
0
()
0
()
()
(c values c (c (v! 0 (u . "nothing")) c (v! 2 (u . "company")))))
#""
#"")
((query-row pgc "select * from the_numbers where n = 0")
((3) 0 () 0 () () (c values c (v! 0 (u . "nothing"))))
#""
#"")
((query-list pgc "select d from the_numbers order by n")
((3)
0
()
0
()
()
(c
values
c
(c
(u . "nothing")
c
(u . "the loneliest number")
c
(u . "company")
c
(u . "a crowd"))))
#""
#"")
((query-value pgc "select count(*) from the_numbers")
((3) 0 () 0 () () (c values c 4))
#""
#"")
((query-value pgc "select d from the_numbers where n = 5")
((3)
0
()
0
()
()
(c
exn
c
"query-value: query returned zero rows (expected 1): \"select d from the_numbers where n = 5\""))
#""
#"")
((query-maybe-value pgc "select d from the_numbers where n = 5")
((3) 0 () 0 () () (c values c #f))
#""
#"")
((for
(((n d) (in-query pgc "select * from the_numbers where n < 4")))
(printf "~a: ~a\n" n d))
((3) 0 () 0 () () (c values c (void)))
#"0: nothing\n1: the loneliest number\n2: company\n3: a crowd\n"
#"")
((for/fold
((sum 0))
((n (in-query pgc "select n from the_numbers")))
(+ sum n))
((3) 0 () 0 () () (c values c 6))
#""
#"")
((begin
(with-handlers
((exn:fail? (lambda (e) (printf "~a~n" (exn-message e)))))
(query-value pgc "select NoSuchField from NoSuchTable"))
(query-value pgc "select 'okay to proceed!'"))
((3) 0 () 0 () () (c values c (u . "okay to proceed!")))
#"query-value: relation \"nosuchtable\" does not exist (SQLSTATE 42P01)\n"
#"")
((query-value pgc "select d from the_numbers where n = $1" 2)
((3) 0 () 0 () () (c values c (u . "company")))
#""
#"")
((query-list pgc "select n from the_numbers where n > $1 and n < $2" 0 3)
((3) 0 () 0 () () (c values c (c 1 c 2)))
#""
#"")
((define get-less-than-pst
(prepare pgc "select n from the_numbers where n < $1"))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-list pgc get-less-than-pst 1)
((3) 0 () 0 () () (c values c (c 0)))
#""
#"")
((query-list pgc (bind-prepared-statement get-less-than-pst '(2)))
((3) 0 () 0 () () (c values c (c 0 c 1)))
#""
#"")
((void) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define pool
(connection-pool
(lambda ()
(displayln "connecting!")
(sqlite3-connect #:database 'memory))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define c1 (connection-pool-lease pool))
((3) 0 () 0 () () (c values c (void)))
#"connecting!\n"
#"")
((define c2 (connection-pool-lease pool))
((3) 0 () 0 () () (c values c (void)))
#"connecting!\n"
#"")
((disconnect c1) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define c3 (connection-pool-lease pool))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define c
(virtual-connection
(lambda () (printf "connecting!\n") (dsn-connect 'db-scribble-env))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((connected? c) ((3) 0 () 0 () () (c values c #f)) #"" #"")
((query-value c "select 1")
((3) 0 () 0 () () (c values c 1))
#"connecting!\n"
#"")
((connected? c) ((3) 0 () 0 () () (c values c #t)) #"" #"")
((void (thread (lambda () (displayln (query-value c "select 2")))))
((3) 0 () 0 () () (c values c (void)))
#"connecting!\n2\n"
#"")
((disconnect c) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((connected? c) ((3) 0 () 0 () () (c values c #f)) #"" #"")
((query-value c "select 3")
((3) 0 () 0 () () (c values c 3))
#"connecting!\n"
#"")
((prepare c "select 2 + $1")
((3)
0
()
0
()
()
(c exn c "prepare: cannot prepare statement with virtual connection"))
#""
#"")
((query-value c "select 2 + $1" 2) ((3) 0 () 0 () () (c values c 4)) #"" #"")
((define pst (virtual-statement "select 2 + $1"))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-value c pst 3) ((3) 0 () 0 () () (c values c 5)) #"" #"")
((begin (set! c #f) (set! pst #f))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define c pgc) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((query-exec pgc "insert into the_numbers values (42, 'the answer')")
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-exec pgc "delete from the_numbers where n = $1" 42)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-rows pgc "select * from the_numbers where n = $1" 2)
((3) 0 () 0 () () (c values c (c (v! 2 (u . "company")))))
#""
#"")
((query-rows c "select 17")
((3) 0 () 0 () () (c values c (c (v! 17))))
#""
#"")
((query-list c "select n from the_numbers where n < 2")
((3) 0 () 0 () () (c values c (c 0 c 1)))
#""
#"")
((query-list c "select 'hello'")
((3) 0 () 0 () () (c values c (c (u . "hello"))))
#""
#"")
((query-row pgc "select * from the_numbers where n = $1" 2)
((3) 0 () 0 () () (c values c (v! 2 (u . "company"))))
#""
#"")
((query-row pgc "select min(n), max(n) from the_numbers")
((3) 0 () 0 () () (c values c (v! 0 3)))
#""
#"")
((query-maybe-row pgc "select * from the_numbers where n = $1" 100)
((3) 0 () 0 () () (c values c #f))
#""
#"")
((query-maybe-row c "select 17")
((3) 0 () 0 () () (c values c (v! 17)))
#""
#"")
((query-value pgc "select timestamp 'epoch'")
((3)
1
(((lib "db/private/generic/sql-data.rkt")
.
deserialize-info:sql-timestamp-v0))
0
()
()
(c values c (0 1970 1 1 0 0 0 0 #f)))
#""
#"")
((query-value pgc "select d from the_numbers where n = $1" 3)
((3) 0 () 0 () () (c values c (u . "a crowd")))
#""
#"")
((query-value pgc "select d from the_numbers where n = $1" 100)
((3)
0
()
0
()
()
(c
exn
c
"query-value: query returned zero rows (expected 1): #<statement-binding>"))
#""
#"")
((query-value c "select count(*) from the_numbers")
((3) 0 () 0 () () (c values c 4))
#""
#"")
((for/list ((n (in-query pgc "select n from the_numbers where n < 2"))) n)
((3) 0 () 0 () () (c values c (c 0 c 1)))
#""
#"")
((call-with-transaction
pgc
(lambda ()
(for
(((n d)
(in-query pgc "select * from the_numbers where n < $1" 4 #:fetch 1)))
(printf "~a: ~a\n" n d))))
((3) 0 () 0 () () (c values c (void)))
#"0: nothing\n1: the loneliest number\n2: company\n3: a crowd\n"
#"")
((for ((n (in-query pgc "select * from the_numbers"))) (displayln n))
((3)
0
()
0
()
()
(c
exn
c
"in-query: query returned 2 columns (expected 1): \"select * from the_numbers\""))
#""
#"")
((define vehicles-result
(rows-result
'(((name . "type")) ((name . "maker")) ((name . "model")))
`(#("car" "honda" "civic")
#("car" "ford" "focus")
#("car" "ford" "pinto")
#("bike" "giant" "boulder")
#("bike" "schwinn" ,sql-null))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((group-rows vehicles-result #:group '(#("type")))
((3)
2
(((lib "db/private/generic/interfaces.rkt")
.
deserialize-info:rows-result-v0)
((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-null-v0))
1
("ford")
()
(c
values
c
(0
(c
(c (c name . "type"))
c
(c
(c name . "grouped")
c
(c grouped c (c (c name . "maker")) c (c (c name . "model")))))
(c
(v!
"car"
(c (v! "honda" "civic") c (v! (? . 0) "focus") c (v! (? . 0) "pinto")))
c
(v! "bike" (c (v! "giant" "boulder") c (v! "schwinn" (1))))))))
#""
#"")
((group-rows
vehicles-result
#:group
'(#("type") #("maker"))
#:group-mode
'(list))
((3)
1
(((lib "db/private/generic/interfaces.rkt")
.
deserialize-info:rows-result-v0))
1
((c name . "grouped"))
()
(c
values
c
(0
(c
(c (c name . "type"))
c
(c
(? . 0)
c
(c
grouped
c
(c (c name . "maker"))
c
(c (? . 0) c (c grouped c (c (c name . "model")))))))
(c
(v!
"car"
(c (v! "honda" (c "civic")) c (v! "ford" (c "focus" c "pinto"))))
c
(v! "bike" (c (v! "giant" (c "boulder")) c (v! "schwinn" ())))))))
#""
#"")
((rows->dict vehicles-result #:key "model" #:value '#("type" "maker"))
((3)
1
(((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-null-v0))
3
("car" "ford" "bike")
()
(c
values
c
(h
-
(equal)
((0) v! (? . 2) "schwinn")
("civic" v! (? . 0) "honda")
("pinto" v! (? . 0) (? . 1))
("focus" v! (? . 0) (? . 1))
("boulder" v! (? . 2) "giant"))))
#""
#"")
((rows->dict
vehicles-result
#:key
"maker"
#:value
"model"
#:value-mode
'(list))
((3)
0
()
0
()
()
(c
values
c
(h
-
(equal)
("ford" c "focus" c "pinto")
("honda" c "civic")
("giant" c "boulder")
("schwinn"))))
#""
#"")
((let* ((get-name-pst (prepare pgc "select d from the_numbers where n = $1"))
(get-name2 (bind-prepared-statement get-name-pst (list 2)))
(get-name3 (bind-prepared-statement get-name-pst (list 3))))
(list (query-value pgc get-name2) (query-value pgc get-name3)))
((3) 0 () 0 () () (c values c (c (u . "company") c (u . "a crowd"))))
#""
#"")
((define pst
(virtual-statement
(lambda (dbsys)
(case (dbsystem-name dbsys)
((postgresql) "select n from the_numbers where n < $1")
((sqlite3) "select n from the_numbers where n < ?")
(else (error "unknown system"))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-list pgc pst 3) ((3) 0 () 0 () () (c values c (c 0 c 1 c 2))) #"" #"")
((query-list pgc pst 3) ((3) 0 () 0 () () (c values c (c 0 c 1 c 2))) #"" #"")
((with-handlers
((exn:fail:sql? exn:fail:sql-info))
(query pgc "select * from nosuchtable"))
((3)
0
()
0
()
()
(c
values
c
(c
(c severity u . "ERROR")
c
(c code u . "42P01")
c
(c message u . "relation \"nosuchtable\" does not exist")
c
(c position u . "15")
c
(c file u . "parse_relation.c")
c
(c line u . "857")
c
(c routine u . "parserOpenTable"))))
#""
#"")
((query-value pgc "select count(*) from the_numbers")
((3) 0 () 0 () () (c values c 4))
#""
#"")
((query-value pgc "select false") ((3) 0 () 0 () () (c values c #f)) #"" #"")
((query-value pgc "select 1 + $1" 2) ((3) 0 () 0 () () (c values c 3)) #"" #"")
((query-value pgc "select inet '127.0.0.1'")
((3)
0
()
0
()
()
(c exn c "query-value: unsupported type: inet (typeid 869)"))
#""
#"")
((query-value pgc "select cast(inet '127.0.0.1' as varchar)")
((3) 0 () 0 () () (c values c (u . "127.0.0.1/32")))
#""
#"")
((query-value pgc "select real '+Infinity'")
((3) 0 () 0 () () (c values c +inf.0))
#""
#"")
((query-value pgc "select numeric '12345678901234567890'")
((3) 0 () 0 () () (c values c 12345678901234567890))
#""
#"")
((query-value pgc "select 1 in (1, 2, 3)")
((3) 0 () 0 () () (c values c #t))
#""
#"")
((query-value
pgc
"select 1 = any ($1::integer[])"
(list->pg-array (list 1 2 3)))
((3) 0 () 0 () () (c values c #t))
#""
#"")
((query-value pgc "select 1 = any ($1)" (list 1 2 3))
((3) 0 () 0 () () (c values c #t))
#""
#"")
((query-value pgc "select $1::integer = any ($2)" 1 (list 1 2 3))
((3) 0 () 0 () () (c values c #t))
#""
#"")
((query-value pgc "select $1 = any ($2)" 1 (list 1 2 3))
((3)
0
()
0
()
()
(c exn c "query-value: cannot convert to PostgreSQL string type: 1"))
#""
#"")
((query-value c "select NULL")
((3)
1
(((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-null-v0))
0
()
()
(c values c (0)))
#""
#"")
((sql-null->false "apple") ((3) 0 () 0 () () (c values c "apple")) #"" #"")
((sql-null->false sql-null) ((3) 0 () 0 () () (c values c #f)) #"" #"")
((sql-null->false #f) ((3) 0 () 0 () () (c values c #f)) #"" #"")
((false->sql-null "apple") ((3) 0 () 0 () () (c values c "apple")) #"" #"")
((false->sql-null #f)
((3)
1
(((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-null-v0))
0
()
()
(c values c (0)))
#""
#"")
((query-value pgc "select date '25-dec-1980'")
((3)
1
(((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-date-v0))
0
()
()
(c values c (0 1980 12 25)))
#""
#"")
((query-value pgc "select time '7:30'")
((3)
1
(((lib "db/private/generic/sql-data.rkt") . deserialize-info:sql-time-v0))
0
()
()
(c values c (0 7 30 0 0 #f)))
#""
#"")
((query-value pgc "select timestamp 'epoch'")
((3)
1
(((lib "db/private/generic/sql-data.rkt")
.
deserialize-info:sql-timestamp-v0))
0
()
()
(c values c (0 1970 1 1 0 0 0 0 #f)))
#""
#"")
((query-value pgc "select timestamp with time zone 'epoch'")
((3)
1
(((lib "db/private/generic/sql-data.rkt")
.
deserialize-info:sql-timestamp-v0))
0
()
()
(c values c (0 1969 12 31 19 0 0 0 -18000)))
#""
#"")
((sql-bits->list (string->sql-bits "1011"))
((3) 0 () 0 () () (c values c (c #t c #f c #t c #t)))
#""
#"")
((sql-bits->string (query-value pgc "select B'010110111'"))
((3) 0 () 0 () () (c values c (u . "010110111")))
#""
#"")
((sql-datetime->srfi-date (query-value pgc "select time '7:30'"))
((3)
1
(((lib "srfi/19/time.rkt") . deserialize-info:tm:date-v0))
0
()
()
(c values c (0 0 0 30 7 0 0 0 0)))
#""
#"")
((sql-datetime->srfi-date (query-value pgc "select date '25-dec-1980'"))
((3)
1
(((lib "srfi/19/time.rkt") . deserialize-info:tm:date-v0))
0
()
()
(c values c (0 0 0 0 0 25 12 1980 0)))
#""
#"")
((sql-datetime->srfi-date (query-value pgc "select timestamp 'epoch'"))
((3)
1
(((lib "srfi/19/time.rkt") . deserialize-info:tm:date-v0))
0
()
()
(c values c (0 0 0 0 0 1 1 1970 0)))
#""
#"")

View File

@ -7,6 +7,12 @@
"tabbing.rkt"
(for-label db db/util/geometry db/util/postgresql racket/dict))
@;{ c - misc connection (alias to pgc)
myc - MySQL connection (???)
slc - SQLite connection (???)
}
@(the-eval '(define c pgc))
@title[#:tag "query-api"]{Queries}
@declare-exporting[db db/base #:use-sources (db/base)]
@ -126,11 +132,9 @@ The types of parameters and returned fields are described in
Executes a SQL statement for effect.
@examples/results[
[(query-exec c "insert into some_table values (1, 'a')")
(void)]
[(query-exec pgc "delete from some_table where n = $1" 42)
(void)]
@examples[#:eval the-eval
(query-exec pgc "insert into the_numbers values (42, 'the answer')")
(query-exec pgc "delete from the_numbers where n = $1" 42)
]
}
@ -150,11 +154,9 @@ The types of parameters and returned fields are described in
Executes a SQL query, which must produce rows, and returns the list
of rows (as vectors) from the query.
@examples/results[
[(query-rows pgc "select * from the_numbers where n = $1" 2)
(list (vector 2 "company"))]
[(query-rows c "select 17")
(list (vector 17))]
@examples[#:eval the-eval
(query-rows pgc "select * from the_numbers where n = $1" 2)
(query-rows c "select 17")
]
If @racket[groupings] is not empty, the result is the same as if
@ -169,11 +171,9 @@ The types of parameters and returned fields are described in
Executes a SQL query, which must produce rows of exactly one
column, and returns the list of values from the query.
@examples/results[
[(query-list c "select n from the_numbers where n < 2")
(list 0 1)]
[(query-list c "select 'hello'")
(list "hello")]
@examples[#:eval the-eval
(query-list c "select n from the_numbers where n < 2")
(query-list c "select 'hello'")
]
}
@ -185,11 +185,9 @@ The types of parameters and returned fields are described in
Executes a SQL query, which must produce exactly one row, and
returns its (single) row result as a vector.
@examples/results[
[(query-row myc "select * from the_numbers where n = ?" 2)
(vector 2 "company")]
[(query-row c "select 17")
(vector 17)]
@examples[#:eval the-eval
(query-row pgc "select * from the_numbers where n = $1" 2)
(query-row pgc "select min(n), max(n) from the_numbers")
]
}
@ -201,11 +199,9 @@ The types of parameters and returned fields are described in
Like @racket[query-row], but the query may produce zero rows; in
that case, @racket[#f] is returned.
@examples/results[
[(query-maybe-row pgc "select * from the_numbers where n = $1" 100)
#f]
[(query-maybe-row c "select 17")
(vector 17)]
@examples[#:eval the-eval
(query-maybe-row pgc "select * from the_numbers where n = $1" 100)
(query-maybe-row c "select 17")
]
}
@ -217,11 +213,9 @@ The types of parameters and returned fields are described in
Executes a SQL query, which must produce exactly one row of exactly
one column, and returns its single value result.
@examples/results[
[(query-value pgc "select timestamp 'epoch'")
(sql-timestamp 1970 1 1 0 0 0 0 #f)]
[(query-value pgc "select s from the_numbers where n = $1" 3)
"a crowd"]
@examples[#:eval the-eval
(query-value pgc "select timestamp 'epoch'")
(query-value pgc "select d from the_numbers where n = $1" 3)
]
}
@ -233,11 +227,9 @@ The types of parameters and returned fields are described in
Like @racket[query-value], but the query may produce zero rows; in
that case, @racket[#f] is returned.
@examples/results[
[(query-value myc "select s from some_table where n = ?" 100)
#f]
[(query-value c "select 17")
17]
@examples[#:eval the-eval
(query-value pgc "select d from the_numbers where n = $1" 100)
(query-value c "select count(*) from the_numbers")
]
}
@ -272,18 +264,15 @@ The types of parameters and returned fields are described in
@racket[groupings] is not empty, then @racket[fetch-size] must
be @racket[+inf.0]; otherwise, an exception is raised.
@examples/results[
[(for/list ([n (in-query pgc "select n from the_numbers where n < 2")])
@examples[#:eval the-eval
(for/list ([n (in-query pgc "select n from the_numbers where n < 2")])
n)
'(0 1)]
[(call-with-transaction pgc
(call-with-transaction pgc
(lambda ()
(for ([(n d)
(in-query pgc "select * from the_numbers where n < $1" 4
#:fetch 1)])
(printf "~a is ~a\n" n d))))
(for-each (lambda (n d) (printf "~a: ~a\n" n d))
'(0 1 2 3) '("nothing" "the loneliest number" "company" "a crowd"))]
(printf "~a: ~a\n" n d))))
]
An @racket[in-query] application can provide better performance when
@ -291,11 +280,9 @@ it appears directly in a @racket[for] clause. In addition, it may
perform stricter checks on the number of columns returned by the query
based on the number of variables in the clause's left-hand side:
@examples/results[
[(for ([n (in-query pgc "select * from the_numbers")])
@examples[#:eval the-eval
(for ([n (in-query pgc "select * from the_numbers")])
(displayln n))
(error 'in-query "query returned 2 columns (expected 1): ~e"
"select * from the_numbers")]
]
}
@ -490,8 +477,8 @@ closed.
but it must be used with the same connection that created
@racket[pst].
@(examples/results
[(let* ([get-name-pst
@examples[#:eval the-eval
(let* ([get-name-pst
(prepare pgc "select d from the_numbers where n = $1")]
[get-name2
(bind-prepared-statement get-name-pst (list 2))]
@ -499,7 +486,7 @@ closed.
(bind-prepared-statement get-name-pst (list 3))])
(list (query-value pgc get-name2)
(query-value pgc get-name3)))
(list "company" "a crowd")])
]
Most query functions perform the binding step implicitly.
}
@ -526,19 +513,17 @@ closed.
function variant allows the SQL syntax to be dynamically customized
for the database system in use.
@examples/results[
[(define pst
@examples[#:eval the-eval
(define pst
(virtual-statement
(lambda (dbsys)
(case (dbsystem-name dbsys)
((postgresql) "select n from the_numbers where n < $1")
((sqlite3) "select n from the_numbers where n < ?")
(else (error "unknown system"))))))
(void)]
[(query-list pgc pst 3)
(list 1 2)]
[(query-list slc pst 3)
(list 1 2)]
(query-list pgc pst 3)
(eval:alts (query-list slc pst 3)
(query-list pgc pst 3))
]
}
@ -748,13 +733,9 @@ type.
@racket['message] key is typically present; its value is a string
containing the error message.
@examples/results[
[(with-handlers ([exn:fail:sql? exn:fail:sql-info])
@examples[#:eval the-eval
(with-handlers ([exn:fail:sql? exn:fail:sql-info])
(query pgc "select * from nosuchtable"))
'((severity . "ERROR")
(code . "42P01")
(message . "relation \"nosuchtable\" does not exist")
...)]
]
Errors originating from the @racketmodname[db] library, such as

View File

@ -16,21 +16,19 @@ Connections automatically convert query results to appropriate Racket
types. Likewise, query parameters are accepted as Racket values and
converted to the appropriate SQL type.
@examples/results[
[(query-value pgc "select count(*) from the_numbers") 4]
[(query-value pgc "select false") (values #f)]
[(query-value pgc "select 1 + $1" 2) 3]
@examples[#:eval the-eval
(query-value pgc "select count(*) from the_numbers")
(query-value pgc "select false")
(query-value pgc "select 1 + $1" 2)
]
If a query result contains a column with a SQL type not supported by
this library, an exception is raised. As a workaround, cast the column
to a supported type:
@examples/results[
[(query-value pgc "select inet '127.0.0.1'")
(error 'query-value "unsupported type: inet (typeid 869)")]
[(query-value pgc "select cast(inet '127.0.0.1' as varchar)")
"127.0.0.1/32"]
@examples[#:eval the-eval
(query-value pgc "select inet '127.0.0.1'")
(query-value pgc "select cast(inet '127.0.0.1' as varchar)")
]
The exception for unsupported types in result columns is raised when
@ -100,11 +98,9 @@ lower precision.) Other real values are converted to decimals with a
loss of precision. In PostgreSQL, @tt{numeric} and @tt{decimal} refer
to the same type.
@examples/results[
[(query-value pgc "select real '+Infinity'")
+inf.0]
[(query-value pgc "select numeric '12345678901234567890'")
12345678901234567890]
@examples[#:eval the-eval
(query-value pgc "select real '+Infinity'")
(query-value pgc "select numeric '12345678901234567890'")
]
The geometric types such as @racket['point] are represented by
@ -128,11 +124,10 @@ the
@tt{= ANY}} syntax with an array parameter instead of dynamically
constructing a SQL @tt{IN} expression:
@examples/results[
[(query-value pgc "select 1 in (1, 2, 3)") #t]
[(query-value pgc "select 1 = any ($1::integer[])"
@examples[#:eval the-eval
(query-value pgc "select 1 in (1, 2, 3)")
(query-value pgc "select 1 = any ($1::integer[])"
(list->pg-array (list 1 2 3)))
#t]
]
A list may be provided for an array parameter, in which case it is
@ -140,15 +135,12 @@ automatically converted using @racket[list->pg-array]. The type
annotation can be dropped when the array type can be inferred from the
left-hand side.
@examples/results[
[(query-value pgc "select 1 = any ($1)" (list 1 2 3))
#t]
[(query-value pgc "select $1::integer = any ($2)"
@examples[#:eval the-eval
(query-value pgc "select 1 = any ($1)" (list 1 2 3))
(query-value pgc "select $1::integer = any ($2)"
1 (list 1 2 3))
#t]
[(query-value pgc "select $1 = any ($2)" (code:comment "what type are we using?")
(query-value pgc "select $1 = any ($2)" (code:comment "what type are we using?")
1 (list 1 2 3))
(error 'query-value "cannot convert to PostgreSQL string type: 1")]
]
PostgreSQL defines many other types, such as network addresses and row
@ -247,7 +239,7 @@ strings, bytes, and real numbers.
An exact integer that cannot be represented as a 64-bit signed integer
is converted as @tt{real}, not @tt{integer}.
@examples/results[
@fake-examples[
[(expt 2 80)
(expt 2 80)]
[(query-value slc "select ?" (expt 2 80))
@ -326,9 +318,9 @@ SQL @tt{NULL} is translated into the unique @racket[sql-null] value.
results. The @racket[sql-null] value may be recognized using
@racket[eq?].
@(examples/results
[(query-value c "select NULL")
sql-null])
@examples[#:eval the-eval
(query-value c "select NULL")
]
}
@defproc[(sql-null? [x any/c]) boolean?]{
@ -410,25 +402,13 @@ values.
support nanosecond precision; PostgreSQL, for example, only supports
microsecond precision.
@(examples/results
[(query-value pgc "select date '25-dec-1980'")
(make-sql-date 1980 12 25)]
[(query-value pgc "select time '7:30'")
(make-sql-time 7 30 0 0 #f)]
[(query-value pgc "select timestamp 'epoch'")
(make-sql-timestamp 1970 1 1 0 0 0 0 #f)]
[(query-value pgc "select timestamp with time zone 'epoch'")
(make-sql-timestamp 1969 12 31 19 0 0 0 -18000)])
}
@examples/results[
[(query-value myc "select date('1980-12-25')")
(make-sql-date 1980 12 25)]
[(query-value myc "select time('7:30')")
(make-sql-time 7 30 0 0 #f)]
[(query-value myc "select from_unixtime(0)")
(make-sql-timestamp 1969 12 31 19 0 0 0 #f)]
@examples[#:eval the-eval
(query-value pgc "select date '25-dec-1980'")
(query-value pgc "select time '7:30'")
(query-value pgc "select timestamp 'epoch'")
(query-value pgc "select timestamp with time zone 'epoch'")
]
}
@defstruct*[sql-interval
([years exact-integer?]
@ -550,10 +530,8 @@ represented by sql-bits values.
Converts a sql-bits value to or from its representation as a list or
string.
@examples/results[
[(sql-bits->list (string->sql-bits "1011"))
(sql-bits->list (string->sql-bits "1011"))]
[(sql-bits->string (query-value pgc "select B'010110111'"))
(sql-bits->string (string->sql-bits "010110111"))]
@examples[#:eval the-eval
(sql-bits->list (string->sql-bits "1011"))
(sql-bits->string (query-value pgc "select B'010110111'"))
]
}

View File

@ -20,40 +20,34 @@ database and perform simple queries. Some of the SQL syntax used below
is PostgreSQL-specific, such as the syntax of query parameters
(@litchar{$1} rather than @litchar{?}).
@my-interaction[
[(require db)
(void)]
@interaction[#:eval the-eval
(require db)
]
First we create a connection. Replace @racket[_user], @racket[_db],
and @racket[_password] below with the appropriate values for your
configuration (see @secref{creating-connections} for other connection examples):
@my-interaction[
[(define pgc
@interaction[#:eval the-eval
(eval:alts
(define pgc
(postgresql-connect #:user _user
#:database _db
#:password _password))
(void)]
[pgc
(new connection%)]
(define pgc (dsn-connect 'db-scribble-env)))
]
Use @racket[query-exec] method to execute a SQL statement for effect.
@my-interaction[
[(query-exec pgc
@interaction[#:eval the-eval
(query-exec pgc
"create temporary table the_numbers (n integer, d varchar(20))")
(void)]
[(query-exec pgc
(query-exec pgc
"insert into the_numbers values (0, 'nothing')")
(void)]
[(query-exec pgc
(query-exec pgc
"insert into the_numbers values (1, 'the loneliest number')")
(void)]
[(query-exec pgc
(query-exec pgc
"insert into the_numbers values (2, 'company')")
(void)]
]
The @racket[query] function is a more general way to execute a
@ -61,121 +55,95 @@ statement. It returns a structure encapsulating information about the
statement's execution. (But some of that information varies from
system to system and is subject to change.)
@my-interaction[
[(query pgc "insert into the_numbers values (3, 'a crowd')")
(simple-result '((command insert 0 1)))]
[(query pgc "select n, d from the_numbers where n % 2 = 0")
(rows-result
(list
'((name . "n") (typeid . 23))
'((name . "d") (typeid . 1043)))
'(#(0 "nothing") #(2 "company")))]
@interaction[#:eval the-eval
(query pgc "insert into the_numbers values (3, 'a crowd')")
(query pgc "select n, d from the_numbers where n % 2 = 0")
]
When the query is known to return rows and when the field
descriptions are not needed, it is more convenient to use the
@racket[query-rows] function.
@my-interaction[
[(query-rows pgc "select n, d from the_numbers where n % 2 = 0")
'(#(0 "nothing") #(2 "company"))]
@interaction[#:eval the-eval
(query-rows pgc "select n, d from the_numbers where n % 2 = 0")
]
Use @racket[query-row] for queries that are known to return exactly
one row.
@my-interaction[
[(query-row pgc "select * from the_numbers where n = 0")
(vector 0 "nothing")]
@interaction[#:eval the-eval
(query-row pgc "select * from the_numbers where n = 0")
]
Similarly, use @racket[query-list] for queries that produce rows of
exactly one column.
@my-interaction[
[(query-list pgc "select d from the_numbers order by n")
(list "nothing" "the loneliest number" "company" "a crowd")]
@interaction[#:eval the-eval
(query-list pgc "select d from the_numbers order by n")
]
When a query is known to return a single value (one row and one
column), use @racket[query-value].
@my-interaction[
[(query-value pgc "select count(*) from the_numbers")
4]
[(query-value pgc "select d from the_numbers where n = 5")
(error 'query-value
"query returned zero rows: ~s"
"select d from the_numbers where n = 5")]
@interaction[#:eval the-eval
(query-value pgc "select count(*) from the_numbers")
(query-value pgc "select d from the_numbers where n = 5")
]
When a query may return zero or one rows, as the last example, use
@racket[query-maybe-row] or @racket[query-maybe-value] instead.
@my-interaction[
[(query-maybe-value pgc "select d from the_numbers where n = 5")
(values #f)]
@interaction[#:eval the-eval
(query-maybe-value pgc "select d from the_numbers where n = 5")
]
The @racket[in-query] function produces a sequence that can be used
with Racket's iteration forms:
@my-interaction[
[(for ([(n d) (in-query pgc "select * from the_numbers where n < 4")])
(printf "~a is ~a\n" n d))
(for-each (lambda (n d) (printf "~a: ~a\n" n d))
'(0 1 2 3)
'("nothing" "the loneliest number" "company" "a crowd"))]
[(for/fold ([sum 0]) ([n (in-query pgc "select n from the_numbers")])
@interaction[#:eval the-eval
(for ([(n d) (in-query pgc "select * from the_numbers where n < 4")])
(printf "~a: ~a\n" n d))
(for/fold ([sum 0]) ([n (in-query pgc "select n from the_numbers")])
(+ sum n))
(for/fold ([sum 0]) ([n (in-list '(0 1 2 3))])
(+ sum n))]
]
Errors in queries generally do not cause the connection to disconnect.
@my-interaction[
[(begin (with-handlers [(exn:fail?
@interaction[#:eval the-eval
(begin (with-handlers [(exn:fail?
(lambda (e)
(printf "~a~n" (exn-message e))))]
(query-value pgc "select NoSuchField from NoSuchTable"))
(query-value pgc "select 'okay to proceed!'"))
(begin (display "query-value: relation \"nosuchtable\" does not exist (SQLSTATE 42P01)")
"okay to proceed!")]
]
Queries may contain parameters. The easiest way to execute a
parameterized query is to provide the parameters ``inline'' after the
SQL statement in the query function call.
@my-interaction[
[(query-value pgc
@interaction[#:eval the-eval
(query-value pgc
"select d from the_numbers where n = $1" 2)
"company"]
[(query-list pgc
(query-list pgc
"select n from the_numbers where n > $1 and n < $2" 0 3)
(list 1 2)]
]
Alternatively, a parameterized query may be prepared in advance and
executed later. @tech{Prepared statements} can be executed multiple
times with different parameter values.
@my-interaction[
[(define get-less-than-pst
@interaction[#:eval the-eval
(define get-less-than-pst
(prepare pgc "select n from the_numbers where n < $1"))
(void)]
[(query-list pgc get-less-than-pst 1)
(list 0)]
[(query-list pgc (bind-prepared-statement get-less-than-pst '(2)))
(list 0 1)]
(query-list pgc get-less-than-pst 1)
(query-list pgc (bind-prepared-statement get-less-than-pst '(2)))
]
When a connection's work is done, it should be disconnected.
@my-interaction[
[(disconnect pgc)
(void)]
@;{ Don't actually disconnect; use this connection for the rest of the examples. }
@interaction[#:eval the-eval
(eval:alts (disconnect pgc) (void))
]
@ -480,32 +448,3 @@ web-server
By using a virtual connection backed by a connection pool, a servlet
can achieve simplicity, isolation, and performance all at the same
time.
@;{
TODO:
- talk about virtual statements, too
- show actual working servlet code
--
A prepared statement is tied to the connection used to create it;
attempting to use it with another connection results in an
error. Unfortunately, in some scenarios such as web servlets, the
lifetimes of connections are short or difficult to track, making
prepared statements inconvenient. In such cases, a better tool is the
@tech{virtual statement}, which prepares statements on demand and
caches them for future use with the same connection.
@my-interaction[
[(define get-less-than-pst
(virtual-statement "select n from the_numbers where n < $1"))
(void)]
[(code:line (query-list pgc1 get-less-than-pst 1) (code:comment "prepares statement for pgc1"))
(list 0)]
[(code:line (query-list pgc2 get-less-than-pst 2) (code:comment "prepares statement for pgc2"))
(list 0 1)]
[(code:line (query-list pgc1 get-less-than-pst 3) (code:comment "uses existing prep. stmt."))
(list 0 1 2)]
]
}

View File

@ -37,17 +37,14 @@ modules below, not by @racketmodname[db] or @racketmodname[db/base].
SRFI date, for example, puts zeroes in the year, month, and day
fields.
@(examples/results
[(sql-datetime->srfi-date
@examples[#:eval the-eval
(sql-datetime->srfi-date
(query-value pgc "select time '7:30'"))
(sql-datetime->srfi-date (make-sql-time 7 30 0 0 #f))]
[(sql-datetime->srfi-date
(sql-datetime->srfi-date
(query-value pgc "select date '25-dec-1980'"))
(sql-datetime->srfi-date
(make-sql-date 1980 12 25))]
[(sql-datetime->srfi-date
(sql-datetime->srfi-date
(query-value pgc "select timestamp 'epoch'"))
(sql-datetime->srfi-date (make-sql-timestamp 1970 1 1 0 0 0 0 #f))])
]
}
@defproc[(sql-day-time-interval->seconds [interval sql-day-time-interval?])