I'm using Qt 4.8.3 and MySQL ODBC 3.51 Driver. When my transaction fails because of a duplicate unique Id in the second table the insert in the first table is not rolled back. Can anyone spot the error?
struct Property //see OMG's Property Service
{
std::string name;
boost::any value;
Property();
Property(const std::string &inName, const boost::any &inValue):name(inName),value(inValue){}
};
Property myFunction(QSqlDatabase &db, int amount)
{
assert(db.driver()->hasFeature(QSqlDriver::Transactions) == true);
db.transaction();
QSqlQuery query(db);
Property ret("MyPropertyTag",0);
try{
query.exec("LOCK TABLES table1 WRITE, table2 WRITE");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
for(int i=0;i<amount;i++)
{
query.exec("INSERT INTO table1 (someUniqueValue, newestId, Created) VALUES ('"+QString::number(i)+"', '1', NOW())");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
query.exec("SELECT id FROM table1 WHERE someUniqueValue = '"+QString::number(i)+"'");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
if(query.next() == false) { throw std::exception("no result for insert id"); }
auto Id1 = query.value(0).toString();
query.exec("INSERT INTO table2 (table1_id, Changed, Created) VALUES ('"+Id1+"', NOW(), NOW())");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
query.exec("SELECT Id, table1_id FROM table2 ORDER BY Id DESC LIMIT 1");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); } //lets say this throws
if(query.next() == false || query.value(1).toString() != Id1) { throw std::exception("no result for inserted id"); }
auto Id2 = query.value(0).toString();
query.exec("UPDATE table1 SET newestId = '"+Id2+"' WHERE Id = '"+Id1+"'");
if(query.lastError().isValid()) { throw std::exception(query.lastError().text().toAscii()); }
}
db.commit();
ret.value = amount;
} catch(std::exception &e) {
query.finish();
db.rollback();
ret.value = std::string("error:") + e.what();
}
query.exec("UNLOCK TABLES");
query.finish();
return ret;
}
Your Tables problably are Myisam, changed them to Innodb to allow transactions.
To change the AutoCommit (I'm not sure if it's necessary) you can try it this way:
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
but this will be working only with the current connection, if you need to apply to all the DB you need to change the DB variables.
Related
I'm using MySQL connector C 6.0.2, I need to select Table1 and read some values there then switch to table2 and read the values there too etc, I have more than two tables I need to switch from table to table. How can I do this ?
example code:
connect=mysql_real_connect(connect,SERVER,USER,PASSWORD,DATABASE,0,NULL,0);
if(connect)
{
MYSQL_RES *res_set;
MYSQL_ROW row;
////table1
mysql_query(connect,"SELECT * FROM `Table1` WHERE `Column2`='1234'");
unsigned int i = 0;
res_set = mysql_store_result(connect);
unsigned int numrows = mysql_num_rows(res_set);
if(numrows==0)
{
return false;
}else
{
while ((row = mysql_fetch_row(res_set)) != NULL)
{
if(strcmp(row[2], "true")==NULL)
{/////////Here I need to read or get the values from Table2
///Select table two
}else
return false;
}
}
}
Update: I think I have solved it, it was pretty simple
connect=mysql_real_connect(connect,SERVER,USER,PASSWORD,DATABASE,0,NULL,0);
if(connect)
{
MYSQL_RES *res_set;
MYSQL_ROW row;
////table1
mysql_query(connect,"SELECT * FROM `Table1` WHERE `Column2`='1234'");
res_set = mysql_store_result(connect);
unsigned int numrows = mysql_num_rows(res_set);
if(numrows==0)
{
return false;
}else
{
while ((row = mysql_fetch_row(res_set)) != NULL)
{
if(strcmp(row[2], "true")==NULL)
{/////////Here I need to read or get the values from Table2
///Select table two
MYSQL_RES *res_set2;
////table2
mysql_query(connect,"SELECT * FROM `Table2` WHERE `Column2`='1234'");
res_set2 = mysql_store_result(connect);
unsigned int numrows2 = mysql_num_rows(res_set2);
if(numrows2==0)
{
//no result
}else
{
//do something
}
}else
return false;
}
}
}
You have the res_set variable that you got from your query; you'd get another variable from your second query. You don't switch between tables, you just get the right values from the right query results.
Also note that this sounds a lot like you should be doing this inside your SQL query, and not inside your code, but that entirely depends on what you want to do.
I suggest you play with your SQL statement to return the result sets from both tables.
Research the "SELECT JOIN" statement.
The rule of thumb is to have the data perform most of the database work, including searching multiple tables.
If I have the 2 following functions:
int AccessDb::InsertColValue(string tableName, string col, string val)
{
try
{
sql::Statement *stmt;
bool ret;
if ((nomTable != "") && (col != "") && (val != ""))
{
string query = "INSERT INTO " + tableName + "(" + col + ") values (";
query += val + ");";
stmt = con->createStatement();
ret = stmt->execute(query);
}
delete stmt;
return 0;
}
catch (sql::SQLException &e)
{
return -1;
}
}
and
long AccessDb::LastInsertId()
{
try
{
sql::Statement *stmt;
sql::ResultSet *res;
string query = "SELECT LAST_INSERT_ID() AS LAST_ID";
stmt = con->createStatement();
res = stmt->executeQuery(query);
delete stmt;
long lastId;
while (res->next())
{
lastId = res->getInt("LAST_ID");
}
return lastId;
}
catch (sql::SQLException &e)
{
return -1;
}
}
Can I be sure that the return of LastInsertId() will always give me the correct id if I write the following lines and if the id is auto generated by the database?
AccessDb adb; // initialize the connexion with the db
int ret = adb.InsertColValue("people", "name", "John");
if (ret == 0)
long lastId = adb.LastInsertId();
If the previous code is called somewhere else at the same time, can I have a wrong value in my lastId variable ? If yes, do I have to use locks and unlocks on my table to avoid that or another solution ?
Here's what the docs says:
The ID that was generated is maintained in the server on a
per-connection basis. This means that the value returned by the
function to a given client is the first AUTO_INCREMENT value generated
for most recent statement affecting an AUTO_INCREMENT column by that
client. This value cannot be affected by other clients, even if they
generate AUTO_INCREMENT values of their own. This behavior ensures
that each client can retrieve its own ID without concern for the
activity of other clients, and without the need for locks or
transactions.
So, unless your own code on the client is sharing a connection between several threads (Which it looks like you're not, since there are no mutexes or locks in your code) you can be sure SELECT LAST_INSERT_ID() isn't mixed up with any other connection or client.
I can't find the docs for the C++ mysql library but verify what the return value of ret = stmt->execute(query); in your InsertColValue() function means, such that you're sure the only possible way that you fail to insert anything is when an exception is thrown.
Have database. Have query with unknown count of columns. Need to put all answer in QList
database = QSqlDatabase::addDatabase("QMYSQL");
...
QString sql = "Select * from test";
QSqlQuery query = QSqlQuery(database);
query.exec(sql);
QList<QStringList> retList;
Use .isValid() on value.
while (query.next()) {
int count = 0;
bool flagValues = true;
QStringList row;
while(flagValues)
{
QVariant value = query.value(count);
if(value.isValid() && !(count == memCount) )
{
count++;
row.append(value.toString());
}
else
{
flagValues = false;
}
}
retList.append(row);
All is ok, but i have a messages (not error) like in every row. :
QMYSQLResult::data: column 3 out of range
I do not want to use additional query (like information_schema) to know columns number.
Use query.record().count() to obtain the number of columns. Thus:
database = QSqlDatabase::addDatabase("QMYSQL");
...
QString sql = "Select * from test";
QSqlQuery query = QSqlQuery(database);
query.exec(sql);
const int memCount = query.record().count();
// query loop goes here
I want to get the total number of rows in a table. How can I achieve this?
I wrote the code as follows:
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("Dictionary");
db.setUserName("root");
db.setPassword("cinesoft");
bool ok = db.open();
int ID;
//SELECT COUNT(*) FROM icecream
QString IDnosql="SELECT * FROM Dictionary.TABLE_ENGLISH";
if(ok)
{
QSqlQuery IDquery(db);
IDquery.prepare(IDnosql);
int Id=IDquery.numRowsAffected();
IDquery.exec();
// int Id=IDquery.numRowsAffected();
QMessageBox::information(0,"sucess",QString::number(Id));
}
I use the count command. I want to get the total no of rows in my table and store to an integer variable.
You can prepare a QSqlQuery with the COUNT command:
QSqlQuery q;
q.prepare(QString("SELECT COUNT (*) FROM %1").arg(tableName));
q.exec();
int rows= 0;
if (q.next()) {
rows= q.value(0).toInt();
}
Check QSqlQuery for more details
Why don't you just use the query SELECT COUNT(*) from Dictionary.TABLE_ENGLISH - this will give you the number of rows in the table. Then get this value from the result set and store it in an integer variable.
When using a COUNT statement, you can use QSqlQuery::value( int index ) like when selecting a single value:
QString IDnosql="SELECT COUNT( * ) FROM Dictionary.TABLE_ENGLISH";
if(ok)
{
QSqlQuery IDquery(db);
IDquery.prepare(IDnosql);
if( !IDquery.exec() )
{
// TODO: perform error handling here!
}
if( !IDquery.next() )
{
// TODO: Error handling: Query did not return a result (no row, which should not be possible when using a count statement as you would always get 1 row)
}
int Id = IDquery.value( 0 ).toInt();
QMessageBox::information(0,"sucess",QString::number(Id));
}
To get the total number of rows in a table A you simply
SELECT count(*) from A;
Then you need to convert the result to an integer.
Hi im using Connector/C++ and executing simple 2 sql commands like this :
the first select sql run ok but the second one cause this exception error :
ERR: Commands out of sync; you can't run this comman d now (MySQL
error code: 2014, SQLState: HY000 )
here is the code :
//member of the class
ResultSet *temp_res;
// in different method
m_driver = get_driver_instance();
m_con = m_driver->connect(m_DBhost,m_User,m_Password);
m_con->setSchema(m_Database);
//here i excute the querys :
vector<string> query;
query.push_back("SELECT * FROM info_tbl");
query.push_back("INSERT INTO info_tbl (id,name,age)VALUES (0,foo,36)");
query.push_back("SELECT * FROM info_tbl");
ResultSet *res;
Statement *stmt;
bool stmtVal = false;
try{
stmt = m_con->createStatement();
for(size_t i = 0;i < querys.size();i++)
{
string query = querys.at(i);
stmtVal = stmt->execute(query);
if(!stmtVal)
{
string error_log ="sql statment:";
error_log.append(query);
error_log.append(" failed!");
cout << error_log << endl;
break;
}
}
if(stmtVal)
{
if(returnSet)
{
res = stmt->getResultSet();
temp_res = res;
}
}
delete stmt;
//close connection to db
m_con->close();
} catch (sql::SQLException &e) {
......
}
UPDATE NEW CODE AS SUGGESTED ( NOT WORKING )
for(size_t i = 0;i < querys.size();i++)
{
string query = querys.at(i);
stmtVal = stmt->execute(query);
if(stmtVal)
{
if(returnSet)
{
if(stmt->getResultSet()->rowsCount() > 0)
{
res = stmt->getResultSet();
temp_res = res;
}
else
{
delete res;
}
}
else
{
delete res;
}
}
if(!stmtVal)
{
string error_log ="sql statment:";
error_log.append(query);
error_log.append(" failed!");
cout << error_log << endl;
break;
}
}
this is my simple table :
Column Type Null
id int(10) No
name varchar(255) No
age int(10) No
You can't have more than one active query on a connection at a time.
From the mysql_use_result docs:
You may not use mysql_data_seek(), mysql_row_seek(), mysql_row_tell(), mysql_num_rows(), or mysql_affected_rows() with a result returned from mysql_use_result(), nor may you issue other queries until mysql_use_result() has finished.
That's not exactly what you're using, but the problem is the same - you'll need to finish processing the first ResultSet and clean it up before you can issue any other query on that connection.
I was getting the same error until I changed my code to how MySQL says to do it.
Old code:
res.reset(stmt->getResultSet());
if (res->next())
{
vret.push_back(res->getDouble("VolumeEntered"));
vret.push_back(res->getDouble("VolumeDispensed"));
vret.push_back(res->getDouble("Balance"));
}
New code without error:
do
{
res.reset(stmt->getResultSet());
while(res->next())
{
vret.push_back(res->getDouble("VolumeEntered"));
vret.push_back(res->getDouble("VolumeDispensed"));
vret.push_back(res->getDouble("Balance"));
}
} while (stmt->getMoreResults());
I ran into this problem also and took me a little while to figure it out. I had even set the "CLIENT_MULTI_RESULTS" and "CLIENT_MULTI_STATEMENTS" with no avail.
What is happening is MySql thinks that there is another result set waiting to be read from the first call to the Query. Then if you try to run another Query, MySql thinks that it still has a ResultSet from last time and sends the "Out of Sync" Error.
This looks like it might be a C++ Connector issue but I have found a workaround and wanted to post it in case anyone else is having this same issue:
sql::PreparedStatement *sqlPrepStmt;
sql::ResultSet *sqlResult;
int id;
std::string name;
try {
//Build the Query String
sqlStr = "CALL my_routine(?,?)";
//Get the Result
sqlPrepStmt = this->sqlConn->prepareStatement(sqlStr);
sqlPrepStmt->setInt(1, itemID);
sqlPrepStmt->setInt(2, groupId);
sqlPrepStmt->executeUpdate();
sqlResult = sqlPrepStmt->getResultSet();
//Get the Results
while (sqlResult->next()) {
id = sqlResult->getInt("id");
name = sqlResult->getString("name");
}
//Workaround: Makes sure there are no more ResultSets
while (sqlPrepStmt->getMoreResults()) {
sqlResult = sqlPrepStmt->getResultSet();
}
sqlResult->close();
sqlPrepStmt->close();
delete sqlResult;
delete sqlPrepStmt;
}
catch (sql::SQLException &e) {
/*** Handle Exception ***/
}