SQLBindParameter to prepare for SQLPutData using C++ and SQL Native Client - c++

I'm trying to use SQLBindParameter to prepare my driver for input via SQLPutData. The field in the database is a TEXT field. My function is crafted based on MS's example here:
http://msdn.microsoft.com/en-us/library/ms713824(VS.85).aspx.
I've setup the environment, made the connection, and prepared my statement successfully but when I call SQLBindParam (using code below) it consistently fails reporting: [Microsoft][SQL Native Client]Invalid precision value
int col_num = 1;
SQLINTEGER length = very_long_string.length( );
retcode = SQLBindParameter( StatementHandle,
col_num,
SQL_PARAM_INPUT,
SQL_C_BINARY,
SQL_LONGVARBINARY,
NULL,
NULL,
(SQLPOINTER) col_num,
NULL,
&length );
The above relies on the driver in use returning "N" for the SQL_NEED_LONG_DATA_LEN information type in SQLGetInfo. My driver returns "Y". How do I bind so that I can use SQLPutData?

Though it doesn't look just like the documentation's example code, I found the following solution to work for what I'm trying to accomplish. Thanks gbjbaanb for making me retest my input combinations to SQLBindParameter.
SQLINTEGER length;
RETCODE retcode = SQLBindParameter( StatementHandle,
col_num, // position of the parameter in the query
SQL_PARAM_INPUT,
SQL_C_CHAR,
SQL_VARCHAR,
data_length, // size of our data
NULL, // decimal precision: not used our data types
&my_string, // SQLParamData will return this value later to indicate what data it's looking for so let's pass in the address of our std::string
data_length,
&length ); // it needs a length buffer
// length in the following operation must still exist when SQLExecDirect or SQLExecute is called
// in my code, I used a pointer on the heap for this.
length = SQL_LEN_DATA_AT_EXEC( data_length );
After a statement is executed, you can use SQLParamData to determine what data SQL wants you to send it as follows:
std::string* my_string;
// set string pointer to value given to SQLBindParameter
retcode = SQLParamData( StatementHandle, (SQLPOINTER*) &my_string );
Finally, use SQLPutData to send the contents of your string to SQL:
// send data in chunks until everything is sent
SQLINTEGER len;
for ( int i(0); i < my_string->length( ); i += CHUNK_SIZE )
{
std::string substr = my_string->substr( i, CHUNK_SIZE );
len = substr.length( );
retcode = SQLPutData( StatementHandle, (SQLPOINTER) substr.c_str( ), len );
}

you're passing NULL as the buffer length, this is an in/out param that shoudl be the size of the col_num parameter. Also, you should pass a value for the ColumnSize or DecimalDigits parameters.
http://msdn.microsoft.com/en-us/library/ms710963(VS.85).aspx

Related

Error while Bind BLOB values in ODBC(PostgreSQL), C/C++

I have some problems about ODBC programming with C/C++, PostgreSQL connector.
I just want to put some BLOB data in my PostgreSQL with C/C++, ODBC
here's my DB Table Create Query
CREATE TABLE BLOB_TEST
(
id number(20),
data BYTEA
);
And, here's my code
.
.
.
int retcode = 0;
SQLCHAR sql[1024] =
"BEGIN \n"
"insert into BLOB_TEST "
"values(9, ?); \n"
"EXCEPTION \n"
"when DUP_VAL_ON_INDEX then \n"
"dbms_output.put_line(1); \n"
"END; ";
char * temp_str = "this is a BLOB input TEST";
retcode = SQLPrepareA(hstmt, sql, SQL_NTS);
.
.
.
SQLBindParameter(hstmt,
1, /* Parameter number, starting at 1 */
SQL_PARAM_INPUT, /* in, out, inout */
SQL_C_BINARY, /* C data type of the parameter */
SQL_LONGVARBINARY, /* SQL data type of the parameter : char(8)*/
0, /* size of the column or expression, precision */
0, /* The decimal digits, scale */
temp_str, /* A pointer to a buffer for the parameter’s data */
0, /* Length of the ParameterValuePtr buffer in bytes */
NULL /* indicator */
);
.
.
.
retcode = SQLExecute(hstmt);
if (retcode == SQL_SUCCESS)
{
printf("Query Execute Success\n");
}
else
{
SQLGetDiagRecA(SQL_HANDLE_STMT, hstmt, ++rec, state, &native, message, sizeof(message), &length);
printf("%s : %ld : %ld : %s\n", state, rec, native, message);
printf("Query Execute ERROR : %d\n", retcode);
}
SQLExecute return -1(SQL_ERROR) and ERROR Message says:
SQLSTATE 42601, Missing ";" at the end of Expression
I know that PostgreSQL BLOB(BYTEA) type matched SQL_LONGVARBINARY Option when using SQLBindParameter, But that makes ERROR...
Is there any odd expression in my prepared query?
Or, Is there any way to check value-combind Query that SQLExcute function made?
I'm very confused, Cause the query that I prepared works well when using PgAdmin Querying tools...
So, I Want to check value-combind Query that SQLExcute function made.
You are trying to run a PL/SQL block on a database that is not Oracle. How is that supposed to work?
PostgreSQL has a DO statement that serves a similar purpose, but you cannot use parameters with it. You should send only the INSERT statement and do the exception handling in your C client code.

