实现一个新的后端
本文的目的是帮助您为DBI实现一个新的后端。
如果您正在编写一个将数据库连接到R的包,我强烈建议您使其与dbi兼容,因为它通过明确说明需要做的事情使您的工作更容易。DBI提供的一致接口使您更容易实现包(因为您可以做出的任意选择更少),也使您的用户更容易实现包(因为它遵循熟悉的模式)。此外,金刚石钻头
包提供了您可以很容易地将其合并到包中的测试用例。
我将使用一个名为Kazam的虚构数据库来说明这个过程。
开始
首先创建一个包。这取决于您如何称呼包,但遵循现有的模式RSQLite
,RMySQL
,RPostgres
而且ROracle
会让人们更容易找到它。对于本例,我将调用我的包RKazam
.
在你的描述
,确保包括:
导入:DBI(>= 0.3.0),方法建议:DBItest, testthat
进口DBI
很好,因为您的用户不应该这样做附加你的包裹;首选的方法是附加DBI
并使用显式限定via::
访问您的包中的驱动程序(这只需要做一次)。
测试
为什么要在这个早期阶段进行测试?因为测试应该是软件开发周期中不可分割的一部分。从一开始就进行测试,在进行的过程中添加自动化测试,并更快地完成(因为测试是自动化的),同时保持极好的代码质量(因为测试还检查您可能没有意识到的极端情况)。不要担心:如果一些测试用例很难或不可能满足,或者运行时间太长,您可以直接关闭它们。
现在花点时间去金刚石钻头
装饰图案。您将发现大量现成的测试用例,它们将在实现新的DBI后端的过程中帮助您。
小插图("test", package = "DBItest")
添加未覆盖的自定义测试金刚石钻头
由你决定,或增强金刚石钻头
如果测试足够通用,对许多DBI后端都有用,就提交pull请求。
司机
首先创建一个继承自的驱动程序类DBIDriver
.这个类不需要做任何事情;它只是用来将其他泛型分派给正确的方法。用户不需要知道这一点,因此您可以使用@ keyword内部
:
#' Kazam数据库的驱动程序。@关键字内部# @导出# @导入DBI # @导入方法setClass("KazamDriver", contains = "DBIDriver")
的旧版本中驱动程序类更重要DBI
,所以你还应该提供一个假人dbUnloadDriver ()
方法:
@rdname kazam类setMethod("dbUnloadDriver", "KazamDriver", function(drv,…){TRUE})
如果您的包需要全局设置或拆卸,请在.onLoad ()
而且.onUnload ()
功能。
你可能还想加个a显示()
方法,以便对象打印良好:
setMethod("show", "KazamDriver",函数(对象){cat("\n")})
接下来,创建个愿望()
,它实例化这个类:
#' @export Kazam <- function() {new("KazamDriver")} Kazam() #>
连接
接下来,创建继承自的连接类DBIConnection
.这将存储连接到数据库所需的所有信息。如果您使用的是C API,这将包括一个插槽,用于保存外部指针。
#' Kazam连接类。内部setClass("KazamConnection", contains = "DBIConnection", slots = list(host = "character", username = "character", #等等ptr = "externalptr"))
现在已经有了一些样板文件,可以开始处理连接了。这里最重要的方法是dbConnect ()
,它允许您连接到数据库的指定实例。注意的用法@rdname个愿望
.这将确保个愿望()
和connect方法被记录在一起。
#' @param drv由\code{Kazam()} #' @rdname Kazam #' @export #' @examples #' \dontrun{#' db <- dbConnect(RKazam::Kazam()) #' dbWriteTable(db, "mtcars", mtcars) #' dbGetQuery(db, "SELECT * FROM mtcars WHERE cyl == 4") #'} setMethod("dbConnect", "KazamDriver", function(drv,…){#…new("KazamConnection", host = host,…)})
取代
...
使用连接到数据库所需的参数。你总是需要包括...
在参数中,即使不使用它,也要与泛型兼容。这可能是人们首先寻求帮助的地方,因此示例应该展示如何连接到数据库以及如何查询数据库。(显然,这些例子还不能奏效。)理想情况下,包括可以立即运行的示例(可能依赖于公共托管的数据库),但如果做不到这一点,就把它包围起来
\ dontrun {}
这样人们至少可以看到代码。
接下来,执行显示()
而且dbDisconnect ()
方法。
结果
最后,您已经准备好实现系统的核心部分:将查询的结果获取到数据帧中。首先定义一个结果类:
#' Kazam结果类。@export setClass("KazamResult", contains = "DBIResult", slots = list(ptr = "externalptr"))
然后写一个dbSendQuery ()
方法。它以一个连接和SQL字符串作为参数,并返回一个结果对象。再一次...
是与泛型的兼容性所必需的,但如果需要,您可以添加其他参数。
#'向Kazam发送查询。这是另一个放例子的好地方setMethod("dbSendQuery", "KazamConnection", function(conn, statement,…){# some code new("KazamResult",…)})
接下来,执行dbClearResult ()
,它应该关闭结果集并释放与之关联的所有资源:
#' @export setMethod("dbClearResult", "KazamResult", function(res,…){# free resources TRUE})
每个DBI包最困难的部分是编写dbFetch ()
方法。这需要获取一个结果集和(可选的)要返回的记录数量,并创建一个数据帧。将R的数据类型映射到数据库的数据类型可能需要自定义实现dbDataType ()
方法的连接类:
@export setMethod("dbFetch", "KazamResult", function(res, n = -1,…){…@export setMethod("dbDataType", "KazamConnection", function(dbObj, obj,…){…})
接下来,执行dbHasCompleted ()
,它应该返回a逻辑
指示是否有任何未取的行。
#' @export setMethod("dbHasCompleted", "KazamResult", function(res,…){})
有了这四个方法,您现在就可以使用默认方法了dbGetQuery ()
要向数据库发送查询,如果有的话检索结果,然后进行清理。现在花些时间确保这可以与现有数据库一起工作,或者放松,让金刚石钻头
软件包为您工作。
SQL的方法
现在已经到了最后阶段,可以通过实现跨数据库包装SQL变量的方法,使包装器更加有用:
dbQuoteString ()
而且dbQuoteIdentifer ()
用于安全地引用字符串和标识符,以避免SQL注入攻击。注意,前者必须是向量化的,而后者则不是。dbWriteTable ()
创建一个给定R数据帧的数据库表。我建议使用前缀为sql
在这个包中生成SQL。这些功能还在进行中,如果有问题请让我知道。dbReadTable ()
是否有一个简单的包装器SELECT * FROM table
.使用dbQuoteIdentifer ()
安全地引用表名,并防止R允许的名称与数据库之间的不匹配。dbListTables ()
而且dbExistsTable ()
让您确定哪些表可用。如果数据库的API没有提供,则可能需要生成检查系统表的SQL。dbListFields ()
显示给定表中哪些字段可用。dbRemoveTable ()
包裹在删除表
.开始SQL: sqlTableDrop ()
.实现
dbBegin ()
,dbCommit ()
而且dbRollback ()
提供基本事务支持。卡塔尔世界杯欧洲预选赛赛程表的中尚未测试此功能金刚石钻头
包中。
元数据方法
对于结果集还有许多额外的元数据方法(以及一个用于连接的元数据方法),您可能还希望实现这些方法。下面将对它们进行描述。
dbIsValid ()
如果连接或结果集打开,则返回(真正的
)或封闭(假
).本节中的所有其他方法仅对结果集有效。dbGetStatement ()
以字符值的形式返回发出的查询。dbColumnInfo ()
列出结果集列的名称和类型。dbGetRowCount ()
而且dbGetRowsAffected ()
对象中返回或修改的行数选择
或插入
/更新
分别查询。dbBind ()
允许使用参数化查询。让我们来看看sqlInterpolate ()
而且sqlParseVariables ()
如果您的SQL引擎不提供本地参数化查询。
完整的DBI合规
到目前为止,您的包应该实现DBI规范中定义的所有方法。如果您想多走一段路,就提供一种只读模式,使用户能够确保他们有价值的数据不会被无意中破坏。