From 238d7edee310838a763ee10342dd07f4334bd3df Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 3 Mar 2018 08:38:32 +0100 Subject: [PATCH] Added convenience select method, improved security of recipe storage. --- model/database/database.cpp | 23 +++++++++++++++----- model/database/database.h | 5 +++++ model/database/recipedatabase.cpp | 35 ++++++++++++++++--------------- model/database/recipedatabase.h | 2 ++ model/database/resulttable.cpp | 5 +++++ model/database/resulttable.h | 2 ++ 6 files changed, 50 insertions(+), 22 deletions(-) diff --git a/model/database/database.cpp b/model/database/database.cpp index d4d533d..0e2c0ed 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -34,8 +34,16 @@ bool Database::insertInto(string tableName, vector columnNames, vectorexecuteSQL(query); - return true; + ResultTable t = this->executeSQL(query); + 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(){ @@ -58,18 +66,23 @@ string Database::combineVector(std::vector strings, string mid){ if (strings.empty()){ return ""; } - std::string result = "'" + strings[0] + "'"; + std::string result = surroundString(strings[0], "'"); for (std::vector::iterator it = strings.begin() + 1; it != strings.end(); ++it){ - result += mid + "'" + (*it) + "'"; + result += mid + surroundString((*it), "'"); } return result; } +string Database::surroundString(string s, string surround){ + return surround+s+surround; +} + bool Database::tableExists(string tableName){ if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ 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(); } diff --git a/model/database/database.h b/model/database/database.h index 3c1b73f..4a60447 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -25,9 +25,14 @@ public: //Executes an SQL string statement in a safe way and returns the result. ResultTable executeSQL(string statement); bool insertInto(string tableName, vector columnNames, vector values); + ResultTable selectFrom(string tableName, string columnNames, string conditions); bool tableExists(string tableName); int getLastInsertedRowId(); + +protected: + string surroundString(string s, string surround); + private: //SQL Instance variables. string filename; diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 231d5c9..5edddbb 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -6,10 +6,11 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ bool RecipeDatabase::storeRecipe(Recipe recipe){ ///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()){ fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); - return false; } else { bool success = this->insertInto("recipe", vector({ @@ -29,27 +30,27 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ if (success){ //If successful, proceed to insert instructions, image, and ingredients. int recipeId = this->getLastInsertedRowId(); + bool ingredientSuccess = true; for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){ if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){ - return false; + ingredientSuccess = false; + break; } } - if (!this->storeInstruction(recipe.getInstruction(), recipeId)){ - return false; + if (ingredientSuccess && this->storeInstruction(recipe.getInstruction(), recipeId) && this->storeImage(recipe.getImage(), recipeId)){ + this->executeSQL("COMMIT;"); + return true; } - if (!this->storeImage(recipe.getImage(), recipeId)){ - return false; - } - return true; - } else { - return false; } } + this->executeSQL("ROLLBACK;"); + return false; } bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ //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; if (t.isEmpty()){ if (!this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ri.getFoodGroup(), ri.getName()}))){ @@ -77,15 +78,15 @@ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ } 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()){ this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); } } bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){ - bool success = FileUtils::saveInstruction(recipeId, instruction); - return success; + return FileUtils::saveInstruction(recipeId, instruction); } bool RecipeDatabase::storeImage(QImage image, int recipeId){ @@ -99,12 +100,12 @@ void RecipeDatabase::ensureTablesExist(){ this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" "ingredientId INTEGER PRIMARY KEY," "foodGroup varchar," - "name varchar);"); + "name varchar UNIQUE);"); //Recipe table. Each recipe can have at most one instruction, and one image. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," "createdDate date," - "name varchar," + "name varchar UNIQUE," "cookTime time," "prepTime time," "servingCount real);"); diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 72cfa2d..1514cea 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -26,6 +26,8 @@ class RecipeDatabase : public Database void storeIngredient(Ingredient ingredient); bool storeInstruction(Instruction instruction, int recipeId); bool storeImage(QImage image, int recipeId); + + vector retrieveRecipe(string name); private: //Utility methods. diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp index 8b4d144..bef5028 100644 --- a/model/database/resulttable.cpp +++ b/model/database/resulttable.cpp @@ -11,6 +11,7 @@ void ResultTable::extractData(sqlite3_stmt *stmt){ processRow(stmt); res = sqlite3_step(stmt); } + this->queryCode = res; } 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(){ if (this->isEmpty()){ return 0; diff --git a/model/database/resulttable.h b/model/database/resulttable.h index 37550e9..dd92ead 100644 --- a/model/database/resulttable.h +++ b/model/database/resulttable.h @@ -27,10 +27,12 @@ class ResultTable bool isEmpty(); string valueAt(unsigned int row, unsigned int col); + int getReturnCode(); unsigned int columnCount(); unsigned int rowCount(); private: vector> values; + int queryCode; //Utility methods. string convertToString(sqlite3_value* val);