query-exec 文档中的第二个示例显示了如何执行接收参数的数据库查询。
例如,假设我们有一个food
包含name
和calories
列的表,我们可以编写一个可以插入该表的函数。
;; insert-food!: database-connection string number -> void
;; Inserts an element into the food table.
(define (insert-food! conn name cals)
(query-exec conn
"insert into food (name, calories) values ($1, $2)"
name
cals))
我们正在使用参数化查询,假设数据库是 PostgreSQL 或一些支持使用占位符$1
、$2
...等的数据库,并将这些占位符的值作为附加参数传递给query-exec
.
如果您从外部世界获取输入并用它构建 SQL 查询,您应该知道如何使用参数化查询,否则您将冒着数据库安全的风险。不要尝试格式化将变量的值全部插入到一个查询字符串中的单个字符串:您可能会弄错并打开您的程序以进行SQL 注入漏洞利用。相反,将这些值作为不同的参数传递给query-exec
.
另外,请注意,上面的函数不需要在 Web 服务器的上下文中使用:例如,它可以在单元测试的上下文中使用。您可能希望测试此功能,而不需要在 Web servlet 中使用它。像这样的东西:
#lang racket
(require db)
;; We want to export the following functions to outside clients.
(provide [struct-out foo]
insert-food!
get-foods!)
;; A food is a:
(struct food (name ;; string
cals) ;; number
#:transparent)
;; insert-food!: database-connection food -> void
;; Inserts an element into the food table.
(define (insert-food! conn a-food)
(query-exec conn
"insert into food (name, calories) values ($1, $2)"
(food-name a-food)
(food-cals a-food)))
;; get-foods!: database-connection -> (listof food)
;; Get a list of the foods in the database.
(define (get-foods! conn)
(for/list ([(name cal)
(in-query conn "select name, calories from food")])
(food name cal)))
(module+ test
(require rackunit)
;; Internal test. We'll use an in-memory SQLite database.
(define conn (sqlite3-connect #:database 'memory))
(query-exec conn "create table food (name string, calories double)")
;; Initially, it should be empty.
(check-equal? (get-foods! conn) '())
;; Now let's add a food:
;; http://www.eiyoukeisan.com/JapaneseFoodCalorie/zryouri/misoramen.html
(insert-food! conn (food "miso ramen" 56000.0))
;; Can we get it back?
(check-equal? (get-foods! conn) (list (food "miso ramen" 56000.0))))
现在您可以通过在 DrRacket 中运行此文件来测试这些数据库函数的功能。一旦我们知道它们可靠地工作,我们就可以在不同的上下文中使用它们,例如 web servlet。
尝试在与 HTTP 请求处理程序相同的位置执行所有这些操作可能是错误的方法。您希望您的数据模型,即数据库操作,与您的 HTTP 请求处理程序位于一个单独的模块中,否则可能会使数据模型非常难以测试。这就是Continue web-server 教程不遗余力地谈论将“模型”作为一个单独的模块提取出来的主要原因。