QtSqlQuery next/first returns false - c++

I have what I think is a very simple case, but I don't understand why it fails.
Database used is sqlite3, linux platform. Qt 5.3.
I have this table: CREATE TABLE db_info (name TEXT, value TEXT);
Containing this data (output from sqlite3 client):
version|1
created|2015-01-16 11:06:12
Yes, it's very simple.
The purpose is to check if the DB is created with another version of the application. So I compare the version in the DB with a compiled constant in the code.
Thus, during startup, I do this (trimmed for brevity):
_db = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "mydb"));
_db->setDatabaseName(dbFile);
_db->open();
QSqlQuery query(*_db);
query.prepare("SELECT value FROM db_info WHERE name = 'version' AND value = :version");
query.bindValue(":version", DB_VERSION);
query.exec();
if(query.first())
// same version
else
// some other version
Not very complicated stuff...
Now to the specific problem:
_db->open() returns true
query.prepare() returns true
query.isSelect() returns true
query.exec() returns true
After exec():
query.isActive() returns true
query.size() returns -1
query.first() or query.next() returns false
What could be the problem?
According to the dox (http://doc.qt.io/qt-5/qsqlquery.html#first), first() returns false if the query is not active or it's not a select, so that can't be it.
But I guess, since size() returns -1 there definitely is something amiss somewhere...
I've tried adding some calls to lastError() but it's always empty. Both from query and from _db. It there some other means of getting some mesages from the database, or driver or some such?
I've also tried removing the WHERE clause. No difference.
Running the exact same query in the sqlite3 client works fine.

Related

Can't retrieve identity field after insert in sql database

Using C++ Qt framework and sql server 2008 I've been trying to insert a record into a table with an identity field and retrieve the identity value. Below is a simplified code sample which actually does do the insert but just doesn't retreive the identity. The identity returned from query.value(0) is an invalid QVariant.
QSqlQuery query(*pConn);
query.prepare("insert into [VH_MANUFACTURER] values ('sRRS test man code','RRS type','RRS logo file',1,'RRS SEO para','RRS description','RS');"
"select SCOPE_IDENTITY();");
if(query.exec())
{
if(query.next())
{
QVariant identity = query.value(0);
int id=identity.toInt();
}
}
I've tried using select ##identity instead of scope_identity with no improvement and also QSqlQuery .lastInsertId() which also returns an invalid QVariant, see below.
bool bFeature = pConn->driver()->hasFeature(QSqlDriver::LastInsertId);
QSqlQuery query(*pConn);
query.prepare("insert into [VH_MANUFACTURER] ([MFG_NAME],[MFG_TYPE],[MFG_LOGO],[MFG_ACTIVE],[MFG_SEO_CONTENT],[MFG_DESCRI],[MFG_CAPMANCODE]) values ('sRRS test man code','RRS type','RRS logo file',1,'RRS SEO para','RRS description','RS')");
if(query.exec())
{
QVariant id=query.lastInsertId();
}
hasFeature returns true, so the driver is supposed to support what I'm trying to do.
Just to test the sql , I ran the sql directly through Sql Server Management Studio and it inserts as expected and returns the identity value correctly.
Finally found a work around using OUTPUT clause in sql. I don't exactly know why the other methods I tried don't work. There is a sql server bug associated with this feature but that doesn't explain why it worked in ssms but not in c++ Qt code. The sample below show's the work around. Here's the reference I used to solve this.
bool bFeature = pConn->driver()->hasFeature(QSqlDriver::LastInsertId);
QSqlQuery query(*pConn);
query.prepare("insert into [VH_MANUFACTURER] ([MFG_NAME],[MFG_TYPE],[MFG_LOGO],[MFG_ACTIVE],[MFG_SEO_CONTENT],[MFG_DESCRI],[MFG_CAPMANCODE]) OUTPUT INSERTED.MFG_ID values ('sRRS test man code','RRS type','RRS logo file',1,'RRS SEO para','RRS description','RS')");
if(query.exec())
{
if(query.next())
{
QVariant id=query.value("MFG_ID");
}
}

Qt and SQLite, error when executing query, without error message

I am using Qt 5.4 and SQLite to to database operations, all other requests work, but this one doesn't seem to work and gives no error.
QSqlQuery query(Globals::db);
QString cmd = "Update VideoFile set isVisible = 0 WHERE Id =1;";
if(query.prepare(cmd))
qDebug("Prepare success..."); //<-- prints out
if (!query.exec()) {
qDebug("Error occurred querying.");
qDebug("%s.", qPrintable(Globals::db.lastError().text())); //<<-- prints out blank
}
Tried so far:
o) Database: exists!
o) Query Prepare() yields True
o) When executing the same Statement in SQLite Browser. Works!
i read about a problem here:, that some databases have a certain delay.
"Portability note: Some databases choose to delay preparing a query until it is executed the first time. In this case, preparing a syntactically wrong query succeeds, but every consecutive exec() will fail." But apparently, the query is not syntactically wrong.

