I have a class assignment that asks us to write a C++ program that tracks spending, allows modification of the records, and returns "satisfaction" numbers about individual expenses (i.e. how good the user felt about spending that money). Our instructor has indicated that he'd like us to use sqlite3 in this program. He's given us a sample program that builds a table in sqlite3 and inputs predetermined values for the columns. This program runs just fine without issue.
What I am trying to do is modify the program to accept user inputs and store them in the sqlite3 database. This is the code I have thus far:
int main()
{
string salesDesc;
int price;
int satisf;
sqlite3 *db;
char *szErrMsg = 0;
cout << "Description of Expense: ";
cin >> saleDesc;
cout << endl;
cout << "Price: ";
cin >> price;
cout << endl;
cout << "Your Satisfaction: ";
cin >> satisf;
cout << endl;
// open database
int rc = sqlite3_open("spending_track.sqlite", &db);
if (rc)
{
cout << "Cannot open database\n";
}
else
{
cout << "Database opened successfully\n";
}
const char *pSQL[6];
pSQL[0] = "CREATE TABLE IF NOT EXISTS expenses(id INTEGER PRIMARY KEY "
"AUTOINCREMENT NOT NULL, logged TIMESTAMP DEFAULT "
"CURRENT_TIMESTAMP NOT NULL, desc VARCHAR(40), price INT,"
"satisfaction INT)";
pSQL[1] = "INSERT INTO expenses('" + string(saleDesc) + "'," price "," satisf ")";
pSQL[2] = "SELECT * FROM expenses";
pSQL[3] = "SELECT sum(satisf) FROM expenses";
// blablabla the rest of the program
When I try to compile this, I receive the following error:
error: cannot convert 'std::_cxx11::basic_string' to 'const char*' in assignment
pSQL[1] = "INSERT INTO expenses('" + string(saleDesc) + "'," price "," satisf ")";
If I change string(saleDesc) to saleDesc, I get the same error.
If I change string saleDesc; to char* saleDesc;, I receive the following error:
error: invalid operands of types 'const char[23]' and 'char*' to binary 'operator+'
pSQL[1] = "INSERT INTO expenses('" + string(saleDesc) + "'," price "," satisf ")";
I'm not sure what else to try to get this to work. I have also heard that it's a bad idea to allow users to directly input to sqlite3 tables. What would be a more "proper" way to do this?
Since this is just a class assignment, I doubt that you are going to have to worry about SQL injection attacks, so I wouldn't bother trying to sanitize your input.
Your other issue is you are confusing char*s and std::strings. The sqlite API requires you to pass it char*s so it can be used from C code, however that doesn't mean you need to use them. std::string is a wrapper for the char array, which you can get with the c_str() method. I don't think you really need to put the SQL statements in an array at the end. How about something like this:
std::string addTable = "CREATE TABLE IF NOT EXISTS expenses(id INTEGER PRIMARY KEY "
"AUTOINCREMENT NOT NULL, logged TIMESTAMP DEFAULT "
"CURRENT_TIMESTAMP NOT NULL, desc VARCHAR(40), price INT,"
"satisfaction INT)";
std::string insertExpense = "INSERT INTO expenses('" + saleDesc + "'," + std::to_string(price) "," + std::to_string(satisf) + ")";
std::string selectAllExpenses = "SELECT * FROM expenses";
Then when you want to pass it to the sqlite API, you could use c_str()
sqlite3_exec(db, addTable.c_str(), ...
Thanks everyone for the responses. I spent about an hour and a half with my professor yesterday going over this, and this actually stumped him. I eventually found a way to make this work with the array, but I want to stress that the solution I came up with is pretty much only good for this assignment. For anyone reading this with a similar problem, this method is not only messy, but also allows for SQL injection which should be avoided.
The problem, as many here have mentioned in comments, was that I was trying to stick a string into a char* array. The workaround we came up with was to add the SQL commands with the variables expanded in them directly to a string variable, like so:
string insertExpense = "INSERT INTO expenses(desc, price, satisf) VALUES ('" + saleDesc + "', "
""+ to_string(price) + ", " + to_string(satisf) + ")";
We then made that variable a c_str and assigned it to a char* variable, like so:
const char *line1 = insertExpense.c_str();
We then simply assigned this char* variable directly to the correct position in the array, like so:
const char *pSQL[6];
pSQL[0] = "CREATE TABLE IF NOT EXISTS expenses(id INTEGER PRIMARY KEY "
"AUTOINCREMENT NOT NULL, logged TIMESTAMP DEFAULT "
"CURRENT_TIMESTAMP NOT NULL, desc VARCHAR(40), price REAL,"
"satisf INT)";
pSQL[1] = line1;
pSQL[2] = "SELECT * FROM expenses";
pSQL[3] = "SELECT sum(satisf) FROM expenses";
This method correctly makes the SQL table and populates it with the correct statements as stored in their respective variables. I want to stress again that this method is both very messy and dangerous, and for anyone with a similar issue, it is probably a much better idea to use prepared statements, as others in the comments have already mentioned. Thank you everyone!
Related
I have a quick question. I'm new in terms of trying to combine both sql and c++ together. My question is when I enter in the author to look for in the database, it say unknown column 'insert author last name here'. It's because of the input variable 'AuthorLast' not having quotes in the select statement. Thing is, I don't know how to fix it or change it.
#include<mysql.h>
#include<string>
#include<iostream>
using namespace std;
int main()
{
string AuthorLast;
mysql_library_init(0, NULL, NULL);
MYSQL* con = mysql_init(NULL);
if (con == NULL)
{
cout << mysql_error(con);
exit(1);
}
if (mysql_real_connect(con, "Insert Host here", "Insert ID here", "Password", "DataBase here", 0, NULL, 0) == NULL)
{
cout << mysql_error(con);
exit(1);
}
cout << "Enter in an author from the database: ";
getline(cin, AuthorLast);
string sql;
sql = "SELECT AuthorLast FROM Author WHERE AuthorLast= " + AuthorLast + ";";
const char* C = sql.c_str();
mysql_query(con, C);
MYSQL_RES* result = mysql_store_result(con);
if (result == NULL)
{
cout << mysql_error(con);
exit(1);
}
int field_nums = mysql_num_fields(result);
MYSQL_ROW row;
while (row = mysql_fetch_row(result))
{
for (int i = 0; i < field_nums; i++)
cout << row[i] << endl;
}
mysql_free_result(result);
mysql_close(con);
}
As other people have said, just adding single quotes in the SQL text would do the trick, but that leaves you vulnerable to SQL injection. Imagine that someone asks for an author name (written in another line for clarity):
SomeAuthor' or ''='
That would result in:
SELECT AuthorLast FROM Author WHERE AuthorLast= 'SomeAuthor' or ''='';
Which would result in your query returning all of the last names of authors. Although this may seem irrelevant for you, if (for example) you use the same approach in a password checking query, it can lead to an attacker being able to login without knowing the user password (in essence, you are allowing the user to modify your query).
You should thoroughly sanitize the user's input (this is, ensuring that it does not include unexpected characters) before including it in the query or (better still) use prepared statements (for mysql, I think that you can take a look at the mysql_stmt_* methods).
Prepared statements are more or less like telling the database server to execute "SELECT AuthorLast FROM Author WHERE AuthorLast=?", and telling it to use "MyAuthorLast" in place of the ?. So if someone tries to include quotes in the name, the server automatically sanitizes the input for you by adding any required escape characters.
In c++ how can I efficiently create a c string based on a literal and appended long/int value? I logically want to do something like this:
const char *sql = "select * from MyTable where ID = " + longId;
Where longId is an int/long parameter.
This compiles an answer from user4581301 comment and I offer to delete if user4581301 makes their own answer and asks me to.
As suggested by user4581301, you can do:
std::string s = "select * from MyTable where ID = " + std::to_string(longId);
You can get a C string equivalent using s.c_str(), e.g.:
std::cout << s.c_str() << std::endl;
I think the answer from H.S. is perfect, given the question asked. Just a quick FYI, since I'm using Qt (which I didn't mention in the OP), I ended up using this code:
QString sql = QStringLiteral("SELECT v.drive_path_if_builtin, "
"m.full_filepath "
"FROM media_table m "
"INNER JOIN volume_table v "
"ON (v.id = m.volume_id) "
"WHERE m.id = %1;").arg(id);
... and then in the appropriate place, where I need a c-string, I used qPrintable(sql).
I am new to SQLite and I may be missing something very basic. I am creating a table as shown below with the following statement.
Creation of Table statement
std::string strQuery = "CREATE TABLE IF NOT EXISTS ";
strQuery += tableNames[msgTable];
strQuery += " (RecKey INTEGER PRIMARY KEY, Origin TEXT NOT NULL, Type INT, Target INT, Header TEXT NOT NULL, Content TEXT)";
Then I am inserting records using the following statement.
Insert statement
std::string appendRecStr = "INSERT INTO " + tableNames[msg._type] + " (RecKey, Origin, Type, Target, Header, Content) VALUES (";
appendRecStr += std::string("NULL") + ", " +
AddQuote4SQL(msg._orig) +", " +
AddQuote4SQL((short)msg._type) +", " +
AddQuote4SQL((short)msg._target) +", " +
AddQuote4SQL(msg._head) +", " +
AddQuote4SQL(msg._data) + ");";
Also,
inline std::string AddQuote4SQL( const std::string & s ) {return std::string("'") + s +
std::string("'");}
inline std::string AddQuote4SQL( short s )
{
std::stringstream ss;
ss << "'" << std::to_string(s) << "'";
return ss.str();
}
The problem is the insert statement overwrites existing records. What is going wrong?
I think you are missing indeed something basic about sql here. An INSERT statement always inserts a new row. What you want in this case is an UPDATE statement, which uses a certain value to recognize its a duplicate (i.e. column1 = "somevalue": below). In case no rows are found that match the value, you should insert, since no duplicates exist. See code below:
UPDATE Table1 SET (...) WHERE Column1='SomeValue'
IF ##ROWCOUNT=0
INSERT INTO Table1 VALUES (...)
Fairly new to sqlite (and sql). I have several tables I need to generate with several column names, which can change as I code (in c++). How do I manage them? Am I doing it right? There must be utility codes out there that's much better.
Edit: Specifically, I'd like to avoid run-time errors by having an abstraction of the table and field names at compile time (e.g. using #defines, but maybe something else is better).
E.g. I'm currently thinking about creating a class TableHandler that will:
sqlite *db;
....
TableHandler tb("TableName");
tb.addField("FirstName", "TEXT");
tb.addField("Id", "INTEGER");
tb.createTable(db); //calls sqlite3_exec("create table TableName(FirstName TEXT, Id INTEGER)");
tb.setEntry("FirstName", "bob");
tb.addEntry(); //calls sqlite3_exec("insert ...");
tb.createCode(stdout);
//this will generate
/*
#define kTableName "TableName"
#define kFirstName "FirstName"
#define kId "Id"
...anything else useful?
*/
I asked a similar question and it was down voted so i deleted it. I wrote some code to do the insert if you are interested. But i agreed with the negative comments that static SQL statements are less error prone. Not to mention less cpu intensive.
For insert i took a std::set of std::pair of std::string. The first string being the column name and the second string its value. And the Query returned a similar structure. I played with std::map and std::vector and std::unordered_set all of them would have different benefits here.
What would be great if you get around to it is a small utility program that could read a definition of a class and write all the SQL for you. I started this and gave up because of parsing the C++ header file got way to complicated for the time I would save on future projects.
Added
std::string Insert(std::string table, std::vector< std::pair<std::string,std::string> > row)
{
if (row.size()==0 || table.size()==0)
return "";
std::stringstream name,value;
auto it = row.begin();
name << "INSERT INTO " << table.c_str()<<"('" << (*it).first << "'";
value << "VALUES('" <<(*it).second << "'";
for ( ; it < row.end(); it++)
{
name << ", '" << (*it).first << "'";
value << ", '" <<(*it).second << "'";
}
name << ") ";
value << ");";
name << value.str();
return name.str();
}
I have built a database in MS Access.
There I have a table called Customers which also has a cell called Employee type: integer.
I also built a program in C++ which controls all data.
Let's say I have a string like this:
string sqlString = "SELECT * FROM Customers Where Customers.Employee = '" + id + "' ";
Id passes through my function correctly and is an integer, so I get an error in compilation saying: "Invalid pointer addition".
If I declare id as a string of course there's no error but there are no results in my form also. If I declare in database cell Employee as text and build my query like this:
string sqlString = "SELECT * FROM Customers WHERE Customers.Employee = 128";
I get results, but I need that Employee as an integer cause its a foreign key from another table.
So, what should I do with my query to have results passing integer as parameter through variable id, to be ok with the cell Employee from database which is also integer?
Any ideas? I would really appreciate some help here.
As I said, if I convert id to string, there are no results in my form since Employee in database is an integer. So this:
std::ostringstream buf;
buf << "SELECT * FROM Customers Where Customers.Employee = '" << id << "' ";
string str = buf.str();
won't do the job or any other conversion.
How can I pass id as an integer in my query?
You could use sprintf, but in C++ you can do:
std::ostringstream buf;
buf << "SELECT * FROM Customers Where Customers.Employee = '" << id << "' ";
string str = buf.str();
(untested)
You need to convert id to a string, then your first approach should work.
See this question for how to do the conversion:
Alternative to itoa() for converting integer to string C++?
use
std::ostringstream buf; buf << "SELECT * FROM Customers Where Customers.Employee = " << id ; string str = buf.str();
This should work please try '12' --- quote should not be placed before and after 12
you can use boost::format with boost::str
string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);
this should be better approach since you will have all the replacement variable in one place at the end.