How to declare an empty rowset properly with SOCI? - c++

Imagine that I have the following function. In case of invalid parameters or exception, the function has to exit with an empty rowset.
rowset<row> SelectAllFromTable(string tableName)
{
session sql(odbc, "...");
// if parameters are not valid -> return empty rowset<row>
if (tableName == "")
{
// query that returns 0 result
rowset<row> res = (sql.prepare << "SELECT ID FROM T1 WHERE ID = -9999");
return res;
}
string query = "SELECT * FROM " + tableName;
try
{
rowset<row> rs = sql.prepare << query;
return rs;
}
catch (exception const &e)
{
cerr << "Error: " << e.what() << endl;
// query that returns 0 result
rowset<row> res = (sql.prepare << "SELECT ID FROM T1 WHERE ID = -9999");
return res;
}
// query that returns 0 result
rowset<row> res = (sql.prepare << "SELECT ID FROM T1 WHERE ID = -9999");
return res;
}
The solution I wrote above works but my question is : Is there a better way to return an empty rowset with SOCI ?

Since the documentation hasn't much to offer to this I looked into the rowset Header: There is no default constructor for it and no public method to set the iterators, ergo you can't get an empty rowset by yourself.
Despite why don't you use exceptions which are just perfect for that case. Just don't catch the soci_error exception, then the caller SelectAllFromTable could catch it. This would have many advantages:
The caller would know if there is really no data in the table or there is no table
The caller could know why he can't use the table (misspelled or security reasons)
The caller could know if there are other troubles and take action or if not, rethrow it, so his caller might can.

Related

How to use a string in a Sql argument?

