SQLite UPDATE fails to overwrite existing value - c++

I have the following problem that has me stumped. As a note to keep in mind, I am using a precompiled sqlite3.dll rather than Qt's built-in SQLite support.
Table creation code:
CREATE TABLE STUDY(
Name NOT NULL,
UserName NOT NULL REFERENCES USERS(UserName),
Description DEFAULT NULL,
PathToOsirixDB DEFAULT NULL,
PRIMARY KEY(Name, UserName)
)
The following C++ code fails to update the value in column PathToOsirixDB if it already contains a value.
It fails silently, with no error returned. That would imply that no rows are matched in the UPDATE. However, if I take the same UPDATE with valid entries for user and study to match a row and run it via either the SQLite Manager Firefox plugin, or the command line tool, it works properly.
void CStudyDatabase::SetOsirixDBForStudy(const QString user, const QString study, const QString path)
{
if (mp_db)
{
int before = sqlite3_total_changes(mp_db);
QString insert = QString("UPDATE STUDY SET PathToOsirixDB = '%1' WHERE Name = '%2' AND UserName = '%3'").arg(path, study, user);
if (!InsertData(insert))
{
int after = sqlite3_total_changes(mp_db);
if (after - before >= 1)
{
SetOsirixDB(path.toAscii().data());
emit ReturnOsirixDB(osirix_db);
}
else
{
emit DatabaseError(QString("Failed to update the target path."));
}
}
}
}
And for Insert Data
int CStudyDatabase::InsertData(const QString insert)
{
char *err_msg = 0;
int rc = sqlite3_exec(mp_db,
insert.toStdString().c_str(),
NULL,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
return rc;
}
Any insights are appreciated. Thank you.
UPDATE: added the following code to SetOsiriXDBForStudy to see if we actually find a row to update:
osirix_db = QString("");
QString query = QString("SELECT PathToOsirixDB FROM STUDY WHERE Name = '%1' AND UserName = '%2'").arg(study, user);
int rc = 0;
char *err_msg = 0;
rc = sqlite3_exec(mp_db,
query.toStdString().c_str(),
&OsirixDBCallback,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
if (!(QString(osirix_db).isEmpty()))
studyrowfound = true;
In this case, it leaves osirix_db as an empty string.
But, if I execute this function:
void CStudyDatabase::QueryOsirixDB(const QString user, const QString study)
{
int rc = 0;
char *err_msg = 0;
// we query the OrisixDB via the callback and then emit the signal if all went well
QString query = QString("SELECT PathToOsirixDB FROM STUDY WHERE Name = '%1' AND UserName = '%2'").arg(study, user);
rc = sqlite3_exec(mp_db,
query.toStdString().c_str(),
&OsirixDBCallback,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
if (QString(osirix_db).isEmpty())
emit NeedToSetOsirixDB(user, study, QString());
else
emit ReturnOsirixDB(osirix_db);
}
Then it does correctly retrieve the expected value into osirix_db.
Last update: found the problem. It was leading spaces on user and study. Had been staring at the same debugger statements for too long and glossed over the extra spaces. The failing SELECT was a big clue that there was something wrong in the construction of the SQL.

There were leading spaces on user and study. Had been staring at the same debugger statements for too long and glossed over the extra spaces. The failing SELECT was a big clue that there was something wrong in the construction of the SQL.

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;

MySQL connector PreparedStatement->setString not working C++

So, basically all this code does is search the database for a user with a certain username and password, and returns a nullptr if it doesn't find one.
Gebruiker* Gebruiker_DAO::getGebruiker(string login, string wachtwoord)
{
PreparedStatement *prep_stmt = nullptr;
ResultSet *res = nullptr;
Gebruiker* gebruiker = nullptr;
int rowcount = 0;
try {
prep_stmt = dbConn->getConnection()->prepareStatement("SELECT * FROM Gebruiker WHERE Gebruiker_login = ? AND Gebruiker_wachtwoord = ?");
prep_stmt->setString(1, login);
prep_stmt->setString(2, wachtwoord);
res = prep_stmt->executeQuery();
while (res->next()) {
gebruiker = new Gebruiker(login, res->getBoolean("Gebruiker_isAdmin"), res->getString("Bedrijf"),res->getBoolean("Gebruiker_isActief"),res->getInt("Werknemer_ID"));
}
}
catch(SQLException& e) {
console.log(e.what(), console.Error);
return nullptr;
}
return gebruiker;
}
The issue is that setString throws an "std::bad_alloc at memory location ..."
Ive noticed before that trying to put a string in an SQLString also throws an error. Until now ive used a workaround by putting stuff in SQLStrings.
SQLString schema = "groep003B";
dbConn->getInstance()->setSchema(schema);
dbConn->getInstance()->connect();
For some weird reason this DOES work...
Is there something i am doing wrong? I have seen examples where people use strings, but for some reason this does not work for me... Every time i try to put a variable std::string in an SQLString it gives a memory issue.

How to know if function 'callback' in sqlite returned something?

For example my inquiry(question?) in SQL is:
SELECT * from COMPANY where imie="John",surname="Wattson",age=31;
I use sqlite3_exec where one of the arguments is callback. I don't know if this record is in my table, and would like to know it using sqlite_exec.
What should I do?
Sorry for my English. :(
If you just want to see if a record exists in the table, then you could do it with sqlite3_exec() using a callback function like this:
int myCallback(void *pUser, int argc, char **colData, char **colNames) {
int *flag = (int*)pUser;
*flag = 1;
return 1;
}
This works because if there are no records matching the query, then the callback function is not called. By returning 1 instead of 0, we are telling SQLite that we don't want any more rows from the query results.
Then, in the function where you are making the db query:
std::string sql = "SELECT * FROM COMPANY WHERE imie='John' AND surname='Wattson' AND age=31;";
char *pSql = sql.c_str(); // char*'s are better for talking to SQLite, and prior to C++14,
// a std::string is not guaranteed to be sequential in memory,
// so 'sql[0]' may not work right
char *pError = NULL;
int fHasResult = 0;
// db is an already-opened sqlite3*
int result = sqlite3_exec(db, pSql, myCallback, &fHasResult, &pError);
if (result) {
cout<<"Error was: "<<pError;
free(pError);
}
if (fHasResult) {
cout<<"The row exists in the database.";
}
else {
cout<<"The row does not exist in the database.";
}
You could use EXISTS, your query should then look something like this;
SELECT EXISTS (SELECT * FROM COMPANY WHERE imie="John" AND surname="Wattson" AND age=31);
For another example you could take a look at this;
Valid query to check if row exists in SQLite3

Saving text in Sqlite3, c++

here is my second post in the community, excuse me if I'm forget to add something, just let me know:
I am trying to do a program in c++ able to save text (i want to save code) in a database using sqlite3. Currently I've made a wxWidget program that call some functions from a DLL and this ones interactuate with the database.
The database that I want to make is really simple, it has 3 columns in one table (id,name, ref). My problem comes when I want to save big amount of text that also contains simblos that can conflict with the sql queries (I would like to save files inside the database, for example in the "ref" column ).
I'm using mostly the sqlite3_exec function, because the functions sqlite3_prepare_v2, sqlite_bind, sqlite3_step crash me the DLL where I'm working.
My doubt: Can I directly save any text as big as I want, without taking care about if it has simbols or not? and how can I do it?.
More info: I am working in c++ with code:block(13.12) making a DLL of sqlite3 functions and using MinGW toolchain. (windows 7).
This is an example of an insert function that I'm using:
int DLL_EXPORT add_item(sqlite3* db, string tbname,string col,string item)
{
char* db_err = 0;
if (tbname==std::string()||col==std::string()||item==std::string())
throw std::invalid_argument( "stoi: invalid argument table name");
char buf[200];
sprintf(buf,"insert into %s (%s) values ('%s');", tbname.c_str(), col.c_str(),item.c_str());
int n = sqlite3_exec(db, buf, NULL, 0, &db_err);
dsperr(&db_err);
if( n != SQLITE_OK )
{
//throw something
}
return 0;
}
Thank you in advance.
Thank to CL. for the up commentary
// Add one text to a table
// The column must be specify
//
int DLL_EXPORT add_text(sqlite3* db, string tbname,string col,string id,string item)
{
char* db_err = 0;
if (tbname==std::string()||col==std::string()||item==std::string())
throw std::invalid_argument( "stoi: invalid argument table name");
char *zSQL = sqlite3_mprintf("UPDATE %q SET %q=(%Q) WHERE id=%q", tbname.c_str(),col.c_str() ,item.c_str(),id.c_str());
int n = sqlite3_exec(db, zSQL, NULL, 0, &db_err);
dsperr(&db_err);
sqlite3_free(zSQL);
if( n != SQLITE_OK )
{
// throw something
}
return 0;
}

Unable to set Reporting Services Parameters

I'm generating a reporting services report from an ASP.NET (MVC) based application but am having problems setting the parameters for the report.
I believe the issue has only occurred since we upgraded SQL Server from 2005 to 2008 R2 (and Reporting Services along with it).
The original error encountered was from calling rsExec.Render:
Procedure or function 'pCommunication_ReturnRegistrationLetterDetails'
expects parameter '#guid', which was not supplied.
Debugging the code I noticed that rsExec.SetExecutionParameters is returning the following response:
Cannot call 'NameOfApp.SQLRSExec.ReportExecutionService.SetExecutionParameters(NameOfApp.SQLRSExec.ParameterValue[],
string)' because it is a web method.
Here is the function in it's entirety:
public static bool ProduceReportToFile(string reportname, string filename, string[,] reportparams,
string fileformat)
{
bool successful = false;
SQLRS.ReportingService2005 rs = new SQLRS.ReportingService2005();
SQLRSExec.ReportExecutionService rsExec = new NameOfApp.SQLRSExec.ReportExecutionService();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
rsExec.Credentials = System.Net.CredentialCache.DefaultCredentials;
// Prepare Render arguments
string historyID = null;
string deviceInfo = null;
// Prepare format - available options are "PDF","Word","CSV","TIFF","XML","EXCEL"
string format = fileformat;
Byte[] results;
string encoding = String.Empty;
string mimeType = String.Empty;
string extension = String.Empty;
SQLRSExec.Warning[] warnings = null;
string[] streamIDs = null;
// Define variables needed for GetParameters() method
// Get the report name
string _reportName = reportname;
string _historyID = null;
bool _forRendering = false;
SQLRS.ParameterValue[] _values = null;
SQLRS.DataSourceCredentials[] _credentials = null;
SQLRS.ReportParameter[] _parameters = null;
// Get if any parameters needed.
_parameters = rs.GetReportParameters(_reportName, _historyID,
_forRendering, _values, _credentials);
// Load the selected report.
SQLRSExec.ExecutionInfo ei =
rsExec.LoadReport(_reportName, historyID);
// Prepare report parameter.
// Set the parameters for the report needed.
SQLRSExec.ParameterValue[] parameters =
new SQLRSExec.ParameterValue[1];
// Place to include the parameter.
if (_parameters.Length > 0)
{
for (int i = 0; i < _parameters.Length; i++)
{
parameters[i] = new SQLRSExec.ParameterValue();
parameters[i].Label = reportparams[i,0];
parameters[i].Name = reportparams[i, 0];
parameters[i].Value = reportparams[i, 1];
}
}
rsExec.SetExecutionParameters(parameters, "en-us");
results = rsExec.Render(format, deviceInfo,
out extension, out encoding,
out mimeType, out warnings, out streamIDs);
// Create a file stream and write the report to it
using (FileStream stream = System.IO.File.OpenWrite(filename))
{
stream.Write(results, 0, results.Length);
}
successful = true;
return successful;
}
Any ideas why I'm now unable to set parameters? The report generation works without issue if parameters aren't required.
Looks like it may have been an issue with how reporting services passes parameters through to the stored procedure providing the data. A string guid was being passed through to the report and the stored procedure expected a varchar guid. I suspect reporting services may have been noticing the string followed the guid format pattern and so passed it through as a uniqueidentifier to the stored procedure.
I changed the data source for the report from "stored procedure" to "text" and set the SQL as "EXEC pMyStoredOProcName #guid".
Please note the guid being passed in as a string to the stored procedure is probably not best practice... I was simply debugging an issue with another developers code.
Parameter _reportName cannot be null or empty. The [CLASSNAME].[METHODNAME]() reflection API could not create and return the SrsReportNameAttribute object
In this specific case it looks like an earlier full compile did not finish.
If you encounter this problem I would suggest that you first compile the class mentioned in the error message and see if this solves the problem.
go to AOT (get Ctrl+D)
in classes find CLASSNAME
3.compile it (F7)