Related
I'm trying to compare all column data against a const char to see if the name in the column matches the player name that is connected to the game server. The comparison in the spaghetticode below does not work properly and results in new rows being made even if the player name already exists in the sqlite database. What is the correct way to do this comparison? Thanks in advance for your help.
Here's the snippet with the problematic section:
if(enable_sqlite_db) {
sqlite3_stmt *stmt;
sqlite3 *db;
char *zErrMsg = 0;
int rc;
const char *sql;
bool name_match = false;
const char* player_database_names;
char *p_name = ci->name;
char *p_ip = ci->ip;
rc = sqlite3_open("playerinfo.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
if(sql_console_msgs) fprintf(stdout, "Opened database successfully\n");
}
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
defformatstring(sqlstrprep)("SELECT NAME FROM PLAYERINFO");
rc = sqlite3_prepare_v2(db, sqlstrprep, -1, &stmt, NULL);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, SQLITE_ROW);
int columns = sqlite3_column_count(stmt);
if(sql_console_msgs) out(ECHO_CONSOLE, "-- id: %d row: %d columns: %d", id, SQLITE_ROW, columns);
player_database_names = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
}
if(player_database_names == p_name) name_match = true;
else if(player_database_names != p_name) name_match = false;
}
Here is the full code:
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
bool sql_console_msgs = true;
void QServ::savestats(clientinfo *ci)
{
if(enable_sqlite_db) {
sqlite3_stmt *stmt;
sqlite3 *db;
char *zErrMsg = 0;
int rc;
const char *sql;
bool name_match = false;
const char* player_database_names;
char *p_name = ci->name;
char *p_ip = ci->ip;
rc = sqlite3_open("playerinfo.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
if(sql_console_msgs) fprintf(stdout, "Opened database successfully\n");
}
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
defformatstring(sqlstrprep)("SELECT NAME FROM PLAYERINFO");
//defformatstring(sqlstrprep)("SELECT group_concat(NAME) FROM PLAYERINFO");
rc = sqlite3_prepare_v2(db, sqlstrprep, -1, &stmt, NULL);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
//sometimes returns incorrectly - keeps creating undesired new rows firstguy, secondguy, firstguy
int id = sqlite3_column_int(stmt, SQLITE_ROW);
int columns = sqlite3_column_count(stmt);
if(sql_console_msgs) out(ECHO_CONSOLE, "-- id: %d row: %d columns: %d", id, SQLITE_ROW, columns);
player_database_names = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
//if (std::to_string(player_database_names).find(p_name) != std::string::npos) name_match = true;
//else name_match = false;
if(sql_console_msgs) out(ECHO_CONSOLE, "-- player db names: %s", player_database_names);
}
//if(!strcmp(player_database_names, p_name)) name_match = true;
//else if(strcmp(player_database_names, p_name)) name_match = false;
if(player_database_names == p_name) name_match = true;
else if(player_database_names != p_name) name_match = false;
}
sql = "CREATE TABLE IF NOT EXISTS PLAYERINFO(" \
"NAME TEXT NOT NULL," \
"FRAGS INT NOT NULL," \
"DEATHS INT NOT NULL," \
"FLAGS INT NOT NULL," \
"PASSES INT NOT NULL," \
"IP TEXT NOT NULL," \
"ACCURACY DECIMAL(4, 2) NOT NULL," \
"KPD DECIMAL(4, 2) NOT NULL);";
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
if( rc != SQLITE_OK ){
fprintf(stderr, "SQLITE3 ERROR # CREATE TABLE IF NOT EXISTS: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
if(sql_console_msgs) {
if(!name_match) fprintf(stdout, "-- No previous record found under that name\n");
else fprintf(stdout, "-- Found name already, updating record instead\n");
}
}
char sqlINSERT[500];
char sqlUPDATE[1000];
int p_frags = ci->state.frags;
int p_deaths = ci->state.deaths;
int p_flags = ci->state.flags;
int p_passes = ci->state.passes;
int p_acc = (ci->state.damage*100)/max(ci->state.shotdamage, 1);
int p_kpd = (ci->state.frags)/max(ci->state.deaths, 1);
//name is different
if(!name_match) {
snprintf(sqlINSERT, 500, "INSERT INTO PLAYERINFO( NAME,FRAGS,DEATHS,FLAGS,PASSES,IP,ACCURACY,KPD ) VALUES (\"%s\", %d, %d, %d, %d, \"%s\", %d, %d)",p_name,p_frags,p_deaths,p_flags,p_passes,p_ip,p_acc,p_kpd);
//sqlEscape(sqlINSERT);
rc = sqlite3_exec(db, sqlINSERT, callback, 0, &zErrMsg);
}
//client name matches db record, update db if new info is > than db info
else if(name_match) {
snprintf(sqlUPDATE, 10000,
"UPDATE PLAYERINFO SET FRAGS = %d+(SELECT FRAGS FROM PLAYERINFO) WHERE NAME = \"%s\";" \
"UPDATE PLAYERINFO SET DEATHS = %d+(SELECT DEATHS FROM PLAYERINFO) WHERE NAME = \"%s\";" \
"UPDATE PLAYERINFO SET FLAGS = %d+(SELECT FLAGS FROM PLAYERINFO) WHERE NAME = \"%s\";" \
"UPDATE PLAYERINFO SET PASSES = %d+(SELECT PASSES FROM PLAYERINFO) WHERE NAME = \"%s\";" \
"UPDATE PLAYERINFO SET ACCURACY = %d+(SELECT ACCURACY FROM PLAYERINFO) WHERE NAME = \"%s\";" \
"UPDATE PLAYERINFO SET KPD = %d+(SELECT KPD FROM PLAYERINFO) WHERE NAME = \"%s\";",
ci->state.frags, ci->name, ci->state.deaths, ci->name, ci->state.flags, ci->name, ci->state.passes, ci->name, p_acc, ci->name, p_kpd, ci->name);
//sqlEscape(sqlUPDATE);
rc = sqlite3_exec(db, sqlUPDATE, callback, 0, &zErrMsg);
}
if( rc != SQLITE_OK ){
fprintf(stderr, "SQLITE3 ERROR # INSERT & UPDATE: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
if(sql_console_msgs) fprintf(stdout, "Playerinfo modified\n");
}
sqlite3_close(db);
}
}
void QServ::getstats(clientinfo *ci)
{
if(enable_sqlite_db) {
sqlite3 *db;
char *zErrMsg = 0;
int rc;
char *sql;
const char* data = "Callback function called";
rc = sqlite3_open("playerinfo.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
sqlite3_stmt *stmt;
defformatstring(sqlstrprep)("SELECT NAME,FRAGS,ACCURACY,KPD FROM PLAYERINFO WHERE NAME == \"%s\";", ci->name);
rc = sqlite3_prepare_v2(db, sqlstrprep, -1, &stmt, NULL);
bool necho = false;
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
const char* name = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
const char* allfrags = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
const char* avgacc = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
const char* avgkpd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
if(!necho) {
if(avgacc == NULL) out(ECHO_SERV, "Name: \f0%s\f7, Total Frags: \f3%s\f7, Average KPD: \f6%s", name, allfrags, avgkpd);
else if(avgkpd == NULL) out(ECHO_SERV, "Name: \f0%s\f7, Total Frags: \f3%s\f7, Average Accuracy: \f2%s%%", name, allfrags, avgacc);
else out(ECHO_SERV, "Name: \f0%s\f7, Total Frags: \f3%s\f7, Average Accuracy: \f2%s%%\f7, Average KPD: \f6%s", name,allfrags,avgacc,avgkpd);
necho = true;
}
}
}
sqlite3_close(db);
}
}
void QServ::getnames(clientinfo *ci) {
if(enable_sqlite_db) {
sqlite3_stmt *stmt3;
sqlite3 *db;
int rc;
rc = sqlite3_open("playerinfo.db", &db);
defformatstring(sqlstrprep3)("SELECT group_concat(NAME, \", \") FROM PLAYERINFO WHERE IP == \"%s\";", ci->ip);
rc = sqlite3_prepare_v2(db, sqlstrprep3, -1, &stmt3, NULL);
while ((rc = sqlite3_step(stmt3)) == SQLITE_ROW) {
std::string names(reinterpret_cast<const char*>(sqlite3_column_text(stmt3, 0)));
defformatstring(nmsg)("Names from IP \f2%s\f7: %s", ci->ip, names.c_str());
out(ECHO_SERV, nmsg);
}
sqlite3_close(db);
}
}
void QServ::disconnectclient(clientinfo *ci)
{
if(enable_sqlite_db) savestats(ci);
}
I'm always amused by the fact that nobody wants to provide a minimal working example. In the end, I created one myself to show how to do this thing:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "sqlite3.h"
int main(void)
{
int rc;
sqlite3 *db;
rc = sqlite3_open("playerinfo.db", &db);
if (rc != SQLITE_OK) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
else {
fprintf(stdout, "Opened database successfully\n");
}
char *zErrMsg = NULL;
rc = sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS PlayerInfo (id INTEGER PRIMARY KEY, name TEXT);"
"REPLACE INTO PlayerInfo(id, name) VALUES (1,'John'),(2,'Paul'),(3,'George'),(4,'Ringo'),(5,'Pete'),(6,'Stuart');",
NULL, 0, &zErrMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
exit(EXIT_FAILURE);
}
const char *search_name = "Ringo";
bool name_match = false;
sqlite3_stmt *stmt;
char *zSql = "SELECT name FROM PlayerInfo";
sqlite3_prepare_v2(db, zSql, -1, &stmt, NULL);
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* player_database_names = sqlite3_column_text(stmt, 0);
if (strcmp(player_database_names, search_name) == 0) {
name_match = true;
break;
}
}
sqlite3_finalize(stmt);
printf("Search name: %s - Result: %s\n", search_name, name_match ? "true" : "false");
sqlite3_close(db);
return EXIT_SUCCESS;
}
The original code had a lot of very strange things. It clearly looks as if a bunch of stuff was pasted together without reason.
I never used SQLite before, but it's really very well done. So the idea is to
Open the DB (sqlite3_open()) and check if successfull.
Create and populate the DB if needed (sqlite3_exec() without a callback function).
Query the DB rows with sqlite3_prepare_v2(), sqlite3_step(), and sqlite3_finalize().
In the loop you search if the name is found.
Close the DB (sqlite3_close()).
As reported in the comments, this is not a good idea. If you want to see if something exists, query if it exists:
sqlite3_stmt *stmt;
char *zSql = "SELECT EXISTS(SELECT 1 FROM PlayerInfo WHERE name=?)";
sqlite3_prepare_v2(db, zSql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, search_name, -1, SQLITE_STATIC);
sqlite3_step(stmt);
name_match = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
In this specific case, probably the parametrized query is an overkill, but who knows where that search_name comes from...
WARNING: no effort to have decent error handling.
I am trying to properly output all of the rows in the names column where the IP column is the same as the client IP in C++. This is working properly, however the output is each name on a newline. I want to somehow use concatstring or some other method to bring all of the names onto the same line. When I use the method I commented out in the code, it doesn't work properly because it's in the while loop - it outputs like such:
name
name, name2
name, name2, name3
which is undesired. if I move stuff out of the while loop it either is not in the scope, or will crash with segmentation fault because it can't access the data. If i use a bool to check and only output once inside the while loop, only the first name from the list will be displayed. I just want all of the names to display on the same line, like such:
name, name2, name3
Here is a snipet from the code below:
while ((rc = sqlite3_step(stmt3)) == SQLITE_ROW) {
std::string names(reinterpret_cast<const char*>(sqlite3_column_text(stmt3, 0)));
out(ECHO_SERV, names.c_str());
/*char msg[MAXTRANS];
string buf;
formatstring(msg)("%s, ", names.c_str());
concatstring(buf, msg, MAXTRANS);
out(ECHO_SERV, buf);*/
}
Any help is greatly appreciated. Thank you for your time!
Here is the full code:
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
void savestats(clientinfo *ci)
{
sqlite3 *db;
char *zErrMsg = 0;
int rc;
const char *sql;
bool name_match, ip_match;
const char* player_database_names;
const char* player_database_ips;
char *p_name = ci->name;
char *p_ip = ci->ip;
//Open database
rc = sqlite3_open("playerinfo.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
fprintf(stdout, "Opened database successfully\n");
}
//Handle errors
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
//Fetch names from database for comparison
}else{
//name match
sqlite3_stmt *stmt;
defformatstring(sqlstrprep)("SELECT NAME FROM PLAYERINFO");
rc = sqlite3_prepare_v2(db, sqlstrprep, -1, &stmt, NULL);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
//int id = sqlite3_column_int(stmt, 0);
player_database_names = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
if(!strcmp(player_database_names, p_name)) name_match = true;
else name_match = false;
}
//ip match (doesn't work)
sqlite3_stmt *stmt2;
defformatstring(sqlstrprep2)("SELECT IP FROM PLAYERINFO");
rc = sqlite3_prepare_v2(db, sqlstrprep2, -1, &stmt2, NULL);
while ((rc = sqlite3_step(stmt2)) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt2, 6);
player_database_ips = reinterpret_cast<const char*>(sqlite3_column_text(stmt2, id));
//if(!strcmp(player_database_ips, p_ip)) ip_match = true; //seg fault EXC_BAD_ACCESS
if(player_database_ips == p_ip) ip_match = true;
else ip_match = false;
}
}
//Create the table if it doesn't exist
sql = "CREATE TABLE IF NOT EXISTS PLAYERINFO(" \
"NAME TEXT NOT NULL," \
"FRAGS INT NOT NULL," \
"DEATHS INT NOT NULL," \
"FLAGS INT NOT NULL," \
"PASSES INT NOT NULL," \
"IP TEXT NOT NULL," \
"ACCURACY DECIMAL(4, 2) NOT NULL," \
"KPD DECIMAL(4, 2) NOT NULL);";
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
if( rc != SQLITE_OK ){
fprintf(stderr, "SQLITE3 ERROR # CREATE TABLE IF NOT EXISTS: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
if(!name_match) fprintf(stdout, "No previous record found under that name\n");
else fprintf(stdout, "Found name and IP already, updating record instead\n");
}
char sqlINSERT[256];
char sqlUPDATE[1000];
int p_frags = ci->state.frags;
int p_deaths = ci->state.deaths;
int p_flags = ci->state.flags;
int p_passes = ci->state.passes;
int p_acc = (ci->state.damage*100)/max(ci->state.shotdamage, 1);
int p_kpd = (ci->state.frags)/max(ci->state.deaths, 1);
//name and ip are different
if(!name_match) {
sprintf(sqlINSERT, "INSERT INTO PLAYERINFO( NAME,FRAGS,DEATHS,FLAGS,PASSES,IP,ACCURACY,KPD ) VALUES ('%s', %d, %d, %d, %d, '%s', %d, %d)",p_name,p_frags,p_deaths,p_flags,p_passes,p_ip,p_acc,p_kpd);
rc = sqlite3_exec(db, sqlINSERT, callback, 0, &zErrMsg);
}
//name is the same, update the database if the new information is > than db_info
else if(name_match) {
sprintf(sqlUPDATE,
"UPDATE PLAYERINFO SET FRAGS = %d+(SELECT FRAGS FROM PLAYERINFO) WHERE NAME = '%s';" \
"UPDATE PLAYERINFO SET DEATHS = %d+(SELECT DEATHS FROM PLAYERINFO) WHERE NAME = '%s';" \
"UPDATE PLAYERINFO SET FLAGS = %d+(SELECT FLAGS FROM PLAYERINFO) WHERE NAME = '%s';" \
"UPDATE PLAYERINFO SET PASSES = %d+(SELECT PASSES FROM PLAYERINFO) WHERE NAME = '%s';" \
"UPDATE PLAYERINFO SET ACCURACY = %d+(SELECT PASSES FROM PLAYERINFO) WHERE NAME = '%s';" \
"UPDATE PLAYERINFO SET KPD = %d+(SELECT PASSES FROM PLAYERINFO) WHERE NAME = '%s';",
ci->state.frags, ci->name, ci->state.deaths, ci->name, ci->state.flags, ci->name, ci->state.passes, ci->name, p_acc, ci->name, p_kpd, ci->name);
rc = sqlite3_exec(db, sqlUPDATE, callback, 0, &zErrMsg);
}
if( rc != SQLITE_OK ){
fprintf(stderr, "SQLITE3 ERROR # INSERT & UPDATE: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
fprintf(stdout, "Playerinfo modified\n");
}
sqlite3_close(db);
}
void getstats(clientinfo *ci)
{
sqlite3 *db;
char *zErrMsg = 0;
int rc;
char *sql;
const char* data = "Callback function called";
/* Open database */
rc = sqlite3_open("playerinfo.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}
//rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL Database Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
sqlite3_stmt *stmt;
//defformatstring(sqlstrprep)("SELECT * FROM PLAYERINFO WHERE NAME GLOB '%s*';", ci->name);
defformatstring(sqlstrprep)("SELECT NAME,FRAGS,DEATHS,FLAGS FROM PLAYERINFO WHERE NAME == '%s';", ci->name);
rc = sqlite3_prepare_v2(db, sqlstrprep, -1, &stmt, NULL);
bool necho = false;
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
//int id = sqlite3_column_int(stmt, 0);
const char* name = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
const char* frags = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
const char* deaths = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
const char* flags = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
if(!necho) {
out(ECHO_SERV, "Name: \f2%s \f7Frags: \f0%s \f7Deaths: \f3%s \f7Flags: \f5%s", name,frags,deaths,flags);
out(ECHO_CONSOLE, "Name: %s, Frags: %s, Deaths: %s, Flags: %s", name,frags,deaths,flags);
necho = true;
}
}
//returns names
sqlite3_stmt *stmt3;
defformatstring(sqlstrprep3)("SELECT NAME FROM PLAYERINFO WHERE IP == '%s';", ci->ip);
rc = sqlite3_prepare_v2(db, sqlstrprep3, -1, &stmt3, NULL);
while ((rc = sqlite3_step(stmt3)) == SQLITE_ROW) {
std::string names(reinterpret_cast<const char*>(sqlite3_column_text(stmt3, 0)));
out(ECHO_SERV, names.c_str());
/*char msg[MAXTRANS];
string buf;
formatstring(msg)("%s, ", names.c_str());
concatstring(buf, msg, MAXTRANS);
out(ECHO_SERV, buf);*/
}
}
sqlite3_close(db);
}
Here is the solution, this gets all of the rows from column NAME where IP = our client IP.
void QServ::getnames(clientinfo *ci) {
if(enable_sqlite_db) {
sqlite3_stmt *stmt3;
sqlite3 *db;
int rc;
rc = sqlite3_open("playerinfo.db", &db);
defformatstring(sqlstrprep3)("SELECT group_concat(NAME, ', ') FROM PLAYERINFO WHERE IP == '%s';", ci->ip);
rc = sqlite3_prepare_v2(db, sqlstrprep3, -1, &stmt3, NULL);
while ((rc = sqlite3_step(stmt3)) == SQLITE_ROW) {
std::string names(reinterpret_cast<const char*>(sqlite3_column_text(stmt3, 0)));
defformatstring(nmsg)("Names from IP \f2%s\f7: %s", ci->ip, names.c_str());
out(ECHO_SERV, nmsg);
}
sqlite3_close(db);
}
}
Considering:
int user_id = 0x01; //dummy
int size_id = 0x01; //dummy
sqlite3_stmt *stmt;
sqlite3 *db;
int rc = sqlite3_open("path_to_database.db", &db);
if( rc != SQLITE_OK )
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return -1;
}
rc = sqlite3_prepare_v2(db, "SELECT id, type, time_registered"
" from myTestTable"
" where user = ? and size = ?", -1, &stmt, NULL);
if (rc != SQLITE_OK)
{
throw std::string(sqlite3_errmsg(db));
sqlite3_finalize(stmt);
return -1;
}
rc = sqlite3_bind_int(stmt, 1, user_id);
if (rc != SQLITE_OK)
{
std::string errmsg(sqlite3_errmsg(db));
sqlite3_finalize(stmt);
throw errmsg;
return -1;
}
rc = sqlite3_bind_int(stmt, 2, size_id);
if (rc != SQLITE_OK)
{
std::string errmsg(sqlite3_errmsg(db));
sqlite3_finalize(stmt);
throw errmsg;
return -1;
}
[...]
This does work, but it seems not user friendly. I could use snprintf to prepare the statement instead of bind, but I'ld loose safety (even if I shouldn't be facing injection attacks on my local db).
Is there a better way to use sqlite3_bind_TYPES to bind more than one value in statement, with same types (or not if possible) ?
You could use a c++ wrapper like SQLiteCpp. If you don't like that one, there's a list at the bottom of the github page.
If you prefer to use the c interface you could invert the logic of the bind statements. This would at least unclutter the code a bit.
int rc = sqlite3_open("path_to_database.db", &db);
if( rc != SQLITE_OK )
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return -1;
}
rc = sqlite3_prepare_v2(db, "SELECT id, type, time_registered"
" from myTestTable"
" where user = ? and size = ?", -1, &stmt, NULL);
if (rc == SQLITE_OK)
rc = sqlite3_bind_int(stmt, 1, user_id);
if (rc == SQLITE_OK)
rc = sqlite3_bind_int(stmt, 2, size_id);
if (rc != SQLITE_OK)
{
throw std::string(sqlite3_errmsg(db));
sqlite3_finalize(stmt);
return -1;
}
[...]
Mike
I have the following table defined in MySQL
INSERT INTO routemaster_log (`EntryDateTime`,`Entry`,`Message`,`EntryType`) VALUES (?,?,?,?);
CREATE TABLE `routemaster_log` (
`EntryDateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`Entry` varchar(127) NOT NULL,
`Message` int(11) NOT NULL DEFAULT '0',
`EntryType` varchar(20) NOT NULL DEFAULT 'INFORMATION'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And the following C++ code to write a record to it
void LogEntries::Insert(string_t LogText, string_t LogType, int MessageNumber)
{
SQLHANDLE hEnv;
SQLRETURN retCode;
SQLCHAR* query;
SQLINTEGER textLength = SQL_NTS;
// now set the text to be written to char *
char* logText = from_string_t(LogText);
char* logType = from_string_t(LogType);
std::string SQLQuery("INSERT INTO routemaster_log (`Entry`,`Message`,`EntryType`) VALUES (?,?,?)");
query = (SQLCHAR *)SQLQuery.c_str();
.
.
.
// log Text
retCode = SQLBindParameter(hStmnt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, 0, 0,
logText, sizeof(logText), &textLength);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (LogText)" << std::endl;
}
// Message Number
retCode = SQLBindParameter(hStmnt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &MessageNumber, 0, NULL);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (Message Number)" << std::endl;
}
// Log Type
retCode = SQLBindParameter(hStmnt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, 0, 0,
logType, sizeof(logType), &textLength);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (LogType)" << std::endl;
}
retCode = SQLExecDirectA(hStmnt, query, SQL_NTS);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO)
{
SQLWCHAR Msg[255], SqlState[6];
SQLINTEGER NativeError;
SQLRETURN ret2;
SQLSMALLINT i, MsgLen;
i = 1;
while ((ret2 = SQLGetDiagRec(SQL_HANDLE_STMT, hStmnt, i, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
std::cout << Msg << std::endl;
i++;
}
std::cout << "SQL Insert failed writing data to database." << std::endl;
}
}
everything is fine until the SQLExecDirectA this fails with the SQLBindParameter not used for all parameters error (retrieved in the following while loop).
I would expect this if the number of Parameters being bound was different to the number of '?' in the query but they are the same.
Anyone got a suggestion as to what is wrong?
There is actually two issues with the code in the question.
a) The Calls to SQLBindParameter go before the call to SQLPrepareA
b) In the Calls to SQLBindParameter the second Parameter gives the position of the parameter
The revised code - which works is as follows;
// log Text
retCode = SQLBindParameter(hStmnt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, 0, 0,
logText, sizeof(logText), &textLength);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (LogText)" << std::endl;
}
// Message Number
retCode = SQLBindParameter(hStmnt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &MsgNumber, 0, NULL);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (Message Number)" << std::endl;
}
// Log Type
retCode = SQLBindParameter(hStmnt, 3, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, 0, 0,
logType, sizeof(logType), &textLength);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed binding parameters (LogType)" << std::endl;
}
retCode = SQLPrepareA(hStmnt, query, SQL_NTS);
if (retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO) {
std::cout << "SQL Insert failed preparing statement" << std::endl;
}
retCode = SQLExecDirectA(hStmnt, query, SQL_NTS);
I'm trying to do the following:
get username typed into the userField
make a SEARCH mysql_query with the username as a variable
I'm having a hard time getting past phase 2 since mysql_query takes a const char* as the query string, and I can only get username as char* or wchar_t*
I'm also compiling in unicode.
My code for now:
void mysql_connect(HWND hLoginWnd) {
MYSQL *con, mysql;
MYSQL_RES *res;
mysql_init(&mysql);
mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "utf8");
mysql_real_connect(&mysql, "localhost", "root", "", "treenitaulu", 3306, NULL, 0);
char name[512], pass[512];
int lenUser = SendMessage(userField, WM_GETTEXT, 512, (LPARAM)name);
int lenPass = SendMessage(passField, WM_GETTEXT, 512, (LPARAM)pass);
if(lenUser > 0 && lenPass > 0) {
std::string query = "SELECT pass FROM users WHERE name='" + std::string(name) + "'";
mysql_query(&mysql, query.c_str());
}
}
What should I do?
You are setting the SQL charset to UTF-8, so use WideCharToMultiByte() to convert the retreived UI Unicode strings into UTF-8, then pass the converted data to the DB query. UTF-8 encoded data can be stored using char buffers, and you can pass a char* (or char[]) where a const char* is expected. For example:
std::string Utf8Encode(WCHAR *wStr, int wLen)
{
int utf8len = WideCharToMultiByte(CP_UTF8, 0, wStr, wLen, NULL, 0, NULL, NULL);
if (utf8len > 0)
{
std::vector<char> utf8(utf8len);
utf8len = WideCharToMultiByte(CP_UTF8, 0, wStr, wLen, &utf8[0], utf8len, NULL, NULL);
if (utf8len > 0)
return std::string(&utf8[0], utf8len);
}
return std::string();
}
void mysql_connect(HWND hLoginWnd)
{
MYSQL *con, mysql;
MYSQL_RES *res;
mysql_init(&mysql);
mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "utf8");
mysql_real_connect(&mysql, "localhost", "root", "", "treenitaulu", 3306, NULL, 0);
WCHAR name[512];
int lenUser = SendMessage(userField, WM_GETTEXTW, 512, (LPARAM)name);
if (lenUser > 0)
{
std::string query = "SELECT pass FROM users WHERE name='" + Utf8Encode(name, lenUser) + "'";
mysql_query(&mysql, query.c_str());
...
}
}
With that said, you really need to escape strings when building SQL statements dynamically. It is safer as it is not prone to SQL Injection attacks:
std::string Utf8EncodeAndEscape(MYSQL *mysql, WCHAR *wStr, int wLen)
{
int utf8len = WideCharToMultiByte(CP_UTF8, 0, wStr, wLen, NULL, 0, NULL, NULL);
if (utf8len > 0)
{
std::vector<char> utf8(utf8len);
utf8len = WideCharToMultiByte(CP_UTF8, 0, wStr, wLen, &utf8[0], utf8len, NULL, NULL);
if (utf8len > 0)
{
std::vector<char> escaped(utf8len*2+1);
unsigned long escapedLen = mysql_real_escape_string(mysql, &escaped[0], &utf8[0], utf8len);
if (escapedLen > 0)
return std::string(&escaped[0], escapedLen);
}
}
return std::string();
}
void mysql_connect(HWND hLoginWnd)
{
MYSQL *con, mysql;
MYSQL_RES *res;
mysql_init(&mysql);
mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "utf8");
mysql_real_connect(&mysql, "localhost", "root", "", "treenitaulu", 3306, NULL, 0);
WCHAR name[512];
int lenUser = SendMessage(userField, WM_GETTEXTW, 512, (LPARAM)name);
if (lenUser > 0)
{
std::string query = "SELECT pass FROM users WHERE name='" + Utf8EncodeAndEscape(&mysql, name, lenUser) + "'";
mysql_query(&mysql, query.c_str());
...
}
}
Parameterized queries would be just as safe, but are more efficient than dynamic SQL statements, especially if you need to execute the same statement multiple times:
std::wstring Utf8Decode(char *utf8Str, int utf8Len)
{
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, utf8Len, NULL, 0);
if (wLen > 0)
{
std::vector<wchar_t> wStr(wLen);
wLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, utf8Len, &wStr[0], wLen);
if (wLen > 0)
return std::wstring(&wStr[0], wLen);
}
return std::wstring();
}
MYSQL_STMT *stmt = mysql_stmt_init(&mysql);
if (stmt)
{
std::string query = "SELECT pass FROM users WHERE name=?"
if (mysql_stmt_prepare(stmt, query.c_str(), query.length()) == 0)
{
if (mysql_stmt_param_count(stmt) == 1)
{
std::string utf8User = Utf8Encode(name, lenUser);
unsigned long utf8len = utf8User.length();
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_STRING;
param.buffer = utf8User.c_str();
param.buffer_length = utf8len;
param.length = &utf8len;
param.is_unsigned = true;
mysql_stmt_bind_param(stmt, ¶m);
char result[512];
unsigned long resultLen = 0;
MYSQL_BIND result = {0};
param.buffer_type = MYSQL_TYPE_STRING;
param.buffer = &result[0];
param.buffer_length = 512;
param.length = &resultlen;
param.is_unsigned = true;
mysql_stmt_bind_result(stmt, &result);
if (mysql_stmt_execute(stmt) == 0)
{
mysql_stmt_fetch(stmt);
mysql_stmt_free_result(stmt);
SendMessage(passField, WM_SETTEXTW, 0, (LPARAM) Utf8Decode(result, resultLen).c_str());
}
}
}
}
mysql_stmt_close(stmt);