包裹包装 "odbc" 不适用于全局连接

Package wrapping "odbc" doesn't work with a global connection

我有一个包裹 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:),并且我(afaik)正确导入了函数importMethodsFrom 在 NAMESPACE 中。

我注意到 odbc 使用 "Microsoft SQL Server" 签名创建继承的函数只有一次 dbGetQuery() 实际上是用这样的参数调用的。但我看不出这有什么关系。

是否有任何原因导致全局连接破坏包?

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

我碰到了this thread, 我建议你仔细阅读,因为你可能需要类似的东西来清理你的连接, 但关键的见解是:

Note that when you install a package, R runs all the code in the package and only stores the results of the code in the installed package. So if you create an object outside of a function in your package, then only the object will be stored in the package, but not the code that creates it. The object will be simply loaded when you load the package, but it will not be re-created.

这意味着您的包正在尝试使用已序列化并随后反序列化的数据库连接, 这不可能工作。 如果您想完全自己管理它, 你可能需要这样的东西:

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 个。