Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Escape raw vectors and support list of raw vectors #433

Merged
merged 14 commits into from
May 22, 2020
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Depends:
Imports:
assertthat (>= 0.2.0),
DBI (>= 1.0.0),
magrittr,
dplyr (>= 0.8.0),
glue (>= 1.2.0),
lifecycle,
Expand Down
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ S3method(escape,integer)
S3method(escape,integer64)
S3method(escape,list)
S3method(escape,logical)
S3method(escape,raw)
S3method(escape,reactivevalues)
S3method(escape,sql)
S3method(explain,tbl_sql)
Expand Down Expand Up @@ -205,6 +206,7 @@ S3method(sql_escape_ident,SQLiteConnection)
S3method(sql_escape_logical,ACCESS)
S3method(sql_escape_logical,DBIConnection)
S3method(sql_escape_logical,SQLiteConnection)
S3method(sql_escape_raw,"Microsoft SQL Server")
S3method(sql_escape_string,DBIConnection)
S3method(sql_join,DBIConnection)
S3method(sql_join,MySQLConnection)
Expand Down Expand Up @@ -333,6 +335,7 @@ export(sql_cot)
export(sql_escape_date)
export(sql_escape_datetime)
export(sql_escape_logical)
export(sql_escape_raw)
export(sql_expr)
export(sql_infix)
export(sql_log)
Expand All @@ -342,6 +345,7 @@ export(sql_paste)
export(sql_paste_infix)
export(sql_prefix)
export(sql_quote)
export(sql_raw_to_hex)
export(sql_render)
export(sql_str_sub)
export(sql_substr)
Expand Down Expand Up @@ -453,6 +457,7 @@ importFrom(dplyr,ungroup)
importFrom(dplyr,union)
importFrom(dplyr,union_all)
importFrom(glue,glue)
importFrom(magrittr,"%>%")
importFrom(methods,setOldClass)
importFrom(stats,setNames)
importFrom(stats,update)
Expand Down
5 changes: 5 additions & 0 deletions R/backend-mssql.R
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@

)}

#' @export
`sql_escape_raw.Microsoft SQL Server` <- function(con, x) {
paste0("0x", sql_raw_to_hex(x))
}

#' @export
`db_analyze.Microsoft SQL Server` <- function(con, table, ...) {
# Using UPDATE STATISTICS instead of ANALYZE as recommended in this article
Expand Down
1 change: 1 addition & 0 deletions R/dbplyr.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#' @importFrom utils head tail
#' @importFrom glue glue
#' @importFrom methods setOldClass
#' @importFrom magrittr %>%
#' @importFrom dplyr n
#' @import rlang
#' @import DBI
Expand Down
28 changes: 25 additions & 3 deletions R/escape.R
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ escape.integer64 <- function(x, parens = NA, collapse = ", ", con = NULL) {
sql_vector(x, parens, collapse, con = con)
}

#' @export
escape.raw <- function(x, parens = NA, collapse = ", ", con = NULL) {
okhoma marked this conversation as resolved.
Show resolved Hide resolved
sql_vector(sql_escape_raw(con, x), parens, collapse, con = con)
}

#' @export
escape.NULL <- function(x, parens = NA, collapse = " ", con = NULL) {
sql("NULL")
Expand Down Expand Up @@ -192,7 +197,6 @@ names_to_as <- function(x, names = names2(x), con = NULL) {
#' If the quote character is present in the string, it will be doubled.
#' `NA`s will be replaced with NULL.
#'
#' @export
okhoma marked this conversation as resolved.
Show resolved Hide resolved
#' @param x Character vector to escape.
#' @param quote Single quoting character.
#' @export
Expand All @@ -214,7 +218,16 @@ sql_quote <- function(x, quote) {
y
}


#' Helper function to convert raw vector to hex string
#'
#' @param x Raw vector
#' @export
#' @keywords internal
#' @examples
#' sql_raw_to_hex(charToRaw("abc"))
sql_raw_to_hex <- function(x) {
okhoma marked this conversation as resolved.
Show resolved Hide resolved
paste(format(x), collapse = "")
}

#' More SQL generics
#'
Expand Down Expand Up @@ -242,6 +255,13 @@ sql_escape_datetime <- function(con, x) {
UseMethod("sql_escape_datetime")
}

#' @keywords internal
#' @export
#' @rdname sql_escape_logical
sql_escape_raw <- function(con, x) {
UseMethod("sql_escape_raw")
}

# DBIConnection methods --------------------------------------------------------

#' @export
Expand Down Expand Up @@ -273,4 +293,6 @@ sql_escape_logical.DBIConnection <- function(con, x) {
y
}


sql_escape_raw.DBIConnection <- function(con, x) {
hadley marked this conversation as resolved.
Show resolved Hide resolved
paste0("X'", sql_raw_to_hex(x), "'")
}
7 changes: 6 additions & 1 deletion R/sql-expr.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ sql_call2 <- function(.fn, ..., con = sql_current_con()) {


replace_expr <- function(x, con) {
if (is.atomic(x)) {
if (is.atomic(x) || is_raw_list(x)) {
as.character(escape(unname(x), con = con))
} else if (is.name(x)) {
as.character(x)
Expand Down Expand Up @@ -77,3 +77,8 @@ replace_expr <- function(x, con) {
}

}

# copied from DBI
is_raw_list <- function(x) {
okhoma marked this conversation as resolved.
Show resolved Hide resolved
is.list(x) && all(vapply(x, is.raw, logical(1)))
}
3 changes: 3 additions & 0 deletions man/sql_escape_logical.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions man/sql_raw_to_hex.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions tests/testthat/test-backend-mssql.R
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,23 @@ test_that("custom lubridate functions translated correctly", {
expect_equal(trans(quarter(x, with_year = TRUE)), sql("(DATENAME(YEAR, `x`) + '.' + DATENAME(QUARTER, `x`))"))
expect_error(trans(quarter(x, fiscal_start = 5)))
})

test_that("custom escapes translated correctly", {

mf <- lazy_frame(x = "abc", con = simulate_mssql())

a <- charToRaw("abc")
b <- as.raw(c(0x01, 0x02))

expect_equal(
mf %>% filter(x == a) %>% sql_render(),
sql("SELECT *\nFROM `df`\nWHERE (`x` = 0x616263)")
)

L <- list(a, b)
expect_equal(
mf %>% filter(x %in% L) %>% sql_render(),
sql("SELECT *\nFROM `df`\nWHERE (`x` IN (0x616263, 0x0102))")
)
})

8 changes: 8 additions & 0 deletions tests/testthat/test-escape.R
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ test_that("date and date-times are converted to ISO 8601", {
expect_equal(escape(x2, con = con), sql("'2000-01-02'"))
})

# Raw -----------------------------------------------------------------

test_that("raw is SQL-99 compatible (by default)", {
con <- simulate_dbi()
expect_equal(escape(raw(0), con = con), sql("X''"))
expect_equal(escape(as.raw(c(0x01, 0x02, 0x03)), con = con), sql("X'010203'"))
})

# names_to_as() -----------------------------------------------------------

test_that("names_to_as() doesn't alias when ident name and value are identical", {
Expand Down