mysql++ query fails - c++

I'm a new user of mysql++ looking for some pointers (pun intended).
The problem: my update statement fails.
The connection is open. The previous statement that uses the connection works.
I'm sure the record I'm trying to update exists. I can see it with the mysql query tools.
I'm sure CustomerId is correct.
// declaration of the customer id
uint32_t CustomerId;
Why does this fail to update?
mysqlpp::Connection conn( true );
try
{
if ( conn.connect( db_rw.Name, db_rw.Host, db_rw.User, db_rw.Password ) )
{
// *snip* insert code here works fine.
// this query fails
mysqlpp::Query query = conn.query( "UPDATE customer SET AccountName=%2q, Active=%3, Password=%1 WHERE CustomerId=%0" );
query.parse();
mysqlpp::SQLQueryParms parms;
parms.push_back( mysqlpp::sql_int( CustomerId ) );
parms.push_back( mysqlpp::sql_blob( data, sizeof(data) ) ); //<- 16 byte binary blob
parms.push_back( mysqlpp::sql_varchar( widget.AccountName->text().toAscii().data() ) ); // string
parms.push_back( mysqlpp::sql_bool( widget.ActiveCheckBox->checkState() == Qt::Checked ? 1 : 0 ) ); //
mysqlpp::SimpleResult res = query.execute( parms );
}
}
If I turn off exceptions for the connection it fails silently (the result.info() method returns nothing).
if I turn exceptions on it seg faults when trying to convert to a string:
std::string Query::str(SQLQueryParms& p)
{
if (!parse_elems_.empty()) {
proc(p);
}
return sbuffer_.str();
}

Warren beat me to the answer on the mailing list, but for posterity:
BLOB data (Password) needs to be quoted and escaped.
Using an SSQLS instead of templated queries handles this automatically.
http://tangentsoft.net/mysql++/doc/html/userman/tutorial.html#id2776204

There were several issues.
The library doesn't escape binary data correctly.
If the user associated with the connection does not have update permission then the library will crash instead of throwing an exception.

Related

Custom CRecordset class does not call DoFieldExchange() when useMultiRowFetch is specified

