【问题标题】:Shiny textInput to sting used in SQL闪亮的 textInput 到 SQL 中使用的 sting
【发布时间】:2020-05-22 18:33:03
【问题描述】:

我在 Shiny 应用程序中有一个 textInput 供用户编写由逗号分隔的三个字符产品代码。例如:F03、F04、F05。

textInput 的输出用于调用 sql 脚本的函数。它将在sql语句中用作过滤器,例如

sqlfunction <- function(text){

sqlQuery(conn, stri_paste("select .... where product_code in (", text, ");"))

}

为了将 textInput 转换为我可以在 sql 语句中使用的字符串,我使用了

toString(sprintf("'%s'", unlist(strsplit(input$text_input, ","))))

这可以工作并将 textInput 转换为“F03”、“F04”。 'F05' 然而,当在 sql 中使用时,只有第一个代码 'F03' 用于搜索,尽管在 () 中使用了 product_code。返回的数据只有产品代码为 F03 的数据。

如何将所有三个代码(如果不是更多)用 textInput 写入一个字符串以在 sql 子句中使用?

安德鲁

【问题讨论】:

  • 您能否包含一些input$text_input 的示例值?
  • (1) SQL 注入,不要只是paste 你的代码(xkcd.com/327)。最好使用至少类似glue::glue_sql的东西,但更有可能使用类似DBI::dbBind或至少DBI::sqlInterpolate的东西(我更喜欢DBI::dbBind)。 db.rstudio.com/best-practices/run-queries-safely。 (2) 使用"'%s'" 很好,使用sQuote 或(首选)DBI::dbQuoteString 会更好。
  • 您的前提看起来不错,至于为什么 in (...) 不起作用听起来 DBMS 没有按预期方式工作,或者数据没有像您认为的那样变化。假设您在专门寻找数据时可以找到具有其他值的数据是否安全?
  • 我怀疑@Jrm_FRL 的回答可能有问题。您可以考虑使用 trimws(unlist(strsplit(...)) 删除前导/尾随空格。

标签: r shiny


【解决方案1】:

如果你的输入是

F03、F04、F05

逗号和下一个值之间有空格,语句给出:

select .... where product_code in ('F03', 'F04', 'F05');

注意空格。然后找不到值“F04”和“F05”。

如果输入是 'F03,F04,F05' 怎么办? (无空格)

【讨论】:

  • 我认为一个好的解决方案是使用trimws 将其删除,而不是要求用户一开始就不要添加它们。
  • (我只是指出在这种特殊情况下出了什么问题,但事实上,有几种方法可以更安全、更优雅地构建你的 sql 请求)
  • 谢谢,这是 Tim Biegeleisen 的 paste0() 解决方案的结合
【解决方案2】:

我会将其他答案和讨论的重要组成部分组合成一个建议的答案。随意接受其中一个,他们首先有想法。

正如 Jrm_FRL 所说,逗号周围的空格可能会保留在您的脚本中,这在 SQL 中不应该匹配。

toString(sQuote(unlist(strsplit("hello, world, again", ","))))
# [1] "'hello', ' world', ' again'"
###              ^--       ^--  leading spaces on the strings

一些选项:

  1. 如果您认为用户能够有意在逗号周围引入空格(意思是:在字符串/模式的开头或结尾)很重要,那么您唯一的希望是指示 用户只在需要时使用空格。

  2. 否则,你可以使用trimws

    toString(sQuote(trimws(unlist(strsplit("hello, world, again", ",")))))
    # [1] "'hello', 'world', 'again'"
    
  3. strsplit(..., ",") 可能是不正确的,如果用户有引号来保持在一起。你可以考虑使用read.csv:

    trimws(unlist(read.csv(text="hello, \"world, too\", again", header = FALSE, stringsAsFactors = FALSE)))
    #           V1           V2           V3 
    #      "hello" "world, too"      "again" 
    

    这与上面的选项 1 不完全兼容。

其次,正如 Tim Biegleisen 和 Jrm_FRL 都同意的那样,您在这里特别容易受到 SQL 注入的影响。来自用户的格式错误(无论是无意还是有意)的搜索字符串最多可能会损坏此查询的结果,最坏的情况是(取决于连接权限)损坏或删除数据库。 (我强烈建议你阅读https://db.rstudio.com/best-practices/run-queries-safely/。)

保护方法:

  1. 不要在数据周围手动添加单引号:如果字符串包含单引号,它不会被转义,最多会导致 SQL 错误。

    toString(sQuote(trimws(unlist(strsplit("hello'; drop table students; --", ",")))))
    # [1] "'hello'; drop table students; --'"
    ### this query may delete the 'students' table
    ### notice that `sQuote` is not enough here, it is not escaping this correctly
    

    改为使用DBI::dbQuoteString。虽然我相信大多数 DBMS 都使用相同的单引号约定(您的问题表明您的也是如此),但让数据库驱动程序确定如何处理文字字符串和嵌入引号的字符串可能是一种好习惯。

    toString(DBI::dbQuoteString(con, trimws(unlist(strsplit("hello'; drop table students; --", ",")))))
    # [1] "'hello''; drop table students; --'"
    ###          ^^ this is SQL's way of escaping an embedded single-quote
    ### this is now a single string, allegedly SQL-safe
    
  2. 请使用DBI::dbBind,而不是将字符串包含在查询中,但诚然,您需要根据值向量的长度包含多个绑定书签(通常但不总是?)。

    val_str <- "hello, world, again"
    val_vec <- trimws(unlist(strsplit("hello, world, again", ",")))
    qmarks <- paste(rep("?", length(val_vec)), collapse = ",")
    qmarks
    # [1] "?,?,?"
    qry <- paste("select ... where product_code in (", qmarks, ")")
    out <- tryCatch({
      res <- NULL
      res <- DBI::dbSendStatement(con, qry)
      DBI::dbBind(res, val_vec)
      DBI::dbFetch(res)
    }, finally = { if (!is.null(res)) suppressWarnings(DBI::dbClearResult(res)) })
    

    ? 的使用因 DMBS 而异,因此您可能需要针对您的具体情况进行一些研究。

    (虽然我在这里使用tryCatch 来“保证”res 将在退出时被清除,但这种模式比不这样做更可靠。如果没有finally= 的部分查询或绑定失败部分,那么它可能会使连接处于不完美状态。)

【讨论】:

    【解决方案3】:

    我怀疑这是你想要的:

    text_input = "A,B,C"
    in_clause <- paste0("'", unlist(strsplit(text_input, ",")), "'", collapse=",")
    sql <- paste0("WHERE product_code IN (", in_clause, ")")
    sql
    
    [1] "WHERE product_code IN ('A','B','C')"
    

    这里我仍然使用unliststrsplit 的组合来为IN 子句生成一个字符串向量。但后来我使用pastecollapse 来获得你想要的输出。

    【讨论】:

    • 你真的不应该使用paste0("'",...,"'"),它会被格式错误的字符串破坏。我建议至少DBI::dbQuoteStringsQuote,因为它们都处理嵌入的单引号。
    • @r2evans 这里还有一个更大的问题:SQL 注入。如果text_input 的任何内容可能来自外部,例如根据用户输入,恶意 SQL 片段有可能被注入到 OP 的数据库中。所以,我不太担心格式错误的字符串。
    • 同意,正如我的first comment 5min ago 建议的那样。我要说的是,由于我们知道数据将进入 SQL 语句,因此建议单引号的文字连接不是一种好的形式。
    • 我非常同意@tim-biegeleisen,如果您需要被说服,请阅读db.rstudio.com/best-practices/run-queries-safely
    • 字面上在我上面的评论中有那个链接。
    猜你喜欢
    • 2016-09-22
    • 2016-04-09
    • 2021-06-15
    • 2015-11-16
    • 1970-01-01
    • 2021-05-06
    • 2021-04-10
    • 1970-01-01
    • 2015-02-20
    相关资源
    最近更新 更多