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

SQLAllocHandle

SQLConnect

SQLDriverConnect

SQLAllocEnv

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLAllocConnect

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLBrowseConnect

Obtaining information about a driver and data source

Function Name

Supported

Notes

SQLDataSources

SQLDrivers

SQLGetInfo

SQLGetFunctions

SQLGetTypeInfo

Setting and retrieving driver attributes

Function Name

Supported

Notes

SQLSetConnectAttr

Setting SQL_ATTR_METADATA_ID only affects the SQLTables and SQLColumns functions (and not the other supported catalog functions).

SQLGetConnectAttr

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.

SQLSetConnectOption

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLGetConnectOption

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLSetEnvAttr

SQLGetEnvAttr

The SQL_ATTR_CONNECTION_POOLING attribute was introduced after ODBC API version 3.52 and is not supported.

SQLSetStmtAttr

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 false. . . Setting SQL_ATTR_METADATA_ID only affects the SQLTables and SQLColumns functions (and not the other supported catalog functions). . . Unsupported attributes: SQL_ATTR_SIMULATE_CURSOR, SQL_ATTR_FETCH_BOOKMARK_PTR, SQL_ATTR_KEYSET_SIZE.

SQLGetStmtAttr

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.

SQLSetStmtOption

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by SQLSetStmtAttr.

SQLGetStmtOption

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by SQLGetStmtAttr.

SQLParamOptions

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by SQLSetStmtAttr.

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

SQLGetDescField

SQLGetDescRec

SQLSetDescField

SQLSetDescRec

Preparing SQL requests

Function Name

Supported

Notes

SQLAllocStmt

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLBindParameter

SQLPrepare

SQLGetCursorName

SQLSetCursorName

SQLSetScrollOptions

Supported by the Snowflake driver, but deprecated ODBC API.

SQLSetParam

Supported by the Snowflake driver, but deprecated in ODBC API version 2.x. Replaced by SQLBindParameter.

Note

Submitting requests

Function Name

Supported

Notes

SQLExecute

SQLExecDirect

SQLNativeSql

SQLDescribeParam

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.

SQLNumParams

SQLParamData

Support for this function was added in version 2.23.3 of the ODBC Driver.

SQLPutData

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

SQLBindCol

The ODBC driver does not currently support semi-structured data, including VARIANT, OBJECT and ARRAY data types.

SQLError

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by SQLGetDiagRec.

SQLGetData

SQLGetDiagField

SQLGetDiagRec

SQLRowCount

SQLNumResultCols

SQLDescribeCol

SQLColAttribute

For GEOGRAPHY columns, SQL_DESC_TYPE_NAME returns GEOGRAPHY. Note that other descriptors (e.g. SQL_DESC_CONCISE_TYPE) do not indicate that the column type is GEOGRAPHY.

SQLColAttributes

Supported by the Snowflake driver, but deprecated in ODBC API version 2.x. Replaced by SQLColAttribute.

SQLFetch

SQLFetchScroll

The FetchOrientation argument supports the SQL_FETCH_NEXT value only. All other types of fetch fail.

SQLExtendedFetch

Replaced by SQLFetchScroll in API version 3.x driver.

SQLSetPos

Snowflake does not support the functionality.

SQLBulkOperations

Snowflake does not support the functionality.

Obtaining information about the data source’s system tables (catalog functions)

Function Name

Supported

Notes

SQLColumnPrivileges

Returns an empty results set.

SQLColumns

SQLForeignKeys

SQLPrimaryKeys

SQLProcedureColumns

SQLProcedures

In the result set, the NUM_INPUT_PARAMS column contains the number of arguments for the procedure (the value of the max_num_arguments column in the output of the SHOW PROCEDURES command). . . The NUM_OUTPUT_PARAMS column contains NULL values because stored procedures in Snowflake don’t support output parameters. . . The NUM_RESULT_SETS column also contains NULL values because stored procedures in Snowflake don’t return result sets. . . The PROCEDURE_TYPE column always contains SQL_PT_FUNCTION because stored procedures in Snowflake always return a value.

SQLSpecialColumns

Returns an empty results set.

SQLStatistics

Returns an empty results set.

SQLTablePrivileges

Returns an empty results set.

SQLTables

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

SQLFreeStmt

SQLCloseCursor

SQLCancel

SQLEndTran

SQLTransact

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x. Replaced by SQLEndTran.

Terminating a connection

Function Name

Supported

Notes

SQLCancelHandle

Introduced into the API after version 3.52.

SQLDisconnect

SQLFreeHandle

SQLFreeConnect

Supported by the Snowflake driver, but deprecated in ODBC API version 3.x.

SQLFreeEnv

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
Copy

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);
Copy

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);
Copy

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);
Copy

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);
Copy

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);
Copy