diff --git a/NAMESPACE b/NAMESPACE index 8bcfdaede..8e990fd03 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(odbcListColumns,OdbcConnection) S3method(odbcListObjectTypes,default) S3method(odbcListObjects,OdbcConnection) S3method(odbcPreviewObject,OdbcConnection) +export(ODBC_TYPE) export(databricks) export(isTempTable) export(odbc) diff --git a/R/RcppExports.R b/R/RcppExports.R index bad8ec995..bf328e6cf 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -77,6 +77,24 @@ bigint_mappings <- function() { .Call(`_odbc_bigint_mappings`) } +#' Access the definition of the named SQL Data Type. +#' +#' @description Helper method returning the integer of the +#' [SQL Data Type](https://learn.microsoft.com/en-us/sql/odbc/reference/appendixes/sql-data-types?view=sql-server-ver16) +#' named in the argument. +#' +#' @seealso +#' @rdname odbcDataType. +#' @examples +#' \dontrun{ +#' library(odbc) +#' ODBC_TYPE("LONGVARCHAR") +#' } +#' @export +ODBC_TYPE <- function(type) { + .Call(`_odbc_ODBC_TYPE`, type) +} + result_release <- function(r) { invisible(.Call(`_odbc_result_release`, r)) } diff --git a/R/dbi-result.R b/R/dbi-result.R index b782651bc..cc45c6192 100644 --- a/R/dbi-result.R +++ b/R/dbi-result.R @@ -126,17 +126,54 @@ setMethod("dbGetRowsAffected", "OdbcResult", } ) +#' @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. +#' +#' For more information see the [ODBC SQLBindParam documentation](https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindparameter-function?view=sql-server-ver16) +#' +#' @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") +#' } #' @rdname OdbcResult #' @inheritParams DBI::dbBind #' @inheritParams DBI-tables #' @export setMethod("dbBind", "OdbcResult", - function(res, params, ..., batch_rows = getOption("odbc.batch_rows", NA)) { + function(res, params, ..., + param_description = data.frame(), + batch_rows = getOption("odbc.batch_rows", NA)) { params <- as.list(params) if (length(params) == 0) { return(invisible(res)) } + if (nrow(param_description)) { + 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." + ) + } + result_describe_parameters(res@ptr, param_description) + } + if (is.na(batch_rows)) { batch_rows <- length(params[[1]]) } diff --git a/man/OdbcResult.Rd b/man/OdbcResult.Rd index 150005a99..537070659 100644 --- a/man/OdbcResult.Rd +++ b/man/OdbcResult.Rd @@ -31,7 +31,13 @@ \S4method{dbGetRowsAffected}{OdbcResult}(res, ...) -\S4method{dbBind}{OdbcResult}(res, params, ..., batch_rows = getOption("odbc.batch_rows", NA)) +\S4method{dbBind}{OdbcResult}( + res, + params, + ..., + param_description = data.frame(), + batch_rows = getOption("odbc.batch_rows", NA) +) } \arguments{ \item{res}{An object inheriting from \linkS4class{DBIResult}.} @@ -52,6 +58,19 @@ or a data frame, with one element/column per query parameter. For \code{dbBindArrow()}, values as a nanoarrow stream, with one column per query parameter.} +\item{param_description}{A data.frame with per-parameter attribute +overrides. Argument is optional; if used it must have columns: +\itemize{ +\item param_index Index of parameter in query ( beginning with 1 ). +\item data_type Integer corresponding to the parameter SQL Data Type. +See \code{odbc::ODBC_TYPE}. +\item column_size Size of parameter. +\item decimal_digits Either precision or the scale of the parameter +depending on type. +} + +For more information see the \href{https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindparameter-function?view=sql-server-ver16}{ODBC SQLBindParam documentation}} + \item{batch_rows}{The number of rows to retrieve. Defaults to \code{NA}, which is set dynamically to the minimum of 1024 and the size of the input. Depending on the database, driver, dataset and free memory, setting this @@ -61,4 +80,19 @@ to a lower value may improve performance.} Implementations of pure virtual functions defined in the \code{DBI} package for OdbcResult objects. } +\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") +} +} \keyword{internal} diff --git a/src/Makevars.in b/src/Makevars.in index 8482ef1bc..af80332e1 100644 --- a/src/Makevars.in +++ b/src/Makevars.in @@ -4,7 +4,7 @@ PKG_CPPFLAGS=@PKG_CFLAGS@ PKG_CXXFLAGS=-Icctz/include -Inanodbc -I. -DBUILD_REAL_64_BIT_MODE -DNANODBC_ODBC_VERSION=SQL_OV_ODBC3 $(CXXPICFLAGS) PKG_LIBS=@PKG_LIBS@ -Lcctz -lcctz -OBJECTS = odbc_result.o connection.o nanodbc.o result.o odbc_connection.o RcppExports.o Iconv.o utils.o +OBJECTS = odbc_types.o odbc_result.o connection.o nanodbc.o result.o odbc_connection.o RcppExports.o Iconv.o utils.o all: $(SHLIB) diff --git a/src/Makevars.win b/src/Makevars.win index 00ddce5c4..9456640ca 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -1,7 +1,7 @@ PKG_CXXFLAGS=-I. -Icctz/include -Inanodbc PKG_LIBS=-lodbc32 -Lcctz -lcctz -OBJECTS = odbc_result.o connection.o nanodbc.o result.o odbc_connection.o RcppExports.o Iconv.o utils.o +OBJECTS = odbc_types.o odbc_result.o connection.o nanodbc.o result.o odbc_connection.o RcppExports.o Iconv.o utils.o all: $(SHLIB) diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 623cacb87..8f41ea717 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -228,6 +228,17 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// ODBC_TYPE +int ODBC_TYPE(const std::string& type); +RcppExport SEXP _odbc_ODBC_TYPE(SEXP typeSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const std::string& >::type type(typeSEXP); + rcpp_result_gen = Rcpp::wrap(ODBC_TYPE(type)); + return rcpp_result_gen; +END_RCPP +} // result_release void result_release(result_ptr r); RcppExport SEXP _odbc_result_release(SEXP rSEXP) { @@ -384,6 +395,7 @@ static const R_CallMethodDef CallEntries[] = { {"_odbc_transactionLevels", (DL_FUNC) &_odbc_transactionLevels, 0}, {"_odbc_set_transaction_isolation", (DL_FUNC) &_odbc_set_transaction_isolation, 2}, {"_odbc_bigint_mappings", (DL_FUNC) &_odbc_bigint_mappings, 0}, + {"_odbc_ODBC_TYPE", (DL_FUNC) &_odbc_ODBC_TYPE, 1}, {"_odbc_result_release", (DL_FUNC) &_odbc_result_release, 1}, {"_odbc_result_active", (DL_FUNC) &_odbc_result_active, 1}, {"_odbc_result_completed", (DL_FUNC) &_odbc_result_completed, 1}, diff --git a/src/odbc_types.cpp b/src/odbc_types.cpp new file mode 100644 index 000000000..7902af6e4 --- /dev/null +++ b/src/odbc_types.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include "utils.h" +#define INSTANTIATE_SQL_DATA_TYPE(TYPE) _the_map[#TYPE] = SQL_##TYPE; + +using namespace odbc; +const std::unordered_map< std::string, int >& generateOdbcTypes() { + static std::unordered_map< std::string, int > _the_map; + INSTANTIATE_SQL_DATA_TYPE(CHAR); + INSTANTIATE_SQL_DATA_TYPE(VARCHAR) + INSTANTIATE_SQL_DATA_TYPE(LONGVARCHAR); + + INSTANTIATE_SQL_DATA_TYPE(WCHAR); + INSTANTIATE_SQL_DATA_TYPE(WVARCHAR); + INSTANTIATE_SQL_DATA_TYPE(WLONGVARCHAR); + + INSTANTIATE_SQL_DATA_TYPE(DECIMAL); + INSTANTIATE_SQL_DATA_TYPE(NUMERIC); + INSTANTIATE_SQL_DATA_TYPE(SMALLINT); + INSTANTIATE_SQL_DATA_TYPE(INTEGER); + INSTANTIATE_SQL_DATA_TYPE(REAL); + INSTANTIATE_SQL_DATA_TYPE(FLOAT); + INSTANTIATE_SQL_DATA_TYPE(DOUBLE); + + INSTANTIATE_SQL_DATA_TYPE(BIT); + INSTANTIATE_SQL_DATA_TYPE(TINYINT); + INSTANTIATE_SQL_DATA_TYPE(BIGINT); + + INSTANTIATE_SQL_DATA_TYPE(BINARY); + INSTANTIATE_SQL_DATA_TYPE(VARBINARY); + INSTANTIATE_SQL_DATA_TYPE(LONGVARBINARY); + + INSTANTIATE_SQL_DATA_TYPE(TYPE_DATE); + INSTANTIATE_SQL_DATA_TYPE(TYPE_TIME); + INSTANTIATE_SQL_DATA_TYPE(TYPE_TIMESTAMP); + return _the_map; +} + +//' Access the definition of the named SQL Data Type. +//' +//' @description Helper method returning the integer of the +//' [SQL Data Type](https://learn.microsoft.com/en-us/sql/odbc/reference/appendixes/sql-data-types?view=sql-server-ver16) +//' named in the argument. +//' +//' @seealso +//' @rdname odbcDataType. +//' @examples +//' \dontrun{ +//' library(odbc) +//' ODBC_TYPE("LONGVARCHAR") +//' } +//' @export +// [[Rcpp::export]] +int ODBC_TYPE(const std::string& type) { + static const std::unordered_map< std::string, int >& map = + generateOdbcTypes(); + auto itr = map.find(type); + if (itr != map.end()) + { + return (int)itr->second; + } + utils::raise_error("Could not map input string to ODBC type"); + return 0; +}