From 658bf9fb2b5486d7b5445bed3750672c7b31c5ea Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 1 Mar 2018 17:19:13 +0100 Subject: [PATCH] Got a working database interaction layer and basic tables. --- RecipeDB.pro | 8 +++- main.cpp | 12 ++++- model/database/database.cpp | 42 +++++++++-------- model/database/database.h | 19 ++++---- model/database/recipedatabase.cpp | 60 +++++++++++++++++++++++++ model/database/recipedatabase.h | 24 ++++++++++ model/database/resulttable.cpp | 75 +++++++++++++++++++++++++++++++ model/database/resulttable.h | 40 +++++++++++++++++ 8 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 model/database/recipedatabase.cpp create mode 100644 model/database/recipedatabase.h create mode 100644 model/database/resulttable.cpp create mode 100644 model/database/resulttable.h diff --git a/RecipeDB.pro b/RecipeDB.pro index 8b1d1c5..eb02709 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -22,7 +22,9 @@ SOURCES += model/recipe/instruction.cpp \ model/recipe/ingredients/ingredientlistmodel.cpp \ model/recipe/ingredients/recipeingredient.cpp \ model/recipe/tags/recipetag.cpp \ - SQLite/sqlite3.c + SQLite/sqlite3.c \ + model/database/resulttable.cpp \ + model/database/recipedatabase.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ @@ -34,7 +36,9 @@ HEADERS += model/recipe/instruction.h \ model/recipe/ingredients/recipeingredient.h \ model/recipe/tags/recipetag.h \ SQLite/sqlite3.h \ - SQLite/sqlite3ext.h + SQLite/sqlite3ext.h \ + model/database/resulttable.h \ + model/database/recipedatabase.h LIBS += -ldl \ diff --git a/main.cpp b/main.cpp index 4f87ea1..5e98fcf 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include #include "model/database/database.h" +#include "model/database/recipedatabase.h" int main(int argc, char *argv[]) { @@ -9,7 +10,14 @@ int main(int argc, char *argv[]) MainWindow w; w.show(); - Database db("test.db"); + //TESTING CODE + Database db("test.db"); + printf("Table exists: %d\n", db.tableExists("ingredients")); + db.executeSQL("SELECT * FROM ingredients;").printData(); + db.executeSQL("PRAGMA table_info('ingredients');").printData(); + db.executeSQL("SELECT name FROM ingredients WHERE foodGroup == 'fruit';").printData(); - return a.exec(); + RecipeDatabase recipeDB("recipes"); + + return a.exec(); } diff --git a/model/database/database.cpp b/model/database/database.cpp index 23716ab..8087e04 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -3,16 +3,27 @@ Database::Database(string filename){ this->filename = filename; openConnection(); - //TESTING CODE - if (tableExists("ingredients")){ - printf("Ingredients table already exists.\n"); - } else { - printf("Couldn't find the ingredients table.\n"); - } } Database::~Database(){ - closeConnection(); + closeConnection(); +} + +ResultTable Database::executeSQL(string statement){ + sqlite3_stmt* stmt; + this->sql = statement; + this->returnCode = sqlite3_prepare_v2(this->db, statement.c_str(), -1, &stmt, NULL); + ResultTable t; + if (this->returnCode != SQLITE_OK){ + fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\nError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); + return t; + } + + t.extractData(stmt); + + this->returnCode = sqlite3_finalize(stmt); + + return t; } void Database::openConnection(){ @@ -32,16 +43,9 @@ void Database::closeConnection(){ } bool Database::tableExists(string tableName){ - if (this->dbIsOpen){ - this->sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"; - const char* str = this->sql.c_str(); - this->returnCode = sqlite3_exec(this->db, str, NULL, 0, &this->errorMsg); - if (this->returnCode == SQLITE_ERROR){ - fprintf(stderr, "Unable to select name from master table list: %s\n", this->errorMsg); - return false; - } else { - return true; - } - } - return false; + if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ + return false; + } + 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 dce935a..cd7ba1c 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -6,17 +6,25 @@ #include "SQLite/sqlite3.h" #include "model/recipe/ingredients/ingredient.h" +#include "resulttable.h" using namespace std; +/** + * @brief The Database class is responsible for generic abstraction of commonly used database features. + */ + class Database { public: + //Creates and opens a database connection to a file of the given name. If not there, this will generate a database. Database(string filename); - ~Database(); + ~Database(); - void insertIngredient(Ingredient); + //Executes an SQL string statement in a safe way and returns the result. + ResultTable executeSQL(string statement); + bool tableExists(string tableName); private: //SQL Instance variables. string filename; @@ -27,12 +35,7 @@ private: char* errorMsg; void openConnection(); - void closeConnection(); - //Guarantees that all tables from the schema exist. - void ensureTablesExist(); - - //Utility methods. - bool tableExists(string tableName); + void closeConnection(); }; #endif // DATABASE_H diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp new file mode 100644 index 0000000..2eecb83 --- /dev/null +++ b/model/database/recipedatabase.cpp @@ -0,0 +1,60 @@ +#include "recipedatabase.h" + +RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ + this->ensureTablesExist(); +} + +void RecipeDatabase::ensureTablesExist(){ + //Make sure that foreign keys are enabled. + this->executeSQL("PRAGMA foreign_keys = ON;"); + //Ingredients table. + this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" + "ingredientId int," + "foodGroup varchar," + "name varchar," + "PRIMARY KEY (ingredientId));"); + //Images table. + this->executeSQL("CREATE TABLE IF NOT EXISTS image(" + "imageNr int," + "contentURL varchar," + "PRIMARY KEY (imageNr));"); + //Instructions table. + this->executeSQL("CREATE TABLE IF NOT EXISTS instruction(" + "instructionNr int," + "contentURL varchar," + "PRIMARY KEY (instructionNr));"); + //Recipe table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" + "recipeId int," + "date date," + "name varchar," + "imageNr int," + "cookTime time," + "prepTime time," + "servingCount real," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (imageNr) REFERENCES image(imageNr));"); + //Recipe tags table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag(" + "recipeId int," + "tagName varchar," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + //RecipeIngredient table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeIngredient(" + "ingredientId int," + "recipeId int," + "quantity real," + "unitName varchar," + "comment varchar," + "PRIMARY KEY (ingredientId, recipeId)," + "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + //Recipe Instruction mapping table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeInstruction(" + "instructionNr int," + "recipeId int," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (instructionNr) REFERENCES instruction(instructionNr)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); +} diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h new file mode 100644 index 0000000..f5d317b --- /dev/null +++ b/model/database/recipedatabase.h @@ -0,0 +1,24 @@ +#ifndef RECIPEDATABASE_H +#define RECIPEDATABASE_H + + + +#include "database.h" + +using namespace std; + +/** + * @brief The RecipeDatabase class represents the precise database used for the recipe storage, and is specialized. + */ + +class RecipeDatabase : public Database +{ + public: + RecipeDatabase(string filename); + private: + + //Utility methods. + void ensureTablesExist(); +}; + +#endif // RECIPEDATABASE_H diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp new file mode 100644 index 0000000..b8f4bca --- /dev/null +++ b/model/database/resulttable.cpp @@ -0,0 +1,75 @@ +#include "resulttable.h" + +ResultTable::ResultTable(){ + +} + +void ResultTable::extractData(sqlite3_stmt *stmt){ + this->values.clear(); + int res = sqlite3_step(stmt); + while (res == SQLITE_ROW){ + processRow(stmt); + res = sqlite3_step(stmt); + } +} + +void ResultTable::processRow(sqlite3_stmt *stmt){ + int colCount = sqlite3_column_count(stmt); + vector currentRow; + + for (int i = 0; i < colCount; i++){ + currentRow.push_back(convertToString(sqlite3_column_value(stmt, i))); + } + + this->values.push_back(currentRow); +} + +void ResultTable::printData(){ + printf("Printing table: %d by %d\n", this->rowCount(), this->columnCount()); + for (unsigned int row = 0; row < this->rowCount(); row++){ + for (unsigned int col = 0; col < this->columnCount(); col++){ + printf("| %s \t", this->values[row][col].c_str()); + } + printf("\n"); + } +} + +bool ResultTable::isEmpty(){ + return this->values.empty(); +} + +string ResultTable::valueAt(unsigned int row, unsigned int col){ + if (isIndexValid(row, col)){ + return this->values[row][col]; + } else { + fprintf(stderr, "Out of bounds error while trying to get value in result table at [%d, %d].\n", row, col); + return ""; + } +} + +unsigned int ResultTable::columnCount(){ + if (this->isEmpty()){ + return 0; + } + return this->values[0].size(); +} + +unsigned int ResultTable::rowCount(){ + if (this->isEmpty()){ + return 0; + } + return this->values.size(); +} + +string ResultTable::convertToString(sqlite3_value *val){ + const unsigned char* raw_text = sqlite3_value_text(val); + if (raw_text == 0){ + return ""; + } + string st = (const char*) raw_text; + return st; +} + +bool ResultTable::isIndexValid(unsigned int row, unsigned int col){ + return (row < this->values.size()) && (col < this->values[0].size()); +} diff --git a/model/database/resulttable.h b/model/database/resulttable.h new file mode 100644 index 0000000..37550e9 --- /dev/null +++ b/model/database/resulttable.h @@ -0,0 +1,40 @@ +#ifndef RESULTTABLE_H +#define RESULTTABLE_H + +#include +#include + +#include "SQLite/sqlite3.h" + +using namespace std; + +/** + * @brief The ResultTable class is an object that contains the results of an SQL query, in string form. + */ + +class ResultTable +{ + public: + //Constructs an empty table. + ResultTable(); + + //Gets all the data from the result set and stores it internally as strings. + void extractData(sqlite3_stmt* stmt); + //Stores the information from one row of a result set. + void processRow(sqlite3_stmt* stmt); + //Displays the data somewhat legibly. + void printData(); + + bool isEmpty(); + string valueAt(unsigned int row, unsigned int col); + unsigned int columnCount(); + unsigned int rowCount(); + private: + vector> values; + + //Utility methods. + string convertToString(sqlite3_value* val); + bool isIndexValid(unsigned int row, unsigned int col); +}; + +#endif // RESULTTABLE_H