Correct way to use QVariant when extracting data from a database

I have recently begun working with QT and I'm having a small issue. I cant seem to understand how QVariant works. I've been going through the help and looking online but it is just not sinking in. In my project i am trying to populate a combo box with a list of manufacturers from a database. I have opened the database and pulled out the entries but I rename them all manValue. Now I understand why this is happening, the problem is I do not understand how to properly use QVariant to get the result I want. Originally i thought "manValue" was going to be the identifier for the string that held the actual value from the database but instead it reads the value from the database and makes sure it is not null, and then renames it. I already tried making a string before I assign the QVariant with any properties and assigning it the text i received from the database, and then inserting that string where manValue is but still no luck. Any help would be greatly appreciated. Sorry, I know this is going to be simple I'm just a noob and the help docs often confuse me.
QSqlDatabase db = QSqlDatabase::addDatabase("QODBC");
db.setHostName("LOCALHOST\\TestERPServer");
db.setDatabaseName("TestERPConnection");
if (db.open())
{
QMessageBox::information(this,"Connected","Connection to the Database was Established\n"
"\nStatus: Connected");
QSqlQuery mfrQry;
if (mfrQry.exec("SELECT * FROM erp_data.manufacturers;"))
{
if (mfrQry.value(1) == "")
{
QMessageBox::information(this,"No Connection","Nothing in the Manufacturer Database\n"
"\nError: " + db.lastError().text());
}
else
{
while (mfrQry.next())
{
ui->mfrComboBox->addItem("manValue",QVariant(mfrQry.value(1)));
}
}
}
else
{
QMessageBox::information(this,"No Connection","Connection to the Manufacturer Database could not be Established\n"
"\nError: " + db.lastError().text());
}
}
else
{
QMessageBox::information(this,"No Connection","Connection to the Database could not be Established\n"
"\nError: " + db.lastError().text());
}
The first problem in the provided code has to do with the way you manipulate the QSqlQuery.
Successfully executed SQL statements set the query's state to active
so that isActive() returns true. Otherwise the query's state is set to
inactive. In either case, when executing a new SQL statement, the
query is positioned on an invalid record. An active query must be
navigated to a valid record before values can be retrieved.
In order to move to a valid record you have to use one of the following:
next()
previous()
first()
last()
seek()
Once you are on a valid record you have to use the value() function in order to take the column you want. The returned value is QVariant so you need to convert to the desired type using one of the many toSomething functions QVariant provides.
So in your case the code should look like this:
QSqlQuery mfrQry;
if (mfrQry.exec("SELECT * FROM erp_data.manufacturers;"))
{
// This will loop through all records returned by the query
while (mfrQry.next()) {
// mfrQry.value(COLID) returns a QVariant containing the data of the current
// record in column COLID.
// Using toString we convert it to String
QString stringValue = mfrQry.value(COLID).toString();
// Now handle the QString the way you want...
}
}

Null Reference Excepion when saving data on subsonic 3