Accessing an entry in std::map causes 'no operator "[]" matches these operands'

I'm trying to learn c++, so apologies if this is a silly question. I am trying to replicate what I can do in C# when accessing field names of a database query
so
myRow["field"]
would return the value of the field in the current row
Now here is the code I have to create a std:map which models the table and Rows.
void DATAENGINE_API DataEngine::ExecuteQuery(char * sqlStatement)
{
SQLRETURN retCode;
SQLHANDLE hEnv;
SQLHANDLE hConn;
SQLHANDLE hStmt;
SQLCHAR* dsnName;
SQLCHAR* uid;
SQLCHAR* pwd;
SQLCHAR* query;
SQLLEN numRows;
SQLSMALLINT numCols;
retCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
CHECK(retCode, "allocate environment handle");
retCode = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
CHECK(retCode, "setting the environment attribute setting to ODBC version 3");
CHECK(SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hConn), "allocate handle");
dsnName = (SQLCHAR*)dsn;
uid = (SQLCHAR*)userId;
pwd = (SQLCHAR*)password;
retCode = SQLConnectA(hConn, dsnName, SQL_NTS, uid, SQL_NTS, pwd, SQL_NTS);
if (!CHECK(retCode, "SqlConnectA", false)) {
Status(SQL_HANDLE_DBC, hConn, __LINE__);
}
CHECK(SQLAllocHandle(SQL_HANDLE_STMT, hConn, &hStmt), "allocate handle for statement");
query = (SQLCHAR*)sqlStatement;
CHECK(SQLExecDirectA(hStmt, query, SQL_NTS), "execute query");
retCode = SQLFetch(hStmt);
CHECK(retCode, "first sqlFetch");
retCode = SQLRowCount(hStmt, &numRows);
retCode = SQLNumResultCols(hStmt, &numCols);
// traverse the results to create a table view.
std::map<int, Row> t;
for (int i = 1; i <= numRows; i++) {
std::map<std::string, std::string> r;
for (int j = 1; j <= numCols; j++) {
SQLCHAR colName[256];
char buf[256];
SQLSMALLINT colNameLen, dataType, numDecimalDigits, allowsNullValues;
SQLUINTEGER columnSize;
SQLINTEGER numBytes;
std::string fieldName;
retCode = SQLDescribeColA(hStmt, j, colName, 255, &colNameLen, &dataType, &columnSize, &numDecimalDigits, &allowsNullValues);
fieldName = (char*)colName;
retCode = SQLGetData(
hStmt,
j, // COLUMN NUMBER of the data to get
SQL_C_CHAR, // the data type that you expect to receive
buf, // the place to put the data that you expect to receive
255, // the size in bytes of buf (-1 for null terminator)
&numBytes // size in bytes of data returned
);
// r.insert(std::make_pair(fieldName, buf))
r.insert(std::make_pair(fieldName, buf));
}
t[i] = r;
}
}
However when I try and use the following code
r["myfield"]
VS is throwing 'no operator "[]" matches these operands' error.
Looking at this link std::map access operator deprecated? no operator [] matches these operands
I have even tried
r.insert(std::make_pair(fieldName, buf));
based on a suggestion here; How to create a std::map of constant values which is still accessible by the [] operator?
and I still get the same error.
I would expect this to work - so what am I doing wrong?
Thanks in advance.
Regarding your problem description, which as I’m writing this says that
” when I try and use the following code
r["myfield"]
VS is throwing 'no operator "[]" matches these operands' error.
Well the following works:
#include <map>
#include <string>
auto main() -> int
{
std::map< std::string, std::string > r;
r["blah"] = "foo";
}
So there your interpretation of things is incorrect. Most likely the code you have presented is not the real code. I would surmise that you are correctly quoting the error message.
The operator[] takes the same type as the key. Your key is a string, but you are passing a constant character pointer.
This is similar to 01d55's answer about your incorrect pair types. Using his suggestion of creating string using the character pointer should fix both problems.
Your use of std::make_pair is the problem. You need to create an std::pair<std::string, std::string> explicitly. The std::make_pair function template creates a pair based on the types of its inputs.
char buf[256];
// [...]
r.insert(std::make_pair(fieldName, buf));
// std::make_pair returns std::pair<std::string, char*>
Normally, in C++, a char* will implicitly convert to std::string. It looks like your compiler or your standard library is not picking up the conversion between the pair containing the type.
Try this instead:
r.insert(std::pair<std::string, std::string>(fieldName, buf));
If you prefer, you can shorten this with a typedef:
typedef std::pair<std::string, std::string> string_pair;
r.insert(string_pair(fieldName, buf));

