如何使用glue_data_sql在SQL Server数据库上编写安全的参数化查询?

Wil*_*lem 8 sql-server r r-dbi

问题

我想围绕一些DBI函数编写一个包装器,以允许安全执行参数化查询。我找到了这个资源,它解释了如何使用该glue包将参数插入 SQL 查询。但是,似乎有两种不同的方法可以使用glue包插入参数:

  1. 方法1涉及?在需要插入参数的sql查询中使用use,然后随后使用usedbBind来填充它们。上面链接中的示例:
library(glue)
library(DBI)

airport_sql <- glue_sql("SELECT * FROM airports WHERE faa = ?")
airport <- dbSendQuery(con, airport_sql)

dbBind(airport, list("GPT"))
dbFetch(airport)
Run Code Online (Sandbox Code Playgroud)
  1. 方法 2涉及使用glue_sqlglue_data_sql自行填写参数(不使用dbBind)。还是上面链接中的一个例子:
airport_sql <- 
  glue_sql(
    "SELECT * FROM airports WHERE faa IN ({airports*})", 
    airports = c("GPT", "MSY"),
    .con = con
  )

airport <- dbSendQuery(con, airport_sql)

dbFetch(airport)
Run Code Online (Sandbox Code Playgroud)

我更喜欢使用第二种方法,因为它有很多额外的功能,例如折叠sql 语句子句in中语句的多个值。where请参阅上面的第二个示例了解其工作原理(请注意*参数后面的 表示它必须折叠)。问题是:这对于 SQL 注入安全吗?(还有其他我需要担心的事情吗?)

我的代码

这是目前我的包装器的代码。

paramQueryWrapper <- function(
  sql,
  params = NULL,
  dsn    = standard_dsn,
  login  = user_login,
  pw     = user_pw
){

  if(missing(sql) || length(sql) != 1 || !is.character(sql)){
    stop("Please provide sql as a character vector of length 1.")
  }

  if(!is.null(params)){
    if(!is.list(params))       stop("params must be a (named) list (or NULL).")
    if(length(params) < 1)     stop("params must be either NULL, or contain at least one element.")
    if(is.null(names(params)) || any(names(params) == "")) stop("All elements in params must be named.")
  }

  con <- DBI::dbConnect(
    odbc::odbc(),
    dsn      = dsn,
    UID      = login,
    PWD      = pw
  )
  on.exit(DBI::dbDisconnect(con), add = TRUE)


  # Replace params with corresponding values and execute query

  sql <- glue::glue_data_sql(.x = params, sql, .con = con)

  query <- DBI::dbSendQuery(conn = con, sql)
  on.exit(DBI::dbClearResult(query), add = TRUE, after = FALSE)

  return(tibble::as_tibble(DBI::dbFetch(query)))

}
Run Code Online (Sandbox Code Playgroud)

我的问题

这对于 SQL 注入安全吗?特别是因为我没有使用dbBind.

结语

我知道已经存在一个名为dbGetQuery允许参数的包装器(有关更多信息,请参阅此问题- 查找 @krlmlr 的答案,以获取参数化查询的示例)。但这又依赖于第一种方法,?该方法在功能方面更为基本。