Added convenience select method, improved security of recipe storage.

This commit is contained in:
Andrew Lalis 2018-03-03 08:38:32 +01:00
parent cacc75f07d
commit 238d7edee3
6 changed files with 50 additions and 22 deletions

View File

@ -34,8 +34,16 @@ bool Database::insertInto(string tableName, vector<string> columnNames, vector<s
string cols = combineVector(columnNames, ", "); string cols = combineVector(columnNames, ", ");
string vals = combineVector(values, ", "); string vals = combineVector(values, ", ");
query += cols + ") VALUES (" + vals + ");"; query += cols + ") VALUES (" + vals + ");";
this->executeSQL(query); ResultTable t = this->executeSQL(query);
return true; return (t.getReturnCode() == SQLITE_DONE);
}
ResultTable Database::selectFrom(string tableName, string columnNames, string conditions){
if (columnNames.size() == 0 || tableName.empty()){
return ResultTable();
}
string query = "SELECT " + columnNames + " FROM " + tableName + " WHERE " + conditions + ";";
return this->executeSQL(query);
} }
void Database::openConnection(){ void Database::openConnection(){
@ -58,18 +66,23 @@ string Database::combineVector(std::vector<string> strings, string mid){
if (strings.empty()){ if (strings.empty()){
return ""; return "";
} }
std::string result = "'" + strings[0] + "'"; std::string result = surroundString(strings[0], "'");
for (std::vector<std::string>::iterator it = strings.begin() + 1; it != strings.end(); ++it){ for (std::vector<std::string>::iterator it = strings.begin() + 1; it != strings.end(); ++it){
result += mid + "'" + (*it) + "'"; result += mid + surroundString((*it), "'");
} }
return result; return result;
} }
string Database::surroundString(string s, string surround){
return surround+s+surround;
}
bool Database::tableExists(string tableName){ bool Database::tableExists(string tableName){
if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ if (tableName.empty() || this->db == NULL || !this->dbIsOpen){
return false; return false;
} }
ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); ResultTable t = this->selectFrom("sqlite_master", "name", "type='table' AND name='"+tableName+"'");
//ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';");
return !t.isEmpty(); return !t.isEmpty();
} }

View File

@ -25,9 +25,14 @@ public:
//Executes an SQL string statement in a safe way and returns the result. //Executes an SQL string statement in a safe way and returns the result.
ResultTable executeSQL(string statement); ResultTable executeSQL(string statement);
bool insertInto(string tableName, vector<string> columnNames, vector<string> values); bool insertInto(string tableName, vector<string> columnNames, vector<string> values);
ResultTable selectFrom(string tableName, string columnNames, string conditions);
bool tableExists(string tableName); bool tableExists(string tableName);
int getLastInsertedRowId(); int getLastInsertedRowId();
protected:
string surroundString(string s, string surround);
private: private:
//SQL Instance variables. //SQL Instance variables.
string filename; string filename;

View File

