void core_sqlsrv_sensitivity_metadata()

in source/shared/core_stmt.cpp [687:792]


void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt )
{
    sqlsrv_malloc_auto_ptr<unsigned char> dcbuf;
    DWORD dcVersion = 0;
    SQLINTEGER dclen = 0, dcIRD = 0;
    SQLINTEGER dclenout = 0;
    SQLHANDLE ird;
    SQLRETURN r;

    try {
        if (!stmt->data_classification) {
            return;
        }

        if (stmt->current_sensitivity_metadata) {
            // Already cached, so return
            return;
        }

        CHECK_CUSTOM_ERROR(!stmt->executed, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION) {
            throw core::CoreException();
        }

        // Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification
        // To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by
        // calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute
        r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, reinterpret_cast<SQLPOINTER*>(&ird), SQL_IS_POINTER, 0);
        CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
            LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in getting Implementation Row Descriptor handle." );
            throw core::CoreException();
        }

        // First call to get dclen
        r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast<SQLPOINTER>(dcbuf.get()), 0, &dclen);
        if (r != SQL_SUCCESS || dclen == 0) {
            // log the error first
            LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW first time." );

            // If this fails, check if it is the "Invalid Descriptor Field error"
            SQLRETURN rc;
            SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'};
            SQLSMALLINT len;
            rc = ::SQLGetDiagField(SQL_HANDLE_DESC, ird, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len);

            CHECK_SQL_ERROR_OR_WARNING(rc, stmt) {
                throw core::CoreException();
            }

            CHECK_CUSTOM_ERROR(!strcmp("HY091", reinterpret_cast<char*>(state)), stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE) {
                throw core::CoreException();
            }

            CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Check if ODBC driver or the server supports the Data Classification feature.") {
                throw core::CoreException();
            }
        }

        // Call again to read SQL_CA_SS_DATA_CLASSIFICATION data
        dcbuf = static_cast<unsigned char*>(sqlsrv_malloc(dclen * sizeof(char)));

        r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast<SQLPOINTER>(dcbuf.get()), dclen, &dclenout);
        if (r != SQL_SUCCESS) {
            LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW again." );

            CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "SQLGetDescFieldW failed unexpectedly") {
                throw core::CoreException();
            }
        }

        // Start parsing the data (blob)
        using namespace data_classification;

        // If make it this far, must be using ODBC 17.2 or above. Prior to ODBC 17.4, checking Data Classification version will fail. 
        // When the function is successful and the version is right, rank info is available for retrieval
        bool getRankInfo = false;
        r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION_VERSION, reinterpret_cast<SQLPOINTER>(&dcVersion), SQL_IS_INTEGER, &dcIRD);
        if (r == SQL_SUCCESS && dcVersion >= VERSION_RANK_AVAILABLE) {
            getRankInfo = true;
        }

        // Start parsing the data (blob)
        unsigned char *dcptr = dcbuf;

        sqlsrv_malloc_auto_ptr<sensitivity_metadata> sensitivity_meta;
        sensitivity_meta = new (sqlsrv_malloc(sizeof(sensitivity_metadata))) sensitivity_metadata();

        // Parse the name id pairs for labels first then info types
        parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_labels, &sensitivity_meta->labels, &dcptr);
        parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, &sensitivity_meta->infotypes, &dcptr);

        // Next parse the sensitivity properties
        parse_column_sensitivity_props(sensitivity_meta, &dcptr, getRankInfo);

        unsigned char *dcend = dcbuf;
        dcend += dclen;

        CHECK_CUSTOM_ERROR(dcptr != dcend, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Metadata parsing ends unexpectedly") {
            throw core::CoreException();
        }

        stmt->current_sensitivity_metadata = sensitivity_meta;
        sensitivity_meta.transferred();
    } catch (core::CoreException& e) {
        throw e;
    }
}