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 vals = combineVector(values, ", ");
query += cols + ") VALUES (" + vals + ");";
this->executeSQL(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<string> strings, string mid){
if (strings.empty()){
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){
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();
}

View File

@ -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<string> columnNames, vector<string> 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;

View File

@ -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<string>({
@ -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<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){
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<string>({"foodGroup", "name"}), vector<string>({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);");

View File

@ -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<Recipe> retrieveRecipe(string name);
private:
//Utility methods.

View File

@ -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;

View File

@ -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<vector<string>> values;
int queryCode;
//Utility methods.
string convertToString(sqlite3_value* val);