实现一个新的后端

    本文的目的是帮助您为DBI实现一个新的后端。

    如果您正在编写一个将数据库连接到R的包,我强烈建议您使其与dbi兼容,因为它通过明确说明需要做的事情使您的工作更容易。DBI提供的一致接口使您更容易实现包(因为您可以做出的任意选择更少),也使您的用户更容易实现包(因为它遵循熟悉的模式)。此外,金刚石钻头包提供了您可以很容易地将其合并到包中的测试用例。

    我将使用一个名为Kazam的虚构数据库来说明这个过程。

    开始

    首先创建一个包。这取决于您如何称呼包,但遵循现有的模式RSQLiteRMySQLRPostgres而且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规范中定义的所有方法。如果您想多走一段路,就提供一种只读模式,使用户能够确保他们有价值的数据不会被无意中破坏。