Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

28 Feb 2021

Racket文档阅读笔记:DB篇

文档所在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是跨进程的概念。

(本篇完)