Skip to content

Commit f63575c

Browse files
detuleatheriel
andauthored
snowflake: add private key connection attributes (#933)
* snowflake: add private key connection attributes * tests: update snaps * Apply suggestions from code review Co-authored-by: Aaron Jacobs <atheriel@users.noreply.github.com> --------- Co-authored-by: Aaron Jacobs <atheriel@users.noreply.github.com>
1 parent 8cf84bd commit f63575c

3 files changed

Lines changed: 55 additions & 8 deletions

File tree

R/odbc-connection.R

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ needs_quoting <- function(x) {
197197
#' * `azure_token`: This should be a string scalar; in particular Azure Active
198198
#' Directory authentication token. Only for use with Microsoft SQL Server and
199199
#' with limited support away from the OEM Microsoft driver.
200+
#' * `sf_private_key`: This parameter is specific to establishing a connection
201+
#' to `snowflake` and is understood by both OEM, as well as `Posit`
202+
#' pro drivers. Argument should be a string (scalar); in particular a
203+
#' PEM-encoded private key. Note,
204+
#' if using private key authentication, the `authenticator` connection
205+
#' string attribute must be set to `SNOWFLAKE_JWT`.
206+
#' Using this *connection* attribute is an alternative to using the
207+
#' `PRIV_KEY_FILE` connection string attribute.
208+
#' * `sf_private_key_password`: If key passed using `sf_private_key` is
209+
#' encrypted, you can use this attribute to communicate the password.
200210
#' @rdname ConnectionAttributes
201211
#' @keywords internal
202212
#' @aliases ConnectionAttributes
@@ -209,9 +219,15 @@ needs_quoting <- function(x) {
209219
#' dsn = "my_azure_mssql_db",
210220
#' Encrypt = "yes",
211221
#' attributes = list("azure_token" = .token)
212-
#' )
222+
#'
223+
#' conn <- dbConnect(
224+
#' odbc::odbc(),
225+
#' dsn = "snowflake",
226+
#' attributes = list("sf_private_key" = paste(readLines("<path-to-private-key-file>"), collapse="\n"),
227+
#' "sf_private_key_password" = "<optional-private-key-encryption-password>"),
228+
#' authenticator = "SNOWFLAKE_JWT")
213229
#' }
214-
SUPPORTED_CONNECTION_ATTRIBUTES <- c("azure_token")
230+
SUPPORTED_CONNECTION_ATTRIBUTES <- c("azure_token", "sf_private_key", "sf_private_key_password")
215231

216232
#' Odbc Connection Methods
217233
#'

src/utils.cpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
#if !defined(_WIN32) && !defined(_WIN64)
66
#include <signal.h>
77
#endif
8+
9+
#ifndef SQL_DRIVER_CONN_ATTR_BASE
10+
#define SQL_DRIVER_CONN_ATTR_BASE 0x00004000
11+
#endif
12+
#define SQL_SF_CONN_ATTR_BASE (SQL_DRIVER_CONN_ATTR_BASE + 0x53)
13+
#define SQL_SF_CONN_ATTR_PRIV_KEY (SQL_SF_CONN_ATTR_BASE + 1)
14+
#define SQL_SF_CONN_ATTR_PRIV_KEY_CONTENT (SQL_SF_CONN_ATTR_BASE + 3)
15+
#define SQL_SF_CONN_ATTR_PRIV_KEY_PASSWORD (SQL_SF_CONN_ATTR_BASE + 4)
816
namespace odbc {
917
namespace utils {
1018

@@ -34,19 +42,42 @@ namespace utils {
3442
attributes.push_back(nanodbc::connection::attribute(
3543
SQL_ATTR_LOGIN_TIMEOUT, SQL_IS_UINTEGER, (void*)(std::intptr_t)timeout));
3644
}
37-
std::shared_ptr< void > buffer;
3845
if ( r_attributes_.isNotNull() )
3946
{
4047
Rcpp::List r_attributes( r_attributes_ );
41-
if ( r_attributes.containsElementNamed( "azure_token" ) &&
42-
!Rf_isNull(r_attributes["azure_token"]) )
48+
if (r_attributes.containsElementNamed( "azure_token" ) &&
49+
!Rf_isNull(r_attributes["azure_token"]))
4350
{
4451
std::string azure_token =
4552
Rcpp::as<std::string>(r_attributes["azure_token"]);
4653
std::shared_ptr< void > buffer = serialize_azure_token( azure_token );
4754
attributes.push_back(nanodbc::connection::attribute(
4855
SQL_COPT_SS_ACCESS_TOKEN, SQL_IS_POINTER, buffer.get()));
49-
buffer_context.push_back( buffer );
56+
buffer_context.push_back(buffer);
57+
}
58+
if (r_attributes.containsElementNamed("sf_private_key") &&
59+
!Rf_isNull(r_attributes["sf_private_key"]))
60+
{
61+
std::shared_ptr<std::string> priv_key =
62+
std::make_shared<std::string>(Rcpp::as<std::string>(r_attributes["sf_private_key"]));
63+
std::shared_ptr< void > buffer(malloc(priv_key->size()), std::free);
64+
// Copy null terminator as well
65+
std::memcpy(buffer.get(), priv_key->c_str(), priv_key->size() + 1);
66+
attributes.push_back(nanodbc::connection::attribute(
67+
SQL_SF_CONN_ATTR_PRIV_KEY_CONTENT, SQL_NTS, buffer.get()));
68+
buffer_context.push_back(buffer);
69+
}
70+
if (r_attributes.containsElementNamed("sf_private_key_password") &&
71+
!Rf_isNull(r_attributes["sf_private_key_password"]))
72+
{
73+
std::shared_ptr<std::string> key_pass =
74+
std::make_shared<std::string>(Rcpp::as<std::string>(r_attributes["sf_private_key_password"]));
75+
std::shared_ptr< void > buffer(malloc(key_pass->size()), std::free);
76+
// Copy null terminator as well
77+
std::memcpy(buffer.get(), key_pass->c_str(), key_pass->size() + 1);
78+
attributes.push_back(nanodbc::connection::attribute(
79+
SQL_SF_CONN_ATTR_PRIV_KEY_PASSWORD, SQL_NTS, buffer.get()));
80+
buffer_context.push_back(buffer);
5081
}
5182
}
5283
}

tests/testthat/_snaps/utils.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
Condition
145145
Error in `dbConnect()`:
146146
! `attributes` does not support the connection attribute "boop".
147-
i Allowed connection attribute is "azure_token".
147+
i Allowed connection attributes are "azure_token", "sf_private_key", and "sf_private_key_password".
148148

149149
---
150150

@@ -153,7 +153,7 @@
153153
Condition
154154
Error in `dbConnect()`:
155155
! `attributes` does not support the connection attributes "boop" and "beep".
156-
i Allowed connection attribute is "azure_token".
156+
i Allowed connection attributes are "azure_token", "sf_private_key", and "sf_private_key_password".
157157

158158
# configure_simba() errors informatively on failure to install unixODBC
159159

0 commit comments

Comments
 (0)