Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented support for the SQL Server sql_variant data type #1354

Merged

Conversation

will-hinson
Copy link
Contributor

This commit adds support for decoding the SQL Server sql_variant data type.

Attempting to read records containing sql_variant columns in the current version of pyodbc (5.1.0) results in the following exception:

pyodbc.ProgrammingError: ('ODBC SQL type -150 is not yet supported.  column-index=0  type=-150', 'HY106')

As mentioned in #307, it is not trivial to write a custom output converter for sql_variant as the bytes returned by SQL Server give no indication as to the underlying data type of each value.

This commit adds a GetData_SqlVariant() method which retrieves the underlying data type per the Microsoft documentation using SQLColAttribute():

// Call SQLGetData on the current column with a data length of 0. According to MS, this makes
// the ODBC driver read the sql_variant header which contains the underlying data type
pBuff = 0;
indicator = 0;
retcode = SQLGetData(cur->hstmt, static_cast<SQLSMALLINT>(iCol + 1), SQL_C_BINARY,   
                                &pBuff, 0, &indicator);
if (!SQL_SUCCEEDED(retcode))
    return RaiseErrorFromHandle(cur->cnxn, "SQLGetData", cur->cnxn->hdbc, cur->hstmt);

// Get the SQL_CA_SS_VARIANT_TYPE field for the column which will contain the underlying data type
variantType = 0;
retcode = SQLColAttribute(cur->hstmt, iCol + 1, SQL_CA_SS_VARIANT_TYPE, NULL, 0, NULL, &variantType);
if (!SQL_SUCCEEDED(retcode))
    return RaiseErrorFromHandle(cur->cnxn, "SQLColAttribute", cur->cnxn->hdbc, cur->hstmt);

This underlying data type is then patched into the ColumnInfo struct for the current column and GetData() is invoked using this new type:

// Replace the original SQL_VARIANT data type with the underlying data type then call GetData() again
cur->colinfos[iCol].sql_type = static_cast<SQLSMALLINT>(variantType);
return GetData(cur, iCol);

The sql_variant type is used by a number of system functions and views and this commit adds support for reading them:

import pyodbc

connection = pyodbc.connect(...)
print(
    connection.execute(
        """
        SELECT
            SERVERPROPERTY('MachineName'),
            SERVERPROPERTY('InstanceName'),
            SERVERPROPERTY('Edition'),
            SERVERPROPERTY('ProductVersion'),
            SERVERPROPERTY('ProductLevel');
        """
    ).fetchone()
)

# ('acfd83beaa19', '', 'Developer Edition (64-bit)', '16.0.4003.1', 'RTM')

@gordthompson gordthompson linked an issue Jun 3, 2024 that may be closed by this pull request
@gordthompson gordthompson requested a review from mkleehammer June 3, 2024 23:58
@gordthompson
Copy link
Collaborator

Hey, Will. This looks promising. Thanks for contributing!

Can we add a test to tests/sqlserver_test.py that will exercise your code a bit?

@will-hinson
Copy link
Contributor Author

Hello @gordthompson,

Of course! I have pushed another commit with an added test for the sql_variant type.

I also made the following related changes:

  • Added a couple lines to reset the sql_type of the ColumnInfo struct to SQL_SS_VARIANT after decoding. Without this, all values in the column would try to take on the type of the first value returned.
  • Added cases for data type values that can be returned for sql_variant columns and are defined in sqlext.h. (See this MS doc)

Please let me know if further action is required on my part. Thanks!

Copy link
Collaborator

@gordthompson gordthompson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@gordthompson
Copy link
Collaborator

Comments @mkleehammer @v-chojas ?

@v-chojas
Copy link
Contributor

Not sure how much DBMS-specific stuff you want in pyODBC.

Copy link
Owner

@mkleehammer mkleehammer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thanks.

@mkleehammer mkleehammer merged commit d11592c into mkleehammer:master Sep 8, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deciphering SQL_VARIANT values
4 participants