ODBC Driver API support¶
The Snowflake ODBC driver supports version 3.52 of the ODBC API. This topic lists the ODBC routines relevant to Snowflake and indicates whether they are supported. The routines are organized into categories based on the function they perform.
For the complete API reference, see the Microsoft ODBC Programmer’s Reference.
Connecting to a data source¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
Obtaining information about a driver and data source¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Setting and retrieving driver attributes¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
Setting SQL_ATTR_METADATA_ID only affects the SQLTables and SQLColumns functions (and not the other supported catalog functions). |
|
✔ |
Read-only mode is not supported. SQL_MODE_READ_ONLY is passed to the driver, but Snowflake still writes to the database. . . Also, some attributes were introduced post API version 3.52: SQL_ATTR_ASYNC_DBC_EVENT, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, SQL_ATTR_ASYNC_DBC_PCALLBACK, SQL_ATTR_ASYNC_DBC_PCONTEXT, SQL_ATTR_DBC_INFO_TOKEN. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
|
|
✔ |
The SQL_ATTR_CONNECTION_POOLING attribute was introduced after ODBC API version 3.52 and is not supported. |
|
✔ |
SQL_ATTR_CURSOR_SCROLLABLE only supports a SQL_NONSCROLLABLE value. . SQL_ATTR_USE_BOOKMARKS only supports a SQL_UB_OFF value. . . For compatibility with third-party tools, SQL_ATTR_ENABLE_AUTO_IPD defaults to true, even though the ODBC standard says it should default to false. To change the default value to false, set the EnableAutoIpdByDefault parameter to |
|
✔ |
In addition to the standard attributes, the Snowflake implementation supports the attribute SQL_SF_STMT_ATTR_LAST_QUERY_ID, which allows the user to retrieve the most recent query ID associated with the specified statement handle. A partial example is in the Examples section below. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by |
Each of the preceding functions has a corresponding function that accepts wide characters (unicode). Each such
unicode function has the name shown above, followed by “W”. For example, the function SQLGetStmtAttr
, which
accepts a char array as the third parameter, has a corresponding function named SQLGetStmtAttrW
, which accepts a
wchar array as the third parameter.
Snowflake-specific behavior¶
SQLSetConnectAttr
This method supports two Snowflake-specific attributes:
Attribute Name
Description
SQL_SF_CONN_ATTR_APPLICATION
This overrides the value specified by the APPLICATION setting in the registry or .ini file.
SQL_SF_CONN_ATTR_PRIV_KEY
This is an EVP_PKEY* pointer that points to an in-memory copy of the private key. This overrides the PRIV_KEY_FILE and PRIV_KEY_PWD settings in the registry or .ini file. Snowflake recommends using this attribute to set the private key.
Setting and retrieving descriptor fields¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Preparing SQL requests¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Supported by the Snowflake driver, but deprecated ODBC API. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 2.x. Replaced by |
Note
There is an upper limit to the size of data that you can bind. For details, see Limits on Query Text Size.
SQL Statements Supported for Preparation lists the types of SQL statements that are supported for preparation.
Submitting requests¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Regardless of the data type bound to the parameter, Snowflake performs a server-side conversion and returns a VARCHAR with a max length of 16777216. |
|
✔ |
|
|
✔ |
Support for this function was added in version 2.23.3 of the ODBC Driver. |
|
✔ |
Support for this function was added in version 2.23.3 of the ODBC Driver. |
Retrieving results and information about results¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
The ODBC driver does not currently support semi-structured data, including |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by |
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
For GEOGRAPHY columns, |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 2.x. Replaced by |
|
✔ |
|
|
✔ |
The |
|
Replaced by |
|
|
Snowflake does not support the functionality. |
|
|
Snowflake does not support the functionality. |
Obtaining information about the data source’s system tables (catalog functions)¶
Function Name |
Supported |
Notes |
---|---|---|
|
Returns an empty results set. |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
In the result set, the |
|
Returns an empty results set. |
|
|
Returns an empty results set. |
|
|
Returns an empty results set. |
|
|
✔ |
If the parameter passed to the function is “TABLE”, the function returns all types of tables, including transient tables and temporary tables. . . If the parameter passed to the function is “VIEW”, the function returns all types of views, including materialized views. . . If the parameter passed to the function is “TABLE, VIEW” or “%”, the function returns information about all types of tables and all types of views. |
If the name passed to the catalog function has an invalid character, or if the name does not match any database object, the function returns an empty result set.
Setting SQL_ATTR_METADATA_ID
only affects the SQLTables
, SQLColumns
, and SQLProcedures
functions.
Terminating a statement¶
Function Name |
Supported |
Notes |
---|---|---|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by |
Terminating a connection¶
Function Name |
Supported |
Notes |
---|---|---|
|
Introduced into the API after version 3.52. |
|
|
✔ |
|
|
✔ |
|
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
|
✔ |
Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. |
Custom SQL data types¶
Some SQL data types supported by Snowflake have no direct mapping in ODBC (e.g. TIMESTAMP_*tz, VARIANT). To enable the ODBC driver to work with the unsupported data types, the header file shipped with the driver includes definitions for the following custom data types:
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Custom SQL Data Type Definition
///
///
////////////////////////////////////////////////////////////////////////////////////////////////////
#define SQL_SF_TIMESTAMP_LTZ 2000
#define SQL_SF_TIMESTAMP_TZ 2001
#define SQL_SF_TIMESTAMP_NTZ 2002
#define SQL_SF_ARRAY 2003
#define SQL_SF_OBJECT 2004
#define SQL_SF_VARIANT 2005
The following code demonstrates sample usage of the custom data types:
// bind insert as timestamp_ntz
SQLRETURN rc;
rc = SQLPrepare(odbc.StmtHandle,
(SQLCHAR *) "insert into testtimestampntz values (?)",
SQL_NTS);
SQL_TIMESTAMP_STRUCT bindData;
SQLLEN datalen = sizeof(SQL_TIMESTAMP_STRUCT);
bindData.year = 2017;
bindData.month = 11;
bindData.day = 30;
bindData.hour = 18;
bindData.minute = 17;
bindData.second = 5;
bindData.fraction = 123456789;
rc = SQLBindParameter(
odbc.StmtHandle, 1, SQL_PARAM_INPUT,
SQL_C_TIMESTAMP, SQL_SF_TIMESTAMP_NTZ,
100, 0, &bindData, sizeof(bindData), &datalen);
rc = SQLExecute(odbc.StmtHandle);
// query table
rc = SQLExecDirect(odbc.StmtHandle, (SQLCHAR *)"select * from testtimestampntz", SQL_NTS);
rc = SQLFetch(odbc.StmtHandle);
// fetch data as timestamp
SQL_TIMESTAMP_STRUCT ret;
SQLLEN retLen = (SQLLEN) 0;
rc = SQLGetData(odbc.StmtHandle, 1, SQL_C_TIMESTAMP, &ret, (SQLLEN)sizeof(ret), &retLen);
Examples¶
This section provides examples of using the API.
Retrieving the last query ID¶
Retrieving the last query ID is a Snowflake extension to the ODBC standard.
To retrieve the last query ID, call the function SQLGetStmtAttr
(or SQLGetStmtAttrW
), passing the attribute
SQL_SF_STMT_ATTR_LAST_QUERY_ID and a character array large enough to hold the query ID.
The example below shows how to retrieve the query ID for a query:
// Space to store the query ID.
// The SQLGetStmtAttr() function fills this in with the actual ID.
char queryId[37]; // Maximum 36 chars plus string terminator.
// The length (in characters) of the query ID. The SQLGetStmtAttr() function fills this in
// with the actual length of the query ID (usually 36).
SQLINTEGER idLen;
// Execute a query.
rc = SQLExecDirect(odbc.StmtHandle, (SQLCHAR *) "select 1", SQL_NTS);
// Retrieve the query ID (queryId) and the length of that query ID (idLen).
SQLGetStmtAttr(odbc.StmtHandle, SQL_SF_STMT_ATTR_LAST_QUERY_ID, queryId, sizeof(queryId), &idLen);
If you are executing on Linux or macOS, call SQLGetStmtAttrW
and pass parameters
of the appropriate data type (for example, “wchar” rather than “char”).
Best practices to improve performance when retrieving data¶
When retrieving data with SQLFetch
, you can use the SQLGetData
or SQLBindCol
functions to access
the contents of the cells. In most cases, using SQLBindCol
provides better performance because it reduces the number
of ODBC calls you need to make to retrieve data and because it lets you take advantage of copying data in-memory.
Using SQLGetData
to retrieve cell data¶
The following example uses the SQLGetData
function to retrieve cell values from the data buffer returned
by SQLFetch
. Notice that you need to call SQLGetData
once for each cell in the row.
SQLRETURN rc;
SQLSMALLINT numCols;
const size_t s_MaxDataLen = 300;
// fetch with SQLGetData()
// query table
rc = SQLExecDirect(stmt, (SQLCHAR *)"select * from table", SQL_NTS);
// Find out the number of result set columns.
rc = SQLNumResultCols(stmt, &numCols);
// buffer for one cell
vector<char> dataBuffer(s_MaxDataLen);
SQLLEN dataLen = (SQLLEN)0;
// call SQLFetch() per row and SQLGetData() per column per row
while (true)
{
rc = SQLFetch(stmt);
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))
{
break;
}
for (SQLUSMALLINT i = 0; i < numCols; i++)
{
rc = SQLGetData(stmt, i + 1, SQL_C_CHAR, dataBuffer.data(), (SQLLEN)s_MaxDataLen, &dataLen);
std::string data;
if (SQL_NULL_DATA == dataLen)
continue;
if (SQL_NO_TOTAL == dataLen)
dataLen = s_MaxDataLen;
data = std::string(dataBuffer.data(), dataLen);
}
}
rc = SQLCloseCursor(stmt);
Using SQLBindCol
to bind the columns for one row of data¶
The following example uses the SQLBindCol
function to retrieve cell values from the data buffer returned by
SQLFetch
. It creates an in-memory buffer for the number of columns in a row and then makes a single
SQLBindCol
call to bind the application buffers to the result set. Finally, it calls SQLFetch
once per row and
loads the cell values into the buffer. This approach can significantly increase the speed and efficiency of retrieving data.
SQLRETURN rc;
SQLSMALLINT numCols;
const size_t s_MaxDataLen = 300;
// fetch with SQLBindCol()
// query table
rc = SQLExecDirect(stmt, (SQLCHAR *)"select * from table", SQL_NTS);
// Find out the number of result set columns.
rc = SQLNumResultCols(stmt, &numCols);
// buffer for one row
vector<char> rowBuffer(s_MaxDataLen * numCols);
vector<SQLLEN> columnLenBuffer(numCols);
// call SQLBindCol() per column
for (SQLSMALLINT i = 0; i < numCols; ++i)
{
SQLBindCol(stmt, i + 1, SQL_C_CHAR, &rowBuffer[s_MaxDataLen * i],
s_MaxDataLen, &columnLenBuffer[i]);
}
// call SQLFetch() per row
while (true)
{
rc = SQLFetch(stmt);
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))
{
break;
}
// go through data for each cell in buffer without ODBC calls
for (SQLUSMALLINT i = 0; i < numCols; i++)
{
std::string data;
SQLLEN len = columnLenBuffer[i];
if (SQL_NULL_DATA == len)
continue;
if (SQL_NO_TOTAL == len)
len = s_MaxDataLen;
data = std::string(&rowBuffer[s_MaxDataLen * i], len);
}
}
rc = SQLCloseCursor(stmt);
Using SQLBindCol
to bind the columns for multiple rows of data¶
You can improve performance even more by fetching multiple rows in a single SQLFetch
call, which reduces
the number of ODBC SQLFetch
calls needed to process all the rows of a query table.
The following example:
Determines the number of columns in the result set.
Creates an in-memory array to store the data from multiple columns.
Calls
SQLBindCol
for each column to bind the application buffers to the result set.Calls
SQLFetch
to get the specified number of rows (100) and processes the data in the in-memory buffer without making ODBC calls, until the end of the query table is reached.
This approach can significantly increase the speed and efficiency of retrieving data. For a query table with 20 columns and 1000 rows, this example would make only 20 SQLBindCol
and 10 SQLFetch
calls instead of 20000 SQLGetData
calls to load all of the table data.
SQLRETURN rc;
SQLSMALLINT numCols;
const size_t s_MaxDataLen = 300;
// fetch with SQLBindCol() and SQL_ATTR_ROW_ARRAY_SIZE > 1
const size_t s_numRowsPerSQLFetch = 100;
SQLULEN numRowsFetched = 0;
rc = SQLSetStmtAttr(stmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)s_numRowsPerSQLFetch, 0);
rc = SQLSetStmtAttr(stmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER)&numRowsFetched, sizeof(SQLULEN));
// query table
rc = SQLExecDirect(stmt, (SQLCHAR *)"select * from table", SQL_NTS);
// Find out the number of result set columns.
rc = SQLNumResultCols(stmt, &numCols);
// buffer for all columns; each column has buffer size of s_numRowsPerSQLFetch
// To retrieve multiple rows per SQLFetch() call, use the default behavior of SQL_BIND_BY_COLUMN
vector<vector<char> > colArray(numCols);
vector<vector<SQLLEN> > colLenArray(numCols);
// call SQLBindCol() per column
for (SQLSMALLINT i = 0; i < numCols; ++i)
{
// initialize buffer for each column
colArray[i].resize(s_MaxDataLen * s_numRowsPerSQLFetch);
colLenArray[i].resize(s_numRowsPerSQLFetch);
SQLBindCol(stmt, i + 1, SQL_C_CHAR, colArray[i].data(),
s_MaxDataLen, colLenArray[i].data());
}
// call SQLFetch() per s_numRowsPerSQLFetch rows
while (true)
{
rc = SQLFetch(stmt);
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))
{
break;
}
// go through data for each cell in buffer without ODBC calls
for (SQLULEN rowIndex = 0; rowIndex < numRowsFetched; rowIndex++)
{
for (SQLUSMALLINT colIndex = 0; colIndex < colIndex; colIndex++)
{
std::string data;
SQLLEN len = colLenArray[colIndex][rowIndex];
if (SQL_NULL_DATA == len)
continue;
if (SQL_NO_TOTAL == len)
len = s_MaxDataLen;
data = std::string(&(colArray[colIndex][s_MaxDataLen * rowIndex]), len);
}
}
}
rc = SQLCloseCursor(stmt);