I'm writing an application that connects to a stored procedure on an MS SQL (2012) Server. The procedure is for inserting data to the database. I'm having real trouble understanding how to define the connection to the stored procedure, bind variables to the parameters and associating those parameters with the variables in the stored procedure. I've spent a few days wading through MSDN reading the APIs and trying to follow examples on here, but there seem to be so many ways of doing this that I can't see the 'wood for the trees'. I think I have the basic structure in place, but I'm falling down on the detail.
Below is the code I have so far. I've omitted the database connection code for simplicity. The function is part of a class 'rigDatabase' which has private members for the various SQL handles.
The main issue I'm having is with the calls to SQLSetDescField. Based on the documentation and examples provided by Microsoft, these calls should work, but instead return HY092 - "Invalid attribute/option identifier". This is what I need help with. Recently I tried logging the output from the ODBC Driver manager to see if that shed any light on the matter. The output to one of the calls to SQLSetDescField can be seen below the Stored Procedure definition.
NB: I haven't yet tried the simpler method of embedding the SQL in the C. I'm trying to interface with existing infrastructure (the Stored Proc).
SQLRETURN rigDatabase::send_SQL(const char* filename,
const char* extn,
const char* path,
DWORD& fSize,
const char* rigName,
FILETIME& created,
const char* notes) {
SQLHDESC hIpd = NULL;
SQLINTEGER PartIDInd = 0;
SQL_TIMESTAMP_STRUCT datetime2;
datetime2.year = fileDate.wYear;
datetime2.month = fileDate.wMonth;
datetime2.day = fileDate.wDay;
datetime2.hour = fileDate.wHour;
datetime2.minute = fileDate.wMinute;
datetime2.second = fileDate.wSecond;
datetime2.fraction = fileDate.wMilliseconds;
retcode = SQLPrepareA(sqlStmtHandle, (SQLCHAR*)"{call insertTestRigDataTest(?, ?, ?, ?, ?, ?, ?)}", SQL_NTS);
retcode = SQLBindParameter(sqlStmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, FILENAME_MAX, 0, (SQLPOINTER)filename, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, MAX_PATH, 0, (SQLPOINTER)path, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, BUF_SIZE, 0, (SQLPOINTER)rigName, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 4, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, (SQLPOINTER)fSize, 0, &PartIDInd);
retcode = SQLBindParameter(sqlStmtHandle, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 4, 0, (SQLPOINTER)extn, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 6, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, sizeof(SQL_TIMESTAMP_STRUCT), 0, &datetime2, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 4000, 0, (SQLPOINTER)notes, 0, NULL);
retcode = SQLGetStmtAttrA(sqlStmtHandle, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0);
// All calls to SQLSetDescField below return -1
// SQLGetDiagRecA returns "Invalid attribute/option identifier"
retcode = SQLSetDescField(hIpd, 1, SQL_DESC_NAME, "#Filename", SQL_NTS);
retcode = SQLSetDescField(hIpd, 2, SQL_DESC_NAME, "#Path", SQL_NTS);
retcode = SQLSetDescField(hIpd, 3, SQL_DESC_NAME, "#Rigname", SQL_NTS);
retcode = SQLSetDescField(hIpd, 4, SQL_DESC_NAME, "#Size", SQL_NTS);
retcode = SQLSetDescField(hIpd, 5, SQL_DESC_NAME, "#Extn", SQL_NTS);
retcode = SQLSetDescField(hIpd, 6, SQL_DESC_NAME, "#Created", SQL_NTS);
retcode = SQLSetDescField(hIpd, 7, SQL_DESC_NAME, "#Notes", SQL_NTS);
retcode = SQLExecute(sqlStmtHandle);
return EXIT_FAILURE;
}
// Function to convert from FILETIME to int64
unsigned __int64 FILETIME_to_int64( const FILETIME& ac_FileTime ) {
ULARGE_INTEGER lv_Large;
lv_Large.LowPart = ac_FileTime.dwLowDateTime;
lv_Large.HighPart = ac_FileTime.dwHighDateTime;
return lv_Large.QuadPart;
}
MS SQL Stored Procedure
USE [TestRigDataTest]
GO
/****** Object: StoredProcedure [dbo].[insertTestRigDataTest] Script Date: 16/10/2018 10:14:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[insertTestRigDataTest]
(
#fileName nvarchar(255),
#path nvarchar(255),
#rigName nvarchar(255),
#size [numeric](18, 0),
#extn nvarchar(255),
#created [datetime],
#notes nvarchar(4000) = NULL
)
AS
BEGIN
SET NOCOUNT ON;
declare #id int
declare #tmpNotes nvarchar(4000)
set #tmpNotes=''
select #id=id,#tmpNotes=notes from [TestRigDataTest].[dbo].[RigData]
where
[Filename]=#filename and
[Path]=#Path and
[Size]=#Size and
[Extension]=#Extn and
[created]=#created
if ##rowcount=0
begin
INSERT INTO [TestRigDataTest].[dbo].[RigData] (
[Filename], [Path], [Rigname] ,[UploadDate] ,[Size] ,[Extension] ,[created] ,[notes]
)
VALUES (
#fileName, #path, #rigName, getdate(), #size, #extn, #created, #notes
)
end
else
begin
if #notes != ''
begin
update [TestRigDataTest].[dbo].[RigData] set [notes]=#tmpNotes + CHAR(13)+CHAR(10) + #notes
where id=#id
end
end
END
ODBC Driver Manager Partial Trace Log:
NB: SQLSetDescField is #defined to SQLSetDescFieldW
AirCatFeeder 26f0-1e50 ENTER SQLSetDescFieldW
SQLHDESC 0x00000000004CB9E8
SQLSMALLINT 2
SQLSMALLINT 1011 <SQL_DESC_NAME>
SQLPOINTER 0x000000013FBA15EC [ -3] "??h\ 0"
SQLINTEGER -3
AirCatFeeder 26f0-1e50 ENTER SQLSetDescField
SQLHDESC 0x00000000004CB9E8
SQLSMALLINT 2
SQLSMALLINT 1011 <SQL_DESC_NAME>
SQLPOINTER 0x000000013FBA15EC [ -3] "#Path\ 0"
SQLINTEGER -3
AirCatFeeder 26f0-1e50 EXIT SQLSetDescField with return code -1 (SQL_ERROR)
SQLHDESC 0x00000000004CB9E8
SQLSMALLINT 2
SQLSMALLINT 1011 <SQL_DESC_NAME>
SQLPOINTER 0x000000013FBA15EC [ -3] "#Path\ 0"
SQLINTEGER -3
DIAG [HY092] [Microsoft][ODBC SQL Server Driver]Invalid attribute/option identifier (0)
AirCatFeeder 26f0-1e50 EXIT SQLSetDescFieldW with return code -1 (SQL_ERROR)
SQLHDESC 0x00000000004CB9E8
SQLSMALLINT 2
SQLSMALLINT 1011 <SQL_DESC_NAME>
SQLPOINTER 0x000000013FBA15EC [ -3] "??h\ 0"
SQLINTEGER -3
DIAG [HY092] [Microsoft][ODBC SQL Server Driver]Invalid attribute/option identifier (0)
The issue was the use of wide strings. SQLSetDescField #defines to SQLSetDescFieldW, but the string literal was not marked as being a wide string. I was missing the 'L' before the string definietion. A sample of the correct code is below.
retcode = SQLSetDescField(hIpd, 1, SQL_DESC_NAME, L"#filename", SQL_NTS);
Interesting to note that I tried using demo code from MSDN, which is based on wide strings, but their own code featured this error. Very frustrating when you're trying to learn, but perhaps they're being like the old meccano kits - putting in deliberate errors. I certainly won't be forgetting this lesson!
When I execute the SQLExecute function it always returns me "[Microsoft][SQL Server Native Client 10.0]String data, right truncation" when my parameter has more than 8k bytes. I will paste the code below.
What I'm trying to do: to store a XML file in a column declared as varbinary(max) through a Stored Procedure via ODBC drivers (Visual C++ 2008) in a SQL Server 2008 R2.
The SP converts from varchar to varbinary calling SET #XML_FILE_BIN = CONVERT(VARBINARY(MAX), #XML_FILE)
It works fine if I try it pasting the whole XML int the SQL Server Management Studio.
I think something is wrong the binding in SQLBindParameter.
The code:
char* cXmlBuf; it contains my buffer
retcode = SQLBindParameter(
hstmt, //StatementHandle
1, //ParameterNumber
SQL_PARAM_INPUT, //InputOutputType
SQL_C_CHAR, //ValueType
SQL_CHAR, //ParameterType
SQL_DESC_LENGTH, //ColumnSize
0, //DecimalDigits
cXmlBuf, //ParameterValuePtr
bufLenght, //BufferLength
&cbXml //StrLen_or_IndPtr
);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
SWORD id = 0;
SQLINTEGER cbId = 0;
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_OUTPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &id, 0, &cbId);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
retcode = SQLPrepare(hstmt, (SQLCHAR*)"{CALL MY_STORE_PROC(?, ?)}", SQL_NTS);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
retcode = SQLFreeStmt(hstmt, SQL_CLOSE); // Clear any cursor state
retcode = SQLExecute(hstmt);
//in this part retcode is -1 and "[Microsoft][SQL Server Native Client
//10.0]String data, right truncation" is returned if my XML buffer
//has more than 8k
Found it!
I've declared SQLINTEGER cbXml = SQL_NTS; instead SQLLEN cbXml = 0;
Thanks.
I've got a script that does a bunch of SQL inserts but I've been trying to add a section that assigns the returned value of a select query into a variable.
The code I've been using to do the inserts is:
if (SQL_SUCCESS != SQLExecDirect(sqlstatementhandle, (SQLCHAR*)"BULK INSERT mytable FROM 'C:/dir/myfile.csv' WITH (FIRSTROW = 1, FIELDTERMINATOR = ',', ROWTERMINATOR = '\n');", SQL_NTS)) {
show_error(SQL_HANDLE_STMT, sqlstatementhandle);
That all works fine, but I can't figure out how to use the output of a query. The output will be a single int value, which I'd like to assign to an int variable.
Apologies if this is something that's blindingly obvious.
EDIT
Based on the answer below, the following has now worked for me. Thanks!
SQLRETURN retcode;
SQLHSTMT hstmt; // I use my own stmnthndl(sqlstatementhandle) below, this line left in for demonstration
retcode = SQLExecDirect(sqlstatementhandle, (SQLCHAR*)"SELECT count(*) FROM mytable;",SQL_NTS);
SQLINTEGER sCustID;
SQLLEN cbCustID;
if (retcode == SQL_SUCCESS) {
while (TRUE) {
retcode = SQLFetch(sqlstatementhandle);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
// get the first column
SQLGetData(sqlstatementhandle, 1, SQL_C_ULONG, &sCustID, 0, &cbCustID);
//You can now print it
cout << "CustID:" << sCustID;
}
else {
break;
}
}
}
You can use SQLGetData to get the data for a single column in the resultset. Taken from this example, first you execute the SQL query with SQLExecDirect:
SQLRETURN retcode;
SQLHSTMT hstmt;
retcode = SQLExecDirect(hstmt,
(SQLCHAR*)"SELECT CUSTID, NAME, PHONE FROM CUSTOMERS ORDER BY 2, 1, 3",
SQL_NTS);
Then you can get the data, I show a reduced version of the same example:
SQLINTEGER sCustID, cbCustID;
if (retcode == SQL_SUCCESS) {
while (TRUE) {
retcode = SQLFetch(hstmt);
if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
show_error();
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
// get the first column
SQLGetData(hstmt, 1, SQL_C_ULONG, &sCustID, 0, &cbCustID);
//You can now print it
fprintf(out, "CustID: %-5d", sCustID);
} else {
break;
}
}
}
The syntax of SQLGetData is:
SQLRETURN SQLGetData(
SQLHSTMT StatementHandle,
SQLUSMALLINT Col_or_Param_Num,
SQLSMALLINT TargetType,
SQLPOINTER TargetValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr);
With:
StatementHandle: the handle to the executed SQL query.
Col_or_Param_Num: the column number, which starts at 1.
TargetType: the data type, you can probably use SQL_INTEGER, see SQL data types and C data types.
TargetValuePtr: the pointer to the output data.
BufferLength: the length, but is not used for fixed length data such as integers.
StrLen_or_IndPtr: an optional output that can return the length of the data or an error code.
Note: like you commented you might need to cast the SQL query to SQLCHAR * since SQLCHAR is an unsigned char, see the data types.
I am trying to insert o blob in a database. I have succeed to enter data i two columns.
I am having problem whit SQLParamData .It returns an error when it should return SQL_NEED_DATA(i will post code)
When i run SQLGetDiagRec it returns S1010 with error text Function sequence error .
I search this error on the internet and i learned that it could be related to a parameter from SQLBindParameter .
// Bind the parameter marker.
retCode = retcode = SQLBindParameter(hstmt, // hstmt
1, // ipar
SQL_PARAM_INPUT, // fParamType
SQL_C_BINARY, // fCType
SQL_LONGVARBINARY, // FSqlType
lbytes, // cbColDef
0, // ibScale
&pParmID, // rgbValue
0, // cbValueMax
&cbTextSize); // pcbValue
SqlError(hstmt,SQL_HANDLE_STMT,_T("WriteBlob"), _T("CTLSqlConnection"), _T("SQLBindParameter"));
if(retCode != SQL_SUCCESS)
{
delete pData;
if(!EndTransaction(FALSE))
return ERR_ENDTRANSACTION_FAILED;
else
return -3;
}
//SQLExec
retcode = retCode = SQLExecDirect(hstmt,(SQLTCHAR*)szSqlStat, SQL_NTS);
SQLRETURN ret;
SQLCHAR* SQLState;
SQLINTEGER NativeError;
SQLSMALLINT errmsglen;
SQLCHAR errmsg[255];
SQLCHAR errstate[50];
retCode = SQLParamData(hstmt, &pParmID);
SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, (SQLCHAR*)errstate, &NativeError, (SQLCHAR*)errmsg, sizeof(errmsg), &errmsglen);
if(retCode == SQL_NEED_DATA)
{
// Put final batch.
SQLPutData(hstmt, pData, lbytes);
}
else
{
delete pData;
If this part of the code is not relevant enough i will post more.
Hope you can help me .Thanks .
Your logic looks wrong here. It is when SQLExecute (SQLExecDirect) returns SQL_NEED_DATA you call SQLParamData and it tells you will parameter you need to supply data for with SQLPutData. If you call SQLParamData when SQLExecute did not return SQL_NEED_DATA I can well imagine it is a function sequence error. I could probably dig you out an example from somewhere if you still need it.
The function is
SQLRETURN SQLBindParameter(
SQLHSTMT StatementHandle,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr);
The documentations I have seen is confusing. Do the arguments depend on the data type or not
I found an example here http://support.microsoft.com/kb/248799
which does not seem to work on DB2. I thought odbc was consistent across databases.
A specific code example would he helpful.
Its not one line as such but
SQLLEN ival;
ret = SQLBindParameter( stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 100, 0, NULL, 0, &ival );
/*
* before execution is called
*/
ival = SQL_NULL_DATA;
That inserts a NULL value as a CHAR(100) datatype. Pick the actual datatype to match what your column type is, but the important thing is to set the indicator value to SQL_NULL_DATA before the SQLExecute or SQLExecDirect is called. And make sure its still set to that value at the execute point.