How should go about inserting a string into a SQL argument?
Something like this:
string clas = "Computer Science";
sql = "SELECT * from STUDENTS where CLASS='clas'";
There are two ways of doing this:
This is the preferred and more secure way. You can use prepared statements like this
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class=?";
// Prepare the request right here
preparedStatement.setString(1, clas);
// Execute the request down here
A simpler but much less secure option (it's vulnerable to SQL-Injections)
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class='" + clas + "'";
Simple answer:
You can just do as follows:
string clas = "Computer Science";
sql = "SELECT * FROM Students WHERE Class='" + clas + "'";
Good answer:
But, we can do better than that. What if multiple value replacement needed, then what? See the code below, it can replace multiple strings. And also, you can write sql injection check if needed. And the best thing, you just have to call the prepare() function and you're done.
Usage Instructions:
Use ? where you need to put a string. If there are multiple string replacement needed, put all the strings in order(as parameters) when calling prepare function. Also, notice prepare function call prepare(sql, {param_1, param_2, param_3, ..., param_n}).
[Note: it'll work with c++11 and higher versions. It won't work with c++11 pre version. So, while compile it, use -std=c++11 flag with g++]
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
// write code for sql injection if you think
// it necessary for your program
// is_safe checks for sql injection
bool is_safe(string str) {
// check if str is sql safe or not
// for sql injection
return true; // or false if not sql injection safe
}
void prepare(string &sql, initializer_list<string> list_buf) {
int idx = 0;
int list_size = (int)list_buf.size();
int i = 0;
for(string it: list_buf) {
// check for sql injection
// if you think it's necessary
if(!is_safe(it)) {
// throw error
// cause, sql injection risk
}
if(i >= list_size) {
// throw error
// cause not enough params are given in list_buf
}
idx = sql.find("?", idx);
if (idx == std::string::npos) {
if(i < list_size - 1) {
// throw error
// cause not all params given in list_buf are used
}
}
sql.replace(idx, 1, it);
idx += 1; // cause "?" is 1 char
i++;
}
}
// now test it
int main() {
string sql = "SELECT * from STUDENTS where CLASS=?";
string clas = "clas";
prepare(sql, {clas});
cout << sql << endl;
string sql2 = "select name from class where marks > ? or attendence > ?";
string marks = "80";
string attendence = "40";
prepare(sql2, {marks, attendence});
cout << sql2 << endl;
return 0;
}
[P.S.]: feel free to ask, if anything is unclear.

Is the return of last_insert_id always correct?

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.

MySQL C++ query Access Violation

I have a DLL where I make a connection to a MySQL database. I have Open(), Close(), Update(), and Find() functions. The Update() functions inserts data into a table and this works just fine. The Find() function however is just doing a simple query against the same table. When I call the resultset getXX() function I'm getting an Access Violation error and I can't figure out why. What am I missing? Note the query is a view and not a direct table but I wouldn't think that would matter.
MT4_EXPFUNC int __stdcall Find(char* pair)
{
try
{
sql::Statement *stmt;
sql::ResultSet* res;
string p = pair;
string buysell = "";
string qry = "select * from forex.GPBUSD_CURRENT_PRICE";
stmt = _connection->createStatement();
res = stmt->executeQuery(qry);
// if we have a record it means we have a trade chance
if(res->next())
{
buysell = res->getString(1); // ACCESS VIOLATION ERROR HERE
}
// clean up
delete res;
delete stmt;
if(buysell == "SELL")
return 1;
else if(buysell == "BUY")
return 2;
else
return 0;
}
catch(sql::SQLException &e)
{
return -1;
}
}
have you considered if getString is a zero indexed method? Or if it's a null datatype your trying to access? or shoot just because res->next() works and doesn't crash, doesn't mean that res is a valid ptr.

MySQL Transactions using C++?

How would I go about wrapping an amount of queries in a transaction in C++? I'm working on Ubuntu 10, using this file:
#include "/usr/include/mysql/mysql.h"
with C++ to interact with a MySQL database.
EDIT: Right now I'm running queries through a small wrapper class, like so:
MYSQL_RES* PDB::query(string query)
{
int s = mysql_query(this->connection, query.c_str());
if( s != 0 )
{
cout << mysql_error(&this->mysql) << endl;
}
return mysql_store_result(this->connection);
}
MYSQL_ROW PDB::getarray(MYSQL_RES *res)
{
return mysql_fetch_row( res );
}
// example one
MYSQL_RES res = db->query( "SELECT * FROM `table` WHERE 1" );
while( MYSQL_ROW row = db->getarray( res ) )
{
cout << row[0] << endl;
}
If you use MySQL++, you get RAII transaction behavior with Transaction objects:
mysqlpp::Connection con( /* login parameters here */ );
auto query = con.query("UPDATE foo SET bar='qux' WHERE ...");
mysqlpp::Transaction trans(con);
if (auto res = query.execute()) {
// query succeeded; optionally use res
trans.commit(); // commit DB changes
}
// else, commit() not called, so changes roll back when
// 'trans' goes out of scope, possibly by stack unwinding
// due to a thrown exception.
You could always just run START TRANSACTION / COMMIT / ... manually.
Another way would be to create a wrapper class which runs START TRANSACTION in the constructor, provides commit/rollback functions and, depending on your use case, does a rollback upon destruction.

Inserting multiple rows in an Oracle database with OCCI

This is my first experience so OCCI so I beg your pardon if the answer is obvious.
I have a Statement object, created the usual way:
string sqlStatement = "INSERT INTO data_tab VALUES(:id, :name, :number, :when)";
m_stmt = m_conn->createStatement(sqlStatement);
m_stmt->setMaxIterations(100);
Then I need to loop over a few objects that I should insert into the database:
for(size_t i = 0; i < data.size(); ++i)
{
m_stmt->setInt(1, i);
try {
m_stmt->setString(2, data[i].client());
}
catch(SQLException& e)
{
cerr << "setString(): " << e.what();
exit(1);
}
m_stmt->setDouble(3, data[i].number());
m_stmt->setDate(4, data[i].when());
// ...
// Checks if maxIterations is lesser than data size,
// oteherwise calls executeUpdate and other kind of
// boilerplate code.
// ...
m_stmt->addIteration();
}
But the code borks in the setString method with the following output:
setString(): ORA-32132: maximum iterations cannot be changed
I believe I should get this error is I call setMaxIterations after a setXXX method but I don't do this. What I am doing wrong?
You should invoke setMaxParamSize() prior to setting a variable length parameter:
m_stmt->setMaxParamSize(2, 1000) // or whatever is the max length of your string.