为什么 dbListTables 在通过函数调用时会给出警告消息?(R DBI)

Wil*_*lem 5 sql-server odbc r r-dbi

我使用 DBI 包中的 dbListTables 编写了一个函数,该函数引发了我无法理解的警告。当我在函数之外运行相同的代码时,我没有收到警告消息。

有关信息,使用的数据库是 Microsoft SQL Server。

可重现的例子

library(odbc)
library(DBI)

# dbListTables in a function: gives a warning message

dbListTablesTest <- function(dsn, userName, password){

  con <- dbConnect(
    odbc::odbc(),
    dsn      = dsn,
    UID      = userName,
    PWD      = password,
    Port     = 1433,
    encoding = "latin1"
  )

  availableTables <- dbListTables(con)
}

availableTables <- 
  dbListTablesTest(
    dsn = "myDsn"
    ,userName = myLogin
    ,password = myPassword
  )

# dbListTables not within a function works fine (no warnings)

con2 <- dbConnect(
  odbc::odbc(),
  dsn      = "myDsn",
  UID      = myLogin,
  PWD      = myPassword,
  Port     = 1433,
  encoding = "latin1"
)

availableTables <- dbListTables(con2)
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,我意识到我应该在使用 dbDisconnect 之后使用它来关闭连接。但这似乎会引发类似的警告。所以为了简单起见,我省略了 dbDisconnect。)

警告信息

执行上面的代码时,我在使用第一个选项(通过函数)时收到以下警告消息,但在使用第二个选项(无函数)时我没有收到它。

warning messages from top-level task callback '1'
Warning message:
Could not notify connection observer. trying to get slot "info" from an object of a basic class ("character") with no slots 
Run Code Online (Sandbox Code Playgroud)

该警告显然是由 dbListTables 引起的,因为当我从上述函数中省略该行时它就会消失。

我的问题

  • 为什么我会收到此警告消息?
  • 更具体地说,为什么我只在通过函数调用 dbListTables 时才得到它?
  • 我做错了什么/我应该怎么做才能避免它?

我的会话信息

R version 3.4.2 (2017-09-28)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

Matrix products: default

locale:
[1] LC_COLLATE=Dutch_Belgium.1252  LC_CTYPE=Dutch_Belgium.1252    LC_MONETARY=Dutch_Belgium.1252 LC_NUMERIC=C                   LC_TIME=Dutch_Belgium.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  tools     methods   base     

other attached packages:
[1] DBI_0.7    odbc_1.1.3

loaded via a namespace (and not attached):
[1] bit_1.1-12     compiler_3.4.2 hms_0.3        tibble_1.3.4   Rcpp_0.12.13   bit64_0.9-7    blob_1.1.0     rlang_0.1.2  
Run Code Online (Sandbox Code Playgroud)

在此先感谢您的帮助!

JAD*_*JAD 5

TL:DR calling odbc::dbConnect within another function causes this warning.

After a lot of digging in the odbc github, I have found the source of the warning. Calling dbConnect creates a db connection. Within this function is the following code:

# perform the connection notification at the top level, to ensure that it's had
# a chance to get its external pointer connected, and so we can capture the
# expression that created it
if (!is.null(getOption("connectionObserver"))) { # nocov start
  addTaskCallback(function(expr, ...) {
    tryCatch({
      if (is.call(expr) && identical(expr[[1]], as.symbol("<-"))) {
        # notify if this is an assignment we can replay
        on_connection_opened(eval(expr[[2]]), paste(
          c("library(odbc)", deparse(expr)), collapse = "\n"))
      }
    }, error = function(e) {
      warning("Could not notify connection observer. ", e$message, call. = FALSE)
    })

    # always return false so the task callback is run at most once
    FALSE
  })
} # nocov end
Run Code Online (Sandbox Code Playgroud)

This warning call should look familiar. This is what generates the warning. So why does it do that?

The snippet above is trying to do some checking on the connection object, to see if everything went well.

How it does that, is by adding a function checking this to the 'TaskCallBack'. This is a list of functions that get executed after a top-level task is completed. I am not 100% sure on this, but from what I can tell, this means that these functions are executed after the highest function in the call stack finishes.

Normally, this would be a line in your script. So for example:

library(odbc)
con <- odbc::dbConnect(odbc::odbc(), ...)
Run Code Online (Sandbox Code Playgroud)

After the assignment in the second line is finished, the following function is executed:

function(expr, ...) {
    tryCatch({
      if (is.call(expr) && identical(expr[[1]], as.symbol("<-"))) {
        # notify if this is an assignment we can replay
        on_connection_opened(eval(expr[[2]]), paste(
          c("library(odbc)", deparse(expr)), collapse = "\n"))
      }
    }, error = function(e) {
      warning("Could not notify connection observer. ", e$message, call. = FALSE)
    }
}
Run Code Online (Sandbox Code Playgroud)

The top-level expression gets passed to the function and used to check if the connection works. Another odbc function called on_connection_opened then does some checks. If this throws an error anywhere, the warning is given, because of the tryCatch.

So why would the function on_connection_opened crash?

The function takes the following arguments:

on_connection_opened <- function(connection, code)
Run Code Online (Sandbox Code Playgroud)

and one of the first things it does is:

display_name <- connection@info$dbname
Run Code Online (Sandbox Code Playgroud)

Which seems to match the warning message:

trying to get slot "info" from an object of a basic class ("character") with no slots

From the name of the argument, it is clear that the function on_connection_opened expects a database connection object in its first argument. What does it get from its caller? eval(expr[[2]])

This is the lefthand side of the original call: con

In this case, this is a connection object and everything is nice.

Now we have enough information to answer your questions:

Why am I getting this warning message?

Your function creates a connection, which queues up the check connection function. If then checks for a list of tables and returns that. The check connection function then interprets the list of tables as if it is a connection, tries to check it, and fails miserably. This throws the warning.

More specifically why am I only getting it when calling dbListTables via a function?

dbListTables is not the culprit, dbConnect is. Because you are calling it from within a function, it doesn't get the connection object it is trying to check back and fails.

What am I doing wrong / should I do to avoid it?

A workaround would be to open a connection separately and pass that into your function. This way the connection gets opened in its own call, so the check works properly.

Alternatively, you can remove the TaskCallback again:

  before <- getTaskCallbackNames()
  con <- odbc::dbConnect(odbc::odbc(), ...)
  after <- getTaskCallbackNames()
  removeTaskCallback(which(!after %in% before))
Run Code Online (Sandbox Code Playgroud)

跑步是on_connection_opened必不可少的吗?它具体有什么作用?

正如包的创建者在Github 上的评论中所解释的那样,该函数处理 RStudio 中连接选项卡中连接的显示。如果您再次关闭同一个函数中的连接,这看起来就不那么有趣了。所以这对你的功能来说不是必需的。