Got a working database interaction layer and basic tables.
This commit is contained in:
		
							parent
							
								
									7f0fd90e73
								
							
						
					
					
						commit
						658bf9fb2b
					
				| 
						 | 
					@ -22,7 +22,9 @@ SOURCES += model/recipe/instruction.cpp \
 | 
				
			||||||
    model/recipe/ingredients/ingredientlistmodel.cpp \
 | 
					    model/recipe/ingredients/ingredientlistmodel.cpp \
 | 
				
			||||||
    model/recipe/ingredients/recipeingredient.cpp \
 | 
					    model/recipe/ingredients/recipeingredient.cpp \
 | 
				
			||||||
    model/recipe/tags/recipetag.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 \
 | 
					HEADERS  += model/recipe/instruction.h \
 | 
				
			||||||
    model/recipe/recipe.h \
 | 
					    model/recipe/recipe.h \
 | 
				
			||||||
| 
						 | 
					@ -34,7 +36,9 @@ HEADERS  += model/recipe/instruction.h \
 | 
				
			||||||
    model/recipe/ingredients/recipeingredient.h \
 | 
					    model/recipe/ingredients/recipeingredient.h \
 | 
				
			||||||
    model/recipe/tags/recipetag.h \
 | 
					    model/recipe/tags/recipetag.h \
 | 
				
			||||||
    SQLite/sqlite3.h \
 | 
					    SQLite/sqlite3.h \
 | 
				
			||||||
    SQLite/sqlite3ext.h
 | 
					    SQLite/sqlite3ext.h \
 | 
				
			||||||
 | 
					    model/database/resulttable.h \
 | 
				
			||||||
 | 
					    model/database/recipedatabase.h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LIBS += -ldl \
 | 
					LIBS += -ldl \
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								main.cpp
								
								
								
								
							
							
						
						
									
										12
									
								
								main.cpp
								
								
								
								
							| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
#include <QApplication>
 | 
					#include <QApplication>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "model/database/database.h"
 | 
					#include "model/database/database.h"
 | 
				
			||||||
 | 
					#include "model/database/recipedatabase.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int main(int argc, char *argv[])
 | 
					int main(int argc, char *argv[])
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -9,7 +10,14 @@ int main(int argc, char *argv[])
 | 
				
			||||||
    MainWindow w;
 | 
					    MainWindow w;
 | 
				
			||||||
    w.show();
 | 
					    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();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,16 +3,27 @@
 | 
				
			||||||
Database::Database(string filename){
 | 
					Database::Database(string filename){
 | 
				
			||||||
    this->filename = filename;
 | 
					    this->filename = filename;
 | 
				
			||||||
    openConnection();
 | 
					    openConnection();
 | 
				
			||||||
    //TESTING CODE
 | 
					 | 
				
			||||||
    if (tableExists("ingredients")){
 | 
					 | 
				
			||||||
        printf("Ingredients table already exists.\n");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        printf("Couldn't find the ingredients table.\n");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Database::~Database(){
 | 
					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(){
 | 
					void Database::openConnection(){
 | 
				
			||||||
| 
						 | 
					@ -32,16 +43,9 @@ void Database::closeConnection(){
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Database::tableExists(string tableName){
 | 
					bool Database::tableExists(string tableName){
 | 
				
			||||||
    if (this->dbIsOpen){
 | 
						if (tableName.empty() || this->db == NULL || !this->dbIsOpen){
 | 
				
			||||||
        this->sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';";
 | 
							return false;
 | 
				
			||||||
        const char* str = this->sql.c_str();
 | 
						}
 | 
				
			||||||
        this->returnCode = sqlite3_exec(this->db, str, NULL, 0, &this->errorMsg);
 | 
						ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';");
 | 
				
			||||||
        if (this->returnCode == SQLITE_ERROR){
 | 
						return !t.isEmpty();
 | 
				
			||||||
            fprintf(stderr, "Unable to select name from master table list: %s\n", this->errorMsg);
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,17 +6,25 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "SQLite/sqlite3.h"
 | 
					#include "SQLite/sqlite3.h"
 | 
				
			||||||
#include "model/recipe/ingredients/ingredient.h"
 | 
					#include "model/recipe/ingredients/ingredient.h"
 | 
				
			||||||
 | 
					#include "resulttable.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief The Database class is responsible for generic abstraction of commonly used database features.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Database
 | 
					class Database
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					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(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:
 | 
					private:
 | 
				
			||||||
    //SQL Instance variables.
 | 
					    //SQL Instance variables.
 | 
				
			||||||
    string filename;
 | 
					    string filename;
 | 
				
			||||||
| 
						 | 
					@ -27,12 +35,7 @@ private:
 | 
				
			||||||
    char* errorMsg;
 | 
					    char* errorMsg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void openConnection();
 | 
					    void openConnection();
 | 
				
			||||||
    void closeConnection();
 | 
						void closeConnection();
 | 
				
			||||||
    //Guarantees that all tables from the schema exist.
 | 
					 | 
				
			||||||
    void ensureTablesExist();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //Utility methods.
 | 
					 | 
				
			||||||
    bool tableExists(string tableName);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // DATABASE_H
 | 
					#endif // DATABASE_H
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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));");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					@ -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<string> 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());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					#ifndef RESULTTABLE_H
 | 
				
			||||||
 | 
					#define RESULTTABLE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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<vector<string>> values;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							//Utility methods.
 | 
				
			||||||
 | 
							string convertToString(sqlite3_value* val);
 | 
				
			||||||
 | 
							bool isIndexValid(unsigned int row, unsigned int col);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // RESULTTABLE_H
 | 
				
			||||||
		Loading…
	
		Reference in New Issue