I've implemented a custom CRecordset class, and have code similar to the following:
ASSERT(prs->GetRowsetSize() == 25);
while (!prs->IsEOF())
{
for (int i = 1; i <= prs->GetRowsFetched(); i++)
{
prs->SetRowsetCursorPosition((WORD)i);
// Inspecting data here...
}
prs->MoveNext();
}
prs->Close();
Apparently, when using multi-row fetch, CRecordset does not call my DoFieldExchange override as it does when not using multi-row fetch, and that is by design. And so my data isn't automatically populated. So the question is how do I get the data?
The answer appears to be by calling GetFieldValue(). But I get an Invalid cursor position error when I do! (GetFieldValue() works fine when I'm not using multi-row fetch.)
Below is a streamlined version of my recordset class. In addition, #EylM was good enough to create a sample in the answers below that he says does work for him. However, when I copied his code exactly and just changed what was needed to connect to and query my database, I still get an Invalid cursor position when I call GetFieldValue().
I don't know what else could be different. I see he's using MySQL where I'm using SQL Server. But surely CRecordset works with SQL Server. I've also tried all the available SQL Server ODBC drivers, but the result is always the same.
class CRS : public CRecordset
{
public:
// Data variables
int m_nId;
TCHAR m_szName[CUSTOMER_NAME_MAXLENGTH + 1];
// Bulk data variables
int* m_pnIds;
long* m_pnIdLengths;
LPTSTR m_pszNames;
long* m_pnNameLengths;
// Constructor
CRS(CDatabase* pDatabase = NULL)
: CRecordset(pDatabase)
{
m_nFields = 2;
m_nId = 0;
m_szName[0] = '\0';
m_pnIds = NULL;
m_pnIdLengths = NULL;
m_pszNames = NULL;
m_pnNameLengths = NULL;
}
CString GetDefaultSQL()
{
return CCustomerData::m_szTableName;
}
// This method is never called when
// CRecordset::useMultiRowFetch is specified!
void DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Int(pFX, _T("Id"), m_nId);
RFX_Text(pFX, _T("Name"), m_szName, CUSTOMER_NAME_MAXLENGTH);
}
// This method is called several times
void DoBulkFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Int_Bulk(pFX, _T("Id"), &m_pnIds, &m_pnIdLengths);
RFX_Text_Bulk(pFX, _T("Name"), &m_pszNames, &m_pnNameLengths, (CUSTOMER_NAME_MAXLENGTH + 1) * 2);
}
};
UPDATE:
Spending more time on this, I have been able to write code that reads the data directly from the rowset data (in my case, from m_pnIds, m_pnIdLengths, m_pszNames and m_pnNameLengths). Perhaps that's the approach I need to take.
But the question still stands. Why can't I use GetFieldValue() on a SQL Server database? And what is the point of SetRowsetCursorPosition()?
From documentation of CRecordset::DoFieldExchange:
When bulk row fetching is not implemented, the framework calls this
member function to automatically exchange data between the field data
members of your recordset object and the corresponding columns of the
current record on the data source.
DoFieldExchange is called only if CRecordset::useMultiRowFetch is not specified in the Open function.
Looking at MFC code CRecordset::BindFieldsToColumns, dbcore.cpp using VS 2019 (14.22.27905):
// Binding depends on fetch type
if (m_dwOptions & useMultiRowFetch)
DoBulkFieldExchange(&fx);
else
DoFieldExchange(&fx);
Sounds like that behaviour your are getting is by design.
Edit:
Here is working example for multi row fetch. The thing that did the trick is CRecordset::useExtendedFetch in the opening flags.
Database:
I used MySQL with a simple table with 2 columns. Here is the creation script.
CREATE TABLE `categories` (
`CatID` int(11) NOT NULL,
`Category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`CatID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
MFC:
CMultiRowSet.h
class CMultiRowSet : public CRecordset
{
public:
CMultiRowSet(CDatabase* pDB);
virtual void DoBulkFieldExchange(CFieldExchange* pFX);
// Field/Param Data
// field data members
long* m_rgID;
LPSTR m_rgName;
// pointers for the lengths
// of the field data
long* m_rgIDLengths;
long* m_rgNameLengths;
};
CMultiRowSet.cpp
void CMultiRowSet::DoBulkFieldExchange(CFieldExchange* pFX)
{
// call the Bulk RFX functions
// for field data members
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Long_Bulk(pFX, _T("[CatID]"),
&m_rgID, &m_rgIDLengths);
RFX_Text_Bulk(pFX, _T("[Category]"),
&m_rgName, &m_rgNameLengths, 30);
}
Usage:
CDatabase database;
CString sCatID, sCategory;
TRY
{
CString connStr = (_T("Driver={MySQL ODBC 8.0 Unicode Driver};Server=localhost;Database=XXXX;User=XXX; Password=XXXX; Option = 3;"));
// Open the database
database.OpenEx(connStr,CDatabase::noOdbcDialog);
// Allocate the recordset
CMultiRowSet recset(&database);
// Execute the query
// make sure you use CRecordset::useExtendedFetch.
recset.Open(CRecordset::forwardOnly, _T("SELECT CatID, Category FROM Categories"), CRecordset::readOnly|CRecordset::useMultiRowFetch|CRecordset::useExtendedFetch);
// Loop through each record
while (!recset.IsEOF())
{
// The default `GetRowsetSize` is 25. I have 4 rows in my database.
// GetRowsFetched returns 4 in my case.
for (int rowCount = 1; rowCount <= (int)recset.GetRowsFetched(); rowCount++)
{
recset.SetRowsetCursorPosition(rowCount);
// Copy each column into a variable
recset.GetFieldValue(_T("CatID"), sCatID);
recset.GetFieldValue(_T("Category"), sCategory);
}
// goto next record
recset.MoveNext();
}
recset.Close();
// Close the database
database.Close();
}
CATCH(CDBException, e)
{
// If a database exception occured, show error msg
AfxMessageBox(_T("Database error: ") + e->m_strError);
}
END_CATCH;

Implementing bulk record fetching

At the start of my program, I need to read data from a MS Access database (.mdb) into a drop down control. This is done so that whenever the user types in that control, the application can auto-complete.
Anyway, the reading from database took forever so I thought I'd implement bulk row fetching.
This is the code I have:
CString sDsn;
CString sField;
sDsn.Format("ODBC;DRIVER={%s};DSN='';DBQ=%s",sDriver,sFile);
TRY
{
// Open the database
database.Open(NULL,false,false,sDsn);
// Allocate the rowset
CMultiRowset recset( &database );
// Build the SQL statement
SqlString = "SELECT NAME "
"FROM INFOTABLE";
// Set the rowset size. These many rows will be fetched in one bulk operation
recset.SetRowsetSize(25);
// Open the rowset
recset.Open(CRecordset::forwardOnly, SqlString, CRecordset::readOnly | CRecordset::useMultiRowFetch);
// Loop through each rowset
while( !recset.IsEOF() )
{
int rowsFetched = (int)recset.GetRowsFetched(); // This value is always 1 somehow
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
// Go to next rowset
recset.MoveNext();
}
// Close the database
database.Close();
}
CATCH(CDBException, e)
{
// If a database exception occured, show error msg
AfxMessageBox("Database error: "+e->m_strError);
}
END_CATCH;
MultiRowset.cpp looks like:
#include "stdafx.h"
#include "afxdb.h"
#include "MultiRowset.h"
// Constructor
CMultiRowset::CMultiRowset(CDatabase *pDB)
: CRecordset(pDB)
{
m_NameData = NULL;
m_NameDataLengths = NULL;
m_nFields = 1;
CRecordset::CRecordset(pDB);
}
void CMultiRowset::DoBulkFieldExchange(CFieldExchange *pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text_Bulk(pFX, _T("[NAME]"), &m_NameData, &m_NameDataLengths, 30);
}
MultiRowset.h looks like:
#if !defined(__MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__)
#define __MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__
class CMultiRowset : public CRecordset
{
public:
// Field data members
LPSTR m_NameData;
// Pointers for the lengths of the field data
long* m_NameDataLengths;
// Constructor
CMultiRowset(CDatabase *);
// Methods
void DoBulkFieldExchange(CFieldExchange *);
};
#endif
And in my database, the INFOTABLE looks like:
NAME AGE
---- ---
Name1 Age1
Name2 Age2
.
.
.
.
All I need to do is only read the data from the database. Can someone please tell me what I'm doing wrong? My code right now behaves exactly like a normal fetch. There's no bulk fetching happening.
EDIT:
I just poked around in DBRFX.cpp and found out that RFX_Text_Bulk() initializes my passed m_NameData as new char[nRowsetSize * nMaxLength]!
This means m_NameData is only a character array! I need to fetch multiple names, so wouldn't I need a 2D character array? The strangest thing is, the same RFX_Text_Bulk() initializes my passed m_NDCDataLengths as new long[nRowsetSize]. Why in the world would a character array need an array of lengths?!
According to http://msdn.microsoft.com/en-us/library/77dcbckz.aspx#_core_how_crecordset_supports_bulk_row_fetching you have to open CRecordset with CRecordset::useMultiRowFetch flag before call SetRowsetSize:
To implement bulk row fetching, you must specify the
CRecordset::useMultiRowFetch option in the dwOptions parameter of the
Open member function. To change the setting for the rowset size, call
SetRowsetSize.
You almost got it right. To fetch the values,
I would change your
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
by something like this
for( int nPosInRowset = 0; nPosInRowset < rowsFetched; nPosInRowset++ )
{
//Check if value is null
if (*(recset.m_NameDataLengths + nPosInRowset) == SQL_NULL_DATA)
continue;
CString csComboString;
csComboString = (recset.m_NameData + (nPosInRowset * 30)); //Where 30 is the size specified in RFX_Text_Bulk
m_nameDropDown.AddString(csComboString);
}
EDIT: To fetch more than one row, remove the CRecordset::forwardOnly option
EDIT 2 : You can also keep CRecordset::forwardonly, but add the CRecordset::useExtendedFetch option
Just faced the same problem.
You should use in recset.Open() call for dwOptions parameter only CRecordset::useMultiRowFetch, and not CRecordset::readOnly | CRecordset::useMultiRowFetch.
Hope this helps someone...
EDIT:- After re-check here is the situation - when using bulk recordset and opening with CRecordset::forwardOnly and CRecordset::readOnly, you must also specify CRecordset::useExtendedFetch in dwOptions. For other types of scrolling, using CRecordset::readOnly | CRecordset::useMultiRowFetch is just fine.

How Can I Call PL/pgSQL Function From C++ Code

I am trying to call a function which is declared in PostgreSQL with PL/pgSQL. For that I write the code below. My function is working but after that I am taking a "PGRES_FATAL_ERROR". Also when I changed "select removestopwords()" with an sql query like "DELETE * FROM TABLE1" it's working successfully.
I am considering, that error can cause some big problem in future even if now working. How can I call a PL/pgSQL function without taking error?
void removeStopWordsDB(PGconn* conn) {
PGresult *res = PQexec(conn, "select removestopwords()");
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
printf("removestopwords failed");
cout<<PQresultStatus(res);
PQclear(res);
exit_nicely(conn);
}
printf("removestopwords - OK\n");
PQclear(res);
}
If you get PGRES_FATAL_ERROR from PQresultStatus you should use PQresultErrorField to get all the error data from the result set to provide a useful error message. This will allow you to determine what the actual error is here (quite likely an error being sent over from the server).
Consider creating a class to hold PostgreSQL error details that can be constructed from q PQresult pointer, e.g.:
PgError(const PGresult *rs)
{
severity = GetErrorField(rs, PG_DIAG_SEVERITY);
sqlstate = GetErrorField(rs, PG_DIAG_SQLSTATE);
primary = GetErrorField(rs, PG_DIAG_MESSAGE_PRIMARY);
// ...
}
static std::string GetErrorField(const PGresult *rs, int fieldCode)
{
const char *message = PQresultErrorField(rs, fieldCode);
if (message == NULL) return "";
return std::string(message);
}
Then you can, for example, encapsulate dumping out the error to a stream in this object to provide details just like psql and friends do (although strictly speaking, you'd need the input SQL as well for all of that)
PostgreSQL API doesn't support some flag like "ignore all errors". If you would to ignore result, then just don't check result in host environment. But it is bad strategy.

App crash on access new Database

My application crashes on reading / writing data from the database. I have one database on c: and I copy-pasted and rename with different name. The following process is what I have used for copy...Please guide me if you have any suggestion or solution.
RFs fs;
fs.Connect();
CFileMan* fileMan=CFileMan::NewL(fs);
CleanupStack::PushL(fileMan);
TInt err=fileMan->Copy(anOld,aNew);
CleanupStack::PopAndDestroy(fileMan);
fs.Close();
if(err==KErrNone)
return ETrue;
else
return EFalse;
It crashes on following line when I am trying to insert or get any data from the database.
User::LeaveIfError( iDatabase.Execute( strSQL ) );
db creation:
TBool Open = OpenL();
if (!Open)
{
User::LeaveIfError(iDbSession.Connect());
CleanupClosePushL(iDbSession);
CleanupClosePushL(iDatabase);
User::LeaveIfError(iDatabase.Replace(iDbSession, iDBPath ));
// create table
_LIT(KSQLtest,"CREATE TABLE testtable(id INTEGER,test1 VARCHAR(50),test2 VARCHAR(50))"); User::LeaveIfError(iDatabase.Execute(KSQLtest));
iDatabase.Compact();
iDatabase.Close();
iDbSession.Close();
CleanupStack::PopAndDestroy();
CleanupStack::PopAndDestroy();
Open database:
User::LeaveIfError( iDbSession.Connect() );
CleanupClosePushL( iDbSession );
if ( KErrNone != iDatabase.Open(iDbSession, iDBPath))
{
iDbSession.Close();
CleanupStack::PopAndDestroy();
return EFalse;
}
else
{
CleanupClosePushL( iDatabase );
iIsDatabaseOpened = ETrue;
return ETrue;
}
User:: LeaveIfError() throws an exception when iDatabase.Execute() returns an error code.
You can find the most common Symbian error codes at NewLC
If the crash happens before RDbDatabase::Execute() is actually run, we'll need to see more code to figure out why iDatabase is in a bad state.
You need to explain what "crashes" means - an exception/leave? a panic? If so, what leave code, or what panic category and number?
If it "crashes" here
User::LeaveIfError( iDatabase.Execute( strSQL ) );
you might want to check the return value, i.e.
TInt error = iDatabase.Execute( strSQL );
//Now log/display the error
User::LeaveIfError(error);
A few other points of note:
If you use CleanupClosePushL() on an object, you don't need to call both Close() and CleanupStack::PopAndDestroy(). The latter will call Close() for you.
Your OpenL() function uses a mix of leaving and return code which is considered bad style generally. In addition, functions which leave something on the cleanup stack are generally named xxxxLC(), the trailing 'C' denoting a cleanup item.

Changing SQL Provider from SQLOLEDB.1 to SQLNCLI.1 causes app to fail when accessing data via stored procedure

I'm supporting a legacy app written in MFC/C++. The database for the app is in SQL Server 2000. We bolted on some new functionality recently and found that when we change the SQL Provider from SQLOLEDB.1 to SQLNCLI.1 some code that is trying to retrieve data from a table via a stored procedure fails.
The table in question is pretty straightforward and was created via the following script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UAllergenText](
[TableKey] [int] IDENTITY(1,1) NOT NULL,
[GroupKey] [int] NOT NULL,
[Description] [nvarchar](150) NOT NULL,
[LanguageEnum] [int] NOT NULL,
CONSTRAINT [PK_UAllergenText] PRIMARY KEY CLUSTERED
(
[TableKey] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[UAllergenText] WITH CHECK ADD CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo] FOREIGN KEY([GroupKey])
REFERENCES [dbo].[UBaseFoodGroupInfo] ([GroupKey])
GO
ALTER TABLE [dbo].[UAllergenText] CHECK CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo]
Bascially four columns, with TableKey being an identity column and everything else is populated via the following script:
INSERT INTO UAllergenText (GroupKey, Description, LanguageEnum)
VALUES (401, 'Egg', 1)
with a long list of other INSERT INTO's that follow the one above. Some of the rows inserted have special characters (like accent marks above letters) in their descriptions. I had originally thought that the inclusion of the special characters was part of the problem but if I completely clear out the table and then repopulate it with just the single INSERT INTO from above that has no special characters, it still fails.
So I moved on...
The data in this table is then accessed via the following code:
std::wstring wSPName = SP_GET_ALLERGEN_DESC;
_variant_t vtEmpty1 (DISP_E_PARAMNOTFOUND, VT_ERROR);
_variant_t vtEmpty2(DISP_E_PARAMNOTFOUND, VT_ERROR);
_CommandPtr pCmd = daxLayer::CDataAccess::GetSPCommand(pConn, wSPName);
pCmd->Parameters->Append(pCmd->CreateParameter("#intGroupKey", adInteger, adParamInput, 0, _variant_t((long)nGroupKey)));
pCmd->Parameters->Append(pCmd->CreateParameter("#intLangaugeEnum", adInteger, adParamInput, 0, _variant_t((int)language)));
_RecordsetPtr pRS = pCmd->Execute(&vtEmpty1, &vtEmpty2, adCmdStoredProc);
//std::wstring wSQL = L"select Description from UAllergenText WHERE GroupKey = 401 AND LanguageEnum = 1";
//_RecordsetPtr pRS = daxLayer::CRecordsetAccess::GetRecordsetPtr(pConn,wSQL);
if (pRS->GetRecordCount() > 0)
{
std::wstring wDescField = L"Description";
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
}
else
{
nameString = "";
}
The daxLayer is a third party data access library the application is using, though we have the source to it (some of which will be seen below.) SP__GET_ALLERGEN_DESC is the stored proc used to get the data out of the table and it was created via this script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[spRET_AllergenDescription]
-- Add the parameters for the stored procedure here
#intGroupKey int,
#intLanguageEnum int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT Description FROM UAllergenText WHERE GroupKey = #intGroupKey AND LanguageEnum = #intLanguageEnum
END
When the SQL Provider is set to SQLNCLI.1, the app blows up at:
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
from the above code snippet. So I stepped into GetField, which looks like the following:
void daxLayer::CRecordsetAccess::GetField(_RecordsetPtr pRS,
const std::wstring wstrFieldName, std::string& sValue, std::string sNullValue)
{
if (pRS == NULL)
{
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Missing recordset pointer."))
}
else
{
try
{
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
if ((tv.vt == VT_EMPTY) || (tv.vt == VT_NULL))
{
sValue = sNullValue;
}
else if (tv.vt != VT_BSTR)
{
// The type in the database is wrong.
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Field type is not string"))
}
else
{
_bstr_t bStr = tv ;//static_cast<_bstr_t>(pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value);
sValue = bStr;
}
}
catch( _com_error &e )
{
RETHROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string"), e.Description())
}
catch(...)
{
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Unknown error"))
}
}
}
The culprit here is:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Stepping into Fields->GetItem brings us to:
GetItem
inline FieldPtr Fields15::GetItem ( const _variant_t & Index ) {
struct Field * _result = 0;
HRESULT _hr = get_Item(Index, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return FieldPtr(_result, false);
}
Which then takes us to:
GetValue
inline _variant_t Field20::GetValue ( ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Value(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}
If you look at _result while stepping through this at runtime, _result's BSTR value is correct, its value is "Egg" from the "Description" field of the table. Continuing to step through traces back through all the COM release calls, etc. When I finally get back to:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
And step past it to the next line, the contents of tv, which should be BSTR="Egg" are now:
tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"
When the GetField function tries to set its return value to the value in tv.BSTR
_bstr_t bStr = tv;
sValue = bStr;
it unsurprisingly chokes and dies.
So what happened to the value of BSTR and why does it only happen when the provider is set to SQLNCLI.1?
For the heck of it, I commented out using the stored procedure in the topmost code and just hard coded the same SQL SELECT statement that the stored procedure uses and found that it works just fine and the value returned is correct.
Also, it's possible for users to add rows to the table through the application. If the application creates a new row in that table and retrieves that row via stored procedure, it also works correctly unless you include a special character in the description in which case it correctly saves the row but blows up again in the exact same way as above upon retrieval of that row.
So to summarize, if I can, rows put into the table via the INSERT script ALWAYS blow up the app when they are accessed by stored procedure (regardless of whether they contain any special characters). Rows put into the table from within the application by the user at runtime are retrieved correctly via stored procedure UNLESS they contain a special character in the Description, at which point they blow up the app. If you access any of the rows in the table by using SQL from the code at runtime instead of the stored procedure it works whether there is a special character in the Description or not.
Any light that can be shed on this will be greatly appreciated, and I thank you in advance.
This line might be problematic:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
If I read it right, ->Value returns a _variant_t, which is a smart pointer. The smart pointer will release its variant when it goes out of scope, right after this line. However, tagVARIANT is not a smart pointer, so it won't increase the reference count when it is assigned to. So after this line, tv might point to a variant which has effectively been released.
What happens if you write the code like this?
_variant_t tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Or alternatively, tell the smart pointer not to release its payload:
_tagVARIANT tv = pRS->Fields->GetItem(
_variant_t(wstrFieldName.c_str()))->Value.Detach();
It's been a long time since I coded in C++, and reading this post, I don't regret moving away!