c++ float to sql server DECIMAL correct conversion

I am creating an application in native c++ using odbc. I am calling on to a sql server stored procedure that has a decimal parameter. I am passing a float to that parameter. The code so far:
SQLDECIMAL *sql_param = new SQLDECIMAL( param);
if( SQL_ERROR == ( result = SQLBindParameter( statement_handle, 5, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_DECIMAL, 12, 6, sql_param, 0, NULL ) ) ){
std::wstring error = get_error_message( SQL_HANDLE_STMT, statement_handle );
throw GenericException( error );
}
param is a float.
What gets stored on the table is always 0 instead of the real value which is 1.45. I'm guessing some conversion is taking place but I can't figured out the correct conversion. SQL_C_FLOAT -> SQL_DECIMAL?
In the reference:
SQLRETURN SQLBindParameter(
SQLHSTMT StatementHandle,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr);
...
BufferLength
[Input/Output] Length of the ParameterValuePtr buffer in bytes.
You have specified BufferLength as zero. Looking here, you can see the length of decimal for different precision values:
Precision StorageBytes
1-9 5
10-19 9
20-28 13
29-38 17
Therefore, you need to call method SQLBindParameter as:
SQLBindParameter( statement_handle, 1, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_DECIMAL, 12, 6, sql_param, 5, NULL )
The SQLDECIMAL is your problem. According to http://publib.boulder.ibm.com/infocenter/soliddb/v6r3/index.jsp?topic=/com.ibm.swg.im.soliddb.programmer.doc/doc/s0005314.c.data.types.html it is a unsigned char [f].
I suggest something like:
void bind_float(float* param) {
SQLBindParameter( statement_handle, 5, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_DECIMAL, 12, 6, param, 0, NULL ) ) );
}
I got it. It shouldn't have been:
SQLDECIMAL *sql_param = new SQLDECIMAL( param );
it should have been:
SQLDECIMAL *sql_param = (SQLDECIMAL *) param;

How do I get an nvarchar from MS SQL as string using ODBC (C++)?