@ -6,10 +6,11 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){
bool RecipeDatabase::storeRecipe(Recipe recipe){ bool RecipeDatabase::storeRecipe(Recipe recipe){
///TODO: Implement this in a smart way using transaction. ///TODO: Implement this in a smart way using transaction.
ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); this->executeSQL("BEGIN;");
ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(recipe.getName(), "'"));
//ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';");
if (!t.isEmpty()){ if (!t.isEmpty()){
fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str());
return false;
} else { } else {
bool success = this->insertInto("recipe", bool success = this->insertInto("recipe",
vector<string>({ vector<string>({
@ -29,27 +30,27 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){
if (success){ if (success){
//If successful, proceed to insert instructions, image, and ingredients. //If successful, proceed to insert instructions, image, and ingredients.
int recipeId = this->getLastInsertedRowId(); int recipeId = this->getLastInsertedRowId();
bool ingredientSuccess = true;
for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){ for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){
if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){ if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){
return false; ingredientSuccess = false;
break;
} }
} }
if (!this->storeInstruction(recipe.getInstruction(), recipeId)){ if (ingredientSuccess && this->storeInstruction(recipe.getInstruction(), recipeId) && this->storeImage(recipe.getImage(), recipeId)){
return false; this->executeSQL("COMMIT;");
}
if (!this->storeImage(recipe.getImage(), recipeId)){
return false;
}
return true; return true;
} else { }
}
}
this->executeSQL("ROLLBACK;");
return false; return false;
}
}
} }
bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){
//First check if the base ingredient has been added to the database. This is done within storeIngredient(). //First check if the base ingredient has been added to the database. This is done within storeIngredient().
ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';"); ResultTable t = this->selectFrom("ingredient", "ingredientId", "name="+surroundString(ri.getName(), "'"));
//ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';");
int ingId = 0; int ingId = 0;
if (t.isEmpty()){ if (t.isEmpty()){
if (!this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ri.getFoodGroup(), ri.getName()}))){ if (!this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ri.getFoodGroup(), ri.getName()}))){
@ -77,15 +78,15 @@ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){
} }
void RecipeDatabase::storeIngredient(Ingredient ingredient){ void RecipeDatabase::storeIngredient(Ingredient ingredient){
ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); ResultTable t = this->selectFrom("ingredient", "*", "name="+surroundString(ingredient.getName(), "'"));
//ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';");
if (t.isEmpty()){ if (t.isEmpty()){
this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ingredient.getFoodGroup(), ingredient.getName()})); this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ingredient.getFoodGroup(), ingredient.getName()}));
} }
} }
bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){ bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){
bool success = FileUtils::saveInstruction(recipeId, instruction); return FileUtils::saveInstruction(recipeId, instruction);
return success;
} }
bool RecipeDatabase::storeImage(QImage image, int recipeId){ bool RecipeDatabase::storeImage(QImage image, int recipeId){
@ -99,12 +100,12 @@ void RecipeDatabase::ensureTablesExist(){
this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient("
"ingredientId INTEGER PRIMARY KEY," "ingredientId INTEGER PRIMARY KEY,"
"foodGroup varchar," "foodGroup varchar,"
"name varchar);"); "name varchar UNIQUE);");
//Recipe table. Each recipe can have at most one instruction, and one image. //Recipe table. Each recipe can have at most one instruction, and one image.
this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" this->executeSQL("CREATE TABLE IF NOT EXISTS recipe("
"recipeId INTEGER PRIMARY KEY," "recipeId INTEGER PRIMARY KEY,"
"createdDate date," "createdDate date,"
"name varchar," "name varchar UNIQUE,"
"cookTime time," "cookTime time,"
"prepTime time," "prepTime time,"
"servingCount real);"); "servingCount real);");

View File

@ -26,6 +26,8 @@ class RecipeDatabase : public Database
void storeIngredient(Ingredient ingredient); void storeIngredient(Ingredient ingredient);
bool storeInstruction(Instruction instruction, int recipeId); bool storeInstruction(Instruction instruction, int recipeId);
bool storeImage(QImage image, int recipeId); bool storeImage(QImage image, int recipeId);
vector<Recipe> retrieveRecipe(string name);
private: private:
//Utility methods. //Utility methods.

View File

@ -11,6 +11,7 @@ void ResultTable::extractData(sqlite3_stmt *stmt){
processRow(stmt); processRow(stmt);
res = sqlite3_step(stmt); res = sqlite3_step(stmt);
} }
this->queryCode = res;
} }
void ResultTable::processRow(sqlite3_stmt *stmt){ void ResultTable::processRow(sqlite3_stmt *stmt){
@ -51,6 +52,10 @@ string ResultTable::valueAt(unsigned int row, unsigned int col){
} }
} }
int ResultTable::getReturnCode(){
return this->queryCode;
}
unsigned int ResultTable::columnCount(){ unsigned int ResultTable::columnCount(){
if (this->isEmpty()){ if (this->isEmpty()){
return 0; return 0;

View File

@ -27,10 +27,12 @@ class ResultTable
bool isEmpty(); bool isEmpty();
string valueAt(unsigned int row, unsigned int col); string valueAt(unsigned int row, unsigned int col);
int getReturnCode();
unsigned int columnCount(); unsigned int columnCount();
unsigned int rowCount(); unsigned int rowCount();
private: private:
vector<vector<string>> values; vector<vector<string>> values;
int queryCode;
//Utility methods. //Utility methods.
string convertToString(sqlite3_value* val); string convertToString(sqlite3_value* val);