打包“ odbc”的包装不适用于全局连接

时间:2019-08-07 13:58:20

标签: r package devtools

我有一个pkg包裹,它包裹着odbc包裹以简化生活。该程序包由单个代码文件sql_con.R组成:

# sql_con.R
getQuery <- function(sql) {
  con <- odbc::dbConnect(odbc::odbc(),
                   Driver = "SQL Server",
                   Server = "Foo",
                   UID = "user",
                   PWD = "password")
  return(odbc::dbGetQuery(con, sql))
}

# DESCRIPTION
Package: NCHUtils
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R: 
    person(given = "First",
           family = "Last",
           role = c("aut", "cre"),
           email = "first.last@example.com",
           comment = c(ORCID = "YOUR-ORCID-ID"))
Description: What the package does (one paragraph).
License: What license it uses
Encoding: UTF-8
LazyData: true
Depends: 
    odbc

# NAMESPACE
exportPattern("^[^\\.]")
importMethodsFrom(odbc, dbGetQuery, dbConnect)

构建此程序包可以正常工作,library(pkg),然后使用getQuery()进行一些SQL调用会产生良好的结果。

但是,必须为每个查询创建一个新连接有点愚蠢,因此我想将con设为仅创建一次并每次重用的全局变量。 (稍后将使用pool软件包对此进行改进)。我还将连接放在一个环境中以克服任何绑定锁定问题(遵循this R-bloggers post中的建议)。

# sql_con.R version 2.0
pkg <- new.env()

pkg$con <- odbc::dbConnect(odbc::odbc(),
                   Driver = "SQL Server",
                   Server = "Foo",
                   UID = "user",
                   PWD = "password")

getQuery <- function(sql) {
  return(odbc::dbGetQuery(pkg$con, sql))
}

构建软件包再次成功。

然后我运行以下测试:

library(pkg)

pkg::pkg
# <environment: 0x000001a9dbcf8f88>

pkg::pkg$con
# An object of class "Microsoft SQL Server"
# [a bunch of attributes...]

pkg::getQuery("SELECT * FROM Foo")
# Error in (function (classes, fdef, mtable)  : 
#   unable to find an inherited method for function ‘dbGetQuery’ for signature ‘"Microsoft SQL Server", "character"’

删除环境的使用不会更改任何内容。我已经设置DESCRIPTION使用Depends:而不是普通的Imports:来解决此问题(第一个版本与Imports:一起使用),并且(正确地)导入了该函数与importMethodsFrom在NAMESPACE中。

我注意到odbc仅在实际使用这样的参数调用dbGetQuery()时创建带有“ Microsoft SQL Server”签名的继承函数。但我看不出这有什么意义。

使连接变为全局会破坏包吗?

1 个答案:

答案 0 :(得分:1)

我现在没有ODBC连接, 但我想我知道问题出在哪里。

我遇到了this thread, 我建议您通读一下,因为您可能需要类似的方法来清理连接, 但关键见解是:

  

请注意,当您安装软件包时,R将运行   包,仅将代码结果存储在已安装的文件中   包。因此,如果您在自己函数的外部创建对象   包,那么只有对象会存储在包中,但不会   创建它的代码。当您   加载程序包,但不会重新创建。

这意味着您的软件包正在尝试使用已序列化并随后反序列化的数据库连接, 这可能行不通。 如果您想完全由自己来管理, 您可能需要类似的东西:

conn_provider <- with(new.env(), {
    conn <- NULL

    function() {
        if (is.null(conn)) {
            conn <<- DBI::dbConnect(your_details)
        }

        conn
    }
})

还有一个reg.finalizer调用,如链接线程中所述。

关于包裹进口, 从我所能收集到的 泛型由DBI定义, 并且在其上构建了不同的软件包, 因此,我认为您最好从DBI导入泛型,并从odbc导入方法。 如果您使用roxygen2(绝对应该这样做), 您需要遵循以下原则:

#' @importFrom DBI dbGetQuery
#' @importMethodsFrom odbc dbGetQuery
#'
getQuery <- function(sql) {
  DBI::dbGetQuery(conn_provider(), sql)
}

同样,您的所有其他功能也是如此。 我认为这样, 程序包中对DBI泛型的调用始终可以分派到任何基于DBI的程序包定义的特定方法, 不只是odbc个。