I'm trying to extract a string from my SQL database, but for some reason my parameters are wrong and I'm not sure why. Here is my code:
SQLHENV environHandle;
SQLHDBC connectHandle;
SQLHSTMT statement;
SQLCHAR* connectString = "MY_CONNECTION_STRING";
string path;
int jobID;
SQLINTEGER pathstrlen = SQL_NTS;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &environHandle);
SQLSetEnvAttr(environHandle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
SQLAllocHandle(SQL_HANDLE_DBC, environHandle, &connectHandle);
SQLDriverConnect(connectHandle, NULL, connectString, SQL_NTS, NULL, 1024, NULL, SQL_DRIVER_NOPROMPT);
SQLAllocHandle(SQL_HANDLE_STMT, connectHandle, &statement);
//THIS IS THE BINDPARAMETER WITH THE ISSUE...
SQLBindParameter(statement, 1, SQL_PARAM_OUTPUT, SQL_C_CHAR, SQL_VARCHAR, 400, 0, (SQLPOINTER)path.c_str(), path.length(), &pathstrlen);
SQLBindParameter(statement, 2, SQL_PARAM_OUTPUT, SQL_INTEGER, SQL_INTEGER, 10, 0, &jobID, 0, &pathstrlen);
SQLExecDirect(statement, (SQLCHAR*)"{CALL SP(?,?)}", SQL_NTS);
It runs fine, but won't get the string information I requested, while the second parameter (to get an integer) works fine. I've tried changing the ParameterType to multiple different things but I either get errors thrown at me (SQL_LONGVARCHAR for example, is deprecated).
In SQL, the data I'm trying to get is as follows:
#Path nvarchar(4000) OUT
set #Path = 'test'
Thanks in advance to anyone who can shed light on this. I've been pulling my hair out all day.
ODBC supports Unicode parameter types so use SQL_C_WCHAR and SQL_WVARCHAR instead of SQL_C_CHAR and SQL_VARCHAR respectively.
You have two other issues as well.
First you are passing an ANSI string as a parameter. If you are using Unicode you need to use a wide string - wstring instead.
Second you are not passing a valid buffer to SQLBindParameter. The value returned by string.c_str() is a const char* that is a read only buffer. It is not valid to pass hat to a function that requires a writable buffer - doing this will corrupt your string. However you won't see any corruption in your case because you the call to path.length() WILL return zero so SQLBindParameter will never return any data.
You will need to declare WCHAR array buffer and pass that to SQLBindParameter which will give it a valid buffer to write data into. You can then transfer that buffer to a wstring if you need it in a C++ object.
So something like this:
WCHAR path[401]; // 401 - width of you column + 1 for the null terminator
SQLBindParameter(statement, 1, SQL_PARAM_OUTPUT, SQL_C_WCHAR, SQL_WVARCHAR, 400, 0, (SQLPOINTER)path, sizeof(path), &pathstrlen);
Edit
Ffrom looking at the ODBC data conversion table it appears that you should be able to get ODBC to convert that data from Unicode to ANSI for you if you to not want to deal with Unicode strings in your application.
char path[401]; // 401 - width of you column + 1 for the null terminator
SQLBindParameter(statement, 1, SQL_PARAM_OUTPUT, SQL_C_WCHAR, SQL_WVARCHAR, 400, 0, (SQLPOINTER)path, sizeof(path), &pathstrlen);

How to write to a varchar(max) column using ODBC

Summary: I'm trying to write a text string to a column of type varchar(max) using ODBC and SQL Server 2005. It fails if the length of the string is greater than 8000. Help!
I have some C++ code that uses ODBC (SQL Native Client) to write a text string to a table. If I change the column from, say, varchar(100) to varchar(max) and try to write a string with length greater than 8000, the write fails with the following error
[Microsoft][ODBC SQL Server
Driver]String data, right truncation
So, can anyone advise me on if this can be done, and how?
Some example (not production) code that shows what I'm trying to do:
SQLHENV hEnv = NULL;
SQLRETURN iError = SQLAllocEnv(&hEnv);
HDBC hDbc = NULL;
SQLAllocConnect(hEnv, &hDbc);
const char* pszConnStr = "Driver={SQL Server};Server=127.0.0.1;Database=MyTestDB";
UCHAR szConnectOut[SQL_MAX_MESSAGE_LENGTH];
SWORD iConnectOutLen = 0;
iError = SQLDriverConnect(hDbc, NULL, (unsigned char*)pszConnStr,
SQL_NTS, szConnectOut,
(SQL_MAX_MESSAGE_LENGTH-1), &iConnectOutLen,
SQL_DRIVER_COMPLETE);
HSTMT hStmt = NULL;
iError = SQLAllocStmt(hDbc, &hStmt);
const char* pszSQL = "INSERT INTO MyTestTable (LongStr) VALUES (?)";
iError = SQLPrepare(hStmt, (SQLCHAR*)pszSQL, SQL_NTS);
char* pszBigString = AllocBigString(8001);
iError = SQLSetParam(hStmt, 1, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)pszBigString, NULL);
iError = SQLExecute(hStmt); // Returns SQL_ERROR if pszBigString len > 8000
The table MyTestTable contains a single colum defined as varchar(max). The function AllocBigString (not shown) creates a string of arbitrary length.
I understand that previous versions of SQL Server had an 8000 character limit to varchars, but not why is this happening in SQL 2005?
Thanks,
Andy
You sure you load the SQL Native Driver for 2005, not the old driver for 2000? The native driver name is {SQL Server Native Client 10.0} for 2k8 or {SQL Native Client} for 2k5
The error message ODBC SQL Server Driver seem to indicate the old 2k driver (I may be wrong, haven't touch ODBC in like 10 years now).
Turns out that although the fix works for SQLSetParam, it does not work for SQLBindParameter.
For example:
int iLength = 18001;
char* pszBigString = new char[iLength + 1];
memset(pszBigString, 'a', iLength);
pszBigString[iLength] = 0;
LONG_PTR lLength = SQL_NTS;
::SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR,
SQL_VARCHAR,
iLength, 0, pszBigString, iLength * sizeof(TCHAR),
&lLength);
will result in the same 22001 "String data, right truncation" error, regardless of which driver is used.
In fact, my experiments have shown that you do not actually need to install version 10 of the client driver. Instead you should use SQL_LONGVARCHAR instead of SQL_VARCHAR if you expect the lengths of your strings to exceed 8000 characters. You could potentially perform a mass find-and-replace, but it's possible that using SQL_LONGVARCHAR might incur some sort of penalty (although that's pure speculation; it's an 'extended datatype').
I have tested this successfully with both drivers on Windows XP:
{SQL Server} 2000.85.1117.00 (04/08/2004)
{SQL Server Native Client 10.0} 2007.100.1600.22 (10/07/2008)