Shift to master. Not done but database abstraction is nearly done. #3

Merged
andrewlalis merged 10 commits from development into master 2018-03-03 09:20:11 +00:00
8 changed files with 249 additions and 31 deletions
Showing only changes of commit 658bf9fb2b - Show all commits

View File

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

View File

@ -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();
} }

View File

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

View File

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

View File

@ -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));");
}

View File

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

View File

@ -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());
}

View File

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