文档所在DB: Database Connectivity。
以SQLite为例,理解Racket对DB的支持。
连接
racket支持几种不同的数据库,SQLite就是其中之一。有些数据库连接是通过连线的方式,有些数据库是通过FFI操作的。SQLite属于后者,需要通过(sqlite3-available?)
判断一下当前的racket发行版是否支持sqlite。
数据库相关的模块是(require db)
。sqlite3-connect用来连接到一个sqlie3数据库
sqlite3-connect是由db-lib中的
db/sqlite3
模块提供的支持。
(require db)
(define dbc
(sqlite3-connect #:database 'memory))
上面的代码在内存里面创建一个sqlite数据库。上面dbc绑定的是一个数据库连接对象,可以在其上进行很多操作:
> dbc
(object:connection% ...)
> (connection? dbc)
#t
> (connected? dbc)
#t
> (define dbsys (connection-dbsystem dbc))
> (dbsystem? dbsys)
#t
> (dbsystem-name dbsys)
'sqlite3
> (dbsystem-supported-types dbsys)
'(any)
> (disconnect dbc)
> (connected? dbc)
#f
查询
racket的数据库查询接口是偏函数式的。默认情况下采用的是UTF-8编码。另外各个连接的操作会有同步,因此是线程安全的。但默认情况下并不是线程退出安全的,需要使用(kill-safe-connection c)
来指定在线程退出的时候主动释放资源。
query-exec用来执行不需要返回值的查询:
> (query-exec dbc "create table the_numbers (n integer, d text)")
如果语句需要参数,则可以在其后指定:
> (query-exec dbc "insert into the_numbers values ($1, $2)" 1 "hello")
> (query-exec dbc "insert into the_numbers values (?, ?)" 2 "world")
query-rows则可以用来执行查询,并返回结果:
> (query-rows dbc "select * from the_numbers")
'(#(1 "hello") #(2 "world"))
> (query-rows dbc "select * from the_numbers where n=?" 1)
'(#(1 "hello"))
> (query-rows dbc "select * from the_numbers where n=$1" 2)
'(#(2 "world"))
> (query-rows dbc "select 100")
'(#(100))
query-rows还支持额外的grouping参数,暂且不表。
query-list用来返回某个表的单列值:
> (query-list dbc "select n from the_numbers")
'(1 2)
> (query-list dbc "select \"hello,world\"")
'("hello,world")
query-row用来返回单行值:
> (query-row dbc "select * from the_numbers where n = $1" 2)
'#(2 "world")
> (query-row dbc "select min(n), max(n) from the_numbers")
'#(1 2)
query-maybe-row和query-row相似,不过前者在找不到结果的时候会返回#f,而不是抛出异常。
query-value用来返回单行单列值:
> (query-value dbc "select 17")
17
in-query返回一个sequence,每个sequence都带有这一行所有的列值:
> (for/list ([n (in-query dbc "select n from the_numbers")])
n)
'(1 2)
query函数支持任意语句,返回对整体结果的描述
> (query dbc "select * from the_numbers")
(rows-result
'(((name . "n") (decltype . "integer"))
((name . "d") (decltype . "text")))
'(#(1 "hello") #(2 "world")))
> (query dbc "insert into the_numbers values(4, '!')")
(simple-result '((insert-id . 3) (affected-rows . 1)))
相关数据类型
一个查询的结果可以是simple-result或者是rows-result。前者多用来表示INSERT或者DELETE语法的返回结果。后者多用来表示SELECT语句的返回结果。
- group-rows对query-*中的grouping提供支持。
- rows-dict顾名思义,将行转为字典
预备查询
相关函数:
(prepare connection stmt)
(prepared-statement? x)
(prepared-statement-parameter-types pst)
(prepared-statement-result-types pst)
(bind-prepared-statement pst params)
(statement-binding? x)
(virtual-statement gen)
(virtual-statement? x)
事务相关
事务可以托管。采用start-transaction以及call-with-transaction产生的事务就是托管的事务。通过SQL的语句(例如START TRANSACTION
)开启的事务则不属于托管事务。事务可以嵌套。
对于SQLite来说不能混合托管事务和非托管事务。
相关函数:
(start-transaction c [...])
(commit-transaction c)
(rollback-transaction c)
(in-transaction? c)
(needs-rollback? c)
(call-with-transaction c proc [...])
SQL错误
错误相关的异常类型是exn:fail:sql,如下所示:
(struct exn:fail:sql exn:fail (sqlstate info)
#:extra-constructor-name make-exn:fail:sql)
sqlstate : (or/c string? symbol?)
info : (listof (cons/c symbol? any/c))
对于SQLite来说,错误码以符号名表示,例如’busy用来表示SQLITE_BUSY。此外还有’ioerr-blocked, ‘ioerr-locked以及’readonly-rollback。
数据库目录信息
> (list-tables dbc)
'("the_numbers")
> (table-exists dbc notebook)
创建新类型的语句
prop:statement可以用来创建新类型的语句。
SQL类型及转化
查询结果会从SQL类型转为racket支持的类型,如果转化失败,就会抛出异常。值得注意的是,异常会在语句执行的时候抛出,而不是语句准备的时候抛出。对于参数化的查询,在参数插入的时候会抛出异常,比如使用prepared-statement-parameter-types
以及prepared-statement-result-types
的时候。
对于SQLite来说,其类型对应很简单:
- integer 对应
exact-integer?
- real 对应
real?
- text 对应
string?
- blob 对应
bytes?
另外SQLite的整型最大值是64位,超过了Racket会将其转变为real:
> (expt 2 80)
1208925819614629174706176
> (query-value slc "select ?" (expt 2 80))
1.2089258196146292e+24
SQL NULL会被转为sql-null
,相关执行诀
(sql-null? x)
(sql-null->false x)
(false->sql-null x)
日期时间相关:
- struct sql-date
- struct sql-time
- struct sql-timestamp
- struct sql-interval
(sql-year-month-interval? x)
(sql-day-time-interval? x)
(sql-interval->sql-time interval [failure])
(sql-time->sql-interval time)
另外还有一些废弃的bit相关的执行诀。
实用工具
由db或者db/base以外的模块提供。
- db-lib提供的
(require db/util/datetime)
,日期时间格式化函数。 - db-lib提供的
(require db/util/geometry)
,几何计算相关的函数。 - db-lib提供的
(require db/util/postgresql)
,PostGreSql相关 - db-lib提供的
(require db/util/cassandra)
,Cassandra相关 - db-lib提供的
(require db/util/testing)
,数据库测试相关- 比如
(high-latency-connection ---)
- 比如
- db-lib提供的
(require db/unsafe/sqlite3)
,用于加载SQLite的扩展(sqlite3-load-extension c extension-path)
(sqlite3-create-function c name arity func)
(sqlite3-create-aggregate c ---)
其他
Windows底下SQlite使用的是自带的sqlite3.dll。
FFI调用会阻塞所有Racket的线程。可以使用place
来实现异步操作,place是跨进程的概念。
(本篇完)