How to write to a varchar(max) column using ODBC - c++

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)

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.

Program type out of range connecting to MS SQL

ALL,
I am trying to execute the following query:
SELECT cast(su.name AS varchar(128)) FROM sysobjects so, sysusers su, sys.tables t, sys.schemas s WHERE so.uid = su.uid AND t.object_id = so.id AND t.schema_id = s.schema_id AND s.name = ? AND so.name = ?;
against SQL Server 10.0 (as returned from SELECT SERVERPROPERTY('productversion')) and am receiving the aforementioned error.
The only google result for this error and MS SQL Server is this link, but doing what is recommended as a 2nd solution didn't work.
Code I use is as follows:
SQLSMALLINT nameBufLength, dataTypePtr, decimalDigitsPtr, isNullable;
SQLULEN columnSizePtr;
SQLLEN cbTableOwner;
retcode = SQLDescribeCol( stmt, 1, NULL, 0, &nameBufLength, &dataTypePtr, &columnSizePtr, &decimalDigitsPtr, &isNullable );
if( retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO )
{
owner = new SQLWCHAR[columnSizePtr + 1];
retcode = SQLBindCol( stmt, 1, dataTypePtr, &owner, columnSizePtr, &cbTableOwner );
if( retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO )
{
}
}
Does anybody know why am I getting the error? It's not even mentioned here.
TIA!
EDIT:
I am using SQL Server Native Client 11.0 ODBC driver version 2011.110.2100.60 from 11 Feb 2012. I also have SQL Server Native Client 10.0 installed. The driver I use should be backward compatible. But I will try with 10.0 just in case tomorrow.
EDIT2:
Trying to use older driver also didn't work. I guess I will try to workaround the error.
In SQLBindCol(), set the dataTypePtr to SQL_WCHAR instead of SQL_WVARCHAR for unicode and to SQL_CHAR instead of SQL_VARCHAR for ascii.

Very long query execution using C++, Sql Server and ODBC connection

I'm using the following code to connect to a sql-server database. I can run all other queries with no difficulties using the same function. However, one query executes in Sql management studio and in R using RODBC in ~11 seconds but takes over an hour (at the "SQLExecDirect" function) using c++. Has anyone else had this issue and how did you resolve it?
std::string sqlQuery="[myquery]";
SQLHANDLE sqlconnectionhandle;
SQLAllocHandle(SQL_HANDLE_DBC, sqlenvhandle, &sqlconnectionhandle);
SQLCHAR retconstring[1024];
SQLDriverConnect (sqlconnectionhandle, NULL,
conn_str,
len, retconstring, 1024, NULL,SQL_DRIVER_NOPROMPT);
SQLHANDLE sqlstatementhandle;
SQLAllocHandle(SQL_HANDLE_STMT, sqlconnectionhandle, &sqlstatementhandle);
//this is where the program "hangs" for over an hour
if(SQL_SUCCESS!=SQLExecDirect(sqlstatementhandle, (SQLCHAR*)(sqlQuery.c_str()), SQL_NTS)){
show_error(SQL_HANDLE_STMT, sqlstatementhandle, errorMsg);
return;
}
int numRow=0;
while(SQLFetch(sqlstatementhandle)==SQL_SUCCESS){
for(int i=1; i<=numFields+1; ++i){
double myVal;
SQLGetData(sqlstatementhandle, i, SQL_C_DOUBLE, &myVal, 0, NULL);
cb(myVal, numRow, i-1); //callback function defined elsewhere
}
numRow++;
}
SQLFreeHandle(SQL_HANDLE_STMT, sqlstatementhandle );
SQLDisconnect(sqlconnectionhandle);
SQLFreeHandle(SQL_HANDLE_DBC, sqlconnectionhandle);
That is because the ArithAbort property on your database is off.
See also http://www.sommarskog.se/query-plan-mysteries.html
You can check it and correct it with this small script
declare #value sql_variant
select #value = SESSIONPROPERTY('ARITHABORT')
if #value <> 1
begin
USE master
ALTER DATABASE [your_database] SET ARITHABORT ON WITH NO_WAIT
use your_database
end
I figured this out: my connection string's default database was different than the one that I had as the default database in SSMS. Switching this made the query run in the same speed as SSMS.

SQLDescribeParams function call - SQL Server Native Client 11.0 driver fails with “Invalid object name”

I want to create a temporary table on SQL Server 2012 and insert into it in bulk. I don't always know the type of parameters that the temp table will be created with, so I need to invoke SQLDescribeParam so that I can bind the parameters. I am using the following stripped down code after copying the sample code for SQLDescribeParam from http://msdn.microsoft.com/en-us/library/ms710188(v=vs.85).aspx
The code below works perfectly fine for the "SQL Server Native Client 10.0" ODBC driver against SQL Server 2012, and I am able to bind parameters and insert into the temp table. But the SQLDescribeParam call fails for "SQL Server Native Client 11.0" driver with the following two errors:
Error record 1:
Description: '[Microsoft][SQL Server Native Client 11.0][SQL Server]Invalid object name '#myTemp'.'
SQL State: 42S02
Native Error: 208
Error record 2:
Description: '[Microsoft][SQL Server Native Client 11.0][SQL Server]The batch could not be analyzed because of compile errors.'
SQL State: 42000
Native Error: 11501
What do I need to do to get SQLDescribeParam to behave on "SQL Server Native Client 11.0"?
I am using the 64 bit "SQL Server Native Client" driver on Windows 7.
The code is below:
SQLSMALLINT NumParams, i, DataType, DecimalDigits, Nullable;
SQLULEN ParamSize;
SQLWCHAR strCreateQuery[] = L"CREATE TABLE [#myTemp] ( mycol INT)";
SQLWCHAR strInsertQuery[] = L"INSERT INTO [#myTemp] ([mycol]) VALUES (?)";
SQLHSTMT hstmt;
{ // Create temp table #myTemp
// Allocate hstmt
SQLExecDirectW( hstmt, strCreateQuery, SQL_NTS );
// Deallocate hstmt
}
// Allocate hstmt
SQLPrepare(hstmt, strInsertQuery, SQL_NTS);
SQLNumParams(hstmt, &NumParams);
if (NumParams) {
for (i = 0; i < NumParams; i++) {
// Describe the parameter.
SQLDescribeParam(hstmt, i + 1, &DataType, &ParamSize, &DecimalDigits, &Nullable) );
}
}
// Deallocate hstmt

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);