OdbcResult: Ability to explicitely describe parameters#863
OdbcResult: Ability to explicitely describe parameters#863detule wants to merge 3 commits intor-dbi:mainfrom
Conversation
simonpcouch
left a comment
There was a problem hiding this comment.
Seems very much useful for power users! I don't have much to offer in terms of critique on the src/ side, but left some notes on the R interface.
| #' @examples | ||
| #' \dontrun{ | ||
| #' library(odbc) | ||
| #' # Writing UNICODE into a VARCHAR | ||
| #' # column with SQL server | ||
| #' DBI::dbRemoveTable(conn, "#tmp") | ||
| #' dbExecute(conn, "CREATE TABLE #tmp (col1 VARCHAR(50) COLLATE Latin1_General_100_CI_AI_SC_UTF8);") | ||
| #' res <- dbSendQuery(conn, "INSERT INTO #tmp SELECT ? COLLATE Latin1_General_100_CI_AI_SC_UTF8") | ||
| #' description <- data.frame(param_index = 1, data_type = ODBC_TYPE("WVARCHAR"), | ||
| #' column_size = 5, decimal_digits = NA_integer_) | ||
| #' dbBind(res, params = list("\u2915"), param_description = description) | ||
| #' dbClearResult(res) | ||
| #' DBI::dbReadTable(conn, "#tmp") | ||
| #' } |
There was a problem hiding this comment.
| #' @examples | |
| #' \dontrun{ | |
| #' library(odbc) | |
| #' # Writing UNICODE into a VARCHAR | |
| #' # column with SQL server | |
| #' DBI::dbRemoveTable(conn, "#tmp") | |
| #' dbExecute(conn, "CREATE TABLE #tmp (col1 VARCHAR(50) COLLATE Latin1_General_100_CI_AI_SC_UTF8);") | |
| #' res <- dbSendQuery(conn, "INSERT INTO #tmp SELECT ? COLLATE Latin1_General_100_CI_AI_SC_UTF8") | |
| #' description <- data.frame(param_index = 1, data_type = ODBC_TYPE("WVARCHAR"), | |
| #' column_size = 5, decimal_digits = NA_integer_) | |
| #' dbBind(res, params = list("\u2915"), param_description = description) | |
| #' dbClearResult(res) | |
| #' DBI::dbReadTable(conn, "#tmp") | |
| #' } | |
| #' @examplesIf FALSE | |
| #' # Writing UNICODE into a VARCHAR column with SQL server | |
| #' dbRemoveTable(conn, "#tmp") | |
| #' | |
| #' dbExecute( | |
| #' conn, | |
| #' "CREATE TABLE #tmp (col1 VARCHAR(50) COLLATE Latin1_General_100_CI_AI_SC_UTF8);" | |
| #' ) | |
| #' | |
| #' res <- dbSendQuery( | |
| #' conn, | |
| #' "INSERT INTO #tmp SELECT ? COLLATE Latin1_General_100_CI_AI_SC_UTF8" | |
| #' ) | |
| #' | |
| #' description <- data.frame( | |
| #' param_index = 1, | |
| #' data_type = ODBC_TYPE("WVARCHAR"), | |
| #' column_size = 5, | |
| #' decimal_digits = NA_integer_ | |
| #' ) | |
| #' | |
| #' dbBind(res, params = list("\u2915"), param_description = description) | |
| #' dbClearResult(res) | |
| #' | |
| #' dbReadTable(conn, "#tmp") |
Some style edits I'd make:
exampleswith\dontrun{}->examplesIf FALSEso that users don't see## Not runtags and CRAN definitely won't try to run the examples- I believe the
DBI::namespacing here may not be needed? - Line-breaking more liberally via codegrip
| #' @param param_description A data.frame with per-parameter attribute | ||
| #' overrides. Argument is optional; if used it must have columns: | ||
| #' * param_index Index of parameter in query ( beginning with 1 ). | ||
| #' * data_type Integer corresponding to the parameter SQL Data Type. | ||
| #' See \code{\link{ODBC_TYPE}}. | ||
| #' * column_size Size of parameter. | ||
| #' * decimal_digits Either precision or the scale of the parameter | ||
| #' depending on type. |
There was a problem hiding this comment.
| #' @param param_description A data.frame with per-parameter attribute | |
| #' overrides. Argument is optional; if used it must have columns: | |
| #' * param_index Index of parameter in query ( beginning with 1 ). | |
| #' * data_type Integer corresponding to the parameter SQL Data Type. | |
| #' See \code{\link{ODBC_TYPE}}. | |
| #' * column_size Size of parameter. | |
| #' * decimal_digits Either precision or the scale of the parameter | |
| #' depending on type. | |
| #' @param param_description A data frame containing per-parameter attribute overrides. | |
| #' Optional argument that, if provided, must contain the following columns: | |
| #' \describe{ | |
| #' \item{param_index}{Index of parameter in query (beginning with 1).} | |
| #' \item{data_type}{Integer corresponding to the parameter SQL Data Type. | |
| #' See [ODBC_TYPE()]. | |
| #' \item{column_size}{Size of parameter.} | |
| #' \item{decimal_digits}{Either precision or the scale of the parameter | |
| #' depending on type.} | |
| #' } |
- Transitions to
\describefor data frame columns - Links to
ODBC_TYPE()with roxygen shorthand - Refers to "data frame" rather than
data.framesince tibbles and other data.frame subclasses are fine
| setMethod("dbBind", "OdbcResult", | ||
| function(res, params, ..., batch_rows = getOption("odbc.batch_rows", NA)) { | ||
| function(res, params, ..., | ||
| param_description = data.frame(), |
There was a problem hiding this comment.
| param_description = data.frame(), | |
| param_description = NULL, |
NULL indicates a bit more clearly to me that "this is the default that won't actually be used if you don't supply anything"
| return(invisible(res)) | ||
| } | ||
|
|
||
| if (nrow(param_description)) { |
There was a problem hiding this comment.
| if (nrow(param_description)) { | |
| if (!is.null(param_description)) { |
Aligning with above.
| if (!all(c("param_index", "data_type", "column_size", "decimal_digits") | ||
| %in% colnames(param_description))) { | ||
| cli::cli_abort( | ||
| "param_description data.frame does not have necessary columns." | ||
| ) | ||
| } |
There was a problem hiding this comment.
| if (!all(c("param_index", "data_type", "column_size", "decimal_digits") | |
| %in% colnames(param_description))) { | |
| cli::cli_abort( | |
| "param_description data.frame does not have necessary columns." | |
| ) | |
| } | |
| check_data_frame(param_description) | |
| needed_columns <- c("param_index", "data_type", "column_size", "decimal_digits") | |
| if (!all(needed_columns %in% colnames(param_description))) { | |
| cli::cli_abort( | |
| "{.arg param_description} must have columns {.field {needed_columns}}, but | |
| doesn't have column{?s} | |
| {.field {.or {needed_columns[needed_columns %in% colnames(param_description)]}}}." | |
| ) | |
| } |
check_data_frame() will first ensure that the thing is a data frame and provide an informative message if not before we do the finer-grain check for needed columns.
| #' ODBC_TYPE("LONGVARCHAR") | ||
| #' } | ||
| #' @export | ||
| ODBC_TYPE <- function(type) { |
There was a problem hiding this comment.
I think, just to situate more comfily in this package's namespace, this may be better formatted as:
| ODBC_TYPE <- function(type) { | |
| odbcType <- function(type) { |
Hi @simonpcouch @hadley
This is a draft/RFC of a feature that would allow (power) users to explicitly describe parameter attributes when submitting parametrized queries.
The details are in #518 and this PR would address the issue there. In summary:
Some notes about the PR:
TODO:
dbSendQuery,dbGetQuery