Skip to content

Commit 032d860

Browse files
detulesimonpcouch
andauthored
Ability to bind data.frames to table valued parameters (#928)
* nanodbc: tvp: complete parameter_* API Compare to upstream: nanodbc/nanodbc@1d03657 * Allow users to bind data.frames to TVPs in parametrized queries * code-review: cli API usage * Update NEWS * Apply suggestions from code review Co-authored-by: Simon P. Couch <[email protected]> * code-review: more robust to first parameter a df * nanodbc: fixup some casts/silence warnings * Add some documentation on binding data frames * fixup docs --------- Co-authored-by: Simon P. Couch <[email protected]>
1 parent f63575c commit 032d860

File tree

10 files changed

+478
-152
lines changed

10 files changed

+478
-152
lines changed

NEWS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# odbc (development version)
22

3+
* SQL Server: You can now bind `data.frames` to table
4+
valued parameters of stored procedures (#928).
5+
6+
* Snowflake: Add `sf_private_key` and `sf_private_key_password`
7+
connection attributes to allow users to pass the contents
8+
of the PEM formatted private key to the driver directly from
9+
memory (#933).
10+
311
* Fix retrieving multiple result sets from parametrized queries
412
in cases when some parameters yield empty results (#927).
513

R/dbi-result.R

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,23 @@ setMethod("dbBind", "OdbcResult",
139139
return(invisible(res))
140140
}
141141

142-
if (is.na(batch_rows)) {
142+
paramDfs <- sapply(params, is.data.frame)
143+
if (any(paramDfs)) {
144+
# When binding table-valued-parameters verify that:
145+
# * The `batch_rows` parameter is either unset or equal to one.
146+
# * All non-tvp parameters are of length 1.
147+
if (!is.na(batch_rows) && batch_rows > 1) {
148+
cli::cli_warn(c("Since some parameters are data frames, {.arg batch_rows} will be overwritten to `1`.",
149+
"Set {.code batch_rows = 1} to quiet this warning."))
150+
}
151+
batch_rows <- 1
152+
paramLengths <- sapply(params[!paramDfs], length)
153+
if (any(paramLengths > 1))
154+
{
155+
cli::cli_abort("When mixing data.frame(s) with other parameter types,
156+
all non-df parameters must be of length one")
157+
}
158+
} else if (is.na(batch_rows)) {
143159
batch_rows <- length(params[[1]])
144160
}
145161

R/driver-sql-server.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
#'
66
#' Details of SQL Server methods for odbc and DBI generics.
77
#'
8+
#' Note on binding \href{https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine}{TVPs}:
9+
#' You can bind `data.frame`s to SQL Server TVPs when executing stored procedures with the following caveats:
10+
#' * All non-df parameters must be of length 1; and
11+
#' * The `batch_rows` parameter (to `dbBind`, for example) should be set to 1.
12+
#' @examples
13+
#' \dontrun{
14+
#' # Bind `data.frame` to a TVP when executing a
15+
#' # stored procedure that takes three parameters:
16+
#' # a TVP, an INT and a VARCHAR(max).
17+
#' res <- dbGetQuery(conn, "{ CALL example_sproc(?, ?, ?) }",
18+
#' params = list(df.data, 100, "Lorem ipsum dolor sit amet"))
19+
#' }
820
#' @rdname SQLServer
921
#' @export
1022
setClass("Microsoft SQL Server", contains = "OdbcConnection")

man/SQLServer.Rd

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nanodbc/nanodbc.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,7 +1973,7 @@ class statement::statement_impl
19731973

19741974
describe_parameters(param_index);
19751975
const SQLSMALLINT& param_scale = param_descr_data_.at(param_index).scale_;
1976-
NANODBC_ASSERT(param_scale < static_cast<SQLULEN>(std::numeric_limits<short>::max()));
1976+
NANODBC_ASSERT(param_scale < static_cast<SQLSMALLINT>(std::numeric_limits<short>::max()));
19771977
return static_cast<short>(param_scale);
19781978
}
19791979

@@ -1986,7 +1986,7 @@ class statement::statement_impl
19861986

19871987
describe_parameters(param_index);
19881988
const SQLSMALLINT& param_type = param_descr_data_.at(param_index).type_;
1989-
NANODBC_ASSERT(param_type < static_cast<SQLULEN>(std::numeric_limits<short>::max()));
1989+
NANODBC_ASSERT(param_type < static_cast<SQLSMALLINT>(std::numeric_limits<short>::max()));
19901990
return static_cast<short>(param_type);
19911991
}
19921992

@@ -2902,6 +2902,48 @@ class table_valued_parameter::table_valued_parameter_impl
29022902
}
29032903
}
29042904

2905+
short parameters() const { return param_descr_data_.size(); }
2906+
2907+
unsigned long parameter_size(short param_index)
2908+
{
2909+
if (param_descr_data_.count(param_index))
2910+
{
2911+
return static_cast<unsigned long>(param_descr_data_.at(param_index).size_);
2912+
}
2913+
2914+
prepare_tvp_param_all();
2915+
const SQLULEN& param_size = param_descr_data_.at(param_index).size_;
2916+
NANODBC_ASSERT(
2917+
param_size < static_cast<SQLULEN>(std::numeric_limits<unsigned long>::max()));
2918+
return static_cast<unsigned long>(param_size);
2919+
}
2920+
2921+
short parameter_scale(short param_index)
2922+
{
2923+
if (param_descr_data_.count(param_index))
2924+
{
2925+
return static_cast<short>(param_descr_data_.at(param_index).scale_);
2926+
}
2927+
2928+
prepare_tvp_param_all();
2929+
const SQLSMALLINT& param_scale = param_descr_data_.at(param_index).scale_;
2930+
NANODBC_ASSERT(param_scale < static_cast<SQLSMALLINT>(std::numeric_limits<short>::max()));
2931+
return static_cast<short>(param_scale);
2932+
}
2933+
2934+
short parameter_type(short param_index)
2935+
{
2936+
if (param_descr_data_.count(param_index))
2937+
{
2938+
return static_cast<short>(param_descr_data_.at(param_index).type_);
2939+
}
2940+
2941+
prepare_tvp_param_all();
2942+
const SQLSMALLINT& param_type = param_descr_data_.at(param_index).type_;
2943+
NANODBC_ASSERT(param_type < static_cast<SQLSMALLINT>(std::numeric_limits<short>::max()));
2944+
return static_cast<short>(param_type);
2945+
}
2946+
29052947
// comparator for null sentry values
29062948
template <class T>
29072949
bool equals(T const& lhs, T const& rhs)
@@ -5272,8 +5314,8 @@ NANODBC_INSTANTIATE_TVP_BIND_STRINGS(wide_string_type);
52725314
NANODBC_INSTANTIATE_TVP_BIND_VECTOR_STRINGS(string_type);
52735315

52745316
#ifdef NANODBC_HAS_STD_STRING_VIEW
5275-
/NANODBC_INSTANTIATE_TVP_BIND_VECTOR_STRINGS(std::string_view);
5276-
/NANODBC_INSTANTIATE_TVP_BIND_VECTOR_STRINGS(wide_string_view);
5317+
NANODBC_INSTANTIATE_TVP_BIND_VECTOR_STRINGS(std::string_view);
5318+
NANODBC_INSTANTIATE_TVP_BIND_VECTOR_STRINGS(wide_string_view);
52775319
#endif
52785320

52795321
#undef NANODBC_INSTANTIATE_TVP_BINDS
@@ -5397,6 +5439,26 @@ void table_valued_parameter::describe_parameters(
53975439
{
53985440
impl_->describe_parameters(idx, type, size, scale);
53995441
}
5442+
5443+
short table_valued_parameter::parameters() const
5444+
{
5445+
return impl_->parameters();
5446+
}
5447+
5448+
unsigned long table_valued_parameter::parameter_size(short param_index) const
5449+
{
5450+
return impl_->parameter_size(param_index);
5451+
}
5452+
5453+
short table_valued_parameter::parameter_scale(short param_index) const
5454+
{
5455+
return impl_->parameter_scale(param_index);
5456+
}
5457+
5458+
short table_valued_parameter::parameter_type(short param_index) const
5459+
{
5460+
return impl_->parameter_type(param_index);
5461+
}
54005462
} // namespace nanodbc
54015463
#endif // NANODBC_DISABLE_MSSQL_TVP
54025464

src/nanodbc/nanodbc.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,19 @@ class table_valued_parameter
648648
const std::vector<unsigned long>& size,
649649
const std::vector<short>& scale);
650650

651+
/// \brief Returns the number of columns in the table valued parameter.
652+
/// \throws database_error
653+
short parameters() const;
654+
655+
/// \brief Returns parameter size for indicated column in the TVP.
656+
unsigned long parameter_size(short param_index) const;
657+
658+
/// \brief Returns parameter scale for indicated column in the TVP.
659+
short parameter_scale(short param_index) const;
660+
661+
/// \brief Returns parameter type for indicated column in the TVP.
662+
short parameter_type(short param_index) const;
663+
651664
private:
652665
class table_valued_parameter_impl;
653666
friend class statement;

0 commit comments

Comments
 (0)