I am testing subsonic 3, i can query my database but when i am inserting a record i have an exception. Here is my code:
Client lClient = new Client();
lClient.Name = "Peter";
lClient.FullName = "Richards";
lCliente.Save();
And i have a null reference exception on this generated code:
var newKey=_repo.Add(this,provider);
Any help is appreciated.
I am using ActiveRecords
I had a similar problem, with code not unlike the following:
var pending = myTable.SingleOrDefault(x => x.Id == 1);
if (pending == null)
pending = new myTable();
pending.Id = 1;
pending.MyDate = DateTime.Now;
pending.MyString = someString;
pending.Save();
This worked the first time I ran it, on an empty table, but not the second time when updating. I got a nullreference exception somewhere inside the subsonic repository. The solution was to add a primary key, as Rob suggested.
Just thought I'd mention it (thanks Rob).
Where does the null reference exception actually happen? Is _repo null?
Try and double check that you don’t have any columns in the “Client” table which are not nullable and you don’t set any value.
Also make sure you PK is handled correctly (check you have the [Property.IsForeignKey=true;] attribute)
Do you have NullReferenceException Checked in (file menu) Debug -> Exceptions (press Find and type NullReferenceException, press OK)?
I downloaded the SubSonic code and used it as reference for my project, the exception is thrown in SubSonic.Core\Repository\SubSonicRepository.cs:209 because prop is not checked for null at line 208, and any exceptions are swallowed as evidenced by line 213.
What happens is visual studio breaks on all the exceptions checked in the mentioned dialog. So in one way, this is the expected behaviour from the code.
What actually causes prop to become null, in my case, the table field name is lower-case, but the property is actually cased properly.
From Immediate:
tbl.PrimaryKey.Name
"playerinformationid"
item.GetType().GetProperties()
{System.Reflection.PropertyInfo[11]}
[0]: {System.Collections.Generic.IList`1[SubSonic.Schema.IColumn] Columns}
// *snip*
[5]: {Int32 PlayerInformationid}
// *snip*
item.GetType().GetProperty("PlayerInformationid")
{Int32 PlayerInformationid} // Works!
item.GetType().GetProperty(tbl.PrimaryKey.Name)
null
I hacked this together to "just make it work" (Warning: newbie at 3.5 stuff)
-var prop = item.GetType().GetProperty(tbl.PrimaryKey.Name);
+var lowerCasedPrimaryKeyName = tbl.PrimaryKey.Name.ToLowerInvariant();
+var prop = item.GetType().GetProperties().First<System.Reflection.PropertyInfo>(x => x.Name.ToLowerInvariant() == lowerCasedPrimaryKeyName);
Does your table have a PrimaryKey? It doesn't sound like it does. If not - this is your problem.

How Do You Call an MSSQL System Function From ADO/C++?

...specifically, the fn_listextendedproperty system function in MSSQL 2005.
I have added an Extended Property to my database object, named 'schemaVersion'. In my MSVC application, using ADO, I need to determine if that Extended Property exists and, if it does, return the string value out of it.
Here is the T-SQL code that does what I want. How do I write this in C++/ADO, or otherwise get the job done?
select value as schemaVer
from fn_listextendedproperty(default, default, default, default, default, default, default)
where name=N'schemaVersion'
Here's the code I tried at first. It failed with the error listed below the code:
_CommandPtr cmd;
cmd.CreateInstance(__uuidof(Command));
cmd->ActiveConnection = cnn;
cmd->PutCommandText("select value "
"from fn_listextendedproperty(default, default, default, default, default, default, default) "
"where name=N'schemaVersion'");
VARIANT varCount;
cmd->Execute(NULL, NULL, adCmdText);
...here are the errors I peeled out of the ADO errors collection. The output is from my little utility function which adds the extra text like the thread ID etc, so ignore that.
(Proc:0x1930, Thread:0x8A0) INFO : === 1 Provider Error Messages : =======================
(Proc:0x1930, Thread:0x8A0) INFO : [ 1] (-2147217900) 'Incorrect syntax near the keyword 'default'.'
(Proc:0x1930, Thread:0x8A0) INFO : (SQLState = '42000')
(Proc:0x1930, Thread:0x8A0) INFO : (Source = 'Microsoft OLE DB Provider for SQL Server')
(Proc:0x1930, Thread:0x8A0) INFO : (NativeError = 156)
(Proc:0x1930, Thread:0x8A0) INFO : ==========================================================
EDIT: Updated the call according to suggestions. Also changed "SELECT value AS schemaVer" to just "SELECT value".
EDIT: Changed the first parameter of Execute() to NULL per suggestion. This fixed my original problem, and I proceeded to the next. :)
Try specifying NULL rather than default for each parameter of fn_listextendedproperty. This should hopefully then execute without errors, just leaving you to retrieve the result as your next step.
I still have not figured out how to do this directly. To get on with my life, I wrote a stored procedure which called the function:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[mh_getSchemaVersion]
#schemaVer VARCHAR(256) OUTPUT
AS
select #schemaVer = CAST( (select value from fn_listextendedproperty(default, default, default, default, default, default, default) where name=N'schemaVersion') AS varchar(256) )
return ##ROWCOUNT
...and then called thst sproc from my ADO/C++ code:
_CommandPtr cmd;
cmd.CreateInstance(__uuidof(Command));
cmd->ActiveConnection = cnn;
cmd->PutCommandText("mh_getSchemaVersion")_l
_variant_t schemaVar;
_ParameterPtr schemaVarParam = cmd->CreateParameter("#schemaVer", adVarChar, adParamOutput, 256);
cmd->GetParameters()->Append((IDispatch*)schemaVarParam);
cmd->Execute(NULL, NULL, adCmdStoredProc);
std::string v = (const char*)(_bstr_t)schemaVarParam->GetValue();
ver->hasVersion_ = true;
...which works, but I didn't want to have to deploy a new stored procedure.
So if anyone can come up with a solution to the original problem and show me how to call the system function directly from ADO/C++, I will accept that as the answer. Otherwise I'll just accept this.