sql-serverstored-procedurestable-valued-parameterssql-server-native-client

SQL Native client interface error 8058 when using table-valued parameter, cannot diagnose cause


I've written a function which sends data to a T-SQL stored procedure using table valued parameters. Its the first time I did this and I got stuck by this Error:

The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect. Table-valued parameter 1, to a parameterized string has no table type defined.

Unfortunately, I cannot find any resources on the net that give solutions how to fix this kind of error. And from the message I am left without insight what went wrong here.

The error happens when the SQLExecDirectA is called in the code below.

ErrorCode SQLif::InsertObjectBatch(ObjTypeKey* pObjectTypeKeys, ObjectId* pObjectIds, unsigned int numObjects)
{
    ErrorCode ec = ACK;
    EnterCriticalSection(m_pcs);

    SQLRETURN ret;
    SQLHSTMT hStmt;

    StorageClass* pStorageClasses = new StorageClass[numObjects];
    RevisionId* pRevisionIds = new RevisionId[numObjects];

    if (!pStorageClasses || !pRevisionIds) 
    {
        if (pStorageClasses) delete pStorageClasses;
        if (pRevisionIds) delete pRevisionIds;
        LeaveCriticalSection(m_pcs);
        return NACK("SQLif::InsertObjectBack: Allocation failure allocating arrays");
    }

    for (unsigned int i = 0; i < numObjects; i++)
        pStorageClasses[i] = PERSIST;
    for (unsigned int i = 0; i < numObjects; i++)
        pRevisionIds[i] = 0;


    // Variable for TVP row count;
    SQLINTEGER cbTVP;

    unsigned long long objectIdParam = 0;
    SQLLEN objectIdParamLen = sizeof(objectIdParam);

    // Allocate a statement handle
    if ((ec = AllocStatementHandle(&hStmt)) != ACK)
    {
        LeaveCriticalSection(m_pcs);
        return ec;
    }

    // Bind paramters for call  
    if (ec == ACK)
    {
        ret = SQLBindParameter( hStmt,  // Statement handle
                                1,      // ParameterNumber
                                SQL_PARAM_INPUT,    // InputOutputType
                                SQL_C_DEFAULT,      // ValueType
                                SQL_SS_TABLE,       // ParameterType
                                (SQLINTEGER) numObjects, // Number of rows in TVP
                                0,                  // Number of columns in TVP
                                NULL,               // not needed
                                NULL,               // not needed
                                &cbTVP );
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBatch: Binding to parameter 1 failed");
    }

    // Bind colums for the TVP (param 1)
    if (ec == ACK)
    {
        ret = SQLSetStmtAttr(hStmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 1, SQL_IS_INTEGER);
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBatch: Setting Focus to TVP failed");
    }

    // Bind column 1
    if (ec == ACK)
    {
        ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, pStorageClasses, sizeof(SQLINTEGER), NULL);
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBach: Binding to column 1 of TVP failed");
    }
    // Bind column 2
    if (ec == ACK)
    {
        ret = SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, pObjectTypeKeys, sizeof(SQLUINTEGER), NULL);
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBach: Binding to column 2 of TVP failed");
    }
    // Bind column 3
    if (ec == ACK)
    {
        ret = SQLBindParameter(hStmt, 3, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, pRevisionIds, sizeof(SQLUBIGINT), NULL);
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBach: Binding to column 3 of TVP failed");
    }
    // Release focus
    if (ec == ACK)
    {
        ret = SQLSetStmtAttr(hStmt, SQL_SOPT_SS_PARAM_FOCUS, (SQLPOINTER) 0, SQL_IS_INTEGER);
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::InsertObjectBatch: Resetting Focus failed");
    }

    // Initialize row count
    cbTVP = numObjects;

    if (ec == ACK)
    {
        ret =SQLExecDirectA(hStmt, (SQLCHAR*)"EXEC Proc_InsertObjectBatch ?", SQL_NTS);     
        if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO)) ec = GetErrorMessage(hStmt);
    }

    if (ec == ACK)
    {   
        ret = SQLBindCol(hStmt, 1, SQL_C_UBIGINT, &objectIdParam, sizeof(objectIdParam), &objectIdParamLen);
        if (ret != SQL_SUCCESS) ec = NACK("SQLif::InsertObjectBatch: Binding to column 1 failed");
    }

    bool bHasData = false;
    unsigned int i = 0;
    if (ec == ACK)
    {
        while ((ret = SQLFetch(hStmt)) != SQL_NO_DATA)
        {
            if ((ret != SQL_SUCCESS) && ( ret != SQL_SUCCESS_WITH_INFO)) ec = NACK("SQLif::RetrieveAttribute: Unable to fetch rows");

            bHasData = true;
            pObjectIds[i] = objectIdParam;
        }
    }
    FreeStatementHandle(hStmt);

    LeaveCriticalSection(m_pcs);

    return ACK;
}

This is the corresponding stored procedure.

-- Create a procedure to insert batch of objects
CREATE PROCEDURE Proc_InsertObjectBatch @InsertObjects_TVP ObjectTableType READONLY AS
-- Declare a variable holding return value
DECLARE @return_value integer;
-- Declare a table to hold oid of created objects
DECLARE @OutputTable TABLE (oid bigint);

-- Insert objects
INSERT INTO dbo.object_table(stkey, otkey, orev)
OUTPUT INSERTED.oid INTO @OutputTable
SELECT * from @InsertObjects_TVP;

-- Return oid's
SELECT oid FROM @OutputTable;

SET @return_value = 0;

RETURN @return_value
-- End of stored procedure Proc_InsertObjectBatch

This is the table type.

-- Create ObjectTableType
CREATE TYPE ObjectTableType AS TABLE
(
    stkey integer,
    otkey integer,
    orev bigint
)

Any help how to fix this would be greatly appreciated.


Solution

  • Specifying the table data type SQLWCHAR* TVP = (SQLWCHAR*) L"ObjectTableType"; - as your answer suggests - doesn't seem necessary. The ODBC driver seems to have some logic to resolve that when called anonymously. The error was caused because the query string wasn't properly escaped for me.

    Try

    ret = SQLExecDirectA(hStmt, (SQLCHAR*)"{EXEC Proc_InsertObjectBatch(?)}", SQL_NTS);
    

    instead of

    ret = SQLExecDirectA(hStmt, (SQLCHAR*)"EXEC Proc_InsertObjectBatch ?", SQL_NTS);
    

    Note the added bracket encapsulation with {}, for the